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