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 "../lv_draw/lv_draw.h"
13 #include "../lv_core/lv_group.h"
14 #include "../lv_themes/lv_theme.h"
15 
16 /*********************
17  *      DEFINES
18  *********************/
19 #if LV_USE_ANIMATION == 0
20 #undef LV_ROLLER_DEF_ANIM_TIME
21 #define LV_ROLLER_DEF_ANIM_TIME 0 /*No animation*/
22 #endif
23 
24 /**********************
25  *      TYPEDEFS
26  **********************/
27 
28 /**********************
29  *  STATIC PROTOTYPES
30  **********************/
31 static bool lv_roller_design(lv_obj_t * roller, const lv_area_t * mask, lv_design_mode_t mode);
32 static lv_res_t lv_roller_scrl_signal(lv_obj_t * roller_scrl, lv_signal_t sign, void * param);
33 static lv_res_t lv_roller_signal(lv_obj_t * roller, lv_signal_t sign, void * param);
34 static void refr_position(lv_obj_t * roller, lv_anim_enable_t animen);
35 static void refr_height(lv_obj_t * roller);
36 static void inf_normalize(void * roller_scrl);
37 #if LV_USE_ANIMATION
38 static void scroll_anim_ready_cb(lv_anim_t * a);
39 #endif
40 static void draw_bg(lv_obj_t * roller, const lv_area_t * mask);
41 
42 /**********************
43  *  STATIC VARIABLES
44  **********************/
45 static lv_signal_cb_t ancestor_signal;
46 static lv_signal_cb_t ancestor_scrl_signal;
47 
48 /**********************
49  *      MACROS
50  **********************/
51 
52 /**********************
53  *   GLOBAL FUNCTIONS
54  **********************/
55 
56 /**
57  * Create a roller object
58  * @param par pointer to an object, it will be the parent of the new roller
59  * @param copy pointer to a roller object, if not NULL then the new object will be copied from it
60  * @return pointer to the created roller
61  */
lv_roller_create(lv_obj_t * par,const lv_obj_t * copy)62 lv_obj_t * lv_roller_create(lv_obj_t * par, const lv_obj_t * copy)
63 {
64     LV_LOG_TRACE("roller create started");
65 
66     /*Create the ancestor of roller*/
67     lv_obj_t * new_roller = lv_ddlist_create(par, copy);
68     lv_mem_assert(new_roller);
69     if(new_roller == NULL) return NULL;
70 
71     if(ancestor_scrl_signal == NULL) ancestor_scrl_signal = lv_obj_get_signal_cb(lv_page_get_scrl(new_roller));
72     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_roller);
73 
74     /*Allocate the roller type specific extended data*/
75     lv_roller_ext_t * ext = lv_obj_allocate_ext_attr(new_roller, sizeof(lv_roller_ext_t));
76     lv_mem_assert(ext);
77     if(ext == NULL) return NULL;
78     ext->ddlist.draw_arrow = 0; /*Do not draw arrow by default*/
79 
80     /*The signal and design functions are not copied so set them here*/
81     lv_obj_set_signal_cb(new_roller, lv_roller_signal);
82     lv_obj_set_design_cb(new_roller, lv_roller_design);
83 
84     /*Init the new roller roller*/
85     if(copy == NULL) {
86         lv_obj_t * scrl = lv_page_get_scrl(new_roller);
87         lv_obj_set_drag(scrl, true);                                  /*In ddlist it might be disabled*/
88         lv_page_set_scrl_fit2(new_roller, LV_FIT_TIGHT, LV_FIT_NONE); /*Height is specified directly*/
89         lv_ddlist_open(new_roller, false);
90         lv_ddlist_set_anim_time(new_roller, LV_ROLLER_DEF_ANIM_TIME);
91         lv_ddlist_set_stay_open(new_roller, true);
92         lv_roller_set_visible_row_count(new_roller, 3);
93         lv_label_set_align(ext->ddlist.label, LV_LABEL_ALIGN_CENTER);
94 
95         lv_obj_set_signal_cb(scrl, lv_roller_scrl_signal);
96 
97         /*Set the default styles*/
98         lv_theme_t * th = lv_theme_get_current();
99         if(th) {
100             lv_roller_set_style(new_roller, LV_ROLLER_STYLE_BG, th->style.roller.bg);
101             lv_roller_set_style(new_roller, LV_ROLLER_STYLE_SEL, th->style.roller.sel);
102         } else {
103             /*Refresh the roller's style*/
104             lv_obj_refresh_style(new_roller); /*To set scrollable size automatically*/
105         }
106     }
107     /*Copy an existing roller*/
108     else {
109         lv_roller_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
110         ext->mode                  = copy_ext->mode;
111 
112         lv_obj_t * scrl = lv_page_get_scrl(new_roller);
113         lv_ddlist_open(new_roller, false);
114         lv_obj_set_signal_cb(scrl, lv_roller_scrl_signal);
115 
116         /*Refresh the roller's style*/
117         lv_obj_refresh_style(new_roller); /*Refresh the style with new signal function*/
118     }
119 
120     LV_LOG_INFO("roller created");
121 
122     return new_roller;
123 }
124 
125 /*=====================
126  * Setter functions
127  *====================*/
128 
129 /**
130  * Set the options on a roller
131  * @param roller pointer to roller object
132  * @param options a string with '\n' separated options. E.g. "One\nTwo\nThree"
133  * @param mode `LV_ROLLER_MODE_NORMAL` or `LV_ROLLER_MODE_INFINITE`
134  */
lv_roller_set_options(lv_obj_t * roller,const char * options,lv_roller_mode_t mode)135 void lv_roller_set_options(lv_obj_t * roller, const char * options, lv_roller_mode_t mode)
136 {
137     lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
138 
139     if(mode == LV_ROLLER_MODE_NORMAL) {
140         ext->mode = LV_ROLLER_MODE_NORMAL;
141         lv_ddlist_set_options(roller, options);
142 
143         /* Make sure the roller's height and the scrollable's height is refreshed.
144          * They are refreshed in `LV_SIGNAL_COORD_CHG` but if the new options has the same width
145          * that signal won't be called. (It called because LV_FIT_TIGHT hor fit)*/
146         refr_height(roller);
147     } else {
148         ext->mode = LV_ROLLER_MODE_INIFINITE;
149 
150         uint32_t opt_len = strlen(options) + 1; /*+1 to add '\n' after option lists*/
151         char * opt_extra = lv_mem_alloc(opt_len * LV_ROLLER_INF_PAGES);
152         uint8_t i;
153         for(i = 0; i < LV_ROLLER_INF_PAGES; i++) {
154             strcpy(&opt_extra[opt_len * i], options);
155             opt_extra[opt_len * (i + 1) - 1] = '\n';
156         }
157         opt_extra[opt_len * LV_ROLLER_INF_PAGES - 1] = '\0';
158         lv_ddlist_set_options(roller, opt_extra);
159         lv_mem_free(opt_extra);
160 
161         /* Make sure the roller's height and the scrollable's height is refreshed.
162          * They are refreshed in `LV_SIGNAL_COORD_CHG` but if the new options has the same width
163          * that signal won't be called. (It called because LV_FIT_TIGHT hor fit)*/
164         refr_height(roller);
165 
166         uint16_t real_id_cnt = ext->ddlist.option_cnt / LV_ROLLER_INF_PAGES;
167         lv_roller_set_selected(roller, ((LV_ROLLER_INF_PAGES / 2) + 1) * real_id_cnt, false); /*Select the middle page*/
168     }
169 }
170 
171 /**
172  * Set the align of the roller's options (left or center)
173  * @param roller - pointer to a roller object
174  * @param align - one of lv_label_align_t values (left, right, center)
175  */
lv_roller_set_align(lv_obj_t * roller,lv_label_align_t align)176 void lv_roller_set_align(lv_obj_t * roller, lv_label_align_t align)
177 {
178     lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
179     lv_mem_assert(ext);
180     if(ext->ddlist.label == NULL) return; /*Probably the roller is being deleted if the label is NULL.*/
181     lv_label_set_align(ext->ddlist.label, align);
182 }
183 
184 /**
185  * Set the selected option
186  * @param roller pointer to a roller object
187  * @param sel_opt id of the selected option (0 ... number of option - 1);
188  * @param anim_en LV_ANIM_ON: set with animation; LV_ANOM_OFF set immediately
189  */
lv_roller_set_selected(lv_obj_t * roller,uint16_t sel_opt,lv_anim_enable_t anim)190 void lv_roller_set_selected(lv_obj_t * roller, uint16_t sel_opt, lv_anim_enable_t anim)
191 {
192 #if LV_USE_ANIMATION == 0
193     anim = LV_ANIM_OFF;
194 #endif
195 
196     if(lv_roller_get_selected(roller) == sel_opt) return;
197 
198     lv_ddlist_set_selected(roller, sel_opt);
199     refr_position(roller, anim);
200 }
201 
202 /**
203  * Set the height to show the given number of rows (options)
204  * @param roller pointer to a roller object
205  * @param row_cnt number of desired visible rows
206  */
lv_roller_set_visible_row_count(lv_obj_t * roller,uint8_t row_cnt)207 void lv_roller_set_visible_row_count(lv_obj_t * roller, uint8_t row_cnt)
208 {
209     lv_roller_ext_t * ext          = lv_obj_get_ext_attr(roller);
210     const lv_style_t * style_label = lv_obj_get_style(ext->ddlist.label);
211     uint8_t n_line_space           = (row_cnt > 1) ? row_cnt - 1 : 1;
212     lv_ddlist_set_fix_height(roller, lv_font_get_line_height(style_label->text.font) * row_cnt +
213                                          style_label->text.line_space * n_line_space);
214 }
215 
216 /**
217  * Set a style of a roller
218  * @param roller pointer to a roller object
219  * @param type which style should be set
220  * @param style pointer to a style
221  */
lv_roller_set_style(lv_obj_t * roller,lv_roller_style_t type,const lv_style_t * style)222 void lv_roller_set_style(lv_obj_t * roller, lv_roller_style_t type, const lv_style_t * style)
223 {
224     switch(type) {
225         case LV_ROLLER_STYLE_BG: lv_obj_set_style(roller, style); break;
226         case LV_ROLLER_STYLE_SEL: lv_ddlist_set_style(roller, LV_DDLIST_STYLE_SEL, style); break;
227     }
228 }
229 
230 /*=====================
231  * Getter functions
232  *====================*/
233 
234 /**
235  * Get the id of the selected option
236  * @param roller pointer to a roller object
237  * @return id of the selected option (0 ... number of option - 1);
238  */
lv_roller_get_selected(const lv_obj_t * roller)239 uint16_t lv_roller_get_selected(const lv_obj_t * roller)
240 {
241     lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
242     if(ext->mode == LV_ROLLER_MODE_INIFINITE) {
243         uint16_t real_id_cnt = ext->ddlist.option_cnt / LV_ROLLER_INF_PAGES;
244         return lv_ddlist_get_selected(roller) % real_id_cnt;
245     } else {
246         return lv_ddlist_get_selected(roller);
247     }
248 }
249 
250 /**
251  * Get the align attribute. Default alignment after _create is LV_LABEL_ALIGN_CENTER
252  * @param roller pointer to a roller object
253  * @return LV_LABEL_ALIGN_LEFT, LV_LABEL_ALIGN_RIGHT or LV_LABEL_ALIGN_CENTER
254  */
lv_roller_get_align(const lv_obj_t * roller)255 lv_label_align_t lv_roller_get_align(const lv_obj_t * roller)
256 {
257     lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
258     lv_mem_assert(ext);
259     lv_mem_assert(ext->ddlist.label);
260     return lv_label_get_align(ext->ddlist.label);
261 }
262 
263 /**
264  * Get the auto width set attribute
265  * @param roller pointer to a roller object
266  * @return true: auto size enabled; false: manual width settings enabled
267  */
lv_roller_get_hor_fit(const lv_obj_t * roller)268 bool lv_roller_get_hor_fit(const lv_obj_t * roller)
269 {
270     return lv_page_get_scrl_fit_left(roller);
271 }
272 
273 /**
274  * Get a style of a roller
275  * @param roller pointer to a roller object
276  * @param type which style should be get
277  * @return style pointer to a style
278  *  */
lv_roller_get_style(const lv_obj_t * roller,lv_roller_style_t type)279 const lv_style_t * lv_roller_get_style(const lv_obj_t * roller, lv_roller_style_t type)
280 {
281     switch(type) {
282         case LV_ROLLER_STYLE_BG: return lv_obj_get_style(roller);
283         case LV_ROLLER_STYLE_SEL: return lv_ddlist_get_style(roller, LV_DDLIST_STYLE_SEL);
284         default: return NULL;
285     }
286 
287     /*To avoid warning*/
288     return NULL;
289 }
290 
291 /**********************
292  *   STATIC FUNCTIONS
293  **********************/
294 
295 /**
296  * Handle the drawing related tasks of the rollers
297  * @param roller pointer to an object
298  * @param mask the object will be drawn only in this area
299  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
300  *                                  (return 'true' if yes)
301  *             LV_DESIGN_DRAW: draw the object (always return 'true')
302  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
303  * @param return true/false, depends on 'mode'
304  */
lv_roller_design(lv_obj_t * roller,const lv_area_t * mask,lv_design_mode_t mode)305 static bool lv_roller_design(lv_obj_t * roller, const lv_area_t * mask, lv_design_mode_t mode)
306 {
307     /*Return false if the object is not covers the mask_p area*/
308     if(mode == LV_DESIGN_COVER_CHK) {
309         return false;
310     }
311     /*Draw the object*/
312     else if(mode == LV_DESIGN_DRAW_MAIN) {
313         draw_bg(roller, mask);
314 
315         const lv_style_t * style = lv_roller_get_style(roller, LV_ROLLER_STYLE_BG);
316         lv_opa_t opa_scale       = lv_obj_get_opa_scale(roller);
317         const lv_font_t * font   = style->text.font;
318         lv_roller_ext_t * ext    = lv_obj_get_ext_attr(roller);
319         lv_coord_t font_h        = lv_font_get_line_height(font);
320         lv_area_t rect_area;
321         rect_area.y1 = roller->coords.y1 + lv_obj_get_height(roller) / 2 - font_h / 2 - style->text.line_space / 2;
322         if((font_h & 0x1) && (style->text.line_space & 0x1)) rect_area.y1--; /*Compensate the two rounding error*/
323         rect_area.y2 = rect_area.y1 + font_h + style->text.line_space - 1;
324         lv_area_t roller_coords;
325         lv_obj_get_coords(roller, &roller_coords);
326         lv_obj_get_inner_coords(roller, &roller_coords);
327 
328         rect_area.x1 = roller_coords.x1;
329         rect_area.x2 = roller_coords.x2;
330 
331         lv_draw_rect(&rect_area, mask, ext->ddlist.sel_style, opa_scale);
332     }
333     /*Post draw when the children are drawn*/
334     else if(mode == LV_DESIGN_DRAW_POST) {
335         const lv_style_t * style = lv_roller_get_style(roller, LV_ROLLER_STYLE_BG);
336         lv_roller_ext_t * ext    = lv_obj_get_ext_attr(roller);
337         const lv_font_t * font   = style->text.font;
338         lv_coord_t font_h        = lv_font_get_line_height(font);
339         lv_opa_t opa_scale       = lv_obj_get_opa_scale(roller);
340 
341         /*Redraw the text on the selected area with a different color*/
342         lv_area_t rect_area;
343         rect_area.y1 = roller->coords.y1 + lv_obj_get_height(roller) / 2 - font_h / 2 - style->text.line_space / 2;
344         if((font_h & 0x1) && (style->text.line_space & 0x1)) rect_area.y1--; /*Compensate the two rounding error*/
345         rect_area.y2 = rect_area.y1 + font_h + style->text.line_space - 1;
346         rect_area.x1 = roller->coords.x1;
347         rect_area.x2 = roller->coords.x2;
348         lv_area_t mask_sel;
349         bool area_ok;
350         area_ok = lv_area_intersect(&mask_sel, mask, &rect_area);
351         if(area_ok) {
352             const lv_style_t * sel_style = lv_roller_get_style(roller, LV_ROLLER_STYLE_SEL);
353             lv_style_t new_style;
354             lv_txt_flag_t txt_align = LV_TXT_FLAG_NONE;
355 
356             {
357                 lv_label_align_t label_align = lv_label_get_align(ext->ddlist.label);
358 
359                 if(LV_LABEL_ALIGN_CENTER == label_align) {
360                     txt_align |= LV_TXT_FLAG_CENTER;
361                 } else if(LV_LABEL_ALIGN_RIGHT == label_align) {
362                     txt_align |= LV_TXT_FLAG_RIGHT;
363                 }
364             }
365 
366             lv_style_copy(&new_style, style);
367             new_style.text.color = sel_style->text.color;
368             new_style.text.opa   = sel_style->text.opa;
369             lv_draw_label(&ext->ddlist.label->coords, &mask_sel, &new_style, opa_scale,
370                           lv_label_get_text(ext->ddlist.label), txt_align, NULL, -1, -1, NULL);
371         }
372     }
373 
374     return true;
375 }
376 
377 /**
378  * Signal function of the roller
379  * @param roller pointer to a roller object
380  * @param sign a signal type from lv_signal_t enum
381  * @param param pointer to a signal specific variable
382  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
383  */
lv_roller_signal(lv_obj_t * roller,lv_signal_t sign,void * param)384 static lv_res_t lv_roller_signal(lv_obj_t * roller, lv_signal_t sign, void * param)
385 {
386     lv_res_t res = LV_RES_OK;
387 
388     /*Don't let the drop down list to handle the control signals. It works differently*/
389     if(sign != LV_SIGNAL_CONTROL && sign != LV_SIGNAL_FOCUS && sign != LV_SIGNAL_DEFOCUS) {
390         /* Include the ancient signal function */
391         res = ancestor_signal(roller, sign, param);
392         if(res != LV_RES_OK) return res;
393     }
394 
395     lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
396 
397     if(sign == LV_SIGNAL_STYLE_CHG) {
398         refr_height(roller);
399 
400         refr_position(roller, false);
401     } else if(sign == LV_SIGNAL_CORD_CHG) {
402 
403         if(lv_obj_get_width(roller) != lv_area_get_width(param) ||
404            lv_obj_get_height(roller) != lv_area_get_height(param)) {
405 
406             refr_height(roller);
407 #if LV_USE_ANIMATION
408             lv_anim_del(lv_page_get_scrl(roller), (lv_anim_exec_xcb_t)lv_obj_set_y);
409 #endif
410             lv_ddlist_set_selected(roller, ext->ddlist.sel_opt_id);
411             refr_position(roller, false);
412         }
413     } else if(sign == LV_SIGNAL_FOCUS) {
414 #if LV_USE_GROUP
415         lv_group_t * g             = lv_obj_get_group(roller);
416         bool editing               = lv_group_get_editing(g);
417         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
418 
419         /*Encoders need special handling*/
420         if(indev_type == LV_INDEV_TYPE_ENCODER) {
421             /*In navigate mode revert the original value*/
422             if(!editing) {
423                 if(ext->ddlist.sel_opt_id != ext->ddlist.sel_opt_id_ori) {
424                     ext->ddlist.sel_opt_id = ext->ddlist.sel_opt_id_ori;
425                     refr_position(roller, true);
426                 }
427             }
428             /*Save the current state when entered to edit mode*/
429             else {
430                 ext->ddlist.sel_opt_id_ori = ext->ddlist.sel_opt_id;
431             }
432         } else {
433             ext->ddlist.sel_opt_id_ori = ext->ddlist.sel_opt_id; /*Save the current value. Used to revert this state if
434                                                                     ENER wont't be pressed*/
435         }
436 #endif
437     } else if(sign == LV_SIGNAL_DEFOCUS) {
438 #if LV_USE_GROUP
439         /*Revert the original state*/
440         if(ext->ddlist.sel_opt_id != ext->ddlist.sel_opt_id_ori) {
441             ext->ddlist.sel_opt_id = ext->ddlist.sel_opt_id_ori;
442             refr_position(roller, true);
443         }
444 #endif
445     } else if(sign == LV_SIGNAL_CONTROL) {
446         char c = *((char *)param);
447         if(c == LV_KEY_RIGHT || c == LV_KEY_DOWN) {
448             if(ext->ddlist.sel_opt_id + 1 < ext->ddlist.option_cnt) {
449                 uint16_t ori_id = ext->ddlist.sel_opt_id_ori; /*lv_roller_set_selceted will overwrite this*/
450                 lv_roller_set_selected(roller, ext->ddlist.sel_opt_id + 1, true);
451                 ext->ddlist.sel_opt_id_ori = ori_id;
452             }
453         } else if(c == LV_KEY_LEFT || c == LV_KEY_UP) {
454             if(ext->ddlist.sel_opt_id > 0) {
455                 uint16_t ori_id = ext->ddlist.sel_opt_id_ori; /*lv_roller_set_selceted will overwrite this*/
456                 lv_roller_set_selected(roller, ext->ddlist.sel_opt_id - 1, true);
457                 ext->ddlist.sel_opt_id_ori = ori_id;
458             }
459         }
460     } else if(sign == LV_SIGNAL_GET_TYPE) {
461         lv_obj_type_t * buf = param;
462         uint8_t i;
463         for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set data*/
464             if(buf->type[i] == NULL) break;
465         }
466         buf->type[i] = "lv_roller";
467     }
468 
469     return res;
470 }
471 
472 /**
473  * Signal function of the scrollable part of the roller.
474  * @param roller_scrl ointer to the scrollable part of roller (page)
475  * @param sign a signal type from lv_signal_t enum
476  * @param param pointer to a signal specific variable
477  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
478  */
lv_roller_scrl_signal(lv_obj_t * roller_scrl,lv_signal_t sign,void * param)479 static lv_res_t lv_roller_scrl_signal(lv_obj_t * roller_scrl, lv_signal_t sign, void * param)
480 {
481     lv_res_t res;
482 
483     /* Include the ancient signal function */
484     res = ancestor_scrl_signal(roller_scrl, sign, param);
485     if(res != LV_RES_OK) return res;
486 
487     lv_indev_t * indev    = lv_indev_get_act();
488     int32_t id            = -1;
489     lv_obj_t * roller     = lv_obj_get_parent(roller_scrl);
490     lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
491 
492     if(ext->ddlist.label == NULL)
493         return LV_RES_INV; /*On delete the ddlist signal deletes the label so nothing left to do
494                               here*/
495 
496     const lv_style_t * style_label = lv_obj_get_style(ext->ddlist.label);
497     const lv_font_t * font         = style_label->text.font;
498     lv_coord_t font_h              = lv_font_get_line_height(font);
499 
500     if(sign == LV_SIGNAL_DRAG_END) {
501         /*If dragged then align the list to there be an element in the middle*/
502         lv_coord_t label_y1   = ext->ddlist.label->coords.y1 - roller->coords.y1;
503         lv_coord_t label_unit = font_h + style_label->text.line_space;
504         lv_coord_t mid        = (roller->coords.y2 - roller->coords.y1) / 2;
505 
506         id = (mid - label_y1 + style_label->text.line_space / 2) / label_unit;
507 
508         if(id < 0) id = 0;
509         if(id >= ext->ddlist.option_cnt) id = ext->ddlist.option_cnt - 1;
510 
511         ext->ddlist.sel_opt_id     = id;
512         ext->ddlist.sel_opt_id_ori = id;
513         res                        = lv_event_send(roller, LV_EVENT_VALUE_CHANGED, &id);
514         if(res != LV_RES_OK) return res;
515     }
516     /*If picked an option by clicking then set it*/
517     else if(sign == LV_SIGNAL_RELEASED) {
518         if(!lv_indev_is_dragging(indev)) {
519             id = ext->ddlist.sel_opt_id;
520 #if LV_USE_GROUP
521             /*In edit mode go to navigate mode if an option is selected*/
522             lv_group_t * g = lv_obj_get_group(roller);
523             bool editing   = lv_group_get_editing(g);
524             if(editing) lv_group_set_editing(g, false);
525 #endif
526         }
527     } else if(sign == LV_SIGNAL_PRESSED) {
528 #if LV_USE_ANIMATION
529         lv_anim_del(roller_scrl, (lv_anim_exec_xcb_t)lv_obj_set_y);
530 #endif
531     }
532 
533     /*Position the scrollable according to the new selected option*/
534     if(id != -1) {
535         refr_position(roller, true);
536     }
537 
538     return res;
539 }
540 
541 /**
542  * Draw a rectangle which has gradient on its top and bottom
543  * @param roller pointer to a roller object
544  * @param mask pointer to the current mask (from the design function)
545  */
draw_bg(lv_obj_t * roller,const lv_area_t * mask)546 static void draw_bg(lv_obj_t * roller, const lv_area_t * mask)
547 {
548     const lv_style_t * style = lv_roller_get_style(roller, LV_ROLLER_STYLE_BG);
549     lv_area_t half_mask;
550     lv_area_t half_roller;
551     lv_coord_t h = lv_obj_get_height(roller);
552     bool union_ok;
553     lv_area_copy(&half_roller, &roller->coords);
554 
555     half_roller.x1 -= roller->ext_draw_pad; /*Add ext size too (e.g. because of shadow draw) */
556     half_roller.x2 += roller->ext_draw_pad;
557     half_roller.y1 -= roller->ext_draw_pad;
558     half_roller.y2 = roller->coords.y1 + h / 2;
559 
560     union_ok = lv_area_intersect(&half_mask, &half_roller, mask);
561 
562     half_roller.x1 += roller->ext_draw_pad; /*Revert ext. size adding*/
563     half_roller.x2 -= roller->ext_draw_pad;
564     half_roller.y1 += roller->ext_draw_pad;
565     half_roller.y2 += style->body.radius;
566 
567     if(union_ok) {
568         lv_draw_rect(&half_roller, &half_mask, style, lv_obj_get_opa_scale(roller));
569     }
570 
571     half_roller.x1 -= roller->ext_draw_pad; /*Add ext size too (e.g. because of shadow draw) */
572     half_roller.x2 += roller->ext_draw_pad;
573     half_roller.y2 = roller->coords.y2 + roller->ext_draw_pad;
574     half_roller.y1 = roller->coords.y1 + h / 2;
575     if((h & 0x1) == 0) half_roller.y1++; /*With even height the pixels in the middle would be drawn twice*/
576 
577     union_ok = lv_area_intersect(&half_mask, &half_roller, mask);
578 
579     half_roller.x1 += roller->ext_draw_pad; /*Revert ext. size adding*/
580     half_roller.x2 -= roller->ext_draw_pad;
581     half_roller.y2 -= roller->ext_draw_pad;
582     half_roller.y1 -= style->body.radius;
583 
584     if(union_ok) {
585         lv_style_t style_tmp;
586         memcpy(&style_tmp, style, sizeof(lv_style_t));
587         style_tmp.body.main_color = style->body.grad_color;
588         style_tmp.body.grad_color = style->body.main_color;
589         lv_draw_rect(&half_roller, &half_mask, &style_tmp, lv_obj_get_opa_scale(roller));
590     }
591 }
592 
593 /**
594  * Refresh the position of the roller. It uses the id stored in: ext->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 * roller,lv_anim_enable_t anim_en)598 static void refr_position(lv_obj_t * roller, lv_anim_enable_t anim_en)
599 {
600 #if LV_USE_ANIMATION == 0
601     anim_en = LV_ANIM_OFF;
602 #endif
603 
604     lv_obj_t * roller_scrl         = lv_page_get_scrl(roller);
605     lv_roller_ext_t * ext          = lv_obj_get_ext_attr(roller);
606     const lv_style_t * style_label = lv_obj_get_style(ext->ddlist.label);
607     const lv_font_t * font         = style_label->text.font;
608     lv_coord_t font_h              = lv_font_get_line_height(font);
609     lv_coord_t h                   = lv_obj_get_height(roller);
610     uint16_t anim_time             = lv_roller_get_anim_time(roller);
611 
612     /* Normally the animtaion's `end_cb` sets correct position of the roller is infinite.
613      * But without animations do it manually*/
614     if(anim_en == LV_ANIM_OFF || anim_time == 0) {
615         inf_normalize(roller_scrl);
616     }
617 
618     int32_t id = ext->ddlist.sel_opt_id;
619     lv_coord_t line_y1 =
620         id * (font_h + style_label->text.line_space) + ext->ddlist.label->coords.y1 - roller_scrl->coords.y1;
621     lv_coord_t new_y = -line_y1 + (h - font_h) / 2;
622 
623     if(anim_en == LV_ANIM_OFF || anim_time == 0) {
624         lv_obj_set_y(roller_scrl, new_y);
625     } else {
626 #if LV_USE_ANIMATION
627         lv_anim_t a;
628         a.var            = roller_scrl;
629         a.start          = lv_obj_get_y(roller_scrl);
630         a.end            = new_y;
631         a.exec_cb        = (lv_anim_exec_xcb_t)lv_obj_set_y;
632         a.path_cb        = lv_anim_path_linear;
633         a.ready_cb       = scroll_anim_ready_cb;
634         a.act_time       = 0;
635         a.time           = anim_time;
636         a.playback       = 0;
637         a.playback_pause = 0;
638         a.repeat         = 0;
639         a.repeat_pause   = 0;
640         lv_anim_create(&a);
641 #endif
642     }
643 }
644 
645 /**
646  * Refresh the height of the roller and the scrolable
647  * @param roller pointer to roller
648  */
refr_height(lv_obj_t * roller)649 static void refr_height(lv_obj_t * roller)
650 {
651     lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
652     lv_align_t obj_align  = LV_ALIGN_IN_LEFT_MID;
653     if(ext->ddlist.label) {
654         lv_label_align_t label_align = lv_label_get_align(ext->ddlist.label);
655         if(LV_LABEL_ALIGN_CENTER == label_align)
656             obj_align = LV_ALIGN_CENTER;
657         else if(LV_LABEL_ALIGN_RIGHT == label_align)
658             obj_align = LV_ALIGN_IN_RIGHT_MID;
659     }
660 
661     lv_obj_set_height(lv_page_get_scrl(roller), lv_obj_get_height(ext->ddlist.label) + lv_obj_get_height(roller));
662     lv_obj_align(ext->ddlist.label, NULL, obj_align, 0, 0);
663 #if LV_USE_ANIMATION
664     lv_anim_del(lv_page_get_scrl(roller), (lv_anim_exec_xcb_t)lv_obj_set_y);
665 #endif
666     lv_ddlist_set_selected(roller, ext->ddlist.sel_opt_id);
667 }
668 
669 /**
670  * Set the middle page for the roller if inifinte is enabled
671  * @param scrl pointer to the roller's scrollable (lv_obj_t *)
672  */
inf_normalize(void * scrl)673 static void inf_normalize(void * scrl)
674 {
675     lv_obj_t * roller_scrl = (lv_obj_t *)scrl;
676     lv_obj_t * roller      = lv_obj_get_parent(roller_scrl);
677     lv_roller_ext_t * ext  = lv_obj_get_ext_attr(roller);
678 
679     if(ext->mode == LV_ROLLER_MODE_INIFINITE) {
680         uint16_t real_id_cnt = ext->ddlist.option_cnt / LV_ROLLER_INF_PAGES;
681 
682         ext->ddlist.sel_opt_id = ext->ddlist.sel_opt_id % real_id_cnt;
683 
684         ext->ddlist.sel_opt_id += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
685 
686         /*Move to the new id*/
687         const lv_style_t * style_label = lv_obj_get_style(ext->ddlist.label);
688         const lv_font_t * font         = style_label->text.font;
689         lv_coord_t font_h              = lv_font_get_line_height(font);
690         lv_coord_t h                   = lv_obj_get_height(roller);
691 
692         lv_coord_t line_y1 = ext->ddlist.sel_opt_id * (font_h + style_label->text.line_space) +
693                              ext->ddlist.label->coords.y1 - roller_scrl->coords.y1;
694         lv_coord_t new_y = -line_y1 + (h - font_h) / 2;
695         lv_obj_set_y(roller_scrl, new_y);
696     }
697 }
698 
699 #if LV_USE_ANIMATION
scroll_anim_ready_cb(lv_anim_t * a)700 static void scroll_anim_ready_cb(lv_anim_t * a)
701 {
702     inf_normalize(a->var);
703 }
704 #endif
705 
706 #endif
707