1 /**
2 * @file lv_roller.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_roller.h"
10 #if LV_USE_ROLLER != 0
11
12 #include "../misc/lv_assert.h"
13 #include "../draw/lv_draw.h"
14 #include "../core/lv_group.h"
15 #include "../core/lv_indev.h"
16 #include "../core/lv_indev_scroll.h"
17
18 /*********************
19 * DEFINES
20 *********************/
21 #define MY_CLASS &lv_roller_class
22 #define MY_CLASS_LABEL &lv_roller_label_class
23
24 /**********************
25 * TYPEDEFS
26 **********************/
27
28 /**********************
29 * STATIC PROTOTYPES
30 **********************/
31 static void lv_roller_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
32 static void lv_roller_event(const lv_obj_class_t * class_p, lv_event_t * e);
33 static void lv_roller_label_event(const lv_obj_class_t * class_p, lv_event_t * e);
34 static void draw_main(lv_event_t * e);
35 static void draw_label(lv_event_t * e);
36 static void get_sel_area(lv_obj_t * obj, lv_area_t * sel_area);
37 static void refr_position(lv_obj_t * obj, lv_anim_enable_t animen);
38 static lv_res_t release_handler(lv_obj_t * obj);
39 static void inf_normalize(lv_obj_t * obj_scrl);
40 static lv_obj_t * get_label(const lv_obj_t * obj);
41 static lv_coord_t get_selected_label_width(const lv_obj_t * obj);
42 static void scroll_anim_ready_cb(lv_anim_t * a);
43 static void set_y_anim(void * obj, int32_t v);
44
45 /**********************
46 * STATIC VARIABLES
47 **********************/
48 const lv_obj_class_t lv_roller_class = {
49 .constructor_cb = lv_roller_constructor,
50 .event_cb = lv_roller_event,
51 .width_def = LV_SIZE_CONTENT,
52 .height_def = LV_DPI_DEF,
53 .instance_size = sizeof(lv_roller_t),
54 .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
55 .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
56 .base_class = &lv_obj_class
57 };
58
59 const lv_obj_class_t lv_roller_label_class = {
60 .event_cb = lv_roller_label_event,
61 .instance_size = sizeof(lv_label_t),
62 .base_class = &lv_label_class
63 };
64
65 /**********************
66 * MACROS
67 **********************/
68
69 /**********************
70 * GLOBAL FUNCTIONS
71 **********************/
72
73 /**
74 * Create a roller object
75 * @param parent pointer to an object, it will be the parent of the new roller
76 * @return pointer to the created roller
77 */
lv_roller_create(lv_obj_t * parent)78 lv_obj_t * lv_roller_create(lv_obj_t * parent)
79 {
80 LV_LOG_INFO("begin");
81 lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
82 lv_obj_class_init_obj(obj);
83 return obj;
84 }
85
86 /*=====================
87 * Setter functions
88 *====================*/
89
90 /**
91 * Set the options on a roller
92 * @param roller pointer to roller object
93 * @param options a string with '\n' separated options. E.g. "One\nTwo\nThree"
94 * @param mode `LV_ROLLER_MODE_NORMAL` or `LV_ROLLER_MODE_INFINITE`
95 */
lv_roller_set_options(lv_obj_t * obj,const char * options,lv_roller_mode_t mode)96 void lv_roller_set_options(lv_obj_t * obj, const char * options, lv_roller_mode_t mode)
97 {
98 LV_ASSERT_OBJ(obj, MY_CLASS);
99 LV_ASSERT_NULL(options);
100
101 lv_roller_t * roller = (lv_roller_t *)obj;
102 lv_obj_t * label = get_label(obj);
103
104 roller->sel_opt_id = 0;
105 roller->sel_opt_id_ori = 0;
106
107 /*Count the '\n'-s to determine the number of options*/
108 roller->option_cnt = 0;
109 uint32_t cnt;
110 for(cnt = 0; options[cnt] != '\0'; cnt++) {
111 if(options[cnt] == '\n') roller->option_cnt++;
112 }
113 roller->option_cnt++; /*Last option has no `\n`*/
114
115 if(mode == LV_ROLLER_MODE_NORMAL) {
116 roller->mode = LV_ROLLER_MODE_NORMAL;
117 lv_label_set_text(label, options);
118 }
119 else {
120 roller->mode = LV_ROLLER_MODE_INFINITE;
121
122 size_t opt_len = strlen(options) + 1; /*+1 to add '\n' after option lists*/
123 char * opt_extra = lv_mem_buf_get(opt_len * LV_ROLLER_INF_PAGES);
124 uint8_t i;
125 for(i = 0; i < LV_ROLLER_INF_PAGES; i++) {
126 strcpy(&opt_extra[opt_len * i], options);
127 opt_extra[opt_len * (i + 1) - 1] = '\n';
128 }
129 opt_extra[opt_len * LV_ROLLER_INF_PAGES - 1] = '\0';
130 lv_label_set_text(label, opt_extra);
131 lv_mem_buf_release(opt_extra);
132
133 roller->sel_opt_id = ((LV_ROLLER_INF_PAGES / 2) + 0) * roller->option_cnt;
134
135 roller->option_cnt = roller->option_cnt * LV_ROLLER_INF_PAGES;
136 inf_normalize(obj);
137 }
138
139 roller->sel_opt_id_ori = roller->sel_opt_id;
140
141 /*If the selected text has larger font the label needs some extra draw padding to draw it.*/
142 lv_obj_refresh_ext_draw_size(label);
143
144 }
145
146 /**
147 * Set the selected option
148 * @param roller pointer to a roller object
149 * @param sel_opt id of the selected option (0 ... number of option - 1);
150 * @param anim_en LV_ANIM_ON: set with animation; LV_ANOM_OFF set immediately
151 */
lv_roller_set_selected(lv_obj_t * obj,uint16_t sel_opt,lv_anim_enable_t anim)152 void lv_roller_set_selected(lv_obj_t * obj, uint16_t sel_opt, lv_anim_enable_t anim)
153 {
154 LV_ASSERT_OBJ(obj, MY_CLASS);
155
156 /*Set the value even if it's the same as the current value because
157 *if moving to the next option with an animation which was just deleted in the PRESS Call the ancestor's event handler
158 *nothing will continue the animation.*/
159
160 lv_roller_t * roller = (lv_roller_t *)obj;
161
162 /*In infinite mode interpret the new ID relative to the currently visible "page"*/
163 if(roller->mode == LV_ROLLER_MODE_INFINITE) {
164 int32_t sel_opt_signed = sel_opt;
165 uint16_t page = roller->sel_opt_id / LV_ROLLER_INF_PAGES;
166
167 /*`sel_opt` should be less than the number of options set by the user.
168 *If it's more then probably it's a reference from not the first page
169 *so normalize `sel_opt`*/
170 if(page != 0) {
171 sel_opt_signed -= page * LV_ROLLER_INF_PAGES;
172 }
173
174 sel_opt = page * LV_ROLLER_INF_PAGES + sel_opt_signed;
175 }
176
177 roller->sel_opt_id = sel_opt < roller->option_cnt ? sel_opt : roller->option_cnt - 1;
178 roller->sel_opt_id_ori = roller->sel_opt_id;
179
180 refr_position(obj, anim);
181 }
182
183 /**
184 * Set the height to show the given number of rows (options)
185 * @param roller pointer to a roller object
186 * @param row_cnt number of desired visible rows
187 */
lv_roller_set_visible_row_count(lv_obj_t * obj,uint8_t row_cnt)188 void lv_roller_set_visible_row_count(lv_obj_t * obj, uint8_t row_cnt)
189 {
190 LV_ASSERT_OBJ(obj, MY_CLASS);
191
192 const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
193 lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
194 lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
195 lv_obj_set_height(obj, (lv_font_get_line_height(font) + line_space) * row_cnt + 2 * border_width);
196 }
197
198 /*=====================
199 * Getter functions
200 *====================*/
201
202 /**
203 * Get the id of the selected option
204 * @param roller pointer to a roller object
205 * @return id of the selected option (0 ... number of option - 1);
206 */
lv_roller_get_selected(const lv_obj_t * obj)207 uint16_t lv_roller_get_selected(const lv_obj_t * obj)
208 {
209 LV_ASSERT_OBJ(obj, MY_CLASS);
210
211 lv_roller_t * roller = (lv_roller_t *)obj;
212 if(roller->mode == LV_ROLLER_MODE_INFINITE) {
213 uint16_t real_id_cnt = roller->option_cnt / LV_ROLLER_INF_PAGES;
214 return roller->sel_opt_id % real_id_cnt;
215 }
216 else {
217 return roller->sel_opt_id;
218 }
219 }
220
221 /**
222 * Get the current selected option as a string
223 * @param ddlist pointer to ddlist object
224 * @param buf pointer to an array to store the string
225 * @param buf_size size of `buf` in bytes. 0: to ignore it.
226 */
lv_roller_get_selected_str(const lv_obj_t * obj,char * buf,uint32_t buf_size)227 void lv_roller_get_selected_str(const lv_obj_t * obj, char * buf, uint32_t buf_size)
228 {
229 LV_ASSERT_OBJ(obj, MY_CLASS);
230
231 lv_roller_t * roller = (lv_roller_t *)obj;
232 lv_obj_t * label = get_label(obj);
233 uint32_t i;
234 uint16_t line = 0;
235 const char * opt_txt = lv_label_get_text(label);
236 size_t txt_len = strlen(opt_txt);
237
238 for(i = 0; i < txt_len && line != roller->sel_opt_id; i++) {
239 if(opt_txt[i] == '\n') line++;
240 }
241
242 uint32_t c;
243 for(c = 0; i < txt_len && opt_txt[i] != '\n'; c++, i++) {
244 if(buf_size && c >= buf_size - 1) {
245 LV_LOG_WARN("lv_dropdown_get_selected_str: the buffer was too small");
246 break;
247 }
248 buf[c] = opt_txt[i];
249 }
250
251 buf[c] = '\0';
252 }
253
254
255 /**
256 * Get the options of a roller
257 * @param roller pointer to roller object
258 * @return the options separated by '\n'-s (E.g. "Option1\nOption2\nOption3")
259 */
lv_roller_get_options(const lv_obj_t * obj)260 const char * lv_roller_get_options(const lv_obj_t * obj)
261 {
262 LV_ASSERT_OBJ(obj, MY_CLASS);
263
264 return lv_label_get_text(get_label(obj));
265 }
266
267
268 /**
269 * Get the total number of options
270 * @param roller pointer to a roller object
271 * @return the total number of options
272 */
lv_roller_get_option_cnt(const lv_obj_t * obj)273 uint16_t lv_roller_get_option_cnt(const lv_obj_t * obj)
274 {
275 LV_ASSERT_OBJ(obj, MY_CLASS);
276
277 lv_roller_t * roller = (lv_roller_t *)obj;
278 if(roller->mode == LV_ROLLER_MODE_INFINITE) {
279 return roller->option_cnt / LV_ROLLER_INF_PAGES;
280 }
281 else {
282 return roller->option_cnt;
283 }
284 }
285
286 /**********************
287 * STATIC FUNCTIONS
288 **********************/
289
290
lv_roller_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)291 static void lv_roller_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
292 {
293 LV_UNUSED(class_p);
294 lv_roller_t * roller = (lv_roller_t *)obj;
295
296 roller->mode = LV_ROLLER_MODE_NORMAL;
297 roller->option_cnt = 0;
298 roller->sel_opt_id = 0;
299 roller->sel_opt_id_ori = 0;
300
301 lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
302 lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN_VER);
303
304 LV_LOG_INFO("begin");
305 lv_obj_t * label = lv_obj_class_create_obj(&lv_roller_label_class, obj);
306 lv_obj_class_init_obj(label);
307 lv_roller_set_options(obj, "Option 1\nOption 2\nOption 3\nOption 4\nOption 5", LV_ROLLER_MODE_NORMAL);
308
309 LV_LOG_TRACE("finshed");
310 }
311
lv_roller_event(const lv_obj_class_t * class_p,lv_event_t * e)312 static void lv_roller_event(const lv_obj_class_t * class_p, lv_event_t * e)
313 {
314 LV_UNUSED(class_p);
315
316 lv_res_t res;
317
318 /*Call the ancestor's event handler*/
319 res = lv_obj_event_base(MY_CLASS, e);
320 if(res != LV_RES_OK) return;
321
322 lv_event_code_t code = lv_event_get_code(e);
323 lv_obj_t * obj = lv_event_get_target(e);
324 lv_roller_t * roller = (lv_roller_t *)obj;
325
326 if(code == LV_EVENT_GET_SELF_SIZE) {
327 lv_point_t * p = lv_event_get_param(e);
328 p->x = get_selected_label_width(obj);
329 }
330 else if(code == LV_EVENT_STYLE_CHANGED) {
331 lv_obj_t * label = get_label(obj);
332 /*Be sure the label's style is updated before processing the roller*/
333 if(label) lv_event_send(label, LV_EVENT_STYLE_CHANGED, NULL);
334 lv_obj_refresh_self_size(obj);
335 refr_position(obj, LV_ANIM_OFF);
336 }
337 else if(code == LV_EVENT_SIZE_CHANGED) {
338 refr_position(obj, LV_ANIM_OFF);
339 }
340 else if(code == LV_EVENT_PRESSED) {
341 roller->moved = 0;
342 lv_anim_del(get_label(obj), set_y_anim);
343 }
344 else if(code == LV_EVENT_PRESSING) {
345 lv_indev_t * indev = lv_indev_get_act();
346 lv_point_t p;
347 lv_indev_get_vect(indev, &p);
348 if(p.y) {
349 lv_obj_t * label = get_label(obj);
350 lv_obj_set_y(label, lv_obj_get_y(label) + p.y);
351 roller->moved = 1;
352 }
353 }
354 else if(code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
355 release_handler(obj);
356 }
357 else if(code == LV_EVENT_FOCUSED) {
358 lv_group_t * g = lv_obj_get_group(obj);
359 bool editing = lv_group_get_editing(g);
360 lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
361
362 /*Encoders need special handling*/
363 if(indev_type == LV_INDEV_TYPE_ENCODER) {
364 /*In navigate mode revert the original value*/
365 if(!editing) {
366 if(roller->sel_opt_id != roller->sel_opt_id_ori) {
367 roller->sel_opt_id = roller->sel_opt_id_ori;
368 refr_position(obj, LV_ANIM_ON);
369 }
370 }
371 /*Save the current state when entered to edit mode*/
372 else {
373 roller->sel_opt_id_ori = roller->sel_opt_id;
374 }
375 }
376 else {
377 roller->sel_opt_id_ori = roller->sel_opt_id; /*Save the current value. Used to revert this state if
378 ENTER won't be pressed*/
379 }
380 }
381 else if(code == LV_EVENT_DEFOCUSED) {
382 /*Revert the original state*/
383 if(roller->sel_opt_id != roller->sel_opt_id_ori) {
384 roller->sel_opt_id = roller->sel_opt_id_ori;
385 refr_position(obj, LV_ANIM_ON);
386 }
387 }
388 else if(code == LV_EVENT_KEY) {
389 char c = *((char *)lv_event_get_param(e));
390 if(c == LV_KEY_RIGHT || c == LV_KEY_DOWN) {
391 if(roller->sel_opt_id + 1 < roller->option_cnt) {
392 uint16_t ori_id = roller->sel_opt_id_ori; /*lv_roller_set_selected will overwrite this*/
393 lv_roller_set_selected(obj, roller->sel_opt_id + 1, LV_ANIM_ON);
394 roller->sel_opt_id_ori = ori_id;
395 }
396 }
397 else if(c == LV_KEY_LEFT || c == LV_KEY_UP) {
398 if(roller->sel_opt_id > 0) {
399 uint16_t ori_id = roller->sel_opt_id_ori; /*lv_roller_set_selected will overwrite this*/
400
401 lv_roller_set_selected(obj, roller->sel_opt_id - 1, LV_ANIM_ON);
402 roller->sel_opt_id_ori = ori_id;
403 }
404 }
405 }
406 else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
407 lv_obj_t * label = get_label(obj);
408 lv_obj_refresh_ext_draw_size(label);
409 }
410 else if(code == LV_EVENT_DRAW_MAIN || code == LV_EVENT_DRAW_POST) {
411 draw_main(e);
412 }
413 }
414
lv_roller_label_event(const lv_obj_class_t * class_p,lv_event_t * e)415 static void lv_roller_label_event(const lv_obj_class_t * class_p, lv_event_t * e)
416 {
417 LV_UNUSED(class_p);
418
419 lv_res_t res;
420
421 lv_event_code_t code = lv_event_get_code(e);
422 /*LV_EVENT_DRAW_MAIN will be called in the draw function*/
423 if(code != LV_EVENT_DRAW_MAIN) {
424 /* Call the ancestor's event handler */
425 res = lv_obj_event_base(MY_CLASS_LABEL, e);
426 if(res != LV_RES_OK) return;
427 }
428
429 lv_obj_t * label = lv_event_get_target(e);
430 if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
431 /*If the selected text has a larger font it needs some extra space to draw it*/
432 lv_coord_t * s = lv_event_get_param(e);
433 lv_obj_t * obj = lv_obj_get_parent(label);
434 lv_coord_t sel_w = get_selected_label_width(obj);
435 lv_coord_t label_w = lv_obj_get_width(label);
436 *s = LV_MAX(*s, sel_w - label_w);
437 }
438 else if(code == LV_EVENT_SIZE_CHANGED) {
439 refr_position(lv_obj_get_parent(label), LV_ANIM_OFF);
440 }
441 else if(code == LV_EVENT_DRAW_MAIN) {
442 draw_label(e);
443 }
444 }
445
446
draw_main(lv_event_t * e)447 static void draw_main(lv_event_t * e)
448 {
449 lv_event_code_t code = lv_event_get_code(e);
450 lv_obj_t * obj = lv_event_get_target(e);
451 if(code == LV_EVENT_DRAW_MAIN) {
452 /*Draw the selected rectangle*/
453 lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
454 lv_area_t sel_area;
455 get_sel_area(obj, &sel_area);
456 lv_draw_rect_dsc_t sel_dsc;
457 lv_draw_rect_dsc_init(&sel_dsc);
458 lv_obj_init_draw_rect_dsc(obj, LV_PART_SELECTED, &sel_dsc);
459 lv_draw_rect(draw_ctx, &sel_dsc, &sel_area);
460 }
461 /*Post draw when the children are drawn*/
462 else if(code == LV_EVENT_DRAW_POST) {
463 lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
464
465 lv_draw_label_dsc_t label_dsc;
466 lv_draw_label_dsc_init(&label_dsc);
467 lv_obj_init_draw_label_dsc(obj, LV_PART_SELECTED, &label_dsc);
468
469 /*Redraw the text on the selected area*/
470 lv_area_t sel_area;
471 get_sel_area(obj, &sel_area);
472 lv_area_t mask_sel;
473 bool area_ok;
474 area_ok = _lv_area_intersect(&mask_sel, draw_ctx->clip_area, &sel_area);
475 if(area_ok) {
476 lv_obj_t * label = get_label(obj);
477
478 /*Get the size of the "selected text"*/
479 lv_point_t res_p;
480 lv_txt_get_size(&res_p, lv_label_get_text(label), label_dsc.font, label_dsc.letter_space, label_dsc.line_space,
481 lv_obj_get_width(obj), LV_TEXT_FLAG_EXPAND);
482
483 /*Move the selected label proportionally with the background label*/
484 lv_coord_t roller_h = lv_obj_get_height(obj);
485 int32_t label_y_prop = label->coords.y1 - (roller_h / 2 +
486 obj->coords.y1); /*label offset from the middle line of the roller*/
487 label_y_prop = (label_y_prop * 16384) / lv_obj_get_height(
488 label); /*Proportional position from the middle line (upscaled by << 14)*/
489
490 /*Apply a correction with different line heights*/
491 const lv_font_t * normal_label_font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
492 lv_coord_t corr = (label_dsc.font->line_height - normal_label_font->line_height) / 2;
493
494 /*Apply the proportional position to the selected text*/
495 res_p.y -= corr;
496 int32_t label_sel_y = roller_h / 2 + obj->coords.y1;
497 label_sel_y += (label_y_prop * res_p.y) >> 14;
498 label_sel_y -= corr;
499
500 lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
501 lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
502 lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
503
504 /*Draw the selected text*/
505 lv_area_t label_sel_area;
506 label_sel_area.x1 = obj->coords.x1 + pleft + bwidth;
507 label_sel_area.y1 = label_sel_y;
508 label_sel_area.x2 = obj->coords.x2 - pright - bwidth;
509 label_sel_area.y2 = label_sel_area.y1 + res_p.y;
510
511 label_dsc.flag |= LV_TEXT_FLAG_EXPAND;
512 const lv_area_t * clip_area_ori = draw_ctx->clip_area;
513 draw_ctx->clip_area = &mask_sel;
514 lv_draw_label(draw_ctx, &label_dsc, &label_sel_area, lv_label_get_text(label), NULL);
515 draw_ctx->clip_area = clip_area_ori;
516 }
517 }
518 }
519
draw_label(lv_event_t * e)520 static void draw_label(lv_event_t * e)
521 {
522 /* Split the drawing of the label into an upper (above the selected area)
523 * and a lower (below the selected area)*/
524 lv_obj_t * label_obj = lv_event_get_target(e);
525 lv_obj_t * roller = lv_obj_get_parent(label_obj);
526 lv_draw_label_dsc_t label_draw_dsc;
527 lv_draw_label_dsc_init(&label_draw_dsc);
528 lv_obj_init_draw_label_dsc(roller, LV_PART_MAIN, &label_draw_dsc);
529 lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
530
531 /*If the roller has shadow or outline it has some ext. draw size
532 *therefore the label can overflow the roller's boundaries.
533 *To solve this limit the clip area to the "plain" roller.*/
534 const lv_area_t * clip_area_ori = draw_ctx->clip_area;
535 lv_area_t roller_clip_area;
536 if(!_lv_area_intersect(&roller_clip_area, draw_ctx->clip_area, &roller->coords)) return;
537 draw_ctx->clip_area = &roller_clip_area;
538
539 lv_area_t sel_area;
540 get_sel_area(roller, &sel_area);
541
542 lv_area_t clip2;
543 clip2.x1 = label_obj->coords.x1;
544 clip2.y1 = label_obj->coords.y1;
545 clip2.x2 = label_obj->coords.x2;
546 clip2.y2 = sel_area.y1;
547 if(_lv_area_intersect(&clip2, draw_ctx->clip_area, &clip2)) {
548 const lv_area_t * clip_area_ori2 = draw_ctx->clip_area;
549 draw_ctx->clip_area = &clip2;
550 lv_draw_label(draw_ctx, &label_draw_dsc, &label_obj->coords, lv_label_get_text(label_obj), NULL);
551 draw_ctx->clip_area = clip_area_ori2;
552 }
553
554 clip2.x1 = label_obj->coords.x1;
555 clip2.y1 = sel_area.y2;
556 clip2.x2 = label_obj->coords.x2;
557 clip2.y2 = label_obj->coords.y2;
558 if(_lv_area_intersect(&clip2, draw_ctx->clip_area, &clip2)) {
559 const lv_area_t * clip_area_ori2 = draw_ctx->clip_area;
560 draw_ctx->clip_area = &clip2;
561 lv_draw_label(draw_ctx, &label_draw_dsc, &label_obj->coords, lv_label_get_text(label_obj), NULL);
562 draw_ctx->clip_area = clip_area_ori2;
563 }
564
565 draw_ctx->clip_area = clip_area_ori;
566 }
567
get_sel_area(lv_obj_t * obj,lv_area_t * sel_area)568 static void get_sel_area(lv_obj_t * obj, lv_area_t * sel_area)
569 {
570
571 const lv_font_t * font_main = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
572 const lv_font_t * font_sel = lv_obj_get_style_text_font(obj, LV_PART_SELECTED);
573 lv_coord_t font_main_h = lv_font_get_line_height(font_main);
574 lv_coord_t font_sel_h = lv_font_get_line_height(font_sel);
575 lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
576 lv_coord_t d = (font_sel_h + font_main_h) / 2 + line_space;
577 sel_area->y1 = obj->coords.y1 + lv_obj_get_height(obj) / 2 - d / 2;
578 sel_area->y2 = sel_area->y1 + d;
579 lv_area_t roller_coords;
580 lv_obj_get_coords(obj, &roller_coords);
581
582 sel_area->x1 = roller_coords.x1;
583 sel_area->x2 = roller_coords.x2;
584
585 }
586
587 /**
588 * Refresh the position of the roller. It uses the id stored in: roller->ddlist.selected_option_id
589 * @param roller pointer to a roller object
590 * @param anim_en LV_ANIM_ON: refresh with animation; LV_ANOM_OFF: without animation
591 */
refr_position(lv_obj_t * obj,lv_anim_enable_t anim_en)592 static void refr_position(lv_obj_t * obj, lv_anim_enable_t anim_en)
593 {
594 lv_obj_t * label = get_label(obj);
595 if(label == NULL) return;
596
597 lv_text_align_t align = lv_obj_calculate_style_text_align(label, LV_PART_MAIN, lv_label_get_text(label));
598
599 switch(align) {
600 case LV_TEXT_ALIGN_CENTER:
601 lv_obj_set_x(label, (lv_obj_get_content_width(obj) - lv_obj_get_width(label)) / 2);
602 break;
603 case LV_TEXT_ALIGN_RIGHT:
604 lv_obj_set_x(label, lv_obj_get_content_width(obj) - lv_obj_get_width(label));
605 break;
606 case LV_TEXT_ALIGN_LEFT:
607 lv_obj_set_x(label, 0);
608 break;
609 }
610
611 lv_roller_t * roller = (lv_roller_t *)obj;
612 const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
613 lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
614 lv_coord_t font_h = lv_font_get_line_height(font);
615 lv_coord_t h = lv_obj_get_content_height(obj);
616 uint16_t anim_time = lv_obj_get_style_anim_time(obj, LV_PART_MAIN);
617
618 /*Normally the animation's `end_cb` sets correct position of the roller if infinite.
619 *But without animations do it manually*/
620 if(anim_en == LV_ANIM_OFF || anim_time == 0) {
621 inf_normalize(obj);
622 }
623
624 int32_t id = roller->sel_opt_id;
625 lv_coord_t sel_y1 = id * (font_h + line_space);
626 lv_coord_t mid_y1 = h / 2 - font_h / 2;
627
628 lv_coord_t new_y = mid_y1 - sel_y1;
629
630 if(anim_en == LV_ANIM_OFF || anim_time == 0) {
631 lv_anim_del(label, set_y_anim);
632 lv_obj_set_y(label, new_y);
633 }
634 else {
635 lv_anim_t a;
636 lv_anim_init(&a);
637 lv_anim_set_var(&a, label);
638 lv_anim_set_exec_cb(&a, set_y_anim);
639 lv_anim_set_values(&a, lv_obj_get_y(label), new_y);
640 lv_anim_set_time(&a, anim_time);
641 lv_anim_set_ready_cb(&a, scroll_anim_ready_cb);
642 lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
643 lv_anim_start(&a);
644 }
645 }
646
release_handler(lv_obj_t * obj)647 static lv_res_t release_handler(lv_obj_t * obj)
648 {
649 lv_obj_t * label = get_label(obj);
650 if(label == NULL) return LV_RES_OK;
651
652 lv_indev_t * indev = lv_indev_get_act();
653 lv_roller_t * roller = (lv_roller_t *)obj;
654
655 /*Leave edit mode once a new option is selected*/
656 lv_indev_type_t indev_type = lv_indev_get_type(indev);
657 if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) {
658 roller->sel_opt_id_ori = roller->sel_opt_id;
659
660 if(indev_type == LV_INDEV_TYPE_ENCODER) {
661 lv_group_t * g = lv_obj_get_group(obj);
662 if(lv_group_get_editing(g)) {
663 lv_group_set_editing(g, false);
664 }
665 }
666 }
667
668 if(lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON) {
669 /*Search the clicked option (For KEYPAD and ENCODER the new value should be already set)*/
670 int16_t new_opt = -1;
671 if(roller->moved == 0) {
672 new_opt = 0;
673 lv_point_t p;
674 lv_indev_get_point(indev, &p);
675 p.y -= label->coords.y1;
676 p.x -= label->coords.x1;
677 uint32_t letter_i;
678 letter_i = lv_label_get_letter_on(label, &p);
679
680 const char * txt = lv_label_get_text(label);
681 uint32_t i = 0;
682 uint32_t i_prev = 0;
683
684 uint32_t letter_cnt = 0;
685 for(letter_cnt = 0; letter_cnt < letter_i; letter_cnt++) {
686 uint32_t letter = _lv_txt_encoded_next(txt, &i);
687 /*Count he lines to reach the clicked letter. But ignore the last '\n' because it
688 * still belongs to the clicked line*/
689 if(letter == '\n' && i_prev != letter_i) new_opt++;
690 i_prev = i;
691 }
692 }
693 else {
694 /*If dragged then align the list to have an element in the middle*/
695 const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
696 lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
697 lv_coord_t font_h = lv_font_get_line_height(font);
698
699 lv_coord_t label_unit = font_h + line_space;
700 lv_coord_t mid = obj->coords.y1 + (obj->coords.y2 - obj->coords.y1) / 2;
701 lv_coord_t label_y1 = label->coords.y1 + lv_indev_scroll_throw_predict(indev, LV_DIR_VER);
702 int32_t id = (mid - label_y1) / label_unit;
703
704 if(id < 0) id = 0;
705 if(id >= roller->option_cnt) id = roller->option_cnt - 1;
706
707 new_opt = id;
708 }
709
710 if(new_opt >= 0) {
711 lv_roller_set_selected(obj, new_opt, LV_ANIM_ON);
712 }
713 }
714
715 uint32_t id = roller->sel_opt_id; /*Just to use uint32_t in event data*/
716 lv_res_t res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &id);
717 return res;
718 }
719
720 /**
721 * Set the middle page for the roller if infinite is enabled
722 * @param roller pointer to a roller object
723 */
inf_normalize(lv_obj_t * obj)724 static void inf_normalize(lv_obj_t * obj)
725 {
726 lv_roller_t * roller = (lv_roller_t *)obj;
727
728 if(roller->mode == LV_ROLLER_MODE_INFINITE) {
729 uint16_t real_id_cnt = roller->option_cnt / LV_ROLLER_INF_PAGES;
730 roller->sel_opt_id = roller->sel_opt_id % real_id_cnt;
731 roller->sel_opt_id += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
732
733 roller->sel_opt_id_ori = roller->sel_opt_id % real_id_cnt;
734 roller->sel_opt_id_ori += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
735
736 /*Move to the new id*/
737 const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
738 lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
739 lv_coord_t font_h = lv_font_get_line_height(font);
740 lv_coord_t h = lv_obj_get_content_height(obj);
741
742 lv_obj_t * label = get_label(obj);
743
744
745 lv_coord_t sel_y1 = roller->sel_opt_id * (font_h + line_space);
746 lv_coord_t mid_y1 = h / 2 - font_h / 2;
747 lv_coord_t new_y = mid_y1 - sel_y1;
748 lv_obj_set_y(label, new_y);
749 }
750 }
751
get_label(const lv_obj_t * obj)752 static lv_obj_t * get_label(const lv_obj_t * obj)
753 {
754 return lv_obj_get_child(obj, 0);
755 }
756
757
get_selected_label_width(const lv_obj_t * obj)758 static lv_coord_t get_selected_label_width(const lv_obj_t * obj)
759 {
760 lv_obj_t * label = get_label(obj);
761 if(label == NULL) return 0;
762
763 const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_SELECTED);
764 lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_SELECTED);
765 const char * txt = lv_label_get_text(label);
766 lv_point_t size;
767 lv_txt_get_size(&size, txt, font, letter_space, 0, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
768 return size.x;
769 }
770
scroll_anim_ready_cb(lv_anim_t * a)771 static void scroll_anim_ready_cb(lv_anim_t * a)
772 {
773 lv_obj_t * obj = lv_obj_get_parent(a->var); /*The label is animated*/
774 inf_normalize(obj);
775 }
776
777
set_y_anim(void * obj,int32_t v)778 static void set_y_anim(void * obj, int32_t v)
779 {
780 lv_obj_set_y(obj, v);
781 }
782
783 #endif
784