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