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