enlightenment-module-desksa.../src/gadgets/core.c

734 lines
21 KiB
C

#include "e_mod_main.h"
#include "gadget.h"
#define ZGS_IS_HORIZ(orient) \
((orient) <= Z_GADGET_SITE_ORIENT_HORIZONTAL)
#define ZGS_GET(obj) \
Z_Gadget_Site *zgs; \
zgs = evas_object_data_get((obj), "__z_gadget_site"); \
if (!zgs) abort()
typedef struct Z_Gadget_Config Z_Gadget_Config;
typedef struct Z_Gadget_Site
{
Evas_Object *layout;
Evas_Object *events;
Z_Gadget_Site_Gravity gravity;
Z_Gadget_Site_Orient orient;
Z_Gadget_Site_Anchor anchor;
Eina_List *gadgets;
int cur_size;
Z_Gadget_Config *action;
Ecore_Event_Handler *move_handler;
Ecore_Event_Handler *mouse_up_handler;
int button;
} Z_Gadget_Site;
/* refcount? */
struct Z_Gadget_Config
{
E_Object *e_obj_inherit; //list?
Evas_Object *gadget; //list?
unsigned int id;
Eina_Stringshare *type;
Z_Gadget_Site *site;
double x, y; //fixed % positioning
Evas_Point offset; //offset from mouse down
Z_Gadget_Config *over; //gadget is animating over another gadget during drag
Eina_Bool modifying : 1;
};
static Eina_Hash *gadget_types;
static Eina_List *sites;
static E_Action *move_act;
static Z_Gadget_Config *
_gadget_at_xy(Z_Gadget_Site *zgs, int x, int y)
{
Eina_List *l;
Z_Gadget_Config *zgc;
Evas_Object *win;
int wx, wy;
win = e_win_evas_object_win_get(zgs->layout);
evas_object_geometry_get(win, &wx, &wy, NULL, NULL);
EINA_LIST_FOREACH(zgs->gadgets, l, zgc)
{
int ox, oy, ow, oh;
if (!zgc->gadget) continue;
evas_object_geometry_get(zgc->gadget, &ox, &oy, &ow, &oh);
if (E_INSIDE(x, y, ox + wx, oy + wy, ow, oh)) return zgc;
}
return NULL;
}
static void
_gravity_apply(Evas_Object *ly, Z_Gadget_Site_Gravity gravity)
{
double ax = 0.5, ay = 0.5;
switch (gravity)
{
case Z_GADGET_SITE_GRAVITY_LEFT:
ax = 0;
break;
case Z_GADGET_SITE_GRAVITY_RIGHT:
ax = 1;
break;
default: break;
}
switch (gravity)
{
case Z_GADGET_SITE_GRAVITY_TOP:
ay = 0;
break;
case Z_GADGET_SITE_GRAVITY_BOTTOM:
ay = 1;
break;
default: break;
}
elm_box_align_set(ly, ax, ay);
}
static void
_gadget_reparent(Z_Gadget_Site *zgs, Evas_Object *g)
{
switch (zgs->gravity)
{
case Z_GADGET_SITE_GRAVITY_NONE:
/* fake */
break;
case Z_GADGET_SITE_GRAVITY_LEFT:
case Z_GADGET_SITE_GRAVITY_TOP:
elm_box_pack_end(zgs->layout, g);
break;
default:
/* right aligned: pack on left */
elm_box_pack_start(zgs->layout, g);
}
}
static void
_gadget_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
Z_Gadget_Config *zgc = data;
if (!e_object_is_del(zgc->e_obj_inherit))
e_object_del(zgc->e_obj_inherit);
}
static void
_gadget_object_free(E_Object *eobj)
{
Z_Gadget_Config *zgc;
Evas_Object *g;
g = e_object_data_get(eobj);
zgc = evas_object_data_get(g, "__z_gadget");
evas_object_smart_callback_call(zgc->site->layout, "gadget_removed", zgc->gadget);
E_FREE_FUNC(zgc->gadget, evas_object_del);
E_FREE(zgc->e_obj_inherit);
}
static void
_site_gadget_resize(Evas_Object *g, int w, int h, Evas_Coord *ww, Evas_Coord *hh, Evas_Coord *ow, Evas_Coord *oh)
{
Evas_Coord mnw, mnh, mxw, mxh;
Z_Gadget_Config *zgc;
zgc = evas_object_data_get(g, "__z_gadget");
evas_object_size_hint_min_get(g, &mnw, &mnh);
evas_object_size_hint_max_get(g, &mxw, &mxh);
/* TODO: aspect */
if (ZGS_IS_HORIZ(zgc->site->orient))
{
*ww = mnw, *hh = h;
if (!(*ww)) *ww = *hh;
}
else
{
*hh = mnh, *ww = w;
if (!(*hh)) *hh = *ww;
}
*ow = *ww;
if ((mxw >= 0) && (mxw < *ow)) *ow = mxw;
*oh = *hh;
if ((mxh >= 0) && (mxh < *oh)) *oh = mxh;
evas_object_resize(g, *ow, *oh);
}
static void
_site_layout(Evas_Object *o, Evas_Object_Box_Data *priv EINA_UNUSED, void *data)
{
Z_Gadget_Site *zgs = data;
Evas_Coord x, y, w, h, xx, yy, px, py;
Eina_List *l;
double ax, ay;
Z_Gadget_Config *zgc;
evas_object_geometry_get(o, &x, &y, &w, &h);
evas_object_geometry_set(zgs->events, x, y, w, h);
evas_object_box_align_get(o, &ax, &ay);
px = xx = x;
py = yy = y;
/* do layout for rest of gadgets now to avoid fixed gadgets */
if (zgs->gravity % 2)//left/top
{
EINA_LIST_FOREACH(zgs->gadgets, l, zgc)
{
Evas_Coord gx = xx, gy = yy;
int ww, hh, ow, oh;
if ((zgc->x > -1) || (zgc->y > -1)) break; //one fixed gadget reached
_site_gadget_resize(zgc->gadget, w, h, &ww, &hh, &ow, &oh);
if (ZGS_IS_HORIZ(zgs->orient))
gx += (Evas_Coord)(((double)(ww - ow)) * 0.5);
else
gy += (Evas_Coord)(((double)(hh - oh)) * 0.5);
evas_object_move(zgc->gadget, gx, gy);
if (ZGS_IS_HORIZ(zgs->orient))
xx += ow;
else
yy += oh;
}
}
else
{
if (ZGS_IS_HORIZ(zgs->orient))
px += w;
else
py += h;
xx = px, yy = py;
EINA_LIST_REVERSE_FOREACH(zgs->gadgets, l, zgc)
{
Evas_Coord gx = xx, gy = yy;
int ww, hh, ow, oh;
if ((zgc->x > -1) || (zgc->y > -1)) continue; //one fixed gadget reached
_site_gadget_resize(zgc->gadget, w, h, &ww, &hh, &ow, &oh);
if (ZGS_IS_HORIZ(zgs->orient))
gx -= (Evas_Coord)(((double)(ww - ow)) * 0.5) + ow;
else
gy -= (Evas_Coord)(((double)(hh - oh)) * 0.5) + oh;
evas_object_move(zgc->gadget, gx, gy);
if (ZGS_IS_HORIZ(zgs->orient))
xx -= ow;
else
yy -= oh;
}
}
px = xx;
py = yy;
if (ZGS_IS_HORIZ(zgs->orient))
zgs->cur_size = abs((ax * w) - px) - x;
else
zgs->cur_size = abs((ay * h) - py) - y;
/* do layout for fixed position gadgets after */
EINA_LIST_REVERSE_FOREACH(zgs->gadgets, l, zgc)
{
Evas_Coord gx = xx, gy = yy;
int ww, hh, ow, oh;
if ((zgc->x < 0) && (zgc->y < 0)) break; //once non-fixed gadget reached
_site_gadget_resize(zgc->gadget, w, h, &ww, &hh, &ow, &oh);
if (ZGS_IS_HORIZ(zgc->site->orient))
{
gx = ((1 - ax) * xx) + (zgc->x * (w - zgs->cur_size));
gx += (Evas_Coord)(((double)(ww - ow)) * 0.5 * -ax);
}
else
{
gy = ((1 - ay) * yy) + (zgc->y * (h - zgs->cur_size));
gy += (Evas_Coord)(((double)(hh - oh)) * 0.5 * -ay);
}
if (zgs->gravity % 2)//left/top
{
if (gx < px) gx = px;
}
else
{
if (gx > px) gx = px;
}
if (zgs->gravity % 2)//left/top
{
if (gy < py) gy = py;
}
else
{
if (gy > py) gy = py;
}
evas_object_move(zgc->gadget, gx, gy);
if (ZGS_IS_HORIZ(zgs->orient))
px = gx + (-ax * ow);
else
py = gy + (-ay * oh);
}
}
static int
_site_gadgets_sort(Z_Gadget_Config *a, Z_Gadget_Config *b)
{
double *ax, *bx;
if (ZGS_IS_HORIZ(a->site->orient))
ax = &a->x, bx = &b->x;
else
ax = &a->y, bx = &b->y;
if (a->site->gravity % 2)//left/top
return lround(*ax - *bx);
return lround(*bx - *ax);
}
static Eina_Bool
_gadget_mouse_move(Z_Gadget_Config *zgc, int t EINA_UNUSED, Ecore_Event_Mouse_Move *ev)
{
int wx, wy;//window coords
int x, y, w, h;//site geom
int mx, my;//mouse coords normalized for layout orientation
int gw, gh;//"relative" region size
int *rw, *rh;//"relative" region size aliasing
int ox, oy, ow, oh;//gadget geom
Eina_List *fixed;
Z_Gadget_Config *z;
Evas_Object *win;
/* adjust window -> screen coords */
win = e_win_evas_object_win_get(zgc->site->layout);
evas_object_geometry_get(win, &wx, &wy, NULL, NULL);
evas_object_geometry_get(zgc->site->layout, &x, &y, &w, &h);
x += wx, y += wy;
gw = w, gh = h;
mx = ev->x;
my = ev->y;
evas_object_geometry_get(zgc->gadget, &ox, &oy, &ow, &oh);
ox += wx, oy += wy;
rw = &gw;
rh = &gh;
/* normalize constrained axis to get a valid coordinate */
if (ZGS_IS_HORIZ(zgc->site->orient))
{
my = y + 1;
*rw = zgc->site->cur_size;
}
else
{
mx = x + 1;
*rh = zgc->site->cur_size;
}
/* find first "fixed" position gadget for later use */
EINA_LIST_FOREACH(zgc->site->gadgets, fixed, z)
if ((z->x > -1) || (z->y > -1)) break;
if (E_INSIDE(mx, my, x, y, w, h))
{
/* dragging inside site */
int sx = x, sy = y;
double ax, ay;
/* adjust contiguous site geometry for gravity */
elm_box_align_get(zgc->site->layout, &ax, &ay);
if (ZGS_IS_HORIZ(zgc->site->orient))
sx = x + ((w - zgc->site->cur_size) * ax);
else
sy = y + ((h - zgc->site->cur_size) * ay);
if (E_INSIDE(mx, my, sx, sy, *rw, *rh))
{
/* dragging inside relative area */
Eina_List *l;
EINA_LIST_FOREACH(zgc->site->gadgets, l, z)
{
int ggx, ggy, ggw, ggh;
Eina_Bool left;//moving gadget is "left" of this gadget
int *pmx, *pggx, *pggw, *pwx, *pw;
double *zx;
if (z == zgc) continue;
if (!z->gadget) continue; //no gadget object for this config
evas_object_geometry_get(z->gadget, &ggx, &ggy, &ggw, &ggh);
ggx += wx, ggy += wy;
/* not inside this gadget! */
if (!E_INSIDE(mx, my, ggx, ggy, ggw, ggh)) continue;
if (ZGS_IS_HORIZ(zgc->site->orient))
left = ox < ggx;
else
left = oy < ggy;
if (ZGS_IS_HORIZ(zgc->site->orient))
pmx = &mx, pggx = &ggx, pggw = &ggw, pwx = &wx, pw = &w, zx = &zgc->x;
else
pmx = &my, pggx = &ggy, pggw = &ggh, pwx = &wy, pw = &h, zx = &zgc->y;
if (left)
{
if (*pmx > *pggx + (*pggw / 2)) // more than halfway over
{
zgc->site->gadgets = eina_list_remove(zgc->site->gadgets, zgc);
zgc->site->gadgets = eina_list_append_relative_list(zgc->site->gadgets, zgc, l);
zgc->over = NULL;
*zx = -1.0;
}
else // less
{
*zx = (double)(*pmx - *pwx) / (double)*pw;
zgc->over = z;
}
}
else
{
if (*pmx < *pggx + (*pggw / 2)) // more than halfway over
{
zgc->site->gadgets = eina_list_remove(zgc->site->gadgets, zgc);
zgc->site->gadgets = eina_list_prepend_relative_list(zgc->site->gadgets, zgc, l);
zgc->over = NULL;
*zx = -1.0;
}
else // less
{
*zx = (double)(*pmx - *pwx) / (double)*pw;
zgc->over = z;
}
}
}
}
else
{
/* dragging outside relative area */
if (fixed)
{
Eina_List *l;
Z_Gadget_Config *zz;
for (l = fixed; l; l = l->next)
{
int zx, zy, zw, zh;
zz = eina_list_data_get(l);
if ((zz = zgc) || (!zz->gadget)) continue;
/* FIXME: shortcut */
evas_object_geometry_get(zz->gadget, &zx, &zy, &zw, &zh);
if (E_INSIDE(mx, my, zx, zy, zw, zh))
{
zgc->over = zz;
break;
}
}
}
if (!((zgc->x > -1) || (zgc->y > -1)))
{
zgc->site->gadgets = eina_list_remove(zgc->site->gadgets, zgc);
zgc->site->gadgets = eina_list_append(zgc->site->gadgets, zgc);
}
if (ZGS_IS_HORIZ(zgc->site->orient))
zgc->x = (double)(mx - wx - zgc->offset.x) / (double)w;
else
zgc->y = (double)(my - wy - zgc->offset.y) / (double)h;
}
}
else
{
/* dragging to edge of site */
Eina_Bool left;
double *fx;
if (ZGS_IS_HORIZ(zgc->site->orient))
{
fx = &zgc->x;
left = mx <= x;
}
else
{
fx = &zgc->y;
left = my <= y;
}
if (left)
{
if (zgc->site->gravity % 2) //left/top
{
*fx = -1.0;
zgc->site->gadgets = eina_list_promote_list(zgc->site->gadgets, eina_list_data_find_list(zgc->site->gadgets, zgc));
}
else
{
*fx = 0.0;
zgc->site->gadgets = eina_list_remove(zgc->site->gadgets, zgc);
zgc->site->gadgets = eina_list_prepend_relative_list(zgc->site->gadgets, zgc, fixed);
}
}
else
{
if (zgc->site->gravity % 2) //left/top
{
*fx = 1.0;
zgc->site->gadgets = eina_list_demote_list(zgc->site->gadgets, eina_list_data_find_list(zgc->site->gadgets, zgc));
}
else
{
*fx = -1.0;
zgc->site->gadgets = eina_list_remove(zgc->site->gadgets, zgc);
zgc->site->gadgets = eina_list_prepend_relative_list(zgc->site->gadgets, zgc, fixed);
}
}
}
elm_box_recalculate(zgc->site->layout);
return ECORE_CALLBACK_RENEW;
}
static void
_gadget_act_modify_end(E_Object *obj, const char *params EINA_UNUSED, E_Binding_Event_Mouse_Button *ev EINA_UNUSED)
{
Z_Gadget_Config *zgc;
Evas_Object *g;
g = e_object_data_get(obj);
zgc = evas_object_data_get(g, "__z_gadget");
zgc->modifying = 0;
if (zgc->over)
{
/* FIXME: animate */
zgc->x = zgc->y = -1.0;
elm_box_recalculate(zgc->site->layout);
}
zgc->over = NULL;
E_FREE_FUNC(zgc->site->move_handler, ecore_event_handler_del);
}
static void
_gadget_act_modify(E_Object *obj, const char *params EINA_UNUSED, E_Binding_Event_Mouse_Button *ev EINA_UNUSED)
{
Z_Gadget_Config *zgc;
Evas_Object *g;
if (obj->type != Z_GADGET_TYPE) return;
g = e_object_data_get(obj);
zgc = evas_object_data_get(g, "__z_gadget");
zgc->modifying = 1;
if (!zgc->site->move_handler)
zgc->site->move_handler = ecore_event_handler_add(ECORE_EVENT_MOUSE_MOVE, (Ecore_Event_Handler_Cb)_gadget_mouse_move, zgc);
}
static Eina_Bool
_site_mouse_up(Z_Gadget_Site *zgs, int t EINA_UNUSED, Ecore_Event_Mouse_Button *ev)
{
if (e_bindings_mouse_up_ecore_event_handle(E_BINDING_CONTEXT_ANY, zgs->action->e_obj_inherit, ev))
{
evas_object_pointer_mode_set(zgs->events, EVAS_OBJECT_POINTER_MODE_NOGRAB);
zgs->action = NULL;
E_FREE_FUNC(zgs->mouse_up_handler, ecore_event_handler_del);
}
return ECORE_CALLBACK_RENEW;
}
static void
_site_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
Z_Gadget_Site *zgs = data;
Evas_Event_Mouse_Down *ev = event_info;
Z_Gadget_Config *zgc;
zgc = _gadget_at_xy(zgs, ev->output.x, ev->output.y);
if (!zgc) return;
if (e_bindings_mouse_down_evas_event_handle(E_BINDING_CONTEXT_ANY, zgc->e_obj_inherit, event_info))
{
int x, y;
evas_object_pointer_mode_set(obj, EVAS_OBJECT_POINTER_MODE_NOGRAB_NO_REPEAT_UPDOWN);
zgs->action = zgc;
if (!zgs->mouse_up_handler)
zgs->mouse_up_handler = ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP, (Ecore_Event_Handler_Cb)_site_mouse_up, zgs);
evas_object_geometry_get(zgc->gadget, &x, &y, NULL, NULL);
zgc->offset.x = ev->canvas.x - x;
zgc->offset.y = ev->canvas.y - y;
}
}
Z_API Evas_Object *
z_gadget_site_add(Evas_Object *parent, Z_Gadget_Site_Orient orient)
{
Z_Gadget_Site *zgs;
zgs = E_NEW(Z_Gadget_Site, 1);
zgs->orient = orient;
switch (orient)
{
case Z_GADGET_SITE_ORIENT_HORIZONTAL:
zgs->gravity = Z_GADGET_SITE_GRAVITY_LEFT;
break;
case Z_GADGET_SITE_ORIENT_VERTICAL:
zgs->gravity = Z_GADGET_SITE_GRAVITY_TOP;
break;
default: break;
}
zgs->layout = elm_box_add(parent);
elm_box_horizontal_set(zgs->layout, orient == Z_GADGET_SITE_ORIENT_HORIZONTAL);
_gravity_apply(zgs->layout, zgs->gravity);
elm_box_layout_set(zgs->layout, _site_layout, zgs, NULL);
zgs->events = evas_object_rectangle_add(evas_object_evas_get(parent));
evas_object_pointer_mode_set(zgs->events, EVAS_OBJECT_POINTER_MODE_NOGRAB);
evas_object_smart_member_add(zgs->events, zgs->layout);
evas_object_color_set(zgs->events, 0, 0, 0, 0);
evas_object_repeat_events_set(zgs->events, 1);
evas_object_show(zgs->events);
evas_object_event_callback_add(zgs->events, EVAS_CALLBACK_MOUSE_DOWN, _site_mouse_down, zgs);
evas_object_data_set(zgs->layout, "__z_gadget_site", zgs);
sites = eina_list_append(sites, zgs);
if (!move_act)
{
move_act = e_action_add("gadget_modify");
e_action_predef_name_set(D_("Gadgets"), D_("Move gadget"), "gadget_modify", NULL, NULL, 0);
move_act->func.go_mouse = _gadget_act_modify;
move_act->func.end_mouse = _gadget_act_modify_end;
}
return zgs->layout;
}
Z_API Z_Gadget_Site_Anchor
z_gadget_site_anchor_get(Evas_Object *obj)
{
ZGS_GET(obj);
return zgs->anchor;
}
Z_API void
z_gadget_site_anchor(Evas_Object *obj, Z_Gadget_Site_Anchor an)
{
ZGS_GET(obj);
zgs->anchor = an;
evas_object_smart_callback_call(obj, "gadget_anchor", NULL);
}
Z_API Z_Gadget_Site_Orient
z_gadget_site_orient_get(Evas_Object *obj)
{
ZGS_GET(obj);
return zgs->orient;
}
Z_API Z_Gadget_Site_Gravity
z_gadget_site_gravity_get(Evas_Object *obj)
{
ZGS_GET(obj);
return zgs->gravity;
}
Z_API void
z_gadget_site_gadget_add(Evas_Object *obj, const char *type)
{
char buf[1024];
Z_Gadget_Create_Cb cb;
Evas_Object *g;
Z_Gadget_Config *zgc;
unsigned int id = 0;
EINA_SAFETY_ON_NULL_RETURN(gadget_types);
ZGS_GET(obj);
strncpy(buf, type, sizeof(buf));
cb = eina_hash_find(gadget_types, buf);
EINA_SAFETY_ON_NULL_RETURN(cb);
/* if id is 0, gadget creates new config and returns id
* otherwise, config of `id` is applied to created object
*/
g = cb(obj, &id, zgs->orient);
EINA_SAFETY_ON_NULL_RETURN(g);
zgc = E_NEW(Z_Gadget_Config, 1);
zgc->e_obj_inherit = E_OBJECT_ALLOC(E_Object, Z_GADGET_TYPE, _gadget_object_free);
e_object_data_set(zgc->e_obj_inherit, g);
zgc->id = id;
zgc->type = eina_stringshare_add(buf);
zgc->gadget = g;
zgc->x = -1;
zgc->y = -1;
zgc->site = zgs;
evas_object_data_set(g, "__z_gadget", zgc);
evas_object_event_callback_add(g, EVAS_CALLBACK_DEL, _gadget_del, zgc);
zgs->gadgets = eina_list_sorted_insert(zgs->gadgets, (Eina_Compare_Cb)_site_gadgets_sort, zgc);
_gadget_reparent(zgs, g);
evas_object_raise(zgs->events);
evas_object_smart_callback_call(obj, "gadget_added", g);
evas_object_smart_callback_call(obj, "gadget_gravity", g);
evas_object_show(g);
}
Z_API Evas_Object *
z_gadget_site_get(Evas_Object *g)
{
Z_Gadget_Site *zgs;
EINA_SAFETY_ON_NULL_RETURN_VAL(g, NULL);
zgs = evas_object_data_get(g, "__z_gadget");
EINA_SAFETY_ON_NULL_RETURN_VAL(zgs, NULL);
return zgs->layout;
}
Z_API void
z_gadget_type_add(const char *type, Z_Gadget_Create_Cb callback)
{
if (!gadget_types) gadget_types = eina_hash_string_superfast_new(NULL);
eina_hash_add(gadget_types, type, callback);
}
Z_API void
z_gadget_type_del(const char *type)
{
Eina_List *l, *ll;
Z_Gadget_Site *zgs;
Z_Gadget_Config *zgc;
char buf[1024];
strncpy(buf, type, sizeof(buf));
if (!gadget_types) return;
EINA_LIST_FOREACH(sites, l, zgs)
EINA_LIST_FOREACH(zgs->gadgets, ll, zgc)
if (eina_streq(buf, zgc->type))
evas_object_del(zgc->gadget);
}