1 /**
2 * @file lv_ddlist.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_ddlist.h"
10 #if LV_USE_DDLIST != 0
11
12 #include "../lv_draw/lv_draw.h"
13 #include "../lv_core/lv_group.h"
14 #include "../lv_core/lv_indev.h"
15 #include "../lv_themes/lv_theme.h"
16 #include "../lv_font/lv_symbol_def.h"
17 #include "../lv_misc/lv_anim.h"
18 #include "../lv_misc/lv_math.h"
19 #include <string.h>
20
21 /*********************
22 * DEFINES
23 *********************/
24 #if LV_USE_ANIMATION == 0
25 #undef LV_DDLIST_DEF_ANIM_TIME
26 #define LV_DDLIST_DEF_ANIM_TIME 0 /*No animation*/
27 #endif
28
29 /**********************
30 * TYPEDEFS
31 **********************/
32
33 /**********************
34 * STATIC PROTOTYPES
35 **********************/
36 static bool lv_ddlist_design(lv_obj_t * ddlist, const lv_area_t * mask, lv_design_mode_t mode);
37 static lv_res_t lv_ddlist_signal(lv_obj_t * ddlist, lv_signal_t sign, void * param);
38 static lv_res_t lv_ddlist_scrl_signal(lv_obj_t * scrl, lv_signal_t sign, void * param);
39 static lv_res_t release_handler(lv_obj_t * ddlist);
40 static void lv_ddlist_refr_size(lv_obj_t * ddlist, lv_anim_enable_t anim);
41 static void lv_ddlist_pos_current_option(lv_obj_t * ddlist);
42 static void lv_ddlist_refr_width(lv_obj_t * ddlist);
43 #if LV_USE_ANIMATION
44 static void lv_ddlist_anim_ready_cb(lv_anim_t * a);
45 static void lv_ddlist_anim_finish(lv_obj_t * ddlist);
46 static void lv_ddlist_adjust_height(lv_obj_t * ddlist, lv_anim_value_t height);
47 #endif
48
49 /**********************
50 * STATIC VARIABLES
51 **********************/
52 static lv_signal_cb_t ancestor_signal;
53 static lv_signal_cb_t ancestor_scrl_signal;
54 static lv_design_cb_t ancestor_design;
55
56 /**********************
57 * MACROS
58 **********************/
59
60 /**********************
61 * GLOBAL FUNCTIONS
62 **********************/
63
64 /**
65 * Create a drop down list objects
66 * @param par pointer to an object, it will be the parent of the new drop down list
67 * @param copy pointer to a drop down list object, if not NULL then the new object will be copied
68 * from it
69 * @return pointer to the created drop down list
70 */
lv_ddlist_create(lv_obj_t * par,const lv_obj_t * copy)71 lv_obj_t * lv_ddlist_create(lv_obj_t * par, const lv_obj_t * copy)
72 {
73 LV_LOG_TRACE("drop down list create started");
74
75 /*Create the ancestor drop down list*/
76 lv_obj_t * new_ddlist = lv_page_create(par, copy);
77 lv_mem_assert(new_ddlist);
78 if(new_ddlist == NULL) return NULL;
79
80 if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_ddlist);
81 if(ancestor_scrl_signal == NULL) ancestor_scrl_signal = lv_obj_get_signal_cb(lv_page_get_scrl(new_ddlist));
82 if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(new_ddlist);
83
84 /*Allocate the drop down list type specific extended data*/
85 lv_ddlist_ext_t * ext = lv_obj_allocate_ext_attr(new_ddlist, sizeof(lv_ddlist_ext_t));
86 lv_mem_assert(ext);
87 if(ext == NULL) return NULL;
88
89 /*Initialize the allocated 'ext' */
90 ext->label = NULL;
91 ext->opened = 0;
92 ext->fix_height = 0;
93 ext->sel_opt_id = 0;
94 ext->sel_opt_id_ori = 0;
95 ext->option_cnt = 0;
96 ext->sel_style = &lv_style_plain_color;
97 ext->draw_arrow = 0; /*Do not draw arrow by default*/
98 ext->stay_open = 0;
99
100 /*The signal and design functions are not copied so set them here*/
101 lv_obj_set_signal_cb(new_ddlist, lv_ddlist_signal);
102 lv_obj_set_signal_cb(lv_page_get_scrl(new_ddlist), lv_ddlist_scrl_signal);
103 lv_obj_set_design_cb(new_ddlist, lv_ddlist_design);
104
105 /*Init the new drop down list drop down list*/
106 if(copy == NULL) {
107 lv_page_set_anim_time(new_ddlist, LV_DDLIST_DEF_ANIM_TIME);
108
109 lv_obj_t * scrl = lv_page_get_scrl(new_ddlist);
110 lv_obj_set_drag(scrl, false);
111 lv_page_set_scrl_fit2(new_ddlist, LV_FIT_FILL, LV_FIT_TIGHT);
112
113 ext->label = lv_label_create(new_ddlist, NULL);
114 lv_cont_set_fit2(new_ddlist, LV_FIT_TIGHT, LV_FIT_NONE);
115 lv_page_set_sb_mode(new_ddlist, LV_SB_MODE_HIDE);
116 lv_page_set_style(new_ddlist, LV_PAGE_STYLE_SCRL, &lv_style_transp_tight);
117
118 lv_ddlist_set_options(new_ddlist, "Option 1\nOption 2\nOption 3");
119
120 /*Set the default styles*/
121 lv_theme_t * th = lv_theme_get_current();
122 if(th) {
123 lv_ddlist_set_style(new_ddlist, LV_DDLIST_STYLE_BG, th->style.ddlist.bg);
124 lv_ddlist_set_style(new_ddlist, LV_DDLIST_STYLE_SEL, th->style.ddlist.sel);
125 lv_ddlist_set_style(new_ddlist, LV_DDLIST_STYLE_SB, th->style.ddlist.sb);
126 } else {
127 lv_ddlist_set_style(new_ddlist, LV_DDLIST_STYLE_BG, &lv_style_pretty);
128 lv_ddlist_set_style(new_ddlist, LV_DDLIST_STYLE_SEL, &lv_style_plain_color);
129 lv_ddlist_set_style(new_ddlist, LV_DDLIST_STYLE_SB, &lv_style_pretty_color);
130 }
131 }
132 /*Copy an existing drop down list*/
133 else {
134 lv_ddlist_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
135 ext->label = lv_label_create(new_ddlist, copy_ext->label);
136 lv_label_set_text(ext->label, lv_label_get_text(copy_ext->label));
137 ext->sel_opt_id = copy_ext->sel_opt_id;
138 ext->sel_opt_id_ori = copy_ext->sel_opt_id;
139 ext->fix_height = copy_ext->fix_height;
140 ext->option_cnt = copy_ext->option_cnt;
141 ext->sel_style = copy_ext->sel_style;
142 ext->draw_arrow = copy_ext->draw_arrow;
143 ext->stay_open = copy_ext->stay_open;
144
145 /*Refresh the style with new signal function*/
146 lv_obj_refresh_style(new_ddlist);
147 }
148
149 LV_LOG_INFO("drop down list created");
150
151 return new_ddlist;
152 }
153
154 /*=====================
155 * Setter functions
156 *====================*/
157
158 /**
159 * Set the options in a drop down list from a string
160 * @param ddlist pointer to drop down list object
161 * @param options a string with '\n' separated options. E.g. "One\nTwo\nThree"
162 */
lv_ddlist_set_options(lv_obj_t * ddlist,const char * options)163 void lv_ddlist_set_options(lv_obj_t * ddlist, const char * options)
164 {
165 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
166
167 /*Count the '\n'-s to determine the number of options*/
168 ext->option_cnt = 0;
169 uint16_t i;
170 for(i = 0; options[i] != '\0'; i++) {
171 if(options[i] == '\n') ext->option_cnt++;
172 }
173 ext->option_cnt++; /*Last option has no `\n`*/
174 ext->sel_opt_id = 0;
175 ext->sel_opt_id_ori = 0;
176
177 lv_label_set_text(ext->label, options);
178
179 lv_ddlist_refr_width(ddlist);
180
181 switch(lv_label_get_align(ext->label)) {
182 case LV_LABEL_ALIGN_LEFT: lv_obj_align(ext->label, NULL, LV_ALIGN_IN_LEFT_MID, 0, 0); break;
183 case LV_LABEL_ALIGN_CENTER: lv_obj_align(ext->label, NULL, LV_ALIGN_CENTER, 0, 0); break;
184 case LV_LABEL_ALIGN_RIGHT: lv_obj_align(ext->label, NULL, LV_ALIGN_IN_RIGHT_MID, 0, 0); break;
185 }
186
187 lv_ddlist_refr_size(ddlist, false);
188 }
189
190 /**
191 * Set the selected option
192 * @param ddlist pointer to drop down list object
193 * @param sel_opt id of the selected option (0 ... number of option - 1);
194 */
lv_ddlist_set_selected(lv_obj_t * ddlist,uint16_t sel_opt)195 void lv_ddlist_set_selected(lv_obj_t * ddlist, uint16_t sel_opt)
196 {
197 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
198 if(ext->sel_opt_id == sel_opt) return;
199
200 ext->sel_opt_id = sel_opt < ext->option_cnt ? sel_opt : ext->option_cnt - 1;
201 ext->sel_opt_id_ori = ext->sel_opt_id;
202 /*Move the list to show the current option*/
203 if(ext->opened == 0) {
204 lv_ddlist_pos_current_option(ddlist);
205 } else {
206 lv_obj_invalidate(ddlist);
207 }
208 }
209
210 /**
211 * Set a fix height for the drop down list
212 * If 0 then the opened ddlist will be auto. sized else the set height will be applied.
213 * @param ddlist pointer to a drop down list
214 * @param h the height when the list is opened (0: auto size)
215 */
lv_ddlist_set_fix_height(lv_obj_t * ddlist,lv_coord_t h)216 void lv_ddlist_set_fix_height(lv_obj_t * ddlist, lv_coord_t h)
217 {
218 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
219 if(ext->fix_height == h) return;
220
221 ext->fix_height = h;
222
223 lv_ddlist_refr_size(ddlist, false);
224 }
225
226 /**
227 * Set a fix width for the drop down list
228 * @param ddlist pointer to a drop down list
229 * @param w the width when the list is opened (0: auto size)
230 */
lv_ddlist_set_fix_width(lv_obj_t * ddlist,lv_coord_t w)231 void lv_ddlist_set_fix_width(lv_obj_t * ddlist, lv_coord_t w)
232 {
233 if(w == 0) {
234 lv_cont_set_fit2(ddlist, LV_FIT_TIGHT, lv_cont_get_fit_bottom(ddlist));
235 } else {
236 lv_cont_set_fit2(ddlist, LV_FIT_NONE, lv_cont_get_fit_bottom(ddlist));
237 lv_obj_set_width(ddlist, w);
238 }
239
240 lv_ddlist_refr_size(ddlist, false);
241 }
242
243 /**
244 * Set arrow draw in a drop down list
245 * @param ddlist pointer to drop down list object
246 * @param en enable/disable a arrow draw. E.g. "true" for draw.
247 */
lv_ddlist_set_draw_arrow(lv_obj_t * ddlist,bool en)248 void lv_ddlist_set_draw_arrow(lv_obj_t * ddlist, bool en)
249 {
250 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
251
252 /*Set the flag*/
253 ext->draw_arrow = en ? 1 : 0;
254 }
255
256 /**
257 * Leave the list opened when a new value is selected
258 * @param ddlist pointer to drop down list object
259 * @param en enable/disable "stay open" feature
260 */
lv_ddlist_set_stay_open(lv_obj_t * ddlist,bool en)261 void lv_ddlist_set_stay_open(lv_obj_t * ddlist, bool en)
262 {
263 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
264
265 /*Set the flag*/
266 ext->stay_open = en ? 1 : 0;
267 }
268
269 /**
270 * Set a style of a drop down list
271 * @param ddlist pointer to a drop down list object
272 * @param type which style should be set
273 * @param style pointer to a style
274 */
lv_ddlist_set_style(lv_obj_t * ddlist,lv_ddlist_style_t type,const lv_style_t * style)275 void lv_ddlist_set_style(lv_obj_t * ddlist, lv_ddlist_style_t type, const lv_style_t * style)
276 {
277 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
278
279 switch(type) {
280 case LV_DDLIST_STYLE_BG:
281 lv_page_set_style(ddlist, LV_PAGE_STYLE_BG, style);
282 lv_ddlist_refr_width(ddlist);
283 break;
284 case LV_DDLIST_STYLE_SB: lv_page_set_style(ddlist, LV_PAGE_STYLE_SB, style); break;
285 case LV_DDLIST_STYLE_SEL:
286 ext->sel_style = style;
287 lv_obj_t * scrl = lv_page_get_scrl(ddlist);
288 lv_obj_refresh_ext_draw_pad(scrl); /*Because of the wider selected rectangle*/
289 break;
290 }
291 }
292
lv_ddlist_set_align(lv_obj_t * ddlist,lv_label_align_t align)293 void lv_ddlist_set_align(lv_obj_t * ddlist, lv_label_align_t align)
294 {
295 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
296
297 lv_label_set_align(ext->label, align);
298 switch(align) {
299 case LV_LABEL_ALIGN_LEFT: lv_obj_align(ext->label, NULL, LV_ALIGN_IN_LEFT_MID, 0, 0); break;
300 case LV_LABEL_ALIGN_CENTER: lv_obj_align(ext->label, NULL, LV_ALIGN_CENTER, 0, 0); break;
301 case LV_LABEL_ALIGN_RIGHT: lv_obj_align(ext->label, NULL, LV_ALIGN_IN_RIGHT_MID, 0, 0); break;
302 }
303 }
304 /*=====================
305 * Getter functions
306 *====================*/
307
308 /**
309 * Get the options of a drop down list
310 * @param ddlist pointer to drop down list object
311 * @return the options separated by '\n'-s (E.g. "Option1\nOption2\nOption3")
312 */
lv_ddlist_get_options(const lv_obj_t * ddlist)313 const char * lv_ddlist_get_options(const lv_obj_t * ddlist)
314 {
315 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
316 return lv_label_get_text(ext->label);
317 }
318
319 /**
320 * Get the selected option
321 * @param ddlist pointer to drop down list object
322 * @return id of the selected option (0 ... number of option - 1);
323 */
lv_ddlist_get_selected(const lv_obj_t * ddlist)324 uint16_t lv_ddlist_get_selected(const lv_obj_t * ddlist)
325 {
326 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
327
328 return ext->sel_opt_id;
329 }
330
331 /**
332 * Get the current selected option as a string
333 * @param ddlist pointer to ddlist object
334 * @param buf pointer to an array to store the string
335 * @param buf_size size of `buf` in bytes. 0: to ignore it.
336 */
lv_ddlist_get_selected_str(const lv_obj_t * ddlist,char * buf,uint16_t buf_size)337 void lv_ddlist_get_selected_str(const lv_obj_t * ddlist, char * buf, uint16_t buf_size)
338 {
339 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
340
341 uint16_t i;
342 uint16_t line = 0;
343 const char * opt_txt = lv_label_get_text(ext->label);
344 uint16_t txt_len = strlen(opt_txt);
345
346 for(i = 0; i < txt_len && line != ext->sel_opt_id; i++) {
347 if(opt_txt[i] == '\n') line++;
348 }
349
350 uint16_t c;
351 for(c = 0; opt_txt[i] != '\n' && i < txt_len; c++, i++) {
352 if(buf_size && c >= buf_size - 1) {
353 LV_LOG_WARN("lv_ddlist_get_selected_str: the buffer was too small")
354 break;
355 }
356 buf[c] = opt_txt[i];
357 }
358
359 buf[c] = '\0';
360 }
361
362 /**
363 * Get the fix height value.
364 * @param ddlist pointer to a drop down list object
365 * @return the height if the ddlist is opened (0: auto size)
366 */
lv_ddlist_get_fix_height(const lv_obj_t * ddlist)367 lv_coord_t lv_ddlist_get_fix_height(const lv_obj_t * ddlist)
368 {
369 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
370 return ext->fix_height;
371 }
372
373 /**
374 * Get arrow draw in a drop down list
375 * @param ddlist pointer to drop down list object
376 */
lv_ddlist_get_draw_arrow(lv_obj_t * ddlist)377 bool lv_ddlist_get_draw_arrow(lv_obj_t * ddlist)
378 {
379 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
380
381 return ext->draw_arrow ? true : false;
382 }
383
384 /**
385 * Get whether the drop down list stay open after selecting a value or not
386 * @param ddlist pointer to drop down list object
387 */
lv_ddlist_get_stay_open(lv_obj_t * ddlist)388 bool lv_ddlist_get_stay_open(lv_obj_t * ddlist)
389 {
390 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
391
392 return ext->stay_open ? true : false;
393 }
394
395 /**
396 * Get a style of a drop down list
397 * @param ddlist pointer to a drop down list object
398 * @param type which style should be get
399 * @return style pointer to a style
400 */
lv_ddlist_get_style(const lv_obj_t * ddlist,lv_ddlist_style_t type)401 const lv_style_t * lv_ddlist_get_style(const lv_obj_t * ddlist, lv_ddlist_style_t type)
402 {
403 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
404
405 switch(type) {
406 case LV_DDLIST_STYLE_BG: return lv_page_get_style(ddlist, LV_PAGE_STYLE_BG);
407 case LV_DDLIST_STYLE_SB: return lv_page_get_style(ddlist, LV_PAGE_STYLE_SB);
408 case LV_DDLIST_STYLE_SEL: return ext->sel_style;
409 default: return NULL;
410 }
411
412 /*To avoid warning*/
413 return NULL;
414 }
415
lv_ddlist_get_align(const lv_obj_t * ddlist)416 lv_label_align_t lv_ddlist_get_align(const lv_obj_t * ddlist)
417 {
418 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
419
420 return lv_label_get_align(ext->label);
421 }
422
423 /*=====================
424 * Other functions
425 *====================*/
426
427 /**
428 * Open the drop down list with or without animation
429 * @param ddlist pointer to drop down list object
430 * @param anim_en LV_ANIM_EN: use animation; LV_ANIM_OFF: not use animations
431 */
lv_ddlist_open(lv_obj_t * ddlist,lv_anim_enable_t anim)432 void lv_ddlist_open(lv_obj_t * ddlist, lv_anim_enable_t anim)
433 {
434 #if LV_USE_ANIMATION == 0
435 anim = false;
436 #endif
437 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
438 ext->opened = 1;
439 lv_obj_set_drag(lv_page_get_scrl(ddlist), true);
440 lv_ddlist_refr_size(ddlist, anim);
441 }
442
443 /**
444 * Close (Collapse) the drop down list
445 * @param ddlist pointer to drop down list object
446 * @param anim_en LV_ANIM_ON: use animation; LV_ANIM_OFF: not use animations
447 */
lv_ddlist_close(lv_obj_t * ddlist,lv_anim_enable_t anim)448 void lv_ddlist_close(lv_obj_t * ddlist, lv_anim_enable_t anim)
449 {
450 #if LV_USE_ANIMATION == 0
451 anim = false;
452 #endif
453 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
454 ext->opened = 0;
455 lv_obj_set_drag(lv_page_get_scrl(ddlist), false);
456 lv_ddlist_refr_size(ddlist, anim);
457 }
458
459 /**********************
460 * STATIC FUNCTIONS
461 **********************/
462
463 /**
464 * Get the text alignment flag for a drop down list.
465 * @param ddlist drop down list
466 * @return text alignment flag
467 */
lv_ddlist_get_txt_flag(const lv_obj_t * ddlist)468 static lv_txt_flag_t lv_ddlist_get_txt_flag(const lv_obj_t * ddlist)
469 {
470 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
471
472 /*The label might be already deleted so just return with some value*/
473 if(!ext->label) return LV_TXT_FLAG_CENTER;
474
475 lv_label_align_t align = lv_label_get_align(ext->label);
476
477 switch(align) {
478 default:
479 case LV_LABEL_ALIGN_LEFT: return LV_TXT_FLAG_NONE;
480 case LV_LABEL_ALIGN_CENTER: return LV_TXT_FLAG_CENTER;
481 case LV_LABEL_ALIGN_RIGHT: return LV_TXT_FLAG_RIGHT;
482 }
483 }
484
485 /**
486 * Handle the drawing related tasks of the drop down lists
487 * @param ddlist pointer to an object
488 * @param mask the object will be drawn only in this area
489 * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
490 * (return 'true' if yes)
491 * LV_DESIGN_DRAW: draw the object (always return 'true')
492 * LV_DESIGN_DRAW_POST: drawing after every children are drawn
493 * @param return true/false, depends on 'mode'
494 */
lv_ddlist_design(lv_obj_t * ddlist,const lv_area_t * mask,lv_design_mode_t mode)495 static bool lv_ddlist_design(lv_obj_t * ddlist, const lv_area_t * mask, lv_design_mode_t mode)
496 {
497 /*Return false if the object is not covers the mask_p area*/
498 if(mode == LV_DESIGN_COVER_CHK) {
499 return ancestor_design(ddlist, mask, mode);
500 }
501 /*Draw the object*/
502 else if(mode == LV_DESIGN_DRAW_MAIN) {
503 ancestor_design(ddlist, mask, mode);
504
505 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
506 lv_opa_t opa_scale = lv_obj_get_opa_scale(ddlist);
507 /*If the list is opened draw a rectangle under the selected item*/
508 if(ext->opened != 0 || ext->force_sel) {
509 const lv_style_t * style = lv_ddlist_get_style(ddlist, LV_DDLIST_STYLE_BG);
510 const lv_font_t * font = style->text.font;
511 lv_coord_t font_h = lv_font_get_line_height(font);
512
513 /*Draw the selected*/
514 lv_area_t rect_area;
515 rect_area.y1 = ext->label->coords.y1;
516 rect_area.y1 += ext->sel_opt_id * (font_h + style->text.line_space);
517 rect_area.y1 -= style->text.line_space / 2;
518
519 rect_area.y2 = rect_area.y1 + font_h + style->text.line_space - 1;
520 rect_area.x1 = ddlist->coords.x1;
521 rect_area.x2 = ddlist->coords.x2;
522
523 lv_draw_rect(&rect_area, mask, ext->sel_style, opa_scale);
524 }
525 }
526 /*Post draw when the children are drawn*/
527 else if(mode == LV_DESIGN_DRAW_POST) {
528 /*Redraw the text on the selected area with a different color*/
529 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
530 lv_opa_t opa_scale = lv_obj_get_opa_scale(ddlist);
531
532 /*Redraw only in opened state*/
533 if(ext->opened || ext->force_sel) {
534 const lv_style_t * style = lv_ddlist_get_style(ddlist, LV_DDLIST_STYLE_BG);
535 const lv_font_t * font = style->text.font;
536 lv_coord_t font_h = lv_font_get_line_height(font);
537
538 lv_area_t area_sel;
539 area_sel.y1 = ext->label->coords.y1;
540 area_sel.y1 += ext->sel_opt_id * (font_h + style->text.line_space);
541 area_sel.y1 -= style->text.line_space / 2;
542
543 area_sel.y2 = area_sel.y1 + font_h + style->text.line_space - 1;
544 area_sel.x1 = ddlist->coords.x1;
545 area_sel.x2 = ddlist->coords.x2;
546 lv_area_t mask_sel;
547 bool area_ok;
548 area_ok = lv_area_intersect(&mask_sel, mask, &area_sel);
549 if(area_ok) {
550 const lv_style_t * sel_style = lv_ddlist_get_style(ddlist, LV_DDLIST_STYLE_SEL);
551 lv_style_t new_style;
552 lv_style_copy(&new_style, style);
553 new_style.text.color = sel_style->text.color;
554 new_style.text.opa = sel_style->text.opa;
555 lv_txt_flag_t flag = lv_ddlist_get_txt_flag(ddlist);
556 lv_draw_label(&ext->label->coords, &mask_sel, &new_style, opa_scale, lv_label_get_text(ext->label),
557 flag, NULL, -1, -1, NULL);
558 }
559 }
560
561 /*Add a down symbol in ddlist when closed*/
562 else {
563 /*Draw a arrow in ddlist if enabled*/
564 if(ext->draw_arrow) {
565 const lv_style_t * style = lv_ddlist_get_style(ddlist, LV_DDLIST_STYLE_BG);
566 const lv_font_t * font = style->text.font;
567 const lv_style_t * sel_style = lv_ddlist_get_style(ddlist, LV_DDLIST_STYLE_BG);
568 lv_coord_t font_h = lv_font_get_line_height(font);
569 lv_style_t new_style;
570 lv_style_copy(&new_style, style);
571 new_style.text.color = sel_style->text.color;
572 new_style.text.opa = sel_style->text.opa;
573 lv_area_t area_arrow;
574 area_arrow.x2 = ddlist->coords.x2 - style->body.padding.right;
575 area_arrow.x1 = area_arrow.x2 -
576 lv_txt_get_width(LV_SYMBOL_DOWN, strlen(LV_SYMBOL_DOWN), sel_style->text.font, 0, 0);
577
578 area_arrow.y1 = ddlist->coords.y1 + style->text.line_space;
579 area_arrow.y2 = area_arrow.y1 + font_h;
580
581 lv_area_t mask_arrow;
582 bool area_ok;
583 area_ok = lv_area_intersect(&mask_arrow, mask, &area_arrow);
584 if(area_ok) {
585 lv_draw_label(&area_arrow, &mask_arrow, &new_style, opa_scale, LV_SYMBOL_DOWN, LV_TXT_FLAG_NONE,
586 NULL, -1, -1, NULL); /*Use a down arrow in ddlist, you can replace it with your
587 custom symbol*/
588 }
589 }
590 }
591 /*Draw the scrollbar in the ancestor page design function*/
592 ancestor_design(ddlist, mask, mode);
593 }
594
595 return true;
596 }
597
598 /**
599 * Signal function of the drop down list
600 * @param ddlist pointer to a drop down list object
601 * @param sign a signal type from lv_signal_t enum
602 * @param param pointer to a signal specific variable
603 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
604 */
lv_ddlist_signal(lv_obj_t * ddlist,lv_signal_t sign,void * param)605 static lv_res_t lv_ddlist_signal(lv_obj_t * ddlist, lv_signal_t sign, void * param)
606 {
607 lv_res_t res;
608 /* Include the ancient signal function */
609 res = ancestor_signal(ddlist, sign, param);
610 if(res != LV_RES_OK) return res;
611
612 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
613
614 if(sign == LV_SIGNAL_STYLE_CHG) {
615 lv_ddlist_refr_size(ddlist, 0);
616 } else if(sign == LV_SIGNAL_CLEANUP) {
617 ext->label = NULL;
618 } else if(sign == LV_SIGNAL_FOCUS) {
619 #if LV_USE_GROUP
620 lv_group_t * g = lv_obj_get_group(ddlist);
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 /*Open the list if editing*/
627 if(editing) {
628 ext->opened = true;
629 ext->sel_opt_id_ori = ext->sel_opt_id;
630 lv_ddlist_refr_size(ddlist, true);
631 }
632 /*Close the lift if navigating*/
633 else {
634 ext->opened = false;
635 ext->sel_opt_id = ext->sel_opt_id_ori;
636 lv_ddlist_refr_size(ddlist, true);
637 }
638 } else {
639 /*Open the list if closed*/
640 if(!ext->opened) {
641 ext->opened = true;
642 ext->sel_opt_id_ori = ext->sel_opt_id; /*Save the current value. Used to revert this
643 state if ENER wont't be pressed*/
644 lv_ddlist_refr_size(ddlist, true);
645 }
646 }
647 #endif
648 } else if(sign == LV_SIGNAL_RELEASED) {
649 release_handler(ddlist);
650 } else if(sign == LV_SIGNAL_DEFOCUS) {
651 if(ext->opened) {
652 ext->opened = false;
653 ext->sel_opt_id = ext->sel_opt_id_ori;
654 lv_ddlist_refr_size(ddlist, true);
655 }
656 } else if(sign == LV_SIGNAL_CONTROL) {
657 char c = *((char *)param);
658 if(c == LV_KEY_RIGHT || c == LV_KEY_DOWN) {
659 if(!ext->opened) {
660 ext->opened = 1;
661 lv_ddlist_refr_size(ddlist, true);
662 }
663
664 if(ext->sel_opt_id + 1 < ext->option_cnt) {
665 ext->sel_opt_id++;
666 lv_ddlist_pos_current_option(ddlist);
667 lv_obj_invalidate(ddlist);
668 }
669 } else if(c == LV_KEY_LEFT || c == LV_KEY_UP) {
670 if(!ext->opened) {
671 ext->opened = 1;
672 lv_ddlist_refr_size(ddlist, true);
673 }
674 if(ext->sel_opt_id > 0) {
675 ext->sel_opt_id--;
676 lv_ddlist_pos_current_option(ddlist);
677 lv_obj_invalidate(ddlist);
678 }
679 } else if(c == LV_KEY_ESC) {
680 if(ext->opened) {
681 ext->opened = 0;
682 ext->sel_opt_id = ext->sel_opt_id_ori;
683 lv_ddlist_refr_size(ddlist, true);
684 }
685 }
686 } else if(sign == LV_SIGNAL_GET_EDITABLE) {
687 bool * editable = (bool *)param;
688 *editable = true;
689 } else if(sign == LV_SIGNAL_GET_TYPE) {
690 lv_obj_type_t * buf = param;
691 uint8_t i;
692 for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set data*/
693 if(buf->type[i] == NULL) break;
694 }
695 buf->type[i] = "lv_ddlist";
696 }
697
698 return res;
699 }
700
701 /**
702 * Signal function of the drop down list's scrollable part
703 * @param scrl pointer to a drop down list's scrollable part
704 * @param sign a signal type from lv_signal_t enum
705 * @param param pointer to a signal specific variable
706 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
707 */
lv_ddlist_scrl_signal(lv_obj_t * scrl,lv_signal_t sign,void * param)708 static lv_res_t lv_ddlist_scrl_signal(lv_obj_t * scrl, lv_signal_t sign, void * param)
709 {
710 lv_res_t res;
711
712 /* Include the ancient signal function */
713 res = ancestor_scrl_signal(scrl, sign, param);
714 if(res != LV_RES_OK) return res;
715
716 lv_obj_t * ddlist = lv_obj_get_parent(scrl);
717
718 if(sign == LV_SIGNAL_REFR_EXT_DRAW_PAD) {
719 /*TODO review this*/
720 /* Because of the wider selected rectangle ext. size
721 * In this way by dragging the scrollable part the wider rectangle area can be redrawn too*/
722 const lv_style_t * style = lv_ddlist_get_style(ddlist, LV_DDLIST_STYLE_BG);
723 lv_coord_t hpad = LV_MATH_MAX(style->body.padding.left, style->body.padding.right);
724 if(scrl->ext_draw_pad < hpad) scrl->ext_draw_pad = hpad;
725 } else if(sign == LV_SIGNAL_RELEASED) {
726 if(lv_indev_is_dragging(lv_indev_get_act()) == false) {
727 release_handler(ddlist);
728 }
729 } else if(sign == LV_SIGNAL_CLEANUP) {
730 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
731 ext->label = NULL; /*The label is already deleted*/
732 }
733
734 return res;
735 }
736
737 /**
738 * Called when a drop down list is released to open it or set new option
739 * @param ddlist pointer to a drop down list object
740 * @return LV_ACTION_RES_INV if the ddlist it deleted in the user callback else LV_ACTION_RES_OK
741 */
release_handler(lv_obj_t * ddlist)742 static lv_res_t release_handler(lv_obj_t * ddlist)
743 {
744 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
745
746 if(ext->opened == 0) { /*Open the list*/
747 ext->opened = 1;
748 lv_obj_set_drag(lv_page_get_scrl(ddlist), true);
749 lv_ddlist_refr_size(ddlist, true);
750 } else {
751
752 lv_indev_t * indev = lv_indev_get_act();
753 #if LV_USE_GROUP
754 /*Leave edit mode once a new item is selected*/
755 if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) {
756 ext->sel_opt_id_ori = ext->sel_opt_id;
757 lv_group_t * g = lv_obj_get_group(ddlist);
758 if(lv_group_get_editing(g)) {
759 lv_group_set_editing(g, false);
760 }
761 }
762 #endif
763
764 /*Search the clicked option (For KEYPAD and ENCODER the new value should be already set)*/
765 if(lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON) {
766 lv_point_t p;
767 lv_indev_get_point(indev, &p);
768 p.y -= ext->label->coords.y1;
769 p.x -= ext->label->coords.x1;
770 uint16_t letter_i;
771 letter_i = lv_label_get_letter_on(ext->label, &p);
772
773 uint16_t new_opt = 0;
774 const char * txt = lv_label_get_text(ext->label);
775 uint32_t i = 0;
776 uint32_t line_cnt = 0;
777 uint32_t letter;
778 for(line_cnt = 0; line_cnt < letter_i; line_cnt++) {
779 letter = lv_txt_encoded_next(txt, &i);
780 /*Count he lines to reach the clicked letter. But ignore the last '\n' because it
781 * still belongs to the clicked line*/
782 if(letter == '\n' && i != letter_i) new_opt++;
783 }
784
785 ext->sel_opt_id = new_opt;
786 ext->sel_opt_id_ori = ext->sel_opt_id;
787 }
788
789 uint32_t id = ext->sel_opt_id; /*Just to use uint32_t in event data*/
790 lv_res_t res = lv_event_send(ddlist, LV_EVENT_VALUE_CHANGED, &id);
791 if(res != LV_RES_OK) return res;
792
793 if(ext->stay_open == 0) {
794 ext->opened = 0;
795 lv_obj_set_drag(lv_page_get_scrl(ddlist), false);
796 lv_ddlist_refr_size(ddlist, true);
797 } else {
798 lv_obj_invalidate(ddlist);
799 }
800 }
801
802 return LV_RES_OK;
803 }
804
805 /**
806 * Refresh the size of drop down list according to its status (open or closed)
807 * @param ddlist pointer to a drop down list object
808 * @param anim Change the size (open/close) with or without animation (true/false)
809 */
lv_ddlist_refr_size(lv_obj_t * ddlist,lv_anim_enable_t anim)810 static void lv_ddlist_refr_size(lv_obj_t * ddlist, lv_anim_enable_t anim)
811 {
812 #if LV_USE_ANIMATION == 0
813 anim = false;
814 #endif
815 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
816 const lv_style_t * style = lv_obj_get_style(ddlist);
817 lv_coord_t new_height;
818
819 /*Open the list*/
820 if(ext->opened) {
821 if(ext->fix_height == 0) {
822 new_height =
823 lv_obj_get_height(lv_page_get_scrl(ddlist)) + style->body.padding.top + style->body.padding.bottom;
824 } else {
825 new_height = ext->fix_height;
826 }
827
828 }
829 /*Close the list*/
830 else {
831 const lv_font_t * font = style->text.font;
832 const lv_style_t * label_style = lv_obj_get_style(ext->label);
833 lv_coord_t font_h = lv_font_get_line_height(font);
834 new_height = font_h + 2 * label_style->text.line_space;
835
836 lv_page_set_sb_mode(ddlist, LV_SB_MODE_HIDE);
837 }
838
839 if(anim == LV_ANIM_OFF) {
840 lv_obj_set_height(ddlist, new_height);
841 lv_ddlist_pos_current_option(ddlist);
842 if(ext->opened) lv_page_set_sb_mode(ddlist, LV_SB_MODE_UNHIDE);
843 #if LV_USE_ANIMATION
844 lv_anim_del(ddlist, (lv_anim_exec_xcb_t)lv_ddlist_adjust_height); /*If an animation is in progress then
845 it will overwrite this changes*/
846
847 /*Force animation complete to fix highlight selection*/
848 lv_ddlist_anim_finish(ddlist);
849 } else {
850 /*Run the animation only if the the size will be different*/
851 if(lv_obj_get_height(ddlist) != new_height) {
852 lv_anim_t a;
853 a.var = ddlist;
854 a.start = lv_obj_get_height(ddlist);
855 a.end = new_height;
856 a.exec_cb = (lv_anim_exec_xcb_t)lv_ddlist_adjust_height;
857 a.path_cb = lv_anim_path_linear;
858 a.ready_cb = lv_ddlist_anim_ready_cb;
859 a.act_time = 0;
860 a.time = lv_ddlist_get_anim_time(ddlist);
861 a.playback = 0;
862 a.playback_pause = 0;
863 a.repeat = 0;
864 a.repeat_pause = 0;
865
866 ext->force_sel = 1; /*Keep the list item selected*/
867 lv_anim_create(&a);
868 }
869 #endif
870 }
871 }
872
873 #if LV_USE_ANIMATION
874 /**
875 * Position the list and remove the selection highlight if it's closed.
876 * Called at end of list animation.
877 * @param a pointer to the animation
878 */
lv_ddlist_anim_ready_cb(lv_anim_t * a)879 static void lv_ddlist_anim_ready_cb(lv_anim_t * a)
880 {
881 lv_obj_t * ddlist = a->var;
882 lv_ddlist_anim_finish(ddlist);
883 }
884
885 /**
886 * Clean up after the open animation
887 * @param ddlist
888 */
lv_ddlist_anim_finish(lv_obj_t * ddlist)889 static void lv_ddlist_anim_finish(lv_obj_t * ddlist)
890 {
891 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
892
893 lv_ddlist_pos_current_option(ddlist);
894 ext->force_sel = 0; /*Turn off drawing of selection*/
895 if(ext->opened) lv_page_set_sb_mode(ddlist, LV_SB_MODE_UNHIDE);
896 }
897
898 /**
899 * Adjusts the ddlist's height and then positions the option within it's new height.
900 * This keeps the option visible during animation.
901 * @param ddlist Drop down list object
902 * @param height New drop down list height
903 */
lv_ddlist_adjust_height(lv_obj_t * ddlist,lv_anim_value_t height)904 static void lv_ddlist_adjust_height(lv_obj_t * ddlist, lv_anim_value_t height)
905 {
906 lv_obj_set_height(ddlist, height);
907 lv_ddlist_pos_current_option(ddlist);
908 }
909 #endif
910
911 /**
912 * Set the position of list when it is closed to show the selected item
913 * @param ddlist pointer to a drop down list
914 */
lv_ddlist_pos_current_option(lv_obj_t * ddlist)915 static void lv_ddlist_pos_current_option(lv_obj_t * ddlist)
916 {
917 lv_ddlist_ext_t * ext = lv_obj_get_ext_attr(ddlist);
918 const lv_style_t * style = lv_obj_get_style(ddlist);
919 const lv_font_t * font = style->text.font;
920 lv_coord_t font_h = lv_font_get_line_height(font);
921 const lv_style_t * label_style = lv_obj_get_style(ext->label);
922 lv_obj_t * scrl = lv_page_get_scrl(ddlist);
923
924 lv_coord_t h = lv_obj_get_height(ddlist);
925 lv_coord_t line_y1 =
926 ext->sel_opt_id * (font_h + label_style->text.line_space) + ext->label->coords.y1 - scrl->coords.y1;
927
928 lv_obj_set_y(scrl, -line_y1 + (h - font_h) / 2);
929 lv_obj_invalidate(ddlist);
930 }
931
932 /**
933 * Be sure the width of the scrollable exactly fits the ddlist
934 * @param ddlist pointer to a ddlist
935 */
lv_ddlist_refr_width(lv_obj_t * ddlist)936 static void lv_ddlist_refr_width(lv_obj_t * ddlist)
937 {
938 /*Set the TIGHT fit horizontally the set the width to the content*/
939 lv_page_set_scrl_fit2(ddlist, LV_FIT_TIGHT, lv_page_get_scrl_fit_bottom(ddlist));
940
941 /*Revert FILL fit to fill the parent with the options area. It allows to RIGHT/CENTER align the text*/
942 lv_page_set_scrl_fit2(ddlist, LV_FIT_FILL, lv_page_get_scrl_fit_bottom(ddlist));
943 }
944
945 #endif
946