1 /**
2  * @file lv_dropdown.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_dropdown_private.h"
10 #include "../../misc/lv_area_private.h"
11 #include "../../core/lv_obj_class_private.h"
12 #include "../../core/lv_obj.h"
13 #if LV_USE_DROPDOWN != 0
14 
15 #include "../../misc/lv_assert.h"
16 #include "../../draw/lv_draw_private.h"
17 #include "../../core/lv_group.h"
18 #include "../../indev/lv_indev.h"
19 #include "../../display/lv_display.h"
20 #include "../../font/lv_symbol_def.h"
21 #include "../../misc/lv_anim.h"
22 #include "../../misc/lv_math.h"
23 #include "../../misc/lv_text_ap.h"
24 #include "../../misc/lv_text_private.h"
25 #include "../../stdlib/lv_string.h"
26 
27 /*********************
28  *      DEFINES
29  *********************/
30 #define MY_CLASS (&lv_dropdown_class)
31 #define MY_CLASS_LIST &lv_dropdownlist_class
32 
33 #define LV_DROPDOWN_PR_NONE 0xFFFF
34 
35 /**********************
36  *      TYPEDEFS
37  **********************/
38 
39 /**********************
40  *  STATIC PROTOTYPES
41  **********************/
42 static lv_obj_t * lv_dropdown_list_create(lv_obj_t * parent);
43 static void lv_dropdown_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
44 static void lv_dropdown_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
45 static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e);
46 static void draw_main(lv_event_t * e);
47 
48 static void lv_dropdownlist_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
49 static void lv_dropdownlist_destructor(const lv_obj_class_t * class_p, lv_obj_t * list_obj);
50 static void lv_dropdown_list_event(const lv_obj_class_t * class_p, lv_event_t * e);
51 static void draw_list(lv_event_t * e);
52 
53 static void draw_box(lv_obj_t * dropdown_obj, lv_layer_t * layer, uint32_t id, lv_state_t state);
54 static void draw_box_label(lv_obj_t * dropdown_obj, lv_layer_t * layer, uint32_t id, lv_state_t state);
55 static lv_result_t btn_release_handler(lv_obj_t * obj);
56 static lv_result_t list_release_handler(lv_obj_t * list_obj);
57 static void list_press_handler(lv_obj_t * page);
58 static uint32_t get_id_on_point(lv_obj_t * dropdown_obj, int32_t y);
59 static void position_to_selected(lv_obj_t * dropdown_obj, lv_anim_enable_t anim_en);
60 static lv_obj_t * get_label(const lv_obj_t * obj);
61 
62 /**********************
63  *  STATIC VARIABLES
64  **********************/
65 #if LV_USE_OBJ_PROPERTY
66 static const lv_property_ops_t properties[] = {
67     {
68         .id = LV_PROPERTY_DROPDOWN_TEXT,
69         .setter = lv_dropdown_set_text,
70         .getter = lv_dropdown_get_text,
71     },
72     {
73         .id = LV_PROPERTY_DROPDOWN_OPTIONS,
74         .setter = lv_dropdown_set_options,
75         .getter = lv_dropdown_get_options,
76     },
77     {
78         .id = LV_PROPERTY_DROPDOWN_OPTION_COUNT,
79         .setter = NULL,
80         .getter = lv_dropdown_get_option_count,
81     },
82     {
83         .id = LV_PROPERTY_DROPDOWN_SELECTED,
84         .setter = lv_dropdown_set_selected,
85         .getter = lv_dropdown_get_selected,
86     },
87     {
88         .id = LV_PROPERTY_DROPDOWN_DIR,
89         .setter = lv_dropdown_set_dir,
90         .getter = lv_dropdown_get_dir,
91     },
92     {
93         .id = LV_PROPERTY_DROPDOWN_SYMBOL,
94         .setter = lv_dropdown_set_symbol,
95         .getter = lv_dropdown_get_symbol,
96     },
97     {
98         .id = LV_PROPERTY_DROPDOWN_SELECTED_HIGHLIGHT,
99         .setter = lv_dropdown_set_selected_highlight,
100         .getter = lv_dropdown_get_selected_highlight,
101     },
102     {
103         .id = LV_PROPERTY_DROPDOWN_LIST,
104         .setter = NULL,
105         .getter = lv_dropdown_get_list,
106     },
107     {
108         .id = LV_PROPERTY_DROPDOWN_IS_OPEN,
109         .setter = NULL,
110         .getter = lv_dropdown_is_open,
111     },
112 };
113 #endif
114 
115 const lv_obj_class_t lv_dropdown_class = {
116     .constructor_cb = lv_dropdown_constructor,
117     .destructor_cb = lv_dropdown_destructor,
118     .event_cb = lv_dropdown_event,
119     .width_def = LV_DPI_DEF,
120     .height_def = LV_SIZE_CONTENT,
121     .instance_size = sizeof(lv_dropdown_t),
122     .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
123     .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
124     .base_class = &lv_obj_class,
125     .name = "dropdown",
126 #if LV_USE_OBJ_PROPERTY
127     .prop_index_start = LV_PROPERTY_DROPDOWN_START,
128     .prop_index_end = LV_PROPERTY_DROPDOWN_END,
129     .properties = properties,
130     .properties_count = sizeof(properties) / sizeof(properties[0]),
131 
132 #if LV_USE_OBJ_PROPERTY_NAME
133     .property_names = lv_dropdown_property_names,
134     .names_count = sizeof(lv_dropdown_property_names) / sizeof(lv_property_name_t),
135 #endif
136 #endif
137 };
138 
139 const lv_obj_class_t lv_dropdownlist_class = {
140     .constructor_cb = lv_dropdownlist_constructor,
141     .destructor_cb = lv_dropdownlist_destructor,
142     .event_cb = lv_dropdown_list_event,
143     .instance_size = sizeof(lv_dropdown_list_t),
144     .base_class = &lv_obj_class,
145     .name = "dropdown-list",
146 };
147 
148 /**********************
149  *      MACROS
150  **********************/
151 
152 /**********************
153  *   GLOBAL FUNCTIONS
154  **********************/
155 
lv_dropdown_create(lv_obj_t * parent)156 lv_obj_t * lv_dropdown_create(lv_obj_t * parent)
157 {
158     LV_LOG_INFO("begin");
159     lv_obj_t * obj = lv_obj_class_create_obj(&lv_dropdown_class, parent);
160     lv_obj_class_init_obj(obj);
161     return obj;
162 }
163 
164 /*=====================
165  * Setter functions
166  *====================*/
167 
lv_dropdown_set_text(lv_obj_t * obj,const char * txt)168 void lv_dropdown_set_text(lv_obj_t * obj, const char * txt)
169 {
170     LV_ASSERT_OBJ(obj, MY_CLASS);
171     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
172     if(dropdown->text == txt) return;
173 
174     dropdown->text = txt;
175 
176     lv_obj_invalidate(obj);
177 }
178 
lv_dropdown_set_options(lv_obj_t * obj,const char * options)179 void lv_dropdown_set_options(lv_obj_t * obj, const char * options)
180 {
181     LV_ASSERT_OBJ(obj, MY_CLASS);
182     LV_ASSERT_NULL(options);
183 
184     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
185 
186     /*Count the '\n'-s to determine the number of options*/
187     dropdown->option_cnt = 0;
188     uint32_t i;
189     for(i = 0; options[i] != '\0'; i++) {
190         if(options[i] == '\n') dropdown->option_cnt++;
191     }
192     dropdown->option_cnt++;   /*Last option has no `\n`*/
193     dropdown->sel_opt_id      = 0;
194     dropdown->sel_opt_id_orig = 0;
195 
196     /*Allocate space for the new text*/
197 #if LV_USE_ARABIC_PERSIAN_CHARS == 0
198     size_t len = lv_strlen(options) + 1;
199 #else
200     size_t len = lv_text_ap_calc_bytes_count(options) + 1;
201 #endif
202 
203     if(dropdown->options != NULL && dropdown->static_txt == 0) {
204         lv_free(dropdown->options);
205         dropdown->options = NULL;
206     }
207 
208     dropdown->options = lv_malloc(len);
209 
210     LV_ASSERT_MALLOC(dropdown->options);
211     if(dropdown->options == NULL) return;
212 
213 #if LV_USE_ARABIC_PERSIAN_CHARS == 0
214     lv_strcpy(dropdown->options, options);
215 #else
216     lv_text_ap_proc(options, dropdown->options);
217 #endif
218 
219     /*Now the text is dynamically allocated*/
220     dropdown->static_txt = 0;
221 
222     lv_obj_invalidate(obj);
223     if(dropdown->list) lv_obj_invalidate(dropdown->list);
224 }
225 
lv_dropdown_set_options_static(lv_obj_t * obj,const char * options)226 void lv_dropdown_set_options_static(lv_obj_t * obj, const char * options)
227 {
228     LV_ASSERT_OBJ(obj, MY_CLASS);
229     LV_ASSERT_NULL(options);
230 
231     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
232 
233     /*Count the '\n'-s to determine the number of options*/
234     dropdown->option_cnt = 0;
235     uint32_t i;
236     for(i = 0; options[i] != '\0'; i++) {
237         if(options[i] == '\n') dropdown->option_cnt++;
238     }
239     dropdown->option_cnt++;   /*Last option has no `\n`*/
240     dropdown->sel_opt_id      = 0;
241     dropdown->sel_opt_id_orig = 0;
242 
243     if(dropdown->static_txt == 0 && dropdown->options != NULL) {
244         lv_free(dropdown->options);
245         dropdown->options = NULL;
246     }
247 
248     dropdown->static_txt = 1;
249     dropdown->options = (char *)options;
250 
251     lv_obj_invalidate(obj);
252     if(dropdown->list) lv_obj_invalidate(dropdown->list);
253 }
254 
lv_dropdown_add_option(lv_obj_t * obj,const char * option,uint32_t pos)255 void lv_dropdown_add_option(lv_obj_t * obj, const char * option, uint32_t pos)
256 {
257     LV_ASSERT_OBJ(obj, MY_CLASS);
258     LV_ASSERT_NULL(option);
259 
260     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
261 
262     /*Convert static options to dynamic*/
263     if(dropdown->static_txt != 0) {
264         char * static_options = dropdown->options;
265         dropdown->options = lv_strdup(static_options);
266         LV_ASSERT_MALLOC(dropdown->options);
267         if(dropdown->options == NULL) return;
268         dropdown->static_txt = 0;
269     }
270 
271     /*Allocate space for the new option*/
272     size_t old_len = (dropdown->options == NULL) ? 0 : lv_strlen(dropdown->options);
273 #if LV_USE_ARABIC_PERSIAN_CHARS == 0
274     size_t ins_len = lv_strlen(option) + 1;
275 #else
276     size_t ins_len = lv_text_ap_calc_bytes_count(option) + 1;
277 #endif
278 
279     size_t new_len = ins_len + old_len + 2; /*+2 for terminating NULL and possible \n*/
280     dropdown->options        = lv_realloc(dropdown->options, new_len + 1);
281     LV_ASSERT_MALLOC(dropdown->options);
282     if(dropdown->options == NULL) return;
283 
284     dropdown->options[old_len] = '\0';
285 
286     /*Find the insert character position*/
287     uint32_t insert_pos = old_len;
288     if(pos != LV_DROPDOWN_POS_LAST) {
289         uint32_t opcnt = 0;
290         for(insert_pos = 0; dropdown->options[insert_pos] != 0; insert_pos++) {
291             if(opcnt == pos)
292                 break;
293             if(dropdown->options[insert_pos] == '\n')
294                 opcnt++;
295         }
296     }
297 
298     /*Add delimiter to existing options*/
299     if((insert_pos > 0) && (pos >= dropdown->option_cnt))
300         lv_text_ins(dropdown->options, lv_text_encoded_get_char_id(dropdown->options, insert_pos++), "\n");
301 
302     /*Insert the new option, adding \n if necessary*/
303     char * ins_buf = lv_malloc(ins_len + 2); /*+ 2 for terminating NULL and possible \n*/
304     LV_ASSERT_MALLOC(ins_buf);
305     if(ins_buf == NULL) return;
306 #if LV_USE_ARABIC_PERSIAN_CHARS == 0
307     lv_strcpy(ins_buf, option);
308 #else
309     lv_text_ap_proc(option, ins_buf);
310 #endif
311     if(pos < dropdown->option_cnt) lv_strcat(ins_buf, "\n");
312 
313     lv_text_ins(dropdown->options, lv_text_encoded_get_char_id(dropdown->options, insert_pos), ins_buf);
314     lv_free(ins_buf);
315 
316     dropdown->option_cnt++;
317 
318     lv_obj_invalidate(obj);
319     if(dropdown->list) lv_obj_invalidate(dropdown->list);
320 }
321 
lv_dropdown_clear_options(lv_obj_t * obj)322 void lv_dropdown_clear_options(lv_obj_t * obj)
323 {
324     LV_ASSERT_OBJ(obj, MY_CLASS);
325     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
326     if(dropdown->options == NULL) return;
327 
328     if(dropdown->static_txt == 0)
329         lv_free(dropdown->options);
330 
331     dropdown->options = NULL;
332     dropdown->static_txt = 0;
333     dropdown->option_cnt = 0;
334 
335     lv_obj_invalidate(obj);
336     if(dropdown->list) lv_obj_invalidate(dropdown->list);
337 }
338 
lv_dropdown_set_selected(lv_obj_t * obj,uint32_t sel_opt,lv_anim_enable_t anim)339 void lv_dropdown_set_selected(lv_obj_t * obj, uint32_t sel_opt, lv_anim_enable_t anim)
340 {
341     LV_ASSERT_OBJ(obj, MY_CLASS);
342 
343     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
344     if(dropdown->sel_opt_id == sel_opt) return;
345 
346     dropdown->sel_opt_id      = sel_opt < dropdown->option_cnt ? sel_opt : dropdown->option_cnt - 1;
347     dropdown->sel_opt_id_orig = dropdown->sel_opt_id;
348 
349     if(dropdown->list) {
350         position_to_selected(obj, anim);
351     }
352 
353     lv_obj_invalidate(obj);
354 }
355 
lv_dropdown_set_dir(lv_obj_t * obj,lv_dir_t dir)356 void lv_dropdown_set_dir(lv_obj_t * obj, lv_dir_t dir)
357 {
358     LV_ASSERT_OBJ(obj, MY_CLASS);
359 
360     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
361     if(dropdown->dir == dir) return;
362 
363     dropdown->dir = dir;
364 
365     lv_obj_invalidate(obj);
366 }
367 
lv_dropdown_set_symbol(lv_obj_t * obj,const void * symbol)368 void lv_dropdown_set_symbol(lv_obj_t * obj, const void * symbol)
369 {
370     LV_ASSERT_OBJ(obj, MY_CLASS);
371 
372     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
373     dropdown->symbol = symbol;
374     lv_obj_invalidate(obj);
375 }
376 
lv_dropdown_set_selected_highlight(lv_obj_t * obj,bool en)377 void lv_dropdown_set_selected_highlight(lv_obj_t * obj, bool en)
378 {
379     LV_ASSERT_OBJ(obj, MY_CLASS);
380 
381     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
382     dropdown->selected_highlight = en;
383     if(dropdown->list) lv_obj_invalidate(dropdown->list);
384 }
385 
386 /*=====================
387  * Getter functions
388  *====================*/
389 
lv_dropdown_get_list(lv_obj_t * obj)390 lv_obj_t * lv_dropdown_get_list(lv_obj_t * obj)
391 {
392     LV_ASSERT_OBJ(obj, MY_CLASS);
393     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
394 
395     return dropdown->list;
396 }
397 
lv_dropdown_get_text(lv_obj_t * obj)398 const char * lv_dropdown_get_text(lv_obj_t * obj)
399 {
400     LV_ASSERT_OBJ(obj, MY_CLASS);
401     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
402 
403     return dropdown->text;
404 }
405 
lv_dropdown_get_options(const lv_obj_t * obj)406 const char * lv_dropdown_get_options(const lv_obj_t * obj)
407 {
408     LV_ASSERT_OBJ(obj, MY_CLASS);
409 
410     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
411     return dropdown->options == NULL ? "" : dropdown->options;
412 }
413 
lv_dropdown_get_selected(const lv_obj_t * obj)414 uint32_t lv_dropdown_get_selected(const lv_obj_t * obj)
415 {
416     LV_ASSERT_OBJ(obj, MY_CLASS);
417 
418     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
419 
420     return dropdown->sel_opt_id;
421 }
422 
lv_dropdown_get_option_count(const lv_obj_t * obj)423 uint32_t lv_dropdown_get_option_count(const lv_obj_t * obj)
424 {
425     LV_ASSERT_OBJ(obj, MY_CLASS);
426 
427     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
428 
429     return dropdown->option_cnt;
430 }
431 
lv_dropdown_get_selected_str(const lv_obj_t * obj,char * buf,uint32_t buf_size)432 void lv_dropdown_get_selected_str(const lv_obj_t * obj, char * buf, uint32_t buf_size)
433 {
434     LV_ASSERT_OBJ(obj, MY_CLASS);
435 
436     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
437 
438     uint32_t i;
439     uint32_t line        = 0;
440     size_t txt_len;
441 
442     if(dropdown->options)  {
443         txt_len     = lv_strlen(dropdown->options);
444     }
445     else {
446         buf[0] = '\0';
447         return;
448     }
449 
450     for(i = 0; i < txt_len && line != dropdown->sel_opt_id_orig; i++) {
451         if(dropdown->options[i] == '\n') line++;
452     }
453 
454     uint32_t c;
455     for(c = 0; i < txt_len && dropdown->options[i] != '\n'; c++, i++) {
456         if(buf_size && c >= buf_size - 1) {
457             LV_LOG_WARN("the buffer was too small");
458             break;
459         }
460         buf[c] = dropdown->options[i];
461     }
462 
463     buf[c] = '\0';
464 }
465 
lv_dropdown_get_option_index(lv_obj_t * obj,const char * option)466 int32_t lv_dropdown_get_option_index(lv_obj_t * obj, const char * option)
467 {
468     const char * opts = lv_dropdown_get_options(obj);
469     uint32_t char_i = 0;
470     uint32_t opt_i = 0;
471     const char * start = opts;
472     const size_t option_len = lv_strlen(option); /*avoid recomputing this multiple times in the loop*/
473 
474     while(start[0] != '\0') {
475         for(char_i = 0; (start[char_i] != '\n') && (start[char_i] != '\0'); char_i++);
476 
477         if(option_len == char_i && lv_memcmp(start, option, LV_MIN(option_len, char_i)) == 0) {
478             return opt_i;
479         }
480 
481         start = &start[char_i];
482         if(start[0] == '\n') start++;
483         char_i = 0;
484         opt_i++;
485     }
486 
487     return -1;
488 }
489 
lv_dropdown_get_symbol(lv_obj_t * obj)490 const char * lv_dropdown_get_symbol(lv_obj_t * obj)
491 {
492     LV_ASSERT_OBJ(obj, MY_CLASS);
493     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
494     return dropdown->symbol;
495 }
496 
lv_dropdown_get_selected_highlight(lv_obj_t * obj)497 bool lv_dropdown_get_selected_highlight(lv_obj_t * obj)
498 {
499     LV_ASSERT_OBJ(obj, MY_CLASS);
500     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
501     return dropdown->selected_highlight;
502 }
503 
lv_dropdown_get_dir(const lv_obj_t * obj)504 lv_dir_t lv_dropdown_get_dir(const lv_obj_t * obj)
505 {
506     LV_ASSERT_OBJ(obj, MY_CLASS);
507     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
508     return dropdown->dir;
509 }
510 
511 /*=====================
512  * Other functions
513  *====================*/
514 
lv_dropdown_open(lv_obj_t * dropdown_obj)515 void lv_dropdown_open(lv_obj_t * dropdown_obj)
516 {
517     LV_ASSERT_OBJ(dropdown_obj, MY_CLASS);
518 
519     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
520 
521     lv_obj_add_state(dropdown_obj, LV_STATE_CHECKED);
522     lv_obj_set_parent(dropdown->list, lv_obj_get_screen(dropdown_obj));
523     lv_obj_move_to_index(dropdown->list, -1);
524     lv_obj_remove_flag(dropdown->list, LV_OBJ_FLAG_HIDDEN);
525 
526     /*To allow styling the list*/
527     lv_obj_send_event(dropdown_obj, LV_EVENT_READY, NULL);
528 
529     lv_obj_t * label = get_label(dropdown_obj);
530     lv_label_set_text_static(label, dropdown->options);
531     lv_obj_set_width(dropdown->list, LV_SIZE_CONTENT);
532 
533     lv_obj_update_layout(label);
534     /*Set smaller width to the width of the button*/
535     if(lv_obj_get_width(dropdown->list) <= lv_obj_get_width(dropdown_obj) &&
536        (dropdown->dir == LV_DIR_TOP || dropdown->dir == LV_DIR_BOTTOM)) {
537         lv_obj_set_width(dropdown->list, lv_obj_get_width(dropdown_obj));
538     }
539 
540     int32_t label_h = lv_obj_get_height(label);
541     int32_t border_width = lv_obj_get_style_border_width(dropdown->list, LV_PART_MAIN);
542     int32_t top = lv_obj_get_style_pad_top(dropdown->list, LV_PART_MAIN) + border_width;
543     int32_t bottom = lv_obj_get_style_pad_bottom(dropdown->list, LV_PART_MAIN) + border_width;
544 
545     int32_t list_fit_h = label_h + top + bottom;
546     int32_t list_h = list_fit_h;
547 
548     lv_dir_t dir = dropdown->dir;
549     /*No space on the bottom? See if top is better.*/
550     if(dropdown->dir == LV_DIR_BOTTOM) {
551         if(dropdown_obj->coords.y2 + list_h > LV_VER_RES) {
552             if(dropdown_obj->coords.y1 > LV_VER_RES - dropdown_obj->coords.y2) {
553                 /*There is more space on the top, so make it drop up*/
554                 dir = LV_DIR_TOP;
555                 list_h = dropdown_obj->coords.y1 - 1;
556             }
557             else {
558                 list_h = LV_VER_RES - dropdown_obj->coords.y2 - 1 ;
559             }
560         }
561     }
562     /*No space on the top? See if bottom is better.*/
563     else if(dropdown->dir == LV_DIR_TOP) {
564         if(dropdown_obj->coords.y1 - list_h < 0) {
565             if(dropdown_obj->coords.y1 < LV_VER_RES - dropdown_obj->coords.y2) {
566                 /*There is more space on the top, so make it drop up*/
567                 dir = LV_DIR_BOTTOM;
568                 list_h = LV_VER_RES - dropdown_obj->coords.y2;
569             }
570             else {
571                 list_h = dropdown_obj->coords.y1;
572             }
573         }
574     }
575 
576     if(list_h > list_fit_h) list_h = list_fit_h;
577     lv_obj_set_height(dropdown->list, list_h);
578 
579     position_to_selected(dropdown_obj, LV_ANIM_OFF);
580 
581     if(dir == LV_DIR_BOTTOM)     lv_obj_align_to(dropdown->list, dropdown_obj, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
582     else if(dir == LV_DIR_TOP)   lv_obj_align_to(dropdown->list, dropdown_obj, LV_ALIGN_OUT_TOP_LEFT, 0, 0);
583     else if(dir == LV_DIR_LEFT)  lv_obj_align_to(dropdown->list, dropdown_obj, LV_ALIGN_OUT_LEFT_TOP, 0, 0);
584     else if(dir == LV_DIR_RIGHT) lv_obj_align_to(dropdown->list, dropdown_obj, LV_ALIGN_OUT_RIGHT_TOP, 0, 0);
585 
586     lv_obj_update_layout(dropdown->list);
587 
588     if(dropdown->dir == LV_DIR_LEFT || dropdown->dir == LV_DIR_RIGHT) {
589         int32_t y1 = lv_obj_get_y(dropdown->list);
590         int32_t y2 = lv_obj_get_y2(dropdown->list);
591         if(y2 >= LV_VER_RES) {
592             lv_obj_set_y(dropdown->list, y1 - (y2 - LV_VER_RES) - 1);
593         }
594     }
595 
596     lv_text_align_t align = lv_obj_calculate_style_text_align(label, LV_PART_MAIN, dropdown->options);
597 
598     switch(align) {
599         default:
600         case LV_TEXT_ALIGN_LEFT:
601             lv_obj_align(label, LV_ALIGN_TOP_LEFT, 0, 0);
602             break;
603         case LV_TEXT_ALIGN_RIGHT:
604             lv_obj_align(label, LV_ALIGN_TOP_RIGHT, 0, 0);
605             break;
606         case LV_TEXT_ALIGN_CENTER:
607             lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0);
608             break;
609 
610     }
611 }
612 
lv_dropdown_close(lv_obj_t * obj)613 void lv_dropdown_close(lv_obj_t * obj)
614 {
615     LV_ASSERT_OBJ(obj, MY_CLASS);
616 
617     lv_obj_remove_state(obj, LV_STATE_CHECKED);
618     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
619 
620     dropdown->pr_opt_id = LV_DROPDOWN_PR_NONE;
621     lv_obj_add_flag(dropdown->list, LV_OBJ_FLAG_HIDDEN);
622 
623     lv_obj_send_event(obj, LV_EVENT_CANCEL, NULL);
624 }
625 
lv_dropdown_is_open(lv_obj_t * obj)626 bool lv_dropdown_is_open(lv_obj_t * obj)
627 {
628     LV_ASSERT_OBJ(obj, MY_CLASS);
629     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
630 
631     return lv_obj_has_flag(dropdown->list, LV_OBJ_FLAG_HIDDEN) ? false : true;
632 }
633 
634 /**********************
635  *   STATIC FUNCTIONS
636  **********************/
637 
lv_dropdown_list_create(lv_obj_t * parent)638 static lv_obj_t * lv_dropdown_list_create(lv_obj_t * parent)
639 {
640     LV_LOG_INFO("begin");
641     lv_obj_t * obj = lv_obj_class_create_obj(&lv_dropdownlist_class, parent);
642     lv_obj_class_init_obj(obj);
643     return obj;
644 }
645 
lv_dropdown_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)646 static void lv_dropdown_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
647 {
648     LV_UNUSED(class_p);
649     LV_TRACE_OBJ_CREATE("begin");
650 
651     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
652 
653     /*Initialize the allocated 'ext'*/
654     dropdown->list          = NULL;
655     dropdown->options     = NULL;
656     dropdown->symbol         = LV_SYMBOL_DOWN;
657     dropdown->text         = NULL;
658     dropdown->static_txt = 1;
659     dropdown->selected_highlight = 1;
660     dropdown->sel_opt_id      = 0;
661     dropdown->sel_opt_id_orig = 0;
662     dropdown->pr_opt_id = LV_DROPDOWN_PR_NONE;
663     dropdown->option_cnt      = 0;
664     dropdown->dir = LV_DIR_BOTTOM;
665 
666     lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
667 #if LV_WIDGETS_HAS_DEFAULT_VALUE
668     lv_dropdown_set_options_static(obj, "Option 1\nOption 2\nOption 3");
669 #endif
670 
671     dropdown->list = lv_dropdown_list_create(lv_obj_get_screen(obj));
672     lv_dropdown_list_t * list = (lv_dropdown_list_t *)dropdown->list;
673     list->dropdown = obj;
674 
675     LV_TRACE_OBJ_CREATE("finished");
676 }
677 
lv_dropdown_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)678 static void lv_dropdown_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
679 {
680     LV_UNUSED(class_p);
681     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
682 
683     if(dropdown->list) {
684         lv_obj_delete(dropdown->list);
685         dropdown->list = NULL;
686     }
687 
688     if(!dropdown->static_txt) {
689         lv_free(dropdown->options);
690         dropdown->options = NULL;
691     }
692 }
693 
lv_dropdownlist_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)694 static void lv_dropdownlist_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
695 {
696     LV_UNUSED(class_p);
697     LV_TRACE_OBJ_CREATE("begin");
698 
699     lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
700     lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
701     lv_obj_add_flag(obj, LV_OBJ_FLAG_IGNORE_LAYOUT);
702     lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
703 
704     lv_label_create(obj);
705 
706     LV_TRACE_OBJ_CREATE("finished");
707 }
708 
lv_dropdownlist_destructor(const lv_obj_class_t * class_p,lv_obj_t * list_obj)709 static void lv_dropdownlist_destructor(const lv_obj_class_t * class_p, lv_obj_t * list_obj)
710 {
711     LV_UNUSED(class_p);
712     lv_dropdown_list_t * list = (lv_dropdown_list_t *)list_obj;
713     lv_obj_t * dropdown_obj = list->dropdown;
714     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
715     dropdown->list = NULL;
716 }
717 
lv_dropdown_event(const lv_obj_class_t * class_p,lv_event_t * e)718 static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e)
719 {
720     LV_UNUSED(class_p);
721 
722     lv_result_t res;
723 
724     /*Call the ancestor's event handler*/
725     res = lv_obj_event_base(MY_CLASS, e);
726     if(res != LV_RESULT_OK) return;
727 
728     lv_event_code_t code = lv_event_get_code(e);
729     lv_obj_t * obj = lv_event_get_current_target(e);
730     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
731 
732     if(code == LV_EVENT_FOCUSED) {
733         lv_group_t * g             = lv_obj_get_group(obj);
734         bool editing               = lv_group_get_editing(g);
735         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_active());
736 
737         /*Encoders need special handling*/
738         if(indev_type == LV_INDEV_TYPE_ENCODER) {
739             /*Open the list if editing*/
740             if(editing) {
741                 lv_dropdown_open(obj);
742             }
743             /*Close the list if navigating*/
744             else {
745                 dropdown->sel_opt_id = dropdown->sel_opt_id_orig;
746                 lv_dropdown_close(obj);
747             }
748         }
749     }
750     else if(code == LV_EVENT_DEFOCUSED || code == LV_EVENT_LEAVE) {
751         lv_dropdown_close(obj);
752     }
753     else if(code == LV_EVENT_RELEASED) {
754         res = btn_release_handler(obj);
755         if(res != LV_RESULT_OK) return;
756     }
757     else if(code == LV_EVENT_STYLE_CHANGED) {
758         lv_obj_refresh_self_size(obj);
759     }
760     else if(code == LV_EVENT_SIZE_CHANGED) {
761         lv_obj_refresh_self_size(obj);
762     }
763     else if(code == LV_EVENT_GET_SELF_SIZE) {
764         lv_point_t * p = lv_event_get_param(e);
765         const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
766         p->y = lv_font_get_line_height(font);
767     }
768     else if(code == LV_EVENT_KEY) {
769         uint32_t c = lv_event_get_key(e);
770         if(c == LV_KEY_RIGHT || c == LV_KEY_DOWN) {
771             if(!lv_dropdown_is_open(obj)) {
772                 lv_dropdown_open(obj);
773             }
774             else if(dropdown->sel_opt_id + 1 < dropdown->option_cnt) {
775                 dropdown->sel_opt_id++;
776                 position_to_selected(obj, LV_ANIM_ON);
777             }
778         }
779         else if(c == LV_KEY_LEFT || c == LV_KEY_UP) {
780 
781             if(!lv_dropdown_is_open(obj)) {
782                 lv_dropdown_open(obj);
783             }
784             else if(dropdown->sel_opt_id > 0) {
785                 dropdown->sel_opt_id--;
786                 position_to_selected(obj, LV_ANIM_ON);
787             }
788         }
789         else if(c == LV_KEY_ESC) {
790             dropdown->sel_opt_id = dropdown->sel_opt_id_orig;
791             lv_dropdown_close(obj);
792         }
793         else if(c == LV_KEY_ENTER) {
794             /* Handle the ENTER key only if it was send by another object.
795              * Do no process it if ENTER is sent by the dropdown because it's handled in LV_EVENT_RELEASED */
796             lv_obj_t * indev_obj = lv_indev_get_active_obj();
797             if(indev_obj != obj) {
798                 res = btn_release_handler(obj);
799                 if(res != LV_RESULT_OK) return;
800             }
801         }
802     }
803     else if(code == LV_EVENT_ROTARY) {
804         if(!lv_dropdown_is_open(obj)) {
805             lv_dropdown_open(obj);
806         }
807         else {
808             int32_t r = lv_event_get_rotary_diff(e);
809             int32_t new_id = dropdown->sel_opt_id + r;
810             new_id = LV_CLAMP(0, new_id, (int32_t)dropdown->option_cnt - 1);
811 
812             dropdown->sel_opt_id = new_id;
813             position_to_selected(obj, LV_ANIM_ON);
814         }
815     }
816     else if(code == LV_EVENT_DRAW_MAIN) {
817         draw_main(e);
818     }
819 }
820 
lv_dropdown_list_event(const lv_obj_class_t * class_p,lv_event_t * e)821 static void lv_dropdown_list_event(const lv_obj_class_t * class_p, lv_event_t * e)
822 {
823     LV_UNUSED(class_p);
824 
825     lv_result_t res;
826 
827     /*Call the ancestor's event handler*/
828     lv_event_code_t code = lv_event_get_code(e);
829     if(code != LV_EVENT_DRAW_POST) {
830         res = lv_obj_event_base(MY_CLASS_LIST, e);
831         if(res != LV_RESULT_OK) return;
832     }
833     lv_obj_t * list = lv_event_get_current_target(e);
834     lv_obj_t * dropdown_obj = ((lv_dropdown_list_t *)list)->dropdown;
835     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
836 
837     if(code == LV_EVENT_RELEASED) {
838         if(lv_indev_get_scroll_obj(lv_indev_active()) == NULL) {
839             list_release_handler(list);
840         }
841     }
842     else if(code == LV_EVENT_PRESSED) {
843         list_press_handler(list);
844     }
845     else if(code == LV_EVENT_SCROLL_BEGIN) {
846         dropdown->pr_opt_id = LV_DROPDOWN_PR_NONE;
847         lv_obj_invalidate(list);
848     }
849     else if(code == LV_EVENT_DRAW_POST) {
850         draw_list(e);
851         res = lv_obj_event_base(MY_CLASS_LIST, e);
852         if(res != LV_RESULT_OK) return;
853     }
854 }
855 
draw_main(lv_event_t * e)856 static void draw_main(lv_event_t * e)
857 {
858     lv_obj_t * obj = lv_event_get_current_target(e);
859     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
860     lv_layer_t * layer = lv_event_get_layer(e);
861 
862     int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
863     int32_t left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
864     int32_t right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN) + border_width;
865 
866     lv_draw_label_dsc_t symbol_dsc;
867     lv_draw_label_dsc_init(&symbol_dsc);
868     symbol_dsc.base.layer = layer;
869     lv_obj_init_draw_label_dsc(obj, LV_PART_INDICATOR, &symbol_dsc);
870 
871     /*If no text specified use the selected option*/
872     const char * opt_txt;
873     char buf[128];
874     if(dropdown->text) opt_txt = dropdown->text;
875     else {
876         lv_dropdown_get_selected_str(obj, buf, 128);
877         opt_txt = buf;
878     }
879 
880     bool symbol_to_left = false;
881     if(dropdown->dir == LV_DIR_LEFT) symbol_to_left = true;
882     if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL) symbol_to_left = true;
883 
884     if(dropdown->symbol) {
885         lv_image_src_t symbol_type = lv_image_src_get_type(dropdown->symbol);
886         int32_t symbol_w;
887         int32_t symbol_h;
888         if(symbol_type == LV_IMAGE_SRC_SYMBOL) {
889             lv_point_t size;
890             lv_text_get_size(&size, dropdown->symbol, symbol_dsc.font, symbol_dsc.letter_space, symbol_dsc.line_space, LV_COORD_MAX,
891                              symbol_dsc.flag);
892             symbol_w = size.x;
893             symbol_h = size.y;
894         }
895         else {
896             lv_image_header_t header;
897             lv_result_t res = lv_image_decoder_get_info(dropdown->symbol, &header);
898             if(res == LV_RESULT_OK) {
899                 symbol_w = header.w;
900                 symbol_h = header.h;
901             }
902             else {
903                 symbol_w = -1;
904                 symbol_h = -1;
905             }
906         }
907 
908         lv_area_t symbol_area;
909         symbol_area.y1 = obj->coords.y1;
910         symbol_area.y2 = symbol_area.y1 + symbol_h - 1;
911         symbol_area.x1 = obj->coords.x1;
912         symbol_area.x2 = symbol_area.x1 + symbol_w - 1;
913         if(symbol_to_left) {
914             lv_area_align(&obj->coords, &symbol_area, LV_ALIGN_LEFT_MID, left, 0);
915         }
916         else {
917             lv_area_align(&obj->coords, &symbol_area, LV_ALIGN_RIGHT_MID, -right, 0);
918         }
919 
920         if(symbol_type == LV_IMAGE_SRC_SYMBOL) {
921             symbol_dsc.text = dropdown->symbol;
922             lv_draw_label(layer, &symbol_dsc, &symbol_area);
923         }
924         else {
925             lv_draw_image_dsc_t img_dsc;
926             lv_draw_image_dsc_init(&img_dsc);
927             img_dsc.base.layer = layer;
928             lv_obj_init_draw_image_dsc(obj, LV_PART_INDICATOR, &img_dsc);
929             lv_point_set(&img_dsc.pivot, symbol_w / 2, symbol_h / 2);
930             img_dsc.rotation = lv_obj_get_style_transform_rotation(obj, LV_PART_INDICATOR);
931             img_dsc.src = dropdown->symbol;
932             lv_draw_image(layer, &img_dsc, &symbol_area);
933         }
934     }
935 
936     lv_draw_label_dsc_t label_dsc;
937     lv_draw_label_dsc_init(&label_dsc);
938     label_dsc.base.layer = layer;
939     lv_obj_init_draw_label_dsc(obj, LV_PART_MAIN, &label_dsc);
940 
941     lv_point_t size;
942     lv_text_get_size(&size, opt_txt, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX,
943                      label_dsc.flag);
944 
945     lv_area_t txt_area;
946     txt_area.x1 = obj->coords.x1;
947     txt_area.x2 = txt_area.x1 + size.x - 1;
948     txt_area.y1 = obj->coords.y1;
949     txt_area.y2 = txt_area.y1 + size.y - 1;
950     /*Center align the text if no symbol*/
951     if(dropdown->symbol == NULL) {
952         lv_area_align(&obj->coords, &txt_area, LV_ALIGN_CENTER, 0, 0);
953     }
954     else {
955         /*Text to the right*/
956         if(symbol_to_left) {
957             lv_area_align(&obj->coords, &txt_area, LV_ALIGN_RIGHT_MID, -right, 0);
958         }
959         else {
960             lv_area_align(&obj->coords, &txt_area, LV_ALIGN_LEFT_MID, left, 0);
961         }
962     }
963 
964     label_dsc.text = opt_txt;
965     if(dropdown->text == NULL) {
966         label_dsc.text_local = true;
967     }
968 
969     lv_draw_label(layer, &label_dsc, &txt_area);
970 }
971 
draw_list(lv_event_t * e)972 static void draw_list(lv_event_t * e)
973 {
974     lv_obj_t * list_obj = lv_event_get_current_target(e);
975     lv_dropdown_list_t * list = (lv_dropdown_list_t *)list_obj;
976     lv_obj_t * dropdown_obj = list->dropdown;
977     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
978     lv_layer_t * layer = lv_event_get_layer(e);
979 
980     /* Clip area might be too large too to shadow but
981      * the selected option can be drawn on only the background*/
982     lv_area_t clip_area_core;
983     bool has_common;
984     has_common = lv_area_intersect(&clip_area_core, &layer->_clip_area, &dropdown->list->coords);
985     if(has_common) {
986         const lv_area_t clip_area_ori = layer->_clip_area;
987         layer->_clip_area = clip_area_core;
988         if(dropdown->selected_highlight) {
989             if(dropdown->pr_opt_id == dropdown->sel_opt_id) {
990                 draw_box(dropdown_obj, layer, dropdown->pr_opt_id, LV_STATE_CHECKED | LV_STATE_PRESSED);
991                 draw_box_label(dropdown_obj, layer, dropdown->pr_opt_id, LV_STATE_CHECKED | LV_STATE_PRESSED);
992             }
993             else {
994                 draw_box(dropdown_obj, layer, dropdown->pr_opt_id, LV_STATE_PRESSED);
995                 draw_box_label(dropdown_obj, layer, dropdown->pr_opt_id, LV_STATE_PRESSED);
996                 draw_box(dropdown_obj, layer, dropdown->sel_opt_id, LV_STATE_CHECKED);
997                 draw_box_label(dropdown_obj, layer, dropdown->sel_opt_id, LV_STATE_CHECKED);
998             }
999         }
1000         else {
1001             draw_box(dropdown_obj, layer, dropdown->pr_opt_id, LV_STATE_PRESSED);
1002             draw_box_label(dropdown_obj, layer, dropdown->pr_opt_id, LV_STATE_PRESSED);
1003         }
1004         layer->_clip_area = clip_area_ori;
1005     }
1006 }
1007 
draw_box(lv_obj_t * dropdown_obj,lv_layer_t * layer,uint32_t id,lv_state_t state)1008 static void draw_box(lv_obj_t * dropdown_obj, lv_layer_t * layer, uint32_t id, lv_state_t state)
1009 {
1010     if(id == LV_DROPDOWN_PR_NONE) return;
1011 
1012     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
1013     lv_obj_t * list_obj = dropdown->list;
1014     lv_state_t state_ori = list_obj->state;
1015 
1016     if(state != list_obj->state) {
1017         list_obj->state = state;
1018         list_obj->skip_trans = 1;
1019     }
1020 
1021     /*Draw a rectangle under the selected item*/
1022     const lv_font_t * font    = lv_obj_get_style_text_font(list_obj, LV_PART_SELECTED);
1023     int32_t line_space = lv_obj_get_style_text_line_space(list_obj,  LV_PART_SELECTED);
1024     int32_t font_h         = lv_font_get_line_height(font);
1025 
1026     /*Draw the selected*/
1027     lv_obj_t * label = get_label(dropdown_obj);
1028     lv_area_t rect_area;
1029     rect_area.y1 = label->coords.y1;
1030     rect_area.y1 += id * (font_h + line_space);
1031     rect_area.y1 -= line_space / 2;
1032 
1033     rect_area.y2 = rect_area.y1 + font_h + line_space - 1;
1034     rect_area.x1 = dropdown->list->coords.x1;
1035     rect_area.x2 = dropdown->list->coords.x2;
1036 
1037     lv_draw_rect_dsc_t sel_rect;
1038     lv_draw_rect_dsc_init(&sel_rect);
1039     sel_rect.base.layer = layer;
1040     lv_obj_init_draw_rect_dsc(list_obj,  LV_PART_SELECTED, &sel_rect);
1041     lv_draw_rect(layer, &sel_rect, &rect_area);
1042 
1043     list_obj->state = state_ori;
1044     list_obj->skip_trans = 0;
1045 }
1046 
draw_box_label(lv_obj_t * dropdown_obj,lv_layer_t * layer,uint32_t id,lv_state_t state)1047 static void draw_box_label(lv_obj_t * dropdown_obj, lv_layer_t * layer, uint32_t id, lv_state_t state)
1048 {
1049     if(id == LV_DROPDOWN_PR_NONE) return;
1050 
1051     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
1052     lv_obj_t * list_obj = dropdown->list;
1053     lv_state_t state_orig = list_obj->state;
1054 
1055     if(state != list_obj->state) {
1056         list_obj->state = state;
1057         list_obj->skip_trans = 1;
1058     }
1059 
1060     lv_draw_label_dsc_t label_dsc;
1061     lv_draw_label_dsc_init(&label_dsc);
1062     label_dsc.base.layer = layer;
1063     lv_obj_init_draw_label_dsc(list_obj, LV_PART_SELECTED, &label_dsc);
1064 
1065     label_dsc.line_space = lv_obj_get_style_text_line_space(list_obj,
1066                                                             LV_PART_SELECTED);  /*Line space should come from the list*/
1067 
1068     lv_obj_t * label = get_label(dropdown_obj);
1069     if(label == NULL) return;
1070 
1071     int32_t font_h        = lv_font_get_line_height(label_dsc.font);
1072 
1073     lv_area_t area_sel;
1074     area_sel.y1 = label->coords.y1;
1075     area_sel.y1 += id * (font_h + label_dsc.line_space);
1076     area_sel.y1 -= label_dsc.line_space / 2;
1077 
1078     area_sel.y2 = area_sel.y1 + font_h + label_dsc.line_space - 1;
1079     area_sel.x1 = list_obj->coords.x1;
1080     area_sel.x2 = list_obj->coords.x2;
1081     lv_area_t mask_sel;
1082     bool area_ok;
1083     area_ok = lv_area_intersect(&mask_sel, &layer->_clip_area, &area_sel);
1084     if(area_ok) {
1085         const lv_area_t clip_area_ori = layer->_clip_area;
1086         layer->_clip_area = mask_sel;
1087         label_dsc.text = lv_label_get_text(label);
1088         lv_draw_label(layer, &label_dsc, &label->coords);
1089         layer->_clip_area = clip_area_ori;
1090     }
1091     list_obj->state = state_orig;
1092     list_obj->skip_trans = 0;
1093 }
1094 
btn_release_handler(lv_obj_t * obj)1095 static lv_result_t btn_release_handler(lv_obj_t * obj)
1096 {
1097     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
1098     lv_indev_t * indev = lv_indev_active();
1099     if(lv_indev_get_scroll_obj(indev) == NULL) {
1100         if(lv_dropdown_is_open(obj)) {
1101             lv_dropdown_close(obj);
1102             if(dropdown->sel_opt_id_orig != dropdown->sel_opt_id) {
1103                 dropdown->sel_opt_id_orig = dropdown->sel_opt_id;
1104                 lv_result_t res;
1105                 uint32_t id  = dropdown->sel_opt_id; /*Just to use uint32_t in event data*/
1106                 res = lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, &id);
1107                 if(res != LV_RESULT_OK) return res;
1108                 lv_obj_invalidate(obj);
1109             }
1110             lv_indev_type_t indev_type = lv_indev_get_type(indev);
1111             if(indev_type == LV_INDEV_TYPE_ENCODER) {
1112                 lv_group_set_editing(lv_obj_get_group(obj), false);
1113             }
1114         }
1115         else {
1116             lv_dropdown_open(obj);
1117         }
1118     }
1119     else {
1120         dropdown->sel_opt_id = dropdown->sel_opt_id_orig;
1121         lv_obj_invalidate(obj);
1122     }
1123     return LV_RESULT_OK;
1124 }
1125 
1126 /**
1127  * Called when a drop down list is released to open it or set new option
1128  * @param list pointer to the drop down list's list
1129  * @return LV_RESULT_INVALID if the list is not being deleted in the user callback. Else LV_RESULT_OK
1130  */
list_release_handler(lv_obj_t * list_obj)1131 static lv_result_t list_release_handler(lv_obj_t * list_obj)
1132 {
1133     lv_dropdown_list_t * list = (lv_dropdown_list_t *) list_obj;
1134     lv_obj_t * dropdown_obj = list->dropdown;
1135     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
1136 
1137     lv_indev_t * indev = lv_indev_active();
1138     /*Leave edit mode once a new item is selected*/
1139     if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) {
1140         dropdown->sel_opt_id_orig = dropdown->sel_opt_id;
1141         lv_group_t * g      = lv_obj_get_group(dropdown_obj);
1142         if(lv_group_get_editing(g)) {
1143             lv_group_set_editing(g, false);
1144         }
1145     }
1146 
1147     /*Search the clicked option (For KEYPAD and ENCODER the new value should be already set)*/
1148     if(lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON) {
1149         lv_point_t p;
1150         lv_indev_get_point(indev, &p);
1151         dropdown->sel_opt_id     = get_id_on_point(dropdown_obj, p.y);
1152         dropdown->sel_opt_id_orig = dropdown->sel_opt_id;
1153     }
1154 
1155     lv_dropdown_close(dropdown_obj);
1156 
1157     /*Invalidate to refresh the text*/
1158     if(dropdown->text == NULL) lv_obj_invalidate(dropdown_obj);
1159 
1160     uint32_t id  = dropdown->sel_opt_id; /*Just to use uint32_t in event data*/
1161     lv_result_t res = lv_obj_send_event(dropdown_obj, LV_EVENT_VALUE_CHANGED, &id);
1162     if(res != LV_RESULT_OK) return res;
1163 
1164     return LV_RESULT_OK;
1165 }
1166 
list_press_handler(lv_obj_t * list_obj)1167 static void list_press_handler(lv_obj_t * list_obj)
1168 {
1169     lv_dropdown_list_t * list = (lv_dropdown_list_t *) list_obj;
1170     lv_obj_t * dropdown_obj = list->dropdown;
1171     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
1172 
1173     lv_indev_t * indev = lv_indev_active();
1174     if(indev && (lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON)) {
1175         lv_point_t p;
1176         lv_indev_get_point(indev, &p);
1177         dropdown->pr_opt_id = get_id_on_point(dropdown_obj, p.y);
1178         lv_obj_invalidate(list_obj);
1179     }
1180 }
1181 
get_id_on_point(lv_obj_t * dropdown_obj,int32_t y)1182 static uint32_t get_id_on_point(lv_obj_t * dropdown_obj, int32_t y)
1183 {
1184     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
1185     lv_obj_t * label = get_label(dropdown_obj);
1186     if(label == NULL) return 0;
1187     y -= label->coords.y1;
1188 
1189     const lv_font_t * font         = lv_obj_get_style_text_font(label, LV_PART_MAIN);
1190     int32_t font_h              = lv_font_get_line_height(font);
1191     int32_t line_space = lv_obj_get_style_text_line_space(label, LV_PART_MAIN);
1192 
1193     y += line_space / 2;
1194     int32_t h = font_h + line_space;
1195 
1196     uint32_t opt = y / h;
1197 
1198     if(opt >= dropdown->option_cnt) opt = dropdown->option_cnt - 1;
1199     return opt;
1200 }
1201 
1202 /**
1203  * Set the position of list when it is closed to show the selected item
1204  * @param ddlist pointer to a drop down list
1205  */
position_to_selected(lv_obj_t * dropdown_obj,lv_anim_enable_t anim_en)1206 static void position_to_selected(lv_obj_t * dropdown_obj, lv_anim_enable_t anim_en)
1207 {
1208     lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
1209 
1210     lv_obj_t * label = get_label(dropdown_obj);
1211     if(label == NULL) return;
1212 
1213     if(lv_obj_get_height(label) <= lv_obj_get_content_height(dropdown_obj)) return;
1214 
1215     const lv_font_t * font         = lv_obj_get_style_text_font(label, LV_PART_MAIN);
1216     int32_t font_h              = lv_font_get_line_height(font);
1217     int32_t line_space = lv_obj_get_style_text_line_space(label, LV_PART_MAIN);
1218     int32_t unit_h = font_h + line_space;
1219     int32_t line_y1 = dropdown->sel_opt_id * unit_h;
1220 
1221     /*Scroll to the selected option*/
1222     lv_obj_scroll_to_y(dropdown->list, line_y1, anim_en);
1223     lv_obj_invalidate(dropdown->list);
1224 }
1225 
get_label(const lv_obj_t * obj)1226 static lv_obj_t * get_label(const lv_obj_t * obj)
1227 {
1228     lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
1229     if(dropdown->list == NULL) return NULL;
1230 
1231     return lv_obj_get_child(dropdown->list, 0);
1232 }
1233 
1234 #endif
1235