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