From: WooHyun Jung <wh0705.jung@samsung.com>
[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    _elm_widget_mirrored_reload(obj);
133    if (!edje_object_part_swallow_get(wd->as, "elm.drag_button_base"))
134       edje_object_part_unswallow(wd->as, wd->drag_button_base);
135
136    _elm_theme_object_set(obj, wd->as, "actionslider",
137                          "base", elm_widget_style_get(obj));
138    _elm_theme_object_set(obj, wd->drag_button_base, "actionslider",
139                          "drag_button", elm_widget_style_get(obj));
140    edje_object_part_swallow(wd->as, "elm.drag_button_base", wd->drag_button_base);
141
142    _mirrored_set(obj, elm_widget_mirrored_get(obj));
143    edje_object_part_text_set(wd->as, "elm.text.center", wd->text_center);
144    edje_object_part_text_set(wd->as, "elm.text.indicator", wd->indicator_label);
145    edje_object_message_signal_process(wd->as);
146    _sizing_eval(obj);
147 }
148
149 static void
150 _drag_button_down_cb(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
151 {
152    Widget_Data *wd = elm_widget_data_get((Evas_Object *) data);
153    if (!wd) return;
154    wd->mouse_down = EINA_TRUE;
155 }
156
157 static void
158 _drag_button_move_cb(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
159 {
160    Evas_Object *obj = (Evas_Object *) data;
161    Widget_Data *wd = elm_widget_data_get(obj);
162    double pos = 0.0;
163    if (!wd) return;
164
165    if (!wd->mouse_down) return;
166    edje_object_part_drag_value_get(wd->as, "elm.drag_button_base", &pos, NULL);
167    if (pos == 0.0)
168       evas_object_smart_callback_call(obj, SIG_CHANGED,
169                                       (void *) ((!elm_widget_mirrored_get(obj)) ?
170                                                 "left" : "right"));
171    else if (pos == 1.0)
172       evas_object_smart_callback_call(obj, SIG_CHANGED,
173                                       (void *) ((!elm_widget_mirrored_get(obj)) ?
174                                                 "right" : "left"));
175    else if (pos >= 0.45 && pos <= 0.55)
176       evas_object_smart_callback_call(obj, SIG_CHANGED, (void *)"center");
177 }
178
179 static Eina_Bool
180 _button_animation(void *data)
181 {
182    Evas_Object *obj = data;
183    Widget_Data *wd = elm_widget_data_get(obj);
184    double cur_position = 0.0, new_position = 0.0;
185    double move_amount = 0.05;
186    Eina_Bool flag_finish_animation = EINA_FALSE;
187    if (!wd) return EINA_FALSE;
188
189    edje_object_part_drag_value_get(wd->as,
190                                    "elm.drag_button_base", &cur_position, NULL);
191      {
192         double adjusted_final;
193         adjusted_final = (!elm_widget_mirrored_get(obj)) ?
194            wd->final_position : 1.0 - wd->final_position;
195         if ((adjusted_final == 0.0) ||
196             (adjusted_final == 0.5 && cur_position >= adjusted_final))
197           {
198              new_position = cur_position - move_amount;
199              if (new_position <= adjusted_final)
200                {
201                   new_position = adjusted_final;
202                   flag_finish_animation = EINA_TRUE;
203                }
204           }
205         else if ((adjusted_final == 1.0) ||
206                  (adjusted_final == 0.5 && cur_position < adjusted_final))
207           {
208              new_position = cur_position + move_amount;
209              if (new_position >= adjusted_final)
210                {
211                   new_position = adjusted_final;
212                   flag_finish_animation = EINA_TRUE;
213                }
214           }
215         edje_object_part_drag_value_set(wd->as,
216                                         "elm.drag_button_base", new_position, 0.5);
217      }
218
219    if (flag_finish_animation)
220      {
221         if ((!wd->final_position) &&
222             (wd->enabled_position & ELM_ACTIONSLIDER_LEFT))
223            evas_object_smart_callback_call(data, SIG_SELECTED,
224                                            (void *)wd->text_left);
225         else if ((wd->final_position == 0.5) &&
226                  (wd->enabled_position & ELM_ACTIONSLIDER_CENTER))
227            evas_object_smart_callback_call(data, SIG_SELECTED,
228                                            (void *)wd->text_center);
229         else if ((wd->final_position == 1) &&
230                  (wd->enabled_position & ELM_ACTIONSLIDER_RIGHT))
231            evas_object_smart_callback_call(data, SIG_SELECTED,
232                                            (void *)wd->text_right);
233         return EINA_FALSE;
234      }
235    return EINA_TRUE;
236 }
237
238 static void
239 _drag_button_up_cb(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
240 {
241    Evas_Object *obj = (Evas_Object *) data;
242    Widget_Data *wd = elm_widget_data_get(obj);
243    double position = 0.0;
244    if (!wd) return;
245
246    wd->mouse_down = EINA_FALSE;
247
248    edje_object_part_drag_value_get(wd->as, "elm.drag_button_base",
249                                    &position, NULL);
250
251    if ((wd->enabled_position & ELM_ACTIONSLIDER_LEFT) &&
252        ((!elm_widget_mirrored_get(obj) && position == 0.0) ||
253         (elm_widget_mirrored_get(obj) && position == 1.0)))
254      {
255         wd->final_position = 0;
256         evas_object_smart_callback_call(data, SIG_SELECTED,
257                                         (void *) wd->text_left);
258         return;
259      }
260    if (position >= 0.45 && position <= 0.55 &&
261        (wd->enabled_position & ELM_ACTIONSLIDER_CENTER))
262      {
263         wd->final_position = 0.5;
264         evas_object_smart_callback_call(data, SIG_SELECTED,
265                                         (void *)wd->text_center);
266         return;
267      }
268    if ((wd->enabled_position & ELM_ACTIONSLIDER_RIGHT) &&
269        ((!elm_widget_mirrored_get(obj) && position == 1.0) ||
270         (elm_widget_mirrored_get(obj) && position == 0.0)))
271      {
272         wd->final_position = 1;
273         evas_object_smart_callback_call(data, SIG_SELECTED,
274                                         (void *) wd->text_right);
275         return;
276      }
277
278    if (wd->magnet_position == ELM_ACTIONSLIDER_NONE) return;
279
280 #define _FINAL_POS_BY_ORIENTATION(x) (x)
281 #define _POS_BY_ORIENTATION(x) \
282    ((!elm_widget_mirrored_get(obj)) ? \
283     x : 1.0 - x)
284
285    position = _POS_BY_ORIENTATION(position);
286
287    if (position < 0.3)
288      {
289         if (wd->magnet_position & ELM_ACTIONSLIDER_LEFT)
290            wd->final_position = _FINAL_POS_BY_ORIENTATION(0);
291         else if (wd->magnet_position & ELM_ACTIONSLIDER_CENTER)
292            wd->final_position = 0.5;
293         else if (wd->magnet_position & ELM_ACTIONSLIDER_RIGHT)
294            wd->final_position = _FINAL_POS_BY_ORIENTATION(1);
295      }
296    else if ((position >= 0.3) && (position <= 0.7))
297      {
298         if (wd->magnet_position & ELM_ACTIONSLIDER_CENTER)
299            wd->final_position = 0.5;
300         else if (position < 0.5)
301           {
302              if (wd->magnet_position & ELM_ACTIONSLIDER_LEFT)
303                 wd->final_position = _FINAL_POS_BY_ORIENTATION(0);
304              else
305                 wd->final_position = _FINAL_POS_BY_ORIENTATION(1);
306           }
307         else
308           {
309              if (wd->magnet_position & ELM_ACTIONSLIDER_RIGHT)
310                 wd->final_position = _FINAL_POS_BY_ORIENTATION(1);
311              else
312                 wd->final_position = _FINAL_POS_BY_ORIENTATION(0);
313           }
314      }
315    else
316      {
317         if (wd->magnet_position & ELM_ACTIONSLIDER_RIGHT)
318            wd->final_position = _FINAL_POS_BY_ORIENTATION(1);
319         else if (wd->magnet_position & ELM_ACTIONSLIDER_CENTER)
320            wd->final_position = 0.5;
321         else
322            wd->final_position = _FINAL_POS_BY_ORIENTATION(0);
323      }
324    wd->button_animator = ecore_animator_add(_button_animation, data);
325
326 #undef _FINAL_POS_BY_ORIENTATION
327 }
328
329 /**
330  * Add a new actionslider to the parent.
331  *
332  * @param parent The parent object
333  * @return The new actionslider object or NULL if it cannot be created
334  *
335  * @ingroup Actionslider
336  */
337 EAPI Evas_Object *
338 elm_actionslider_add(Evas_Object *parent)
339 {
340    Evas_Object *obj;
341    Widget_Data *wd;
342    Evas *e;
343
344    EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
345
346    wd = ELM_NEW(Widget_Data);
347    e = evas_object_evas_get(parent);
348    if (!e) return NULL;
349    obj = elm_widget_add(e);
350    ELM_SET_WIDTYPE(widtype, "actionslider");
351    elm_widget_type_set(obj, "actionslider");
352    elm_widget_sub_object_add(parent, obj);
353    elm_widget_data_set(obj, wd);
354    elm_widget_del_hook_set(obj, _del_hook);
355    elm_widget_theme_hook_set(obj, _theme_hook);
356
357    wd->mouse_down = EINA_FALSE;
358    wd->enabled_position = ELM_ACTIONSLIDER_ALL;
359
360    wd->as = edje_object_add(e);
361    _elm_theme_object_set(obj, wd->as, "actionslider", "base", "default");
362    elm_widget_resize_object_set(obj, wd->as);
363
364    wd->drag_button_base = evas_object_rectangle_add(e);
365    evas_object_color_set(wd->drag_button_base, 0, 0, 0, 0);
366    edje_object_part_swallow(wd->as, "elm.drag_button_base", wd->drag_button_base);
367
368    edje_object_signal_callback_add(wd->as,
369                                    "elm.drag_button,mouse,up", "",
370                                    _drag_button_up_cb, obj);
371    edje_object_signal_callback_add(wd->as,
372                                    "elm.drag_button,mouse,down", "",
373                                    _drag_button_down_cb, obj);
374    edje_object_signal_callback_add(wd->as,
375                                    "elm.drag_button,mouse,move", "",
376                                    _drag_button_move_cb, obj);
377
378    evas_object_smart_callbacks_descriptions_set(obj, _signals);
379    _mirrored_set(obj, elm_widget_mirrored_get(obj));
380    _sizing_eval(obj);
381    return obj;
382 }
383
384 /**
385  * Set actionslider indicator position.
386  *
387  * @param obj The actionslider object.
388  * @param pos The position of the indicator.
389  *
390  * @ingroup Actionslider
391  */
392 EAPI void
393 elm_actionslider_indicator_pos_set(Evas_Object *obj, Elm_Actionslider_Pos pos)
394 {
395    ELM_CHECK_WIDTYPE(obj, widtype);
396    Widget_Data *wd = elm_widget_data_get(obj);
397    double position = 0.0;
398    if (!wd) return;
399    pos = _get_pos_by_orientation(obj, pos);
400    if (pos == ELM_ACTIONSLIDER_CENTER) position = 0.5;
401    else if (pos == ELM_ACTIONSLIDER_RIGHT) position = 1.0;
402    edje_object_part_drag_value_set(wd->as, "elm.drag_button_base", position, 0.5);
403 }
404
405 /**
406  * Get actionslider indicator position.
407  *
408  * @param obj The actionslider object.
409  * @return The position of the indicator.
410  *
411  * @ingroup Actionslider
412  */
413 EAPI Elm_Actionslider_Pos
414 elm_actionslider_indicator_pos_get(const Evas_Object *obj)
415 {
416    ELM_CHECK_WIDTYPE(obj, widtype) ELM_ACTIONSLIDER_NONE;
417    Widget_Data *wd = elm_widget_data_get(obj);
418    double position;
419    if (!wd) return ELM_ACTIONSLIDER_NONE;
420
421    edje_object_part_drag_value_get(wd->as, "elm.drag_button_base", &position, NULL);
422    if (position < 0.3)
423       return _get_pos_by_orientation(obj, ELM_ACTIONSLIDER_LEFT);
424    else if (position < 0.7)
425       return ELM_ACTIONSLIDER_CENTER;
426    else
427       return _get_pos_by_orientation(obj, ELM_ACTIONSLIDER_RIGHT);
428 }
429
430 /**
431  * Set actionslider magnet position.
432  *
433  * @param obj The actionslider object.
434  * @param pos Bit mask indicating the magnet positions.
435  * Example: use (ELM_ACTIONSLIDER_LEFT | ELM_ACTIONSLIDER_RIGHT)
436  * to put magnet property on both positions
437  *
438  * @ingroup Actionslider
439  */
440 EAPI void
441 elm_actionslider_magnet_pos_set(Evas_Object *obj, Elm_Actionslider_Pos pos)
442 {
443    ELM_CHECK_WIDTYPE(obj, widtype);
444    Widget_Data *wd = elm_widget_data_get(obj);
445    if (!wd) return;
446    wd->magnet_position = pos;
447 }
448
449 /**
450  * Get actionslider magnet position.
451  *
452  * @param obj The actionslider object.
453  * @return The positions with magnet property.
454  *
455  * @ingroup Actionslider
456  */
457 EAPI Elm_Actionslider_Pos
458 elm_actionslider_magnet_pos_get(const Evas_Object *obj)
459 {
460    ELM_CHECK_WIDTYPE(obj, widtype) ELM_ACTIONSLIDER_NONE;
461    Widget_Data *wd = elm_widget_data_get(obj);
462    if (!wd) return ELM_ACTIONSLIDER_NONE;
463    return wd->magnet_position;
464 }
465
466 /**
467  * Set actionslider enabled position.
468  *
469  * All the positions are enabled by default.
470  *
471  * @param obj The actionslider object.
472  * @param pos Bit mask indicating the enabled positions.
473  * Example: use (ELM_ACTIONSLIDER_LEFT | ELM_ACTIONSLIDER_RIGHT)
474  * to enable both positions, so the user can select it.
475  *
476  * @ingroup Actionslider
477  */
478 EAPI void
479 elm_actionslider_enabled_pos_set(Evas_Object *obj, Elm_Actionslider_Pos pos)
480 {
481    ELM_CHECK_WIDTYPE(obj, widtype);
482    Widget_Data *wd = elm_widget_data_get(obj);
483    if (!wd) return;
484    wd->enabled_position = pos;
485 }
486
487 /**
488  * Get actionslider enabled position.
489  *
490  * All the positions are enabled by default.
491  *
492  * @param obj The actionslider object.
493  * @return The enabled positions.
494  *
495  * @ingroup Actionslider
496  */
497 EAPI Elm_Actionslider_Pos
498 elm_actionslider_enabled_pos_get(const Evas_Object *obj)
499 {
500    ELM_CHECK_WIDTYPE(obj, widtype) ELM_ACTIONSLIDER_NONE;
501    Widget_Data *wd = elm_widget_data_get(obj);
502    if (!wd) return ELM_ACTIONSLIDER_NONE;
503    return wd->enabled_position;
504 }
505
506 /**
507  * Set actionslider labels.
508  *
509  * @param obj The actionslider object
510  * @param left_label The label which is going to be set.
511  * @param center_label The label which is going to be set.
512  * @param right_label The label which is going to be set.
513  *
514  * @ingroup Actionslider
515  */
516 EAPI void
517 elm_actionslider_labels_set(Evas_Object *obj, const char *left_label, const char *center_label, const char *right_label)
518 {
519    ELM_CHECK_WIDTYPE(obj, widtype);
520    Widget_Data *wd = elm_widget_data_get(obj);
521    if (!wd) return;
522
523    eina_stringshare_replace(&wd->text_left, left_label);
524    eina_stringshare_replace(&wd->text_center, center_label);
525    eina_stringshare_replace(&wd->text_right, right_label);
526    if (!elm_widget_mirrored_get(obj))
527      {
528         edje_object_part_text_set(wd->as, "elm.text.left", wd->text_left);
529         edje_object_part_text_set(wd->as, "elm.text.right", wd->text_right);
530      }
531    else
532      {
533         edje_object_part_text_set(wd->as, "elm.text.left", wd->text_right);
534         edje_object_part_text_set(wd->as, "elm.text.right", wd->text_left);
535      }
536    edje_object_part_text_set(wd->as, "elm.text.center", center_label);
537 }
538
539 /**
540  * Get actionslider labels.
541  *
542  * @param obj The actionslider object
543  * @param left_label A char** to place the left_label of @p obj into
544  * @param center_label A char** to place the center_label of @p obj into
545  * @param right_label A char** to place the right_label of @p obj into
546  *
547  * @ingroup Actionslider
548  */
549 EAPI void
550 elm_actionslider_labels_get(const Evas_Object *obj, const char **left_label, const char **center_label, const char **right_label)
551 {
552    if (left_label) *left_label= NULL;
553    if (center_label) *center_label= NULL;
554    if (right_label) *right_label= NULL;
555    ELM_CHECK_WIDTYPE(obj, widtype);
556    Widget_Data *wd = elm_widget_data_get(obj);
557    if (!wd) return;
558    if (left_label) *left_label = wd->text_left;
559    if (center_label) *center_label = wd->text_center;
560    if (right_label) *right_label = wd->text_right;
561 }
562
563 /**
564  * Get actionslider selected label.
565  *
566  * @param obj The actionslider object
567  * @return The selected label
568  *
569  * @ingroup Actionslider
570  */
571 EAPI const char *
572 elm_actionslider_selected_label_get(const Evas_Object *obj)
573 {
574    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
575    Widget_Data *wd = elm_widget_data_get(obj);
576    if (!wd) return NULL;
577
578    if ((wd->final_position == 0.0) &&
579        (wd->enabled_position & ELM_ACTIONSLIDER_LEFT))
580       return wd->text_left;
581
582    if ((wd->final_position == 0.5) &&
583        (wd->enabled_position & ELM_ACTIONSLIDER_CENTER))
584       return wd->text_center;
585
586    if ((wd->final_position == 1.0) &&
587        (wd->enabled_position & ELM_ACTIONSLIDER_RIGHT))
588       return wd->text_right;
589
590    return NULL;
591 }
592
593 /**
594  * Set the label used on the indicator object.
595  *
596  * @param obj The actionslider object
597  * @param label The label which is going to be set.
598  *
599  * @ingroup Actionslider
600  */
601 EAPI void 
602 elm_actionslider_indicator_label_set(Evas_Object *obj, const char *label)
603 {
604    ELM_CHECK_WIDTYPE(obj, widtype);
605    Widget_Data *wd = elm_widget_data_get(obj);
606    if (!wd) return;
607
608    eina_stringshare_replace(&wd->indicator_label, label);
609    edje_object_part_text_set(wd->as, "elm.text.indicator", wd->indicator_label);
610 }
611
612 /**
613  * Get the label used on the indicator object.
614  *
615  * @param obj The actionslider object
616  * @return The indicator label
617  *
618  * @ingroup Actionslider
619  */
620 EAPI const char *
621 elm_actionslider_indicator_label_get(Evas_Object *obj)
622 {
623    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
624    Widget_Data *wd = elm_widget_data_get(obj);
625    if (!wd) return NULL;
626    return wd->indicator_label;
627 }