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