1 /**
2  * @file lv_slider.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_slider.h"
10 #if LV_USE_SLIDER != 0
11 
12 #include "../misc/lv_assert.h"
13 #include "../core/lv_group.h"
14 #include "../core/lv_indev.h"
15 #include "../draw/lv_draw.h"
16 #include "../misc/lv_math.h"
17 #include "../core/lv_disp.h"
18 #include "lv_img.h"
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 #define MY_CLASS &lv_slider_class
24 
25 #define LV_SLIDER_KNOB_COORD(is_rtl, area) (is_rtl ? area.x1 : area.x2)
26 
27 /**********************
28  *      TYPEDEFS
29  **********************/
30 
31 /**********************
32  *  STATIC PROTOTYPES
33  **********************/
34 static void lv_slider_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
35 static void lv_slider_event(const lv_obj_class_t * class_p, lv_event_t * e);
36 static void position_knob(lv_obj_t * obj, lv_area_t * knob_area, const lv_coord_t knob_size, const bool hor);
37 static void draw_knob(lv_event_t * e);
38 static bool is_slider_horizontal(lv_obj_t * obj);
39 
40 /**********************
41  *  STATIC VARIABLES
42  **********************/
43 const lv_obj_class_t lv_slider_class = {
44     .constructor_cb = lv_slider_constructor,
45     .event_cb = lv_slider_event,
46     .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
47     .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
48     .instance_size = sizeof(lv_slider_t),
49     .base_class = &lv_bar_class
50 };
51 
52 /**********************
53  *      MACROS
54  **********************/
55 
56 /**********************
57  *   GLOBAL FUNCTIONS
58  **********************/
59 
lv_slider_create(lv_obj_t * parent)60 lv_obj_t * lv_slider_create(lv_obj_t * parent)
61 {
62     LV_LOG_INFO("begin");
63     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
64     lv_obj_class_init_obj(obj);
65     return obj;
66 }
67 
lv_slider_is_dragged(const lv_obj_t * obj)68 bool lv_slider_is_dragged(const lv_obj_t * obj)
69 {
70     LV_ASSERT_OBJ(obj, MY_CLASS);
71     lv_slider_t * slider = (lv_slider_t *)obj;
72 
73     return slider->dragging ? true : false;
74 }
75 
76 /**********************
77  *   STATIC FUNCTIONS
78  **********************/
79 
lv_slider_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)80 static void lv_slider_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
81 {
82     LV_UNUSED(class_p);
83     lv_slider_t * slider = (lv_slider_t *)obj;
84 
85     /*Initialize the allocated 'slider'*/
86     slider->value_to_set = NULL;
87     slider->dragging = 0U;
88     slider->left_knob_focus = 0U;
89 
90     lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN);
91     lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
92     lv_obj_set_ext_click_area(obj, LV_DPX(8));
93 }
94 
lv_slider_event(const lv_obj_class_t * class_p,lv_event_t * e)95 static void lv_slider_event(const lv_obj_class_t * class_p, lv_event_t * e)
96 {
97     LV_UNUSED(class_p);
98 
99     lv_res_t res;
100 
101     /*Call the ancestor's event handler*/
102     res = lv_obj_event_base(MY_CLASS, e);
103     if(res != LV_RES_OK) return;
104 
105     lv_event_code_t code = lv_event_get_code(e);
106     lv_obj_t * obj = lv_event_get_target(e);
107     lv_slider_t * slider = (lv_slider_t *)obj;
108     lv_slider_mode_t type = lv_slider_get_mode(obj);
109 
110     /*Advanced hit testing: react only on dragging the knob(s)*/
111     if(code == LV_EVENT_HIT_TEST) {
112         lv_hit_test_info_t * info = lv_event_get_param(e);
113         lv_coord_t ext_click_area = obj->spec_attr ? obj->spec_attr->ext_click_pad : 0;
114 
115         /*Ordinary slider: was the knob area hit?*/
116         lv_area_t a;
117         lv_area_copy(&a, &slider->right_knob_area);
118         lv_area_increase(&a, ext_click_area, ext_click_area);
119         info->res = _lv_area_is_point_on(&a, info->point, 0);
120 
121         /*There's still a chance that there is a hit if there is another knob*/
122         if((info->res == false) && (type == LV_SLIDER_MODE_RANGE)) {
123             lv_area_copy(&a, &slider->left_knob_area);
124             lv_area_increase(&a, ext_click_area, ext_click_area);
125             info->res = _lv_area_is_point_on(&a, info->point, 0);
126         }
127     }
128     else if(code == LV_EVENT_PRESSED) {
129         lv_obj_invalidate(obj);
130 
131         lv_point_t p;
132         slider->dragging = true;
133         if(type == LV_SLIDER_MODE_NORMAL || type == LV_SLIDER_MODE_SYMMETRICAL) {
134             slider->value_to_set = &slider->bar.cur_value;
135         }
136         else if(type == LV_SLIDER_MODE_RANGE) {
137             lv_indev_get_point(lv_indev_get_act(), &p);
138             bool hor = lv_obj_get_width(obj) >= lv_obj_get_height(obj);
139             lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
140 
141             lv_coord_t dist_left, dist_right;
142             if(hor) {
143                 if((base_dir != LV_BASE_DIR_RTL && p.x > slider->right_knob_area.x2) || (base_dir == LV_BASE_DIR_RTL &&
144                                                                                          p.x < slider->right_knob_area.x1)) {
145                     slider->value_to_set = &slider->bar.cur_value;
146                 }
147                 else if((base_dir != LV_BASE_DIR_RTL && p.x < slider->left_knob_area.x1) || (base_dir == LV_BASE_DIR_RTL &&
148                                                                                              p.x > slider->left_knob_area.x2)) {
149                     slider->value_to_set = &slider->bar.start_value;
150                 }
151                 else {
152                     /*Calculate the distance from each knob*/
153                     dist_left = LV_ABS((slider->left_knob_area.x1 + (slider->left_knob_area.x2 - slider->left_knob_area.x1) / 2) - p.x);
154                     dist_right = LV_ABS((slider->right_knob_area.x1 + (slider->right_knob_area.x2 - slider->right_knob_area.x1) / 2) - p.x);
155 
156                     /*Use whichever one is closer*/
157                     if(dist_right < dist_left) {
158                         slider->value_to_set = &slider->bar.cur_value;
159                         slider->left_knob_focus = 0;
160                     }
161                     else {
162                         slider->value_to_set = &slider->bar.start_value;
163                         slider->left_knob_focus = 1;
164                     }
165                 }
166             }
167             else {
168                 if(p.y < slider->right_knob_area.y1) {
169                     slider->value_to_set = &slider->bar.cur_value;
170                 }
171                 else if(p.y > slider->left_knob_area.y2) {
172                     slider->value_to_set = &slider->bar.start_value;
173                 }
174                 else {
175                     /*Calculate the distance from each knob*/
176                     dist_left = LV_ABS((slider->left_knob_area.y1 + (slider->left_knob_area.y2 - slider->left_knob_area.y1) / 2) - p.y);
177                     dist_right = LV_ABS((slider->right_knob_area.y1 + (slider->right_knob_area.y2 - slider->right_knob_area.y1) / 2) - p.y);
178 
179                     /*Use whichever one is closer*/
180                     if(dist_right < dist_left) {
181                         slider->value_to_set = &slider->bar.cur_value;
182                         slider->left_knob_focus = 0;
183                     }
184                     else {
185                         slider->value_to_set = &slider->bar.start_value;
186                         slider->left_knob_focus = 1;
187                     }
188                 }
189             }
190         }
191     }
192     else if(code == LV_EVENT_PRESSING && slider->value_to_set != NULL) {
193         lv_indev_t * indev = lv_indev_get_act();
194         if(lv_indev_get_type(indev) != LV_INDEV_TYPE_POINTER) return;
195 
196         lv_point_t p;
197         lv_indev_get_point(indev, &p);
198         int32_t new_value = 0;
199 
200         const int32_t range = slider->bar.max_value - slider->bar.min_value;
201         if(is_slider_horizontal(obj)) {
202             const lv_coord_t bg_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
203             const lv_coord_t bg_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
204             const lv_coord_t w = lv_obj_get_width(obj);
205             const lv_coord_t indic_w = w - bg_left - bg_right;
206 
207             if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL) {
208                 /*Make the point relative to the indicator*/
209                 new_value = (obj->coords.x2 - bg_right) - p.x;
210             }
211             else {
212                 /*Make the point relative to the indicator*/
213                 new_value = p.x - (obj->coords.x1 + bg_left);
214             }
215             new_value = (new_value * range + indic_w / 2) / indic_w;
216             new_value += slider->bar.min_value;
217         }
218         else {
219             const lv_coord_t bg_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
220             const lv_coord_t bg_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
221             const lv_coord_t h = lv_obj_get_height(obj);
222             const lv_coord_t indic_h = h - bg_bottom - bg_top;
223 
224             /*Make the point relative to the indicator*/
225             new_value = p.y - (obj->coords.y2 + bg_bottom);
226             new_value = (-new_value * range + indic_h / 2) / indic_h;
227             new_value += slider->bar.min_value;
228         }
229 
230         int32_t real_max_value = slider->bar.max_value;
231         int32_t real_min_value = slider->bar.min_value;
232         /*Figure out the min. and max. for this mode*/
233         if(slider->value_to_set == &slider->bar.start_value) {
234             real_max_value = slider->bar.cur_value;
235         }
236         else {
237             real_min_value = slider->bar.start_value;
238         }
239 
240         new_value = LV_CLAMP(real_min_value, new_value, real_max_value);
241         if(*slider->value_to_set != new_value) {
242             if(slider->value_to_set == &slider->bar.start_value) {
243                 lv_bar_set_start_value(obj, new_value, LV_ANIM_ON);
244             }
245             else {
246                 lv_bar_set_value(obj, new_value, LV_ANIM_ON);
247             }
248             res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
249             if(res != LV_RES_OK) return;
250         }
251 
252     }
253     else if(code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
254         slider->dragging = false;
255         slider->value_to_set = NULL;
256 
257         lv_obj_invalidate(obj);
258 
259         /*Leave edit mode if released. (No need to wait for LONG_PRESS)*/
260         lv_group_t * g   = lv_obj_get_group(obj);
261         bool editing     = lv_group_get_editing(g);
262         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
263         if(indev_type == LV_INDEV_TYPE_ENCODER) {
264             if(editing) {
265                 if(lv_slider_get_mode(obj) == LV_SLIDER_MODE_RANGE) {
266                     if(slider->left_knob_focus == 0) slider->left_knob_focus = 1;
267                     else {
268                         slider->left_knob_focus = 0;
269                         lv_group_set_editing(g, false);
270                     }
271                 }
272                 else {
273                     lv_group_set_editing(g, false);
274                 }
275             }
276         }
277 
278     }
279     else if(code == LV_EVENT_FOCUSED) {
280         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
281         if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) {
282             slider->left_knob_focus = 0;
283         }
284     }
285     else if(code == LV_EVENT_SIZE_CHANGED) {
286         lv_obj_refresh_ext_draw_size(obj);
287     }
288     else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
289         lv_coord_t knob_left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
290         lv_coord_t knob_right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
291         lv_coord_t knob_top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
292         lv_coord_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
293 
294         /*The smaller size is the knob diameter*/
295         lv_coord_t zoom = lv_obj_get_style_transform_zoom(obj, LV_PART_KNOB);
296         lv_coord_t trans_w = lv_obj_get_style_transform_width(obj, LV_PART_KNOB);
297         lv_coord_t trans_h = lv_obj_get_style_transform_height(obj, LV_PART_KNOB);
298         lv_coord_t knob_size = LV_MIN(lv_obj_get_width(obj) + 2 * trans_w, lv_obj_get_height(obj) + 2 * trans_h) >> 1;
299         knob_size = (knob_size * zoom) >> 8;
300         knob_size += LV_MAX(LV_MAX(knob_left, knob_right), LV_MAX(knob_bottom, knob_top));
301         knob_size += 2;         /*For rounding error*/
302         knob_size += lv_obj_calculate_ext_draw_size(obj, LV_PART_KNOB);
303 
304         /*Indic. size is handled by bar*/
305         lv_coord_t * s = lv_event_get_param(e);
306         *s  = LV_MAX(*s, knob_size);
307 
308     }
309     else if(code == LV_EVENT_KEY) {
310         char c = *((char *)lv_event_get_param(e));
311 
312         if(c == LV_KEY_RIGHT || c == LV_KEY_UP) {
313             if(!slider->left_knob_focus) lv_slider_set_value(obj, lv_slider_get_value(obj) + 1, LV_ANIM_ON);
314             else lv_slider_set_left_value(obj, lv_slider_get_left_value(obj) + 1, LV_ANIM_ON);
315         }
316         else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) {
317             if(!slider->left_knob_focus) lv_slider_set_value(obj, lv_slider_get_value(obj) - 1, LV_ANIM_ON);
318             else lv_slider_set_left_value(obj, lv_slider_get_left_value(obj) - 1, LV_ANIM_ON);
319         }
320         else {
321             return;
322         }
323 
324         res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
325         if(res != LV_RES_OK) return;
326     }
327     else if(code == LV_EVENT_DRAW_MAIN) {
328         draw_knob(e);
329     }
330 }
331 
draw_knob(lv_event_t * e)332 static void draw_knob(lv_event_t * e)
333 {
334     lv_obj_t * obj = lv_event_get_target(e);
335     lv_slider_t * slider = (lv_slider_t *)obj;
336     lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
337 
338     const bool is_rtl = LV_BASE_DIR_RTL == lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
339     const bool is_horizontal = is_slider_horizontal(obj);
340 
341     lv_area_t knob_area;
342     lv_coord_t knob_size;
343     bool is_symmetrical = false;
344     if(slider->bar.mode == LV_BAR_MODE_SYMMETRICAL && slider->bar.min_value < 0 &&
345        slider->bar.max_value > 0) is_symmetrical = true;
346 
347     if(is_horizontal) {
348         knob_size = lv_obj_get_height(obj);
349         if(is_symmetrical && slider->bar.cur_value < 0) knob_area.x1 = slider->bar.indic_area.x1;
350         else knob_area.x1 = LV_SLIDER_KNOB_COORD(is_rtl, slider->bar.indic_area);
351     }
352     else {
353         knob_size = lv_obj_get_width(obj);
354         if(is_symmetrical && slider->bar.cur_value < 0) knob_area.y1 = slider->bar.indic_area.y2;
355         else knob_area.y1 = slider->bar.indic_area.y1;
356     }
357 
358     lv_draw_rect_dsc_t knob_rect_dsc;
359     lv_draw_rect_dsc_init(&knob_rect_dsc);
360     lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &knob_rect_dsc);
361     /* Update knob area with knob style */
362     position_knob(obj, &knob_area, knob_size, is_horizontal);
363     /* Update right knob area with calculated knob area */
364     lv_area_copy(&slider->right_knob_area, &knob_area);
365 
366     lv_obj_draw_part_dsc_t part_draw_dsc;
367     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
368     part_draw_dsc.part = LV_PART_KNOB;
369     part_draw_dsc.class_p = MY_CLASS;
370     part_draw_dsc.type = LV_SLIDER_DRAW_PART_KNOB;
371     part_draw_dsc.id = 0;
372     part_draw_dsc.draw_area = &slider->right_knob_area;
373     part_draw_dsc.rect_dsc = &knob_rect_dsc;
374 
375     if(lv_slider_get_mode(obj) != LV_SLIDER_MODE_RANGE) {
376         lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
377         lv_draw_rect(draw_ctx, &knob_rect_dsc, &slider->right_knob_area);
378         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
379     }
380     else {
381         /*Save the draw part_draw_dsc. because it can be modified in the event*/
382         lv_draw_rect_dsc_t knob_rect_dsc_tmp;
383         lv_memcpy(&knob_rect_dsc_tmp, &knob_rect_dsc, sizeof(lv_draw_rect_dsc_t));
384         /* Draw the right knob */
385         lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
386         lv_draw_rect(draw_ctx, &knob_rect_dsc, &slider->right_knob_area);
387         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
388 
389         /*Calculate the second knob area*/
390         if(is_horizontal) {
391             /*use !is_rtl to get the other knob*/
392             knob_area.x1 = LV_SLIDER_KNOB_COORD(!is_rtl, slider->bar.indic_area);
393         }
394         else {
395             knob_area.y1 = slider->bar.indic_area.y2;
396         }
397         position_knob(obj, &knob_area, knob_size, is_horizontal);
398         lv_area_copy(&slider->left_knob_area, &knob_area);
399 
400         lv_memcpy(&knob_rect_dsc, &knob_rect_dsc_tmp, sizeof(lv_draw_rect_dsc_t));
401         part_draw_dsc.type = LV_SLIDER_DRAW_PART_KNOB_LEFT;
402         part_draw_dsc.draw_area = &slider->left_knob_area;
403         part_draw_dsc.rect_dsc = &knob_rect_dsc;
404         part_draw_dsc.id = 1;
405 
406         lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
407         lv_draw_rect(draw_ctx, &knob_rect_dsc, &slider->left_knob_area);
408         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
409     }
410 }
411 
position_knob(lv_obj_t * obj,lv_area_t * knob_area,const lv_coord_t knob_size,const bool hor)412 static void position_knob(lv_obj_t * obj, lv_area_t * knob_area, const lv_coord_t knob_size, const bool hor)
413 {
414     if(hor) {
415         knob_area->x1 -= (knob_size >> 1);
416         knob_area->x2 = knob_area->x1 + knob_size - 1;
417         knob_area->y1 = obj->coords.y1;
418         knob_area->y2 = obj->coords.y2;
419     }
420     else {
421         knob_area->y1 -= (knob_size >> 1);
422         knob_area->y2 = knob_area->y1 + knob_size - 1;
423         knob_area->x1 = obj->coords.x1;
424         knob_area->x2 = obj->coords.x2;
425     }
426 
427     lv_coord_t knob_left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
428     lv_coord_t knob_right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
429     lv_coord_t knob_top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
430     lv_coord_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
431 
432     lv_coord_t transf_w = lv_obj_get_style_transform_width(obj, LV_PART_KNOB);
433     lv_coord_t transf_h = lv_obj_get_style_transform_height(obj, LV_PART_KNOB);
434 
435     /*Apply the paddings on the knob area*/
436     knob_area->x1 -= knob_left + transf_w;
437     knob_area->x2 += knob_right + transf_w;
438     knob_area->y1 -= knob_top + transf_h;
439     knob_area->y2 += knob_bottom + transf_h;
440 }
441 
is_slider_horizontal(lv_obj_t * obj)442 static bool is_slider_horizontal(lv_obj_t * obj)
443 {
444     return lv_obj_get_width(obj) >= lv_obj_get_height(obj);
445 }
446 
447 #endif
448