Elementary: Added ui-mirroring support for all the widgets.
[framework/uifw/elementary.git] / src / lib / elm_actionslider.c
1 /**
2  * @addtogroup Actionslider Actionslider
3  *
4  * A magnet slider is a switcher for 3 labels with customizable
5  * magnet properties. When the position is set with magnet, the knob
6  * will be moved to it if it's nearest the magnetized position.
7  *
8  * Signals emmitted:
9  * "selected" - when user selects a position (the label is passed as
10  * event info)".
11  * "pos_changed" - when a button reaches to the special position like
12  * "left", "right" and "center".
13  */
14
15 #include <Elementary.h>
16 #include <math.h>
17 #include "elm_priv.h"
18
19 typedef struct _Widget_Data Widget_Data;
20
21 struct _Widget_Data
22 {
23    Evas_Object *as;     // actionslider
24    Evas_Object *drag_button_base;
25    Elm_Actionslider_Pos magnet_position, enabled_position;
26    const char *text_left, *text_right, *text_center;
27    const char *indicator_label;
28    Ecore_Animator *button_animator;
29    double final_position;
30    Eina_Bool mouse_down : 1;
31 };
32
33 static const char *widtype = NULL;
34
35 #define SIG_CHANGED "pos_changed"
36 #define SIG_SELECTED "selected"
37
38 static const Evas_Smart_Cb_Description _signals[] =
39 {
40    {SIG_CHANGED, ""},
41    {SIG_SELECTED, ""},
42    {NULL, NULL}
43 };
44
45
46 static void
47 _del_hook(Evas_Object *obj)
48 {
49    Widget_Data *wd = elm_widget_data_get(obj);
50    if (!wd) return;
51    if (wd->drag_button_base) 
52      {
53         evas_object_del(wd->drag_button_base);
54         wd->drag_button_base = NULL;
55      }
56    if (wd->text_left) eina_stringshare_del(wd->text_left);
57    if (wd->text_right) eina_stringshare_del(wd->text_right);
58    if (wd->text_center) eina_stringshare_del(wd->text_center);
59    if (wd->indicator_label) eina_stringshare_del(wd->indicator_label);
60    free(wd);
61 }
62
63 static Elm_Actionslider_Pos
64 _get_pos_by_orientation(const Evas_Object *obj, Elm_Actionslider_Pos pos)
65 {
66    if (elm_widget_mirrored_get(obj))
67      {
68         switch (pos)
69           {
70            case ELM_ACTIONSLIDER_LEFT:
71               pos = ELM_ACTIONSLIDER_RIGHT;
72               break;
73            case ELM_ACTIONSLIDER_RIGHT:
74               pos = ELM_ACTIONSLIDER_LEFT;
75               break;
76            default:
77               break;
78           }
79      }
80    return pos;
81 }
82
83 static void
84 _mirrored_set(Evas_Object *obj, Eina_Bool rtl)
85 {
86    Widget_Data *wd = elm_widget_data_get(obj);
87    double pos;
88
89    if (!wd) return;
90    if (edje_object_mirrored_get(wd->as) == rtl)
91      return;
92
93    edje_object_mirrored_set(wd->as, rtl);
94    if (!elm_widget_mirrored_get(obj))
95      {
96         edje_object_part_text_set(wd->as, "elm.text.left", wd->text_left);
97         edje_object_part_text_set(wd->as, "elm.text.right", wd->text_right);
98      }
99    else
100      {
101         edje_object_part_text_set(wd->as, "elm.text.left", wd->text_right);
102         edje_object_part_text_set(wd->as, "elm.text.right", wd->text_left);
103      }
104    edje_object_part_drag_value_get(wd->as, "elm.drag_button_base", &pos, NULL);
105    edje_object_part_drag_value_set(wd->as, "elm.drag_button_base", 1.0 - pos, 0.5);
106 }
107
108 static void
109 _sizing_eval(Evas_Object *obj)
110 {
111    Widget_Data *wd = elm_widget_data_get(obj);
112    Evas_Coord minw = -1, minh = -1;
113
114    if (!wd) return;
115    elm_coords_finger_size_adjust(1, &minw, 1, &minh);
116    evas_object_size_hint_min_set(wd->drag_button_base, minw, minh);
117    evas_object_size_hint_max_set(wd->drag_button_base, -1, -1);
118
119    minw = -1;
120    minh = -1;
121    elm_coords_finger_size_adjust(3, &minw, 1, &minh);
122    edje_object_size_min_restricted_calc(wd->as, &minw, &minh, minw, minh);
123    evas_object_size_hint_min_set(obj, minw, minh);
124    evas_object_size_hint_max_set(obj, -1, -1);
125 }
126
127 static void
128 _theme_hook(Evas_Object *obj)
129 {
130    Widget_Data *wd = elm_widget_data_get(obj);
131    if (!wd) return;
132    if (!edje_object_part_swallow_get(wd->as, "elm.drag_button_base"))
133      edje_object_part_unswallow(wd->as, wd->drag_button_base);
134
135    _elm_theme_object_set(obj, wd->as, "actionslider",
136                          "base", elm_widget_style_get(obj));
137    _elm_theme_object_set(obj, wd->drag_button_base, "actionslider",
138                          "drag_button", elm_widget_style_get(obj));
139    edje_object_part_swallow(wd->as, "elm.drag_button_base", wd->drag_button_base);
140
141    _mirrored_set(obj, elm_widget_mirrored_get(obj));
142    edje_object_part_text_set(wd->as, "elm.text.center", wd->text_center);
143    edje_object_part_text_set(wd->as, "elm.text.indicator", wd->indicator_label);
144    edje_object_message_signal_process(wd->as);
145    _sizing_eval(obj);
146 }
147
148 static void
149 _drag_button_down_cb(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
150 {
151    Widget_Data *wd = elm_widget_data_get((Evas_Object *) data);
152    if (!wd) return;
153    wd->mouse_down = EINA_TRUE;
154 }
155
156 static void
157 _drag_button_move_cb(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
158 {
159    Evas_Object *obj = (Evas_Object *) data;
160    Widget_Data *wd = elm_widget_data_get(obj);
161    double pos = 0.0;
162    if (!wd) return;
163
164    if (!wd->mouse_down) return;
165    edje_object_part_drag_value_get(wd->as, "elm.drag_button_base", &pos, NULL);
166    if (pos == 0.0)
167      evas_object_smart_callback_call(obj, SIG_CHANGED,
168               (void *) ((!elm_widget_mirrored_get(obj)) ?
169                  "left" : "right"));
170    else if (pos == 1.0)
171      evas_object_smart_callback_call(obj, SIG_CHANGED,
172               (void *) ((!elm_widget_mirrored_get(obj)) ?
173                  "right" : "left"));
174    else if (pos >= 0.45 && pos <= 0.55)
175      evas_object_smart_callback_call(obj, SIG_CHANGED, (void *)"center");
176 }
177
178 static Eina_Bool
179 _button_animation(void *data)
180 {
181    Evas_Object *obj = data;
182    Widget_Data *wd = elm_widget_data_get(obj);
183    double cur_position = 0.0, new_position = 0.0;
184    double move_amount = 0.05;
185    Eina_Bool flag_finish_animation = EINA_FALSE;
186    if (!wd) return EINA_FALSE;
187
188    edje_object_part_drag_value_get(wd->as,
189                                    "elm.drag_button_base", &cur_position, NULL);
190      {
191         double adjusted_final;
192         adjusted_final = (!elm_widget_mirrored_get(obj)) ?
193            wd->final_position : 1.0 - wd->final_position;
194         if ((adjusted_final == 0.0) ||
195               (adjusted_final == 0.5 && cur_position >= adjusted_final))
196           {
197              new_position = cur_position - move_amount;
198              if (new_position <= adjusted_final)
199                {
200                   new_position = adjusted_final;
201                   flag_finish_animation = EINA_TRUE;
202                }
203           }
204         else if ((adjusted_final == 1.0) ||
205               (adjusted_final == 0.5 && cur_position < adjusted_final))
206           {
207              new_position = cur_position + move_amount;
208              if (new_position >= adjusted_final)
209                {
210                   new_position = adjusted_final;
211                   flag_finish_animation = EINA_TRUE;
212                }
213           }
214         edje_object_part_drag_value_set(wd->as,
215               "elm.drag_button_base", new_position, 0.5);
216      }
217
218    if (flag_finish_animation)
219      {
220         if ((!wd->final_position) &&
221             (wd->enabled_position & ELM_ACTIONSLIDER_LEFT))
222           evas_object_smart_callback_call(data, SIG_SELECTED,
223                                           (void *)wd->text_left);
224         else if ((wd->final_position == 0.5) &&
225                  (wd->enabled_position & ELM_ACTIONSLIDER_CENTER))
226           evas_object_smart_callback_call(data, SIG_SELECTED,
227                                           (void *)wd->text_center);
228         else if ((wd->final_position == 1) &&
229                  (wd->enabled_position & ELM_ACTIONSLIDER_RIGHT))
230           evas_object_smart_callback_call(data, SIG_SELECTED,
231                                           (void *)wd->text_right);
232         return EINA_FALSE;
233      }
234    return EINA_TRUE;
235 }
236
237 static void
238 _drag_button_up_cb(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
239 {
240    Evas_Object *obj = (Evas_Object *) data;
241    Widget_Data *wd = elm_widget_data_get(obj);
242    double position = 0.0;
243    if (!wd) return;
244
245    wd->mouse_down = EINA_FALSE;
246
247    edje_object_part_drag_value_get(wd->as, "elm.drag_button_base",
248                                    &position, NULL);
249
250    if ((wd->enabled_position & ELM_ACTIONSLIDER_LEFT) &&
251          ((!elm_widget_mirrored_get(obj) && position == 0.0) ||
252          (elm_widget_mirrored_get(obj) && position == 1.0)))
253      {
254         wd->final_position = 0;
255         evas_object_smart_callback_call(data, SIG_SELECTED,
256               (void *) wd->text_left);
257         return;
258      }
259    if (position >= 0.45 && position <= 0.55 &&
260        (wd->enabled_position & ELM_ACTIONSLIDER_CENTER))
261      {
262         wd->final_position = 0.5;
263         evas_object_smart_callback_call(data, SIG_SELECTED,
264                                         (void *)wd->text_center);
265         return;
266      }
267    if ((wd->enabled_position & ELM_ACTIONSLIDER_RIGHT) &&
268          ((!elm_widget_mirrored_get(obj) && position == 1.0) ||
269          (elm_widget_mirrored_get(obj) && position == 0.0)))
270      {
271         wd->final_position = 1;
272         evas_object_smart_callback_call(data, SIG_SELECTED,
273               (void *) wd->text_right);
274         return;
275      }
276
277    if (wd->magnet_position == ELM_ACTIONSLIDER_NONE) return;
278
279 #define _FINAL_POS_BY_ORIENTATION(x) (x)
280 #define _POS_BY_ORIENTATION(x) \
281    ((!elm_widget_mirrored_get(obj)) ? \
282      x : 1.0 - x)
283
284    position = _POS_BY_ORIENTATION(position);
285
286    if (position < 0.3)
287      {
288         if (wd->magnet_position & ELM_ACTIONSLIDER_LEFT)
289           wd->final_position = _FINAL_POS_BY_ORIENTATION(0);
290         else if (wd->magnet_position & ELM_ACTIONSLIDER_CENTER)
291           wd->final_position = 0.5;
292         else if (wd->magnet_position & ELM_ACTIONSLIDER_RIGHT)
293           wd->final_position = _FINAL_POS_BY_ORIENTATION(1);
294      }
295    else if ((position >= 0.3) && (position <= 0.7))
296      {
297         if (wd->magnet_position & ELM_ACTIONSLIDER_CENTER)
298           wd->final_position = 0.5;
299         else if (position < 0.5)
300           {
301              if (wd->magnet_position & ELM_ACTIONSLIDER_LEFT)
302                wd->final_position = _FINAL_POS_BY_ORIENTATION(0);
303              else
304                wd->final_position = _FINAL_POS_BY_ORIENTATION(1);
305           }
306         else
307           {
308              if (wd->magnet_position & ELM_ACTIONSLIDER_RIGHT)
309                wd->final_position = _FINAL_POS_BY_ORIENTATION(1);
310              else
311                wd->final_position = _FINAL_POS_BY_ORIENTATION(0);
312           }
313      }
314    else
315      {
316         if (wd->magnet_position & ELM_ACTIONSLIDER_RIGHT)
317           wd->final_position = _FINAL_POS_BY_ORIENTATION(1);
318         else if (wd->magnet_position & ELM_ACTIONSLIDER_CENTER)
319           wd->final_position = 0.5;
320         else
321           wd->final_position = _FINAL_POS_BY_ORIENTATION(0);
322      }
323    wd->button_animator = ecore_animator_add(_button_animation, data);
324
325 #undef _FINAL_POS_BY_ORIENTATION
326 }
327
328 /**
329  * Add a new actionslider to the parent.
330  *
331  * @param parent The parent object
332  * @return The new actionslider object or NULL if it cannot be created
333  *
334  * @ingroup Actionslider
335  */
336 EAPI Evas_Object *
337 elm_actionslider_add(Evas_Object *parent)
338 {
339    Evas_Object *obj;
340    Widget_Data *wd;
341    Evas *e;
342
343    EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
344
345    wd = ELM_NEW(Widget_Data);
346    e = evas_object_evas_get(parent);
347    if (!e) return NULL;
348    obj = elm_widget_add(e);
349    ELM_SET_WIDTYPE(widtype, "actionslider");
350    elm_widget_type_set(obj, "actionslider");
351    elm_widget_sub_object_add(parent, obj);
352    elm_widget_data_set(obj, wd);
353    elm_widget_del_hook_set(obj, _del_hook);
354    elm_widget_theme_hook_set(obj, _theme_hook);
355
356    wd->mouse_down = EINA_FALSE;
357    wd->enabled_position = ELM_ACTIONSLIDER_ALL;
358
359    wd->as = edje_object_add(e);
360    _elm_theme_object_set(obj, wd->as, "actionslider", "base", "default");
361    elm_widget_resize_object_set(obj, wd->as);
362
363    wd->drag_button_base = evas_object_rectangle_add(e);
364    evas_object_color_set(wd->drag_button_base, 0, 0, 0, 0);
365    edje_object_part_swallow(wd->as, "elm.drag_button_base", wd->drag_button_base);
366
367    edje_object_signal_callback_add(wd->as,
368                                    "elm.drag_button,mouse,up", "",
369                                    _drag_button_up_cb, obj);
370    edje_object_signal_callback_add(wd->as,
371                                    "elm.drag_button,mouse,down", "",
372                                    _drag_button_down_cb, obj);
373    edje_object_signal_callback_add(wd->as,
374                                    "elm.drag_button,mouse,move", "",
375                                    _drag_button_move_cb, obj);
376
377    evas_object_smart_callbacks_descriptions_set(obj, _signals);
378    _mirrored_set(obj, elm_widget_mirrored_get(obj));
379    _sizing_eval(obj);
380    return obj;
381 }
382
383 /**
384  * Set actionslider indicator position.
385  *
386  * @param obj The actionslider object.
387  * @param pos The position of the indicator.
388  *
389  * @ingroup Actionslider
390  */
391 EAPI void
392 elm_actionslider_indicator_pos_set(Evas_Object *obj, Elm_Actionslider_Pos pos)
393 {
394    ELM_CHECK_WIDTYPE(obj, widtype);
395    Widget_Data *wd = elm_widget_data_get(obj);
396    double position = 0.0;
397    if (!wd) return;
398    pos = _get_pos_by_orientation(obj, pos);
399    if (pos == ELM_ACTIONSLIDER_CENTER) position = 0.5;
400    else if (pos == ELM_ACTIONSLIDER_RIGHT) position = 1.0;
401    edje_object_part_drag_value_set(wd->as, "elm.drag_button_base", position, 0.5);
402 }
403
404 /**
405  * Get actionslider indicator position.
406  *
407  * @param obj The actionslider object.
408  * @return The position of the indicator.
409  *
410  * @ingroup Actionslider
411  */
412 EAPI Elm_Actionslider_Pos
413 elm_actionslider_indicator_pos_get(const Evas_Object *obj)
414 {
415    ELM_CHECK_WIDTYPE(obj, widtype) ELM_ACTIONSLIDER_NONE;
416    Widget_Data *wd = elm_widget_data_get(obj);
417    double position;
418    if (!wd) return ELM_ACTIONSLIDER_NONE;
419
420    edje_object_part_drag_value_get(wd->as, "elm.drag_button_base", &position, NULL);
421    if (position < 0.3)
422      return _get_pos_by_orientation(obj, ELM_ACTIONSLIDER_LEFT);
423    else if (position < 0.7)
424      return ELM_ACTIONSLIDER_CENTER;
425    else
426      return _get_pos_by_orientation(obj, ELM_ACTIONSLIDER_RIGHT);
427 }
428
429 /**
430  * Set actionslider magnet position.
431  *
432  * @param obj The actionslider object.
433  * @param pos Bit mask indicating the magnet positions.
434  * Example: use (ELM_ACTIONSLIDER_LEFT | ELM_ACTIONSLIDER_RIGHT)
435  * to put magnet property on both positions
436  *
437  * @ingroup Actionslider
438  */
439 EAPI void
440 elm_actionslider_magnet_pos_set(Evas_Object *obj, Elm_Actionslider_Pos pos)
441 {
442    ELM_CHECK_WIDTYPE(obj, widtype);
443    Widget_Data *wd = elm_widget_data_get(obj);
444    if (!wd) return;
445    wd->magnet_position = pos;
446 }
447
448 /**
449  * Get actionslider magnet position.
450  *
451  * @param obj The actionslider object.
452  * @return The positions with magnet property.
453  *
454  * @ingroup Actionslider
455  */
456 EAPI Elm_Actionslider_Pos
457 elm_actionslider_magnet_pos_get(const Evas_Object *obj)
458 {
459    ELM_CHECK_WIDTYPE(obj, widtype) ELM_ACTIONSLIDER_NONE;
460    Widget_Data *wd = elm_widget_data_get(obj);
461    if (!wd) return ELM_ACTIONSLIDER_NONE;
462    return wd->magnet_position;
463 }
464
465 /**
466  * Set actionslider enabled position.
467  *
468  * All the positions are enabled by default.
469  *
470  * @param obj The actionslider object.
471  * @param pos Bit mask indicating the enabled positions.
472  * Example: use (ELM_ACTIONSLIDER_LEFT | ELM_ACTIONSLIDER_RIGHT)
473  * to enable both positions, so the user can select it.
474  *
475  * @ingroup Actionslider
476  */
477 EAPI void
478 elm_actionslider_enabled_pos_set(Evas_Object *obj, Elm_Actionslider_Pos pos)
479 {
480    ELM_CHECK_WIDTYPE(obj, widtype);
481    Widget_Data *wd = elm_widget_data_get(obj);
482    if (!wd) return;
483    wd->enabled_position = pos;
484 }
485
486 /**
487  * Get actionslider enabled position.
488  *
489  * All the positions are enabled by default.
490  *
491  * @param obj The actionslider object.
492  * @return The enabled positions.
493  *
494  * @ingroup Actionslider
495  */
496 EAPI Elm_Actionslider_Pos
497 elm_actionslider_enabled_pos_get(const Evas_Object *obj)
498 {
499    ELM_CHECK_WIDTYPE(obj, widtype) ELM_ACTIONSLIDER_NONE;
500    Widget_Data *wd = elm_widget_data_get(obj);
501    if (!wd) return ELM_ACTIONSLIDER_NONE;
502    return wd->enabled_position;
503 }
504
505 /**
506  * Set actionslider labels.
507  *
508  * @param obj The actionslider object
509  * @param left_label The label which is going to be set.
510  * @param center_label The label which is going to be set.
511  * @param right_label The label which is going to be set.
512  *
513  * @ingroup Actionslider
514  */
515 EAPI void
516 elm_actionslider_labels_set(Evas_Object *obj, const char *left_label, const char *center_label, const char *right_label)
517 {
518    ELM_CHECK_WIDTYPE(obj, widtype);
519    Widget_Data *wd = elm_widget_data_get(obj);
520    if (!wd) return;
521
522    eina_stringshare_replace(&wd->text_left, left_label);
523    eina_stringshare_replace(&wd->text_center, center_label);
524    eina_stringshare_replace(&wd->text_right, right_label);
525    if (!elm_widget_mirrored_get(obj))
526      {
527         edje_object_part_text_set(wd->as, "elm.text.left", wd->text_left);
528         edje_object_part_text_set(wd->as, "elm.text.right", wd->text_right);
529      }
530    else
531      {
532         edje_object_part_text_set(wd->as, "elm.text.left", wd->text_right);
533         edje_object_part_text_set(wd->as, "elm.text.right", wd->text_left);
534      }
535    edje_object_part_text_set(wd->as, "elm.text.center", center_label);
536 }
537
538 /**
539  * Get actionslider labels.
540  *
541  * @param obj The actionslider object
542  * @param left_label A char** to place the left_label of @p obj into
543  * @param center_label A char** to place the center_label of @p obj into
544  * @param right_label A char** to place the right_label of @p obj into
545  *
546  * @ingroup Actionslider
547  */
548 EAPI void
549 elm_actionslider_labels_get(const Evas_Object *obj, const char **left_label, const char **center_label, const char **right_label)
550 {
551    if (left_label) *left_label= NULL;
552    if (center_label) *center_label= NULL;
553    if (right_label) *right_label= NULL;
554    ELM_CHECK_WIDTYPE(obj, widtype);
555    Widget_Data *wd = elm_widget_data_get(obj);
556    if (!wd) return;
557    if (left_label) *left_label = wd->text_left;
558    if (center_label) *center_label = wd->text_center;
559    if (right_label) *right_label = wd->text_right;
560 }
561
562 /**
563  * Get actionslider selected label.
564  *
565  * @param obj The actionslider object
566  * @return The selected label
567  *
568  * @ingroup Actionslider
569  */
570 EAPI const char *
571 elm_actionslider_selected_label_get(const Evas_Object *obj)
572 {
573    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
574    Widget_Data *wd = elm_widget_data_get(obj);
575    if (!wd) return NULL;
576
577    if ((wd->final_position == 0.0) &&
578        (wd->enabled_position & ELM_ACTIONSLIDER_LEFT))
579      return wd->text_left;
580
581    if ((wd->final_position == 0.5) &&
582        (wd->enabled_position & ELM_ACTIONSLIDER_CENTER))
583      return wd->text_center;
584
585    if ((wd->final_position == 1.0) &&
586        (wd->enabled_position & ELM_ACTIONSLIDER_RIGHT))
587      return wd->text_right;
588
589    return NULL;
590 }
591
592 /**
593  * Set the label used on the indicator object.
594  *
595  * @param obj The actionslider object
596  * @param label The label which is going to be set.
597  *
598  * @ingroup Actionslider
599  */
600 EAPI void 
601 elm_actionslider_indicator_label_set(Evas_Object *obj, const char *label)
602 {
603    ELM_CHECK_WIDTYPE(obj, widtype);
604    Widget_Data *wd = elm_widget_data_get(obj);
605    if (!wd) return;
606
607    eina_stringshare_replace(&wd->indicator_label, label);
608    edje_object_part_text_set(wd->as, "elm.text.indicator", wd->indicator_label);
609 }
610
611 /**
612  * Get the label used on the indicator object.
613  *
614  * @param obj The actionslider object
615  * @return The indicator label
616  *
617  * @ingroup Actionslider
618  */
619 EAPI const char *
620 elm_actionslider_indicator_label_get(Evas_Object *obj)
621 {
622    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
623    Widget_Data *wd = elm_widget_data_get(obj);
624    if (!wd) return NULL;
625    return wd->indicator_label;
626 }