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_misc/lv_debug.h"
13 #include "../lv_draw/lv_draw.h"
14 #include "../lv_core/lv_group.h"
15 #include "../lv_themes/lv_theme.h"
16
17 /*********************
18 * DEFINES
19 *********************/
20 #define LV_OBJX_NAME "lv_roller"
21
22 #if LV_USE_ANIMATION == 0
23 #undef LV_ROLLER_DEF_ANIM_TIME
24 #define LV_ROLLER_DEF_ANIM_TIME 0 /*No animation*/
25 #endif
26
27 /**********************
28 * TYPEDEFS
29 **********************/
30
31 /**********************
32 * STATIC PROTOTYPES
33 **********************/
34 static lv_design_res_t lv_roller_design(lv_obj_t * roller, const lv_area_t * clip_area, lv_design_mode_t mode);
35 static lv_design_res_t lv_roller_label_design(lv_obj_t * label, const lv_area_t * clip_area, lv_design_mode_t mode);
36 static lv_res_t lv_roller_scrl_signal(lv_obj_t * roller_scrl, lv_signal_t sign, void * param);
37 static lv_style_list_t * lv_roller_get_style(lv_obj_t * roller, uint8_t part);
38 static lv_res_t lv_roller_signal(lv_obj_t * roller, lv_signal_t sign, void * param);
39 static void refr_position(lv_obj_t * roller, lv_anim_enable_t animen);
40 static void refr_height(lv_obj_t * roller);
41 static void refr_width(lv_obj_t * roller);
42 static lv_res_t release_handler(lv_obj_t * roller);
43 static void inf_normalize(void * roller_scrl);
44 static lv_obj_t * get_label(const lv_obj_t * roller);
45 #if LV_USE_ANIMATION
46 static void scroll_anim_ready_cb(lv_anim_t * a);
47 #endif
48 static void draw_bg(lv_obj_t * roller, const lv_area_t * clip_area);
49
50 /**********************
51 * STATIC VARIABLES
52 **********************/
53 static lv_signal_cb_t ancestor_signal;
54 static lv_design_cb_t ancestor_label_design;
55 static lv_signal_cb_t ancestor_scrl_signal;
56
57 /**********************
58 * MACROS
59 **********************/
60
61 /**********************
62 * GLOBAL FUNCTIONS
63 **********************/
64
65 /**
66 * Create a roller object
67 * @param par pointer to an object, it will be the parent of the new roller
68 * @param copy pointer to a roller object, if not NULL then the new object will be copied from it
69 * @return pointer to the created roller
70 */
lv_roller_create(lv_obj_t * par,const lv_obj_t * copy)71 lv_obj_t * lv_roller_create(lv_obj_t * par, const lv_obj_t * copy)
72 {
73 LV_LOG_TRACE("roller create started");
74
75 /*Create the ancestor of roller*/
76 lv_obj_t * roller = lv_page_create(par, copy);
77 LV_ASSERT_MEM(roller);
78 if(roller == NULL) return NULL;
79
80 if(ancestor_scrl_signal == NULL) ancestor_scrl_signal = lv_obj_get_signal_cb(lv_page_get_scrollable(roller));
81 if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(roller);
82
83 /*Allocate the roller type specific extended data*/
84 lv_roller_ext_t * ext = lv_obj_allocate_ext_attr(roller, sizeof(lv_roller_ext_t));
85 LV_ASSERT_MEM(ext);
86 if(ext == NULL) {
87 lv_obj_del(roller);
88 return NULL;
89 }
90
91 ext->mode = LV_ROLLER_MODE_NORMAL;
92 ext->option_cnt = 0;
93 ext->sel_opt_id = 0;
94 ext->sel_opt_id_ori = 0;
95 ext->auto_fit = 1;
96 lv_style_list_init(&ext->style_sel);
97
98 /*The signal and design functions are not copied so set them here*/
99 lv_obj_set_signal_cb(roller, lv_roller_signal);
100 lv_obj_set_design_cb(roller, lv_roller_design);
101
102 /*Init the new roller roller*/
103 if(copy == NULL) {
104
105 lv_obj_t * label = lv_label_create(roller, NULL);
106 lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
107 if(ancestor_label_design == NULL) ancestor_label_design = lv_obj_get_design_cb(label);
108 lv_obj_set_design_cb(label, lv_roller_label_design);
109 lv_obj_t * scrl = lv_page_get_scrollable(roller);
110 lv_obj_set_drag(scrl, true);
111 lv_page_set_scrollable_fit2(roller, LV_FIT_PARENT, LV_FIT_NONE); /*Height is specified directly*/
112 lv_roller_set_anim_time(roller, LV_ROLLER_DEF_ANIM_TIME);
113 lv_roller_set_options(roller, "Option 1\nOption 2\nOption 3\nOption 4\nOption 5", LV_ROLLER_MODE_NORMAL);
114
115 lv_obj_set_signal_cb(scrl, lv_roller_scrl_signal);
116
117 lv_obj_clean_style_list(roller, LV_PAGE_PART_SCROLLABLE); /*Use a transparent scrollable*/
118 lv_theme_apply(roller, LV_THEME_ROLLER);
119 refr_height(roller);
120
121 lv_roller_set_visible_row_count(roller, 3);
122 }
123 /*Copy an existing roller*/
124 else {
125 lv_label_create(roller, get_label(copy));
126
127 lv_roller_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
128 ext->mode = copy_ext->mode;
129 ext->option_cnt = copy_ext->option_cnt;
130 ext->sel_opt_id = copy_ext->sel_opt_id;
131 ext->sel_opt_id_ori = copy_ext->sel_opt_id;
132 ext->auto_fit = copy_ext->auto_fit;
133 lv_obj_t * scrl = lv_page_get_scrollable(roller);
134 lv_obj_set_signal_cb(scrl, lv_roller_scrl_signal);
135
136 lv_style_list_copy(&ext->style_sel, ©_ext->style_sel);
137 lv_obj_refresh_style(roller, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
138 }
139
140 LV_LOG_INFO("roller created");
141
142 return roller;
143 }
144
145 /*=====================
146 * Setter functions
147 *====================*/
148
149 /**
150 * Set the options on a roller
151 * @param roller pointer to roller object
152 * @param options a string with '\n' separated options. E.g. "One\nTwo\nThree"
153 * @param mode `LV_ROLLER_MODE_NORMAL` or `LV_ROLLER_MODE_INFINITE`
154 */
lv_roller_set_options(lv_obj_t * roller,const char * options,lv_roller_mode_t mode)155 void lv_roller_set_options(lv_obj_t * roller, const char * options, lv_roller_mode_t mode)
156 {
157 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
158 LV_ASSERT_STR(options);
159
160
161 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
162 lv_obj_t * label = get_label(roller);
163
164 ext->sel_opt_id = 0;
165 ext->sel_opt_id_ori = 0;
166
167 /*Count the '\n'-s to determine the number of options*/
168 ext->option_cnt = 0;
169 uint32_t cnt;
170 for(cnt = 0; options[cnt] != '\0'; cnt++) {
171 if(options[cnt] == '\n') ext->option_cnt++;
172 }
173 ext->option_cnt++; /*Last option has no `\n`*/
174
175 if(mode == LV_ROLLER_MODE_NORMAL) {
176 ext->mode = LV_ROLLER_MODE_NORMAL;
177 lv_label_set_text(label, options);
178 }
179 else {
180 ext->mode = LV_ROLLER_MODE_INIFINITE;
181
182 size_t opt_len = strlen(options) + 1; /*+1 to add '\n' after option lists*/
183 char * opt_extra = _lv_mem_buf_get(opt_len * LV_ROLLER_INF_PAGES);
184 uint8_t i;
185 for(i = 0; i < LV_ROLLER_INF_PAGES; i++) {
186 strcpy(&opt_extra[opt_len * i], options);
187 opt_extra[opt_len * (i + 1) - 1] = '\n';
188 }
189 opt_extra[opt_len * LV_ROLLER_INF_PAGES - 1] = '\0';
190 lv_label_set_text(label, opt_extra);
191 _lv_mem_buf_release(opt_extra);
192
193 ext->sel_opt_id = ((LV_ROLLER_INF_PAGES / 2) + 0) * ext->option_cnt;
194
195 ext->option_cnt = ext->option_cnt * LV_ROLLER_INF_PAGES;
196 }
197
198 ext->sel_opt_id_ori = ext->sel_opt_id;
199
200 refr_height(roller);
201 refr_width(roller);
202 }
203
204 /**
205 * Set the align of the roller's options (left or center)
206 * @param roller - pointer to a roller object
207 * @param align - one of lv_label_align_t values (left, right, center)
208 */
lv_roller_set_align(lv_obj_t * roller,lv_label_align_t align)209 void lv_roller_set_align(lv_obj_t * roller, lv_label_align_t align)
210 {
211 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
212
213 lv_obj_t * label = get_label(roller);
214 if(label == NULL) return; /*Probably the roller is being deleted if the label is NULL.*/
215
216 lv_label_set_align(label, align);
217 refr_width(roller); /*To set the correct label position*/
218 }
219
220 /**
221 * Set the selected option
222 * @param roller pointer to a roller object
223 * @param sel_opt id of the selected option (0 ... number of option - 1);
224 * @param anim_en LV_ANIM_ON: set with animation; LV_ANOM_OFF set immediately
225 */
lv_roller_set_selected(lv_obj_t * roller,uint16_t sel_opt,lv_anim_enable_t anim)226 void lv_roller_set_selected(lv_obj_t * roller, uint16_t sel_opt, lv_anim_enable_t anim)
227 {
228 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
229
230 #if LV_USE_ANIMATION == 0
231 anim = LV_ANIM_OFF;
232 #endif
233
234 /* Set the value even if it's the same as the current value because
235 * if moving to the next option with an animation which was just deleted in the PRESS signal
236 * nothing will continue the animation. */
237
238 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
239
240 /*In infinite mode interpret the new ID relative to the currently visible "page"*/
241 if(ext->mode == LV_ROLLER_MODE_INIFINITE) {
242 int32_t sel_opt_signed = sel_opt;
243 uint16_t page = ext->sel_opt_id / LV_ROLLER_INF_PAGES;
244
245 /* `sel_opt` should be less than the number of options set by the user.
246 * If it's more then probably it's a reference from not the first page
247 * so normalize `sel_opt` */
248 if(page != 0) {
249 sel_opt_signed -= page * LV_ROLLER_INF_PAGES;
250 }
251
252 sel_opt = page * LV_ROLLER_INF_PAGES + sel_opt_signed;
253 }
254
255 ext->sel_opt_id = sel_opt < ext->option_cnt ? sel_opt : ext->option_cnt - 1;
256 ext->sel_opt_id_ori = ext->sel_opt_id;
257
258 refr_position(roller, anim);
259 }
260
261 /**
262 * Set the height to show the given number of rows (options)
263 * @param roller pointer to a roller object
264 * @param row_cnt number of desired visible rows
265 */
lv_roller_set_visible_row_count(lv_obj_t * roller,uint8_t row_cnt)266 void lv_roller_set_visible_row_count(lv_obj_t * roller, uint8_t row_cnt)
267 {
268 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
269
270 const lv_font_t * font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG);
271 lv_style_int_t line_space = lv_obj_get_style_text_line_space(roller, LV_ROLLER_PART_BG);
272 lv_obj_set_height(roller, (lv_font_get_line_height(font) + line_space) * row_cnt);
273
274 refr_height(roller);
275 refr_position(roller, LV_ANIM_OFF);
276 }
277
278 /**
279 * Allow automatically setting the width of roller according to it's content.
280 * @param roller pointer to a roller object
281 * @param auto_fit true: enable auto fit
282 */
lv_roller_set_auto_fit(lv_obj_t * roller,bool auto_fit)283 void lv_roller_set_auto_fit(lv_obj_t * roller, bool auto_fit)
284 {
285 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
286 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
287 ext->auto_fit = auto_fit;
288 refr_width(roller);
289 }
290
291 /*=====================
292 * Getter functions
293 *====================*/
294
295 /**
296 * Get the id of the selected option
297 * @param roller pointer to a roller object
298 * @return id of the selected option (0 ... number of option - 1);
299 */
lv_roller_get_selected(const lv_obj_t * roller)300 uint16_t lv_roller_get_selected(const lv_obj_t * roller)
301 {
302 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
303
304 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
305 if(ext->mode == LV_ROLLER_MODE_INIFINITE) {
306 uint16_t real_id_cnt = ext->option_cnt / LV_ROLLER_INF_PAGES;
307 return ext->sel_opt_id % real_id_cnt;
308 }
309 else {
310 return ext->sel_opt_id;
311 }
312 }
313
314
315 /**
316 * Get the current selected option as a string
317 * @param ddlist pointer to ddlist object
318 * @param buf pointer to an array to store the string
319 * @param buf_size size of `buf` in bytes. 0: to ignore it.
320 */
lv_roller_get_selected_str(const lv_obj_t * roller,char * buf,uint32_t buf_size)321 void lv_roller_get_selected_str(const lv_obj_t * roller, char * buf, uint32_t buf_size)
322 {
323 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
324
325 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
326 lv_obj_t * label = get_label(roller);
327 uint32_t i;
328 uint16_t line = 0;
329 const char * opt_txt = lv_label_get_text(label);
330 size_t txt_len = strlen(opt_txt);
331
332 for(i = 0; i < txt_len && line != ext->sel_opt_id; i++) {
333 if(opt_txt[i] == '\n') line++;
334 }
335
336 uint32_t c;
337 for(c = 0; i < txt_len && opt_txt[i] != '\n'; c++, i++) {
338 if(buf_size && c >= buf_size - 1) {
339 LV_LOG_WARN("lv_dropdown_get_selected_str: the buffer was too small")
340 break;
341 }
342 buf[c] = opt_txt[i];
343 }
344
345 buf[c] = '\0';
346 }
347
348 /**
349 * Get the total number of options
350 * @param roller pointer to a roller object
351 * @return the total number of options
352 */
lv_roller_get_option_cnt(const lv_obj_t * roller)353 uint16_t lv_roller_get_option_cnt(const lv_obj_t * roller)
354 {
355 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
356
357 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
358 if(ext->mode == LV_ROLLER_MODE_INIFINITE) {
359 return ext->option_cnt / LV_ROLLER_INF_PAGES;
360 }
361 else {
362 return ext->option_cnt;
363 }
364 }
365
366 /**
367 * Get the align attribute. Default alignment after _create is LV_LABEL_ALIGN_CENTER
368 * @param roller pointer to a roller object
369 * @return LV_LABEL_ALIGN_LEFT, LV_LABEL_ALIGN_RIGHT or LV_LABEL_ALIGN_CENTER
370 */
lv_roller_get_align(const lv_obj_t * roller)371 lv_label_align_t lv_roller_get_align(const lv_obj_t * roller)
372 {
373 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
374
375 return lv_label_get_align(get_label(roller));
376 }
377
378 /**
379 * Get whether the auto fit option is enabled or not.
380 * @param roller pointer to a roller object
381 * @return true: auto fit is enabled
382 */
lv_roller_get_auto_fit(lv_obj_t * roller)383 bool lv_roller_get_auto_fit(lv_obj_t * roller)
384 {
385 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
386 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
387 return ext->auto_fit ? true : false;
388 }
389
390 /**
391 * Get the options of a roller
392 * @param roller pointer to roller object
393 * @return the options separated by '\n'-s (E.g. "Option1\nOption2\nOption3")
394 */
lv_roller_get_options(const lv_obj_t * roller)395 const char * lv_roller_get_options(const lv_obj_t * roller)
396 {
397 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
398
399 return lv_label_get_text(get_label(roller));
400 }
401
402
403 /**********************
404 * STATIC FUNCTIONS
405 **********************/
406
407 /**
408 * Handle the drawing related tasks of the rollers
409 * @param roller pointer to an object
410 * @param clip_area the object will be drawn only in this area
411 * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
412 * (return 'true' if yes)
413 * LV_DESIGN_DRAW: draw the object (always return 'true')
414 * LV_DESIGN_DRAW_POST: drawing after all children are drawn
415 * @param return an element of `lv_design_res_t`
416 */
lv_roller_design(lv_obj_t * roller,const lv_area_t * clip_area,lv_design_mode_t mode)417 static lv_design_res_t lv_roller_design(lv_obj_t * roller, const lv_area_t * clip_area, lv_design_mode_t mode)
418 {
419 if(mode == LV_DESIGN_COVER_CHK) {
420 return LV_DESIGN_RES_NOT_COVER;
421 }
422 /*Draw the object*/
423 else if(mode == LV_DESIGN_DRAW_MAIN) {
424 draw_bg(roller, clip_area);
425
426 const lv_font_t * font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG);
427 lv_style_int_t line_space = lv_obj_get_style_text_line_space(roller, LV_ROLLER_PART_BG);
428 lv_coord_t font_h = lv_font_get_line_height(font);
429 lv_area_t rect_area;
430 rect_area.y1 = roller->coords.y1 + lv_obj_get_height(roller) / 2 - font_h / 2 - line_space / 2;
431 if((font_h & 0x1) && (line_space & 0x1)) rect_area.y1--; /*Compensate the two rounding error*/
432 rect_area.y2 = rect_area.y1 + font_h + line_space - 1;
433 lv_area_t roller_coords;
434 lv_obj_get_coords(roller, &roller_coords);
435 lv_obj_get_inner_coords(roller, &roller_coords);
436
437 rect_area.x1 = roller_coords.x1;
438 rect_area.x2 = roller_coords.x2;
439
440 lv_draw_rect_dsc_t sel_dsc;
441 lv_draw_rect_dsc_init(&sel_dsc);
442 lv_obj_init_draw_rect_dsc(roller, LV_ROLLER_PART_SELECTED, &sel_dsc);
443 lv_draw_rect(&rect_area, clip_area, &sel_dsc);
444 }
445 /*Post draw when the children are drawn*/
446 else if(mode == LV_DESIGN_DRAW_POST) {
447 lv_draw_label_dsc_t label_dsc;
448 lv_draw_label_dsc_init(&label_dsc);
449 lv_obj_init_draw_label_dsc(roller, LV_ROLLER_PART_SELECTED, &label_dsc);
450
451 lv_coord_t bg_font_h = lv_font_get_line_height(lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG));
452
453 /*Redraw the text on the selected area*/
454 lv_area_t rect_area;
455 rect_area.y1 = roller->coords.y1 + lv_obj_get_height(roller) / 2 - bg_font_h / 2 - label_dsc.line_space / 2;
456 if((bg_font_h & 0x1) && (label_dsc.line_space & 0x1)) rect_area.y1--; /*Compensate the two rounding error*/
457 rect_area.y2 = rect_area.y1 + bg_font_h + label_dsc.line_space - 1;
458 rect_area.x1 = roller->coords.x1;
459 rect_area.x2 = roller->coords.x2;
460 lv_area_t mask_sel;
461 bool area_ok;
462 area_ok = _lv_area_intersect(&mask_sel, clip_area, &rect_area);
463 if(area_ok) {
464 lv_obj_t * label = get_label(roller);
465 lv_label_align_t label_align = lv_roller_get_align(roller);
466
467 if(LV_LABEL_ALIGN_CENTER == label_align) {
468 label_dsc.flag |= LV_TXT_FLAG_CENTER;
469 }
470 else if(LV_LABEL_ALIGN_RIGHT == label_align) {
471 label_dsc.flag |= LV_TXT_FLAG_RIGHT;
472 }
473
474 /*Get the size of the "selected text"*/
475 lv_point_t res_p;
476 _lv_txt_get_size(&res_p, lv_label_get_text(label), label_dsc.font, label_dsc.letter_space, label_dsc.line_space,
477 lv_obj_get_width(roller), LV_TXT_FLAG_EXPAND);
478
479 /*Move the selected label proportionally with the background label*/
480 lv_coord_t roller_h = lv_obj_get_height(roller);
481 int32_t label_y_prop = label->coords.y1 - (roller_h / 2 +
482 roller->coords.y1); /*label offset from the middle line of the roller*/
483 label_y_prop = (label_y_prop << 14) / lv_obj_get_height(
484 label); /*Proportional position from the middle line (upscaled)*/
485
486 /*Apply a correction with different line heights*/
487 const lv_font_t * normal_label_font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG);
488 lv_coord_t corr = (label_dsc.font->line_height - normal_label_font->line_height) / 2;
489
490 /*Apply the proportional position to the selected text*/
491 res_p.y -= corr;
492 int32_t label_sel_y = roller_h / 2 + roller->coords.y1;
493 label_sel_y += (label_y_prop * res_p.y) >> 14;
494 label_sel_y -= corr;
495
496 /*Draw the selected text*/
497 lv_area_t label_sel_area;
498 label_sel_area.x1 = label->coords.x1;
499 label_sel_area.y1 = label_sel_y;
500 label_sel_area.x2 = label->coords.x2;
501 label_sel_area.y2 = label_sel_area.y1 + res_p.y;
502
503 label_dsc.flag |= LV_TXT_FLAG_EXPAND;
504 lv_draw_label(&label_sel_area, &mask_sel, &label_dsc, lv_label_get_text(label), NULL);
505 }
506 }
507
508 return LV_DESIGN_RES_OK;
509 }
510
511
512 /**
513 * Handle the drawing related tasks of the roller's label
514 * @param roller pointer to an object
515 * @param clip_area the object will be drawn only in this area
516 * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
517 * (return 'true' if yes)
518 * LV_DESIGN_DRAW: draw the object (always return 'true')
519 * LV_DESIGN_DRAW_POST: drawing after all children are drawn
520 * @param return an element of `lv_design_res_t`
521 */
lv_roller_label_design(lv_obj_t * label,const lv_area_t * clip_area,lv_design_mode_t mode)522 static lv_design_res_t lv_roller_label_design(lv_obj_t * label, const lv_area_t * clip_area, lv_design_mode_t mode)
523 {
524 if(mode == LV_DESIGN_COVER_CHK) {
525 return ancestor_label_design(label, clip_area, mode);
526 }
527 /*Draw the object*/
528 else if(mode == LV_DESIGN_DRAW_MAIN) {
529 /* Split the drawing of the label into an upper (above the selected area)
530 * and a lower (below the selected area)*/
531 lv_obj_t * roller = lv_obj_get_parent(lv_obj_get_parent(label));
532 const lv_font_t * font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG);
533 lv_style_int_t line_space = lv_obj_get_style_text_line_space(roller, LV_ROLLER_PART_BG);
534 lv_coord_t font_h = lv_font_get_line_height(font);
535
536 lv_area_t rect_area;
537 rect_area.y1 = roller->coords.y1 + lv_obj_get_height(roller) / 2 - font_h / 2 - line_space / 2;
538 if((font_h & 0x1) && (line_space & 0x1)) rect_area.y1--; /*Compensate the two rounding error*/
539 rect_area.y2 = rect_area.y1 + font_h + line_space - 1;
540 lv_area_t roller_coords;
541 lv_obj_get_coords(roller, &roller_coords);
542 lv_obj_get_inner_coords(roller, &roller_coords);
543
544 rect_area.x1 = roller_coords.x1;
545 rect_area.x2 = roller_coords.x2;
546
547 lv_area_t clip2;
548 clip2.x1 = label->coords.x1;
549 clip2.y1 = label->coords.y1;
550 clip2.x2 = label->coords.x2;
551 clip2.y2 = rect_area.y1;
552 if(_lv_area_intersect(&clip2, clip_area, &clip2)) {
553 ancestor_label_design(label, &clip2, mode);
554 }
555
556 clip2.x1 = label->coords.x1;
557 clip2.y1 = rect_area.y2;
558 clip2.x2 = label->coords.x2;
559 clip2.y2 = label->coords.y2;
560 if(_lv_area_intersect(&clip2, clip_area, &clip2)) {
561 ancestor_label_design(label, &clip2, mode);
562 }
563 }
564
565 return LV_DESIGN_RES_OK;
566 }
567
568 /**
569 * Signal function of the roller
570 * @param roller pointer to a roller object
571 * @param sign a signal type from lv_signal_t enum
572 * @param param pointer to a signal specific variable
573 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
574 */
lv_roller_signal(lv_obj_t * roller,lv_signal_t sign,void * param)575 static lv_res_t lv_roller_signal(lv_obj_t * roller, lv_signal_t sign, void * param)
576 {
577 lv_res_t res = LV_RES_OK;
578 if(sign == LV_SIGNAL_GET_STYLE) {
579 lv_get_style_info_t * info = param;
580 info->result = lv_roller_get_style(roller, info->part);
581 if(info->result != NULL) return LV_RES_OK;
582 else return ancestor_signal(roller, sign, param);
583 }
584
585 /* Include the ancient signal function */
586 if(sign != LV_SIGNAL_CONTROL) { /*Don't let the page to scroll on keys*/
587 res = ancestor_signal(roller, sign, param);
588 if(res != LV_RES_OK) return res;
589 }
590
591 if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
592
593 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
594 LV_UNUSED(ext);
595
596 if(sign == LV_SIGNAL_STYLE_CHG) {
597 lv_obj_t * label = get_label(roller);
598 /*Be sure the label's style is updated before processing the roller*/
599 if(label) lv_signal_send(label, LV_SIGNAL_STYLE_CHG, NULL);
600 refr_height(roller);
601 refr_width(roller);
602 refr_position(roller, false);
603 }
604 else if(sign == LV_SIGNAL_COORD_CHG) {
605
606 if(lv_obj_get_width(roller) != lv_area_get_width(param) ||
607 lv_obj_get_height(roller) != lv_area_get_height(param)) {
608 #if LV_USE_ANIMATION
609 lv_anim_del(lv_page_get_scrollable(roller), (lv_anim_exec_xcb_t)lv_obj_set_y);
610 #endif
611 refr_position(roller, false);
612 refr_width(roller);
613 }
614 }
615 else if(sign == LV_SIGNAL_RELEASED) {
616 release_handler(roller);
617 }
618 else if(sign == LV_SIGNAL_FOCUS) {
619 #if LV_USE_GROUP
620 lv_group_t * g = lv_obj_get_group(roller);
621 bool editing = lv_group_get_editing(g);
622 lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
623
624 /*Encoders need special handling*/
625 if(indev_type == LV_INDEV_TYPE_ENCODER) {
626 /*In navigate mode revert the original value*/
627 if(!editing) {
628 if(ext->sel_opt_id != ext->sel_opt_id_ori) {
629 ext->sel_opt_id = ext->sel_opt_id_ori;
630 refr_position(roller, true);
631 }
632 }
633 /*Save the current state when entered to edit mode*/
634 else {
635 ext->sel_opt_id_ori = ext->sel_opt_id;
636 }
637 }
638 else {
639 ext->sel_opt_id_ori = ext->sel_opt_id; /*Save the current value. Used to revert this state if
640 ENTER won't be pressed*/
641 }
642 #endif
643 }
644 else if(sign == LV_SIGNAL_DEFOCUS) {
645 #if LV_USE_GROUP
646 /*Revert the original state*/
647 if(ext->sel_opt_id != ext->sel_opt_id_ori) {
648 ext->sel_opt_id = ext->sel_opt_id_ori;
649 refr_position(roller, true);
650 }
651 #endif
652 }
653 else if(sign == LV_SIGNAL_CONTROL) {
654 #if LV_USE_GROUP
655 char c = *((char *)param);
656 if(c == LV_KEY_RIGHT || c == LV_KEY_DOWN) {
657 if(ext->sel_opt_id + 1 < ext->option_cnt) {
658 uint16_t ori_id = ext->sel_opt_id_ori; /*lv_roller_set_selected will overwrite this*/
659 lv_roller_set_selected(roller, ext->sel_opt_id + 1, true);
660 ext->sel_opt_id_ori = ori_id;
661 }
662 }
663 else if(c == LV_KEY_LEFT || c == LV_KEY_UP) {
664 if(ext->sel_opt_id > 0) {
665 uint16_t ori_id = ext->sel_opt_id_ori; /*lv_roller_set_selected will overwrite this*/
666
667 lv_roller_set_selected(roller, ext->sel_opt_id - 1, true);
668 ext->sel_opt_id_ori = ori_id;
669 }
670 }
671 #endif
672 }
673 else if(sign == LV_SIGNAL_CLEANUP) {
674 lv_obj_clean_style_list(roller, LV_ROLLER_PART_SELECTED);
675 }
676 return res;
677 }
678
679 /**
680 * Get the style descriptor of a part of the object
681 * @param page pointer the object
682 * @param part the part from `lv_roller_part_t`. (LV_ROLLER_PART_...)
683 * @return pointer to the style descriptor of the specified part
684 */
lv_roller_get_style(lv_obj_t * roller,uint8_t part)685 static lv_style_list_t * lv_roller_get_style(lv_obj_t * roller, uint8_t part)
686 {
687 LV_ASSERT_OBJ(roller, LV_OBJX_NAME);
688
689 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
690 lv_style_list_t * style_dsc_p;
691
692 switch(part) {
693 case LV_ROLLER_PART_BG:
694 style_dsc_p = &roller->style_list;
695 break;
696 case LV_ROLLER_PART_SELECTED:
697 style_dsc_p = &ext->style_sel;
698 break;
699 default:
700 style_dsc_p = NULL;
701 }
702
703 return style_dsc_p;
704 }
705
706 /**
707 * Signal function of the scrollable part of the roller.
708 * @param roller_scrl pointer to the scrollable part of roller (page)
709 * @param sign a signal type from lv_signal_t enum
710 * @param param pointer to a signal specific variable
711 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
712 */
lv_roller_scrl_signal(lv_obj_t * roller_scrl,lv_signal_t sign,void * param)713 static lv_res_t lv_roller_scrl_signal(lv_obj_t * roller_scrl, lv_signal_t sign, void * param)
714 {
715 lv_res_t res;
716
717 /* Include the ancient signal function */
718 res = ancestor_scrl_signal(roller_scrl, sign, param);
719 if(res != LV_RES_OK) return res;
720 if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
721
722 lv_obj_t * roller = lv_obj_get_parent(roller_scrl);
723
724 /*On delete the ddlist signal deletes the label so nothing left to do here*/
725 lv_obj_t * label = get_label(roller);
726 if(label == NULL) return LV_RES_INV;
727
728 int32_t id = -1;
729 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
730
731 const lv_font_t * font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG);
732 lv_style_int_t line_space = lv_obj_get_style_text_line_space(roller, LV_ROLLER_PART_BG);
733 lv_coord_t font_h = lv_font_get_line_height(font);
734
735 if(sign == LV_SIGNAL_DRAG_END) {
736 /*If dragged then align the list to have an element in the middle*/
737 lv_coord_t label_y1 = label->coords.y1 - roller->coords.y1;
738 lv_coord_t label_unit = font_h + line_space;
739 lv_coord_t mid = (roller->coords.y2 - roller->coords.y1) / 2;
740
741 id = (mid - label_y1 + line_space / 2) / label_unit;
742
743 if(id < 0) id = 0;
744 if(id >= ext->option_cnt) id = ext->option_cnt - 1;
745
746 ext->sel_opt_id = id;
747 ext->sel_opt_id_ori = id;
748 res = lv_event_send(roller, LV_EVENT_VALUE_CHANGED, &id);
749 if(res != LV_RES_OK) return res;
750 }
751 /*If picked an option by clicking then set it*/
752 else if(sign == LV_SIGNAL_RELEASED) {
753 release_handler(roller);
754 }
755 else if(sign == LV_SIGNAL_PRESSED) {
756 #if LV_USE_ANIMATION
757 lv_anim_del(roller_scrl, (lv_anim_exec_xcb_t)lv_obj_set_y);
758 #endif
759 }
760 else if(sign == LV_SIGNAL_PARENT_SIZE_CHG) {
761 #if LV_USE_ANIMATION
762 lv_anim_del(lv_page_get_scrollable(roller), (lv_anim_exec_xcb_t)lv_obj_set_y);
763 #endif
764 refr_position(roller, false);
765 refr_width(roller);
766
767 }
768
769 /*Position the scrollable according to the new selected option*/
770 if(id != -1) {
771 refr_position(roller, LV_ANIM_ON);
772 }
773
774 return res;
775 }
776
777 /**
778 * Draw a rectangle which has gradient on its top and bottom
779 * @param roller pointer to a roller object
780 * @param clip_area pointer to the current mask (from the design function)
781 */
draw_bg(lv_obj_t * roller,const lv_area_t * clip_area)782 static void draw_bg(lv_obj_t * roller, const lv_area_t * clip_area)
783 {
784 lv_draw_rect_dsc_t bg_dsc;
785 lv_draw_rect_dsc_init(&bg_dsc);
786 lv_obj_init_draw_rect_dsc(roller, LV_ROLLER_PART_BG, &bg_dsc);
787
788 /*With non-vertical gradient simply draw the background*/
789 if(bg_dsc.bg_grad_dir == LV_GRAD_DIR_NONE) {
790 lv_draw_rect(&roller->coords, clip_area, &bg_dsc);
791 return;
792 }
793
794 /*With vertical gradient mirror it*/
795
796 lv_area_t half_mask;
797 lv_coord_t h = lv_obj_get_height(roller);
798 bool union_ok;
799
800 lv_area_copy(&half_mask, &roller->coords);
801 half_mask.x1 -= roller->ext_draw_pad; /*Add ext size too (e.g. because of shadow draw) */
802 half_mask.x2 += roller->ext_draw_pad;
803 half_mask.y1 -= roller->ext_draw_pad;
804 half_mask.y2 = roller->coords.y1 + h / 2;
805
806 union_ok = _lv_area_intersect(&half_mask, &half_mask, clip_area);
807 bg_dsc.bg_main_color_stop = bg_dsc.bg_main_color_stop / 2;
808 bg_dsc.bg_grad_color_stop = 128 - (255 - bg_dsc.bg_grad_color_stop) / 2;
809 if(union_ok) {
810 lv_draw_rect(&roller->coords, &half_mask, &bg_dsc);
811 }
812
813 lv_area_copy(&half_mask, &roller->coords);
814 half_mask.x1 -= roller->ext_draw_pad; /*Revert ext. size adding*/
815 half_mask.x2 += roller->ext_draw_pad;
816 half_mask.y1 = roller->coords.y1 + h / 2;
817 half_mask.y2 += roller->ext_draw_pad;
818
819 union_ok = _lv_area_intersect(&half_mask, &half_mask, clip_area);
820 if(union_ok) {
821 lv_color_t c = bg_dsc.bg_color;
822 bg_dsc.bg_color = bg_dsc.bg_grad_color;
823 bg_dsc.bg_grad_color = c;
824
825 bg_dsc.bg_main_color_stop += 127;
826 bg_dsc.bg_grad_color_stop += 127;
827 lv_draw_rect(&roller->coords, &half_mask, &bg_dsc);
828 }
829 }
830
831
832 /**
833 * Refresh the position of the roller. It uses the id stored in: ext->ddlist.selected_option_id
834 * @param roller pointer to a roller object
835 * @param anim_en LV_ANIM_ON: refresh with animation; LV_ANOM_OFF: without animation
836 */
refr_position(lv_obj_t * roller,lv_anim_enable_t anim_en)837 static void refr_position(lv_obj_t * roller, lv_anim_enable_t anim_en)
838 {
839 #if LV_USE_ANIMATION == 0
840 anim_en = LV_ANIM_OFF;
841 #endif
842
843 lv_obj_t * roller_scrl = lv_page_get_scrollable(roller);
844 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
845 const lv_font_t * font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG);
846 lv_style_int_t line_space = lv_obj_get_style_text_line_space(roller, LV_ROLLER_PART_BG);
847 lv_coord_t font_h = lv_font_get_line_height(font);
848 lv_coord_t h = lv_obj_get_height(roller);
849 uint16_t anim_time = lv_roller_get_anim_time(roller);
850
851 /* Normally the animation's `end_cb` sets correct position of the roller if infinite.
852 * But without animations do it manually*/
853 if(anim_en == LV_ANIM_OFF || anim_time == 0) {
854 inf_normalize(roller_scrl);
855 }
856
857 lv_obj_t * label = get_label(roller);
858
859 int32_t id = ext->sel_opt_id;
860 lv_coord_t line_y1 = id * (font_h + line_space) + label->coords.y1 - roller_scrl->coords.y1;
861 lv_coord_t new_y = -line_y1 + (h - font_h) / 2;
862
863 if(anim_en == LV_ANIM_OFF || anim_time == 0) {
864 #if LV_USE_ANIMATION
865 lv_anim_del(roller_scrl, (lv_anim_exec_xcb_t)lv_obj_set_y);
866 #endif
867 lv_obj_set_y(roller_scrl, new_y);
868 }
869 else {
870 #if LV_USE_ANIMATION
871 lv_anim_t a;
872 lv_anim_init(&a);
873 lv_anim_set_var(&a, roller_scrl);
874 lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_y);
875 lv_anim_set_values(&a, lv_obj_get_y(roller_scrl), new_y);
876 lv_anim_set_time(&a, anim_time);
877 lv_anim_set_ready_cb(&a, scroll_anim_ready_cb);
878 lv_anim_start(&a);
879 #endif
880 }
881 }
882
883
release_handler(lv_obj_t * roller)884 static lv_res_t release_handler(lv_obj_t * roller)
885 {
886
887 /*If there was dragging `DRAG_END` signal will refresh the position and update the selected option*/
888 if(lv_indev_is_dragging(lv_indev_get_act())) return LV_RES_OK;
889
890 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
891
892 lv_indev_t * indev = lv_indev_get_act();
893 #if LV_USE_GROUP
894 /*Leave edit mode once a new option is selected*/
895 lv_indev_type_t indev_type = lv_indev_get_type(indev);
896 if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) {
897 ext->sel_opt_id_ori = ext->sel_opt_id;
898
899 if(indev_type == LV_INDEV_TYPE_ENCODER) {
900 lv_group_t * g = lv_obj_get_group(roller);
901 if(lv_group_get_editing(g)) {
902 lv_group_set_editing(g, false);
903 }
904 }
905 }
906 #endif
907
908 lv_obj_t * label = get_label(roller);
909 if(label == NULL) return LV_RES_OK;
910
911 if(lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON) {
912 /*Search the clicked option (For KEYPAD and ENCODER the new value should be already set)*/
913 uint16_t new_opt = 0;
914 lv_point_t p;
915 lv_indev_get_point(indev, &p);
916 p.y -= label->coords.y1;
917 p.x -= label->coords.x1;
918 uint32_t letter_i;
919 letter_i = lv_label_get_letter_on(label, &p);
920
921 const char * txt = lv_label_get_text(label);
922 uint32_t i = 0;
923 uint32_t i_prev = 0;
924
925 uint32_t letter_cnt = 0;
926 for(letter_cnt = 0; letter_cnt < letter_i; letter_cnt++) {
927 uint32_t letter = _lv_txt_encoded_next(txt, &i);
928 /*Count he lines to reach the clicked letter. But ignore the last '\n' because it
929 * still belongs to the clicked line*/
930 if(letter == '\n' && i_prev != letter_i) new_opt++;
931 i_prev = i;
932 }
933 lv_roller_set_selected(roller, new_opt, LV_ANIM_ON);
934 }
935
936 uint32_t id = ext->sel_opt_id; /*Just to use uint32_t in event data*/
937 lv_res_t res = lv_event_send(roller, LV_EVENT_VALUE_CHANGED, &id);
938 return res;
939 }
940
refr_width(lv_obj_t * roller)941 static void refr_width(lv_obj_t * roller)
942 {
943 lv_obj_t * label = get_label(roller);
944 if(label == NULL) return;
945
946 switch(lv_label_get_align(label)) {
947 case LV_LABEL_ALIGN_LEFT:
948 lv_obj_align(label, NULL, LV_ALIGN_IN_LEFT_MID, 0, 0);
949 break;
950 case LV_LABEL_ALIGN_CENTER:
951 lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, 0);
952 break;
953 case LV_LABEL_ALIGN_RIGHT:
954 lv_obj_align(label, NULL, LV_ALIGN_IN_RIGHT_MID, 0, 0);
955 break;
956 }
957
958
959 if(lv_roller_get_auto_fit(roller) == false) return;
960
961 lv_coord_t label_w = lv_obj_get_width(label);
962
963 lv_style_int_t left = lv_obj_get_style_pad_left(roller, LV_ROLLER_PART_BG);
964 lv_style_int_t right = lv_obj_get_style_pad_right(roller, LV_ROLLER_PART_BG);
965
966 const lv_font_t * base_font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG);
967 const lv_font_t * sel_font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_SELECTED);
968
969 /*The selected text might be larger to get its size*/
970 if(base_font != sel_font) {
971 lv_coord_t letter_sp = lv_obj_get_style_text_letter_space(roller, LV_ROLLER_PART_SELECTED);
972 lv_coord_t line_sp = lv_obj_get_style_text_line_space(roller, LV_ROLLER_PART_SELECTED);
973 lv_point_t p;
974 _lv_txt_get_size(&p, lv_label_get_text(label), sel_font, letter_sp, line_sp, LV_COORD_MAX, LV_TXT_FLAG_NONE);
975 if(label_w < p.x)label_w = p.x;
976 }
977
978 lv_obj_set_width(roller, label_w + left + right);
979 }
980
981 /**
982 * Refresh the height of the roller and the scrollable
983 * @param roller pointer to roller
984 */
refr_height(lv_obj_t * roller)985 static void refr_height(lv_obj_t * roller)
986 {
987 lv_obj_t * label = get_label(roller);
988 if(label == NULL) return;
989
990 lv_obj_set_height(lv_page_get_scrollable(roller), lv_obj_get_height(label) + lv_obj_get_height(roller));
991
992 #if LV_USE_ANIMATION
993 lv_anim_del(lv_page_get_scrollable(roller), (lv_anim_exec_xcb_t)lv_obj_set_y);
994 #endif
995 refr_position(roller, LV_ANIM_OFF);
996 }
997
998 /**
999 * Set the middle page for the roller if infinite is enabled
1000 * @param scrl pointer to the roller's scrollable (lv_obj_t *)
1001 */
inf_normalize(void * scrl)1002 static void inf_normalize(void * scrl)
1003 {
1004 lv_obj_t * roller_scrl = (lv_obj_t *)scrl;
1005 lv_obj_t * roller = lv_obj_get_parent(roller_scrl);
1006 lv_roller_ext_t * ext = lv_obj_get_ext_attr(roller);
1007
1008 if(ext->mode == LV_ROLLER_MODE_INIFINITE) {
1009 uint16_t real_id_cnt = ext->option_cnt / LV_ROLLER_INF_PAGES;
1010 ext->sel_opt_id = ext->sel_opt_id % real_id_cnt;
1011 ext->sel_opt_id += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
1012
1013 ext->sel_opt_id_ori = ext->sel_opt_id % real_id_cnt;
1014 ext->sel_opt_id_ori += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
1015
1016 /*Move to the new id*/
1017 const lv_font_t * font = lv_obj_get_style_text_font(roller, LV_ROLLER_PART_BG);
1018 lv_style_int_t line_space = lv_obj_get_style_text_line_space(roller, LV_ROLLER_PART_BG);
1019 lv_coord_t font_h = lv_font_get_line_height(font);
1020 lv_coord_t h = lv_obj_get_height(roller);
1021
1022 lv_obj_t * label = get_label(roller);
1023
1024 lv_coord_t line_y1 = ext->sel_opt_id * (font_h + line_space) + label->coords.y1 - roller_scrl->coords.y1;
1025 lv_coord_t new_y = -line_y1 + (h - font_h) / 2;
1026 lv_obj_set_y(roller_scrl, new_y);
1027 }
1028 }
1029
get_label(const lv_obj_t * roller)1030 static lv_obj_t * get_label(const lv_obj_t * roller)
1031 {
1032 lv_obj_t * scrl = lv_page_get_scrollable(roller);
1033 if(scrl == NULL) return NULL; /*The roller is being deleted, the scrollable already not exists*/
1034 return lv_obj_get_child(scrl, NULL);
1035 }
1036
1037 #if LV_USE_ANIMATION
scroll_anim_ready_cb(lv_anim_t * a)1038 static void scroll_anim_ready_cb(lv_anim_t * a)
1039 {
1040 inf_normalize(a->var);
1041 }
1042 #endif
1043
1044 #endif
1045