[elm_pager] merge with latest EFL
[framework/uifw/elementary.git] / src / lib / elm_pager.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup Pager Pager
6  *
7  * The pager is an object that allows flipping (with animation) between 1 or
8  * more ?\9cpages??of objects, much like a stack of windows within the window.
9  *
10  * Objects can be pushed or popped from he stack or deleted as normal.
11  * Pushes and pops will animate (and a pop will delete the object once the
12  * animation is finished). Any object in the pager can be promoted to the top
13  * (from its current stacking position) as well. Objects are pushed to the
14  * top with elm_pager_content_push() and when the top item is no longer
15  * wanted, simply pop it with elm_pager_content_pop() and it will also be
16  * deleted. Any object you wish to promote to the top that is already in the
17  * pager, simply use elm_pager_content_promote(). If an object is no longer
18  * needed and is not the top item, just delete it as normal. You can query
19  * which objects are the top and bottom with elm_pager_content_bottom_get()
20  * and elm_pager_content_top_get().
21  */
22
23 typedef struct _Widget_Data Widget_Data;
24 typedef struct _Item Item;
25
26 struct _Widget_Data
27 {
28    Eina_List *stack;
29    Item *top, *oldtop;
30    Evas_Object *rect, *clip;
31 };
32
33 struct _Item
34 {
35    Evas_Object *obj, *base, *content;
36    Evas_Coord minw, minh;
37    Eina_Bool popme : 1;
38 };
39
40 static Eina_Bool ani = EINA_TRUE;
41 static const char *widtype = NULL;
42 static void _del_hook(Evas_Object *obj);
43 static void _theme_hook(Evas_Object *obj);
44 static void _sizing_eval(Evas_Object *obj);
45 static void _changed_size_hints(void *data, Evas *e, Evas_Object *obj, void *event_info);
46 static void _sub_del(void *data, Evas_Object *obj, void *event_info);
47 static void _signal_hide_finished(void *data, Evas_Object *obj, const char *emission, const char *source);
48
49 static void
50 _del_hook(Evas_Object *obj)
51 {
52    Widget_Data *wd = elm_widget_data_get(obj);
53    if (!wd) return;
54    free(wd);
55 }
56
57 static void
58 _theme_hook(Evas_Object *obj)
59 {
60    Widget_Data *wd = elm_widget_data_get(obj);
61    Eina_List *l;
62    Item *it;
63    if (!wd) return;
64    EINA_LIST_FOREACH(wd->stack, l, it)
65      edje_object_scale_set(it->base, elm_widget_scale_get(obj) * 
66                            _elm_config->scale);
67    _sizing_eval(obj);
68 }
69
70 static void
71 _sizing_eval(Evas_Object *obj)
72 {
73    Widget_Data *wd = elm_widget_data_get(obj);
74    Evas_Coord minw = -1, minh = -1;
75    Eina_List *l;
76    Item *it;
77    if (!wd) return;
78    EINA_LIST_FOREACH(wd->stack, l, it)
79      {
80         if (it->minw > minw) minw = it->minw;
81         if (it->minh > minh) minh = it->minh;
82      }
83    evas_object_size_hint_min_set(obj, minw, minh);
84    evas_object_size_hint_max_set(obj, -1, -1);
85 }
86
87 static void
88 _changed_size_hints(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
89 {
90    Item *it = data;
91    Evas_Coord minw = -1, minh = -1;
92    evas_object_size_hint_min_get(it->content, &minw, &minh);
93    // FIXME: why is this needed? how does edje get this unswallowed or
94    // lose its callbacks to edje
95    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
96    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
97    _sizing_eval(it->obj);
98 }
99
100 static void 
101 _complete_cb( void* data )
102 {
103         Item *it = data;
104         Evas_Object *obj = it->obj;
105
106         evas_object_hide(it->base);
107         edje_object_signal_emit(it->base, "elm,action,reset", "elm");
108         evas_object_smart_callback_call(obj, "hide,finished", it->content);
109         edje_object_message_signal_process(it->base);
110         if (it->popme) evas_object_del(it->content);
111         _sizing_eval(obj);
112 }
113
114 static void
115 _eval_top(Evas_Object *obj)
116 {
117    Widget_Data *wd = elm_widget_data_get(obj);
118    Item *it, *ittop;
119
120    if (!wd) return;
121    if (!wd->stack) return;
122    ittop = eina_list_last(wd->stack)->data;
123    if (ittop != wd->top)
124      {
125         Evas_Object *o= NULL, *prev_o = NULL;
126         const char *onshow, *onhide;
127         Eina_Bool pop = EINA_FALSE;
128         Evas_Coord w, y;
129         Elm_Transit *transit;
130
131         transit = elm_transit_add(obj);
132         evas_object_geometry_get( obj, NULL, &y, &w, NULL );
133
134         if (wd->top)
135           {
136              it = wd->top;
137              o = wd->top->base;
138
139              if (wd->top->popme) pop = EINA_TRUE;
140              if (ani) 
141                 {
142                   if (pop) elm_transit_fx_insert( transit, elm_fx_transfer_add(o , 0, y, w, y));
143                   else elm_transit_fx_insert( transit, elm_fx_transfer_add(o , 0, y, -w, y));
144                   elm_transit_completion_set( transit, _complete_cb, it);
145                 }
146              else 
147                 {
148                   _signal_hide_finished(wd->top, o, "elm,action,hide,finished", "");
149                 }       
150                  
151              onhide = edje_object_data_get(o, "onhide");
152              if (onhide)
153                {
154                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
155                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
156                }
157           }
158         wd->top = ittop;
159         prev_o = wd->top->base;
160         evas_object_show(prev_o);
161         
162         //add show/hide transition direction & animation on/off. 10.04.14 sohyun
163         if (ani && o) 
164           {
165             if (pop) elm_transit_fx_insert( transit, elm_fx_transfer_add(prev_o, -w, y, 0, y));
166             else elm_transit_fx_insert( transit, elm_fx_transfer_add(prev_o, w, y, 0, y));
167             elm_transit_run( transit, 0.3 );
168           }
169         elm_transit_del( transit ); 
170
171         onshow = edje_object_data_get(prev_o, "onshow");
172         if (onshow)
173           {
174              if (!strcmp(onshow, "raise")) evas_object_raise(prev_o);
175              else if (!strcmp(onshow, "lower")) evas_object_lower(prev_o);
176           }
177      }
178 }
179
180 static void
181 _move(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
182 {
183    Widget_Data *wd = elm_widget_data_get(data);
184    Evas_Coord x, y;
185    Eina_List *l;
186    Item *it;
187    if (!wd) return;
188    evas_object_geometry_get(obj, &x, &y, NULL, NULL);
189    EINA_LIST_FOREACH(wd->stack, l, it)
190      evas_object_move(it->base, x, y);
191 }
192
193 static void
194 _sub_del(void *data, Evas_Object *obj __UNUSED__, void *event_info)
195 {
196    Widget_Data *wd = elm_widget_data_get(data);
197    Evas_Object *sub = event_info;
198    Eina_List *l;
199    Item *it;
200    if (!wd) return;
201    EINA_LIST_FOREACH(wd->stack, l, it)
202      {
203         if (it->content == sub)
204           {
205              wd->stack = eina_list_remove_list(wd->stack, l);
206              evas_object_event_callback_del_full
207                (sub, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _changed_size_hints, it);
208              evas_object_del(it->base);
209              _eval_top(it->obj);
210              free(it);
211              return;
212           }
213      }
214 }
215
216 static void
217 _resize(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
218 {   
219    Widget_Data *wd = elm_widget_data_get(data);
220    Evas_Coord w, h;
221    Eina_List *l;
222    Item *it;
223    if (!wd) return;
224    evas_object_geometry_get(obj, NULL, NULL, &w, &h);
225    EINA_LIST_FOREACH(wd->stack, l, it) evas_object_resize(it->base, w, h);
226 }
227
228 static void
229 _signal_hide_finished(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
230 {
231    Item *it = data;
232    Evas_Object *obj2 = it->obj;
233    evas_object_hide(it->base);
234    edje_object_signal_emit(it->base, "elm,action,reset", "elm");
235    evas_object_smart_callback_call(obj2, "hide,finished", it->content);
236    edje_object_message_signal_process(it->base);
237    if (it->popme) evas_object_del(it->content);
238    _sizing_eval(obj2);
239 }
240
241 /**
242  * Add a new pager to the parent
243  *
244  * @param parent The parent object
245  * @return The new object or NULL if it cannot be created
246  *
247  * @ingroup Pager
248  */
249 EAPI Evas_Object *
250 elm_pager_add(Evas_Object *parent)
251 {
252    Evas_Object *obj;
253    Evas *e;
254    Widget_Data *wd;
255
256    wd = ELM_NEW(Widget_Data);
257    e = evas_object_evas_get(parent);
258    obj = elm_widget_add(e);
259    ELM_SET_WIDTYPE(widtype, "pager");
260    elm_widget_type_set(obj, "pager");
261    elm_widget_sub_object_add(parent, obj);
262    elm_widget_data_set(obj, wd);
263    elm_widget_del_hook_set(obj, _del_hook);
264    elm_widget_theme_hook_set(obj, _theme_hook);
265
266    wd->clip = evas_object_rectangle_add(e);
267    elm_widget_resize_object_set(obj, wd->clip);
268    elm_widget_sub_object_add(obj, wd->clip);
269
270    wd->rect = evas_object_rectangle_add(e);
271    elm_widget_sub_object_add(obj, wd->rect);
272    evas_object_color_set(wd->rect, 255, 255, 255, 0); 
273    evas_object_clip_set(wd->rect, wd->clip);
274
275    evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE, _move, obj);
276    evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, _resize, obj);
277
278    evas_object_smart_callback_add(obj, "sub-object-del", _sub_del, obj);
279
280    _sizing_eval(obj);
281    return obj;
282 }
283
284 /**
285  * Push an object to the top of the pager stack (and show it)
286  *
287  * The object pushed becomes a child of the pager and will be controlled
288  * it and deleted when the pager is deleted.
289  *
290  * @param obj The pager object
291  * @param content The object to push
292  *
293  * @ingroup Pager
294  */
295 EAPI void
296 elm_pager_content_push(Evas_Object *obj, Evas_Object *content)
297 {
298    ELM_CHECK_WIDTYPE(obj, widtype);
299    Widget_Data *wd = elm_widget_data_get(obj);
300    Item *it = ELM_NEW(Item);
301    Evas_Coord x, y, w, h;
302    if (!wd) return;
303    if (!it) return;
304    it->obj = obj;
305    it->content = content;
306    it->base = edje_object_add(evas_object_evas_get(obj));
307    evas_object_smart_member_add(it->base, obj);
308    evas_object_geometry_get(obj, &x, &y, &w, &h);
309    evas_object_move(it->base, x, y);
310    evas_object_resize(it->base, w, h);
311    evas_object_clip_set(it->base, wd->clip);
312    elm_widget_sub_object_add(obj, it->base);
313    if (it->content) elm_widget_sub_object_add(obj, it->content);
314    _elm_theme_object_set(obj, it->base,  "pager", "base", elm_widget_style_get(obj));
315    edje_object_signal_callback_add(it->base, "elm,action,hide,finished", "", 
316                                    _signal_hide_finished, it);
317    evas_object_event_callback_add(it->content,
318                                   EVAS_CALLBACK_CHANGED_SIZE_HINTS,
319                                   _changed_size_hints, it);
320    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
321    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
322    evas_object_show(it->content);
323    wd->stack = eina_list_append(wd->stack, it);
324    _eval_top(obj);
325    _sizing_eval(obj);
326 }
327
328 /**
329  * Pop the object that is on top of the stack
330  *
331  * This pops the object that is on top (visible) in the pager, makes it
332  * disappear, then deletes the object. The object that was underneath it
333  * on the stack will become visible.
334  *
335  * @param obj The pager object
336  *
337  * @ingroup Pager
338  */
339 EAPI void
340 elm_pager_content_pop(Evas_Object *obj)
341 {
342    ELM_CHECK_WIDTYPE(obj, widtype);
343    Widget_Data *wd = elm_widget_data_get(obj);
344    Eina_List *ll;
345    Item *it;
346    if (!wd) return;
347    if (!wd->stack) return;
348    it = eina_list_last(wd->stack)->data;
349    it->popme = EINA_TRUE;
350    ll = eina_list_last(wd->stack);
351    if (ll)
352      {
353         ll = ll->prev;
354         if (!ll)
355           {
356              Evas_Object *o;
357              const char *onhide;
358
359              wd->top = it;
360              o = wd->top->base;
361              if (ani) 
362                {
363                   Elm_Effect *transit;
364                   Evas_Coord w, y;
365       
366                   transit = elm_transit_add(obj);
367                   evas_object_geometry_get(obj, NULL, &y, &w, NULL);
368                   elm_transit_fx_insert(transit, elm_fx_transfer_add(o , 0, y, w, y));
369                   elm_transit_completion_set(transit, _complete_cb, it);
370                   elm_transit_run(transit, 0.3 );
371                   elm_transit_del(transit); 
372                 }
373              else _signal_hide_finished(wd->top, o, "elm,action,hide,finished", "");
374
375              onhide = edje_object_data_get(o, "onhide");
376              if (onhide)
377                {
378                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
379                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
380                }
381              wd->top = NULL;
382           }
383         else
384           {
385              it = ll->data;
386              elm_pager_content_promote(obj, it->content);
387           }
388      }
389 }
390
391 /**
392  * Promote an object already in the pager stack to the top of the stack
393  *
394  * This will take the indicated object and promote it to the top of the stack
395  * as if it had been pushed there. The object must already be inside the
396  * pager stack to work.
397  *
398  * @param obj The pager object
399  * @param content The object to promote
400  *
401  * @ingroup Pager
402  */
403 EAPI void
404 elm_pager_content_promote(Evas_Object *obj, Evas_Object *content)
405 {
406    ELM_CHECK_WIDTYPE(obj, widtype);
407    Widget_Data *wd = elm_widget_data_get(obj);
408    Eina_List *l;
409    Item *it;
410    if (!wd) return;
411    EINA_LIST_FOREACH(wd->stack, l, it)
412      {
413         if (it->content == content)
414           {
415              wd->stack = eina_list_remove_list(wd->stack, l);
416              wd->stack = eina_list_append(wd->stack, it);
417              _eval_top(obj);
418              return;
419           }
420      }
421 }
422
423 /**
424  * Return the object at the bottom of the pager stack
425  *
426  * @param obj The pager object
427  * @return The bottom object or NULL if none
428  *
429  * @ingroup Pager
430  */
431 EAPI Evas_Object *
432 elm_pager_content_bottom_get(const Evas_Object *obj)
433 {
434    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
435    Widget_Data *wd = elm_widget_data_get(obj);
436    Item *it;
437    if (!wd) return NULL;
438    if (!wd->stack) return NULL;
439    it = wd->stack->data;
440    return it->content;
441 }
442
443 /**
444  * Return the object at the top of the pager stack
445  *
446  * @param obj The pager object
447  * @return The top object or NULL if none
448  *
449  * @ingroup Pager
450  */
451 EAPI Evas_Object *
452 elm_pager_content_top_get(const Evas_Object *obj)
453 {
454    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
455    Widget_Data *wd = elm_widget_data_get(obj);
456    Item *it;
457    if (!wd) return NULL;
458    if (!wd->stack) return NULL;
459    it = eina_list_last(wd->stack)->data;
460    return it->content;
461 }
462
463 /**
464  * set animation on/off for content transition effect
465  *
466  * @param obj The pager object
467  * @param animation transition when contents are changed (default value : TRUE)
468  *
469  * @ingroup Pager
470  */
471 EAPI void
472 elm_pager_animation_set(Evas_Object *obj, Eina_Bool animation)
473 {
474         ani = animation;
475 }
476