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