1 /**
2 * @file lv_tab.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_tabview.h"
10 #if LV_USE_TABVIEW != 0
11
12 #include "lv_btnmatrix.h"
13 #include "../lv_misc/lv_debug.h"
14 #include "../lv_themes/lv_theme.h"
15 #include "../lv_misc/lv_anim.h"
16 #include "../lv_core/lv_disp.h"
17
18 /*********************
19 * DEFINES
20 *********************/
21 #define LV_OBJX_NAME "lv_tabview"
22
23 #if LV_USE_ANIMATION
24 #ifndef LV_TABVIEW_DEF_ANIM_TIME
25 #define LV_TABVIEW_DEF_ANIM_TIME 300 /*Animation time of focusing to the a list element [ms] (0: no animation) */
26 #endif
27 #else
28 #undef LV_TABVIEW_DEF_ANIM_TIME
29 #define LV_TABVIEW_DEF_ANIM_TIME 0 /*No animations*/
30 #endif
31
32 /**********************
33 * TYPEDEFS
34 **********************/
35
36 /**********************
37 * STATIC PROTOTYPES
38 **********************/
39 static lv_res_t lv_tabview_signal(lv_obj_t * tabview, lv_signal_t sign, void * param);
40 static lv_res_t tabview_scrl_signal(lv_obj_t * tabview_scrl, lv_signal_t sign, void * param);
41 static lv_style_list_t * lv_tabview_get_style(lv_obj_t * tabview, uint8_t part);
42
43 static void tab_btnm_event_cb(lv_obj_t * tab_btnm, lv_event_t event);
44 static void tabview_realign(lv_obj_t * tabview);
45 static void refr_indic_size(lv_obj_t * tabview);
46 static void refr_btns_size(lv_obj_t * tabview);
47 static void refr_content_size(lv_obj_t * tabview);
48 static void refr_align(lv_obj_t * tabview);
49
50 /**********************
51 * STATIC VARIABLES
52 **********************/
53 static lv_signal_cb_t ancestor_signal;
54 static lv_signal_cb_t ancestor_scrl_signal;
55 static lv_signal_cb_t page_signal;
56 static const char * tab_def[] = {""};
57
58 /**********************
59 * MACROS
60 **********************/
61
62 /**********************
63 * GLOBAL FUNCTIONS
64 **********************/
65
66 /**
67 * Create a Tab view object
68 * @param par pointer to an object, it will be the parent of the new tab
69 * @param copy pointer to a tab object, if not NULL then the new object will be copied from it
70 * @return pointer to the created tab
71 */
lv_tabview_create(lv_obj_t * par,const lv_obj_t * copy)72 lv_obj_t * lv_tabview_create(lv_obj_t * par, const lv_obj_t * copy)
73 {
74 LV_LOG_TRACE("tab view create started");
75
76 /*Create the ancestor of tab*/
77 lv_obj_t * tabview = lv_obj_create(par, copy);
78 LV_ASSERT_MEM(tabview);
79 if(tabview == NULL) return NULL;
80 if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(tabview);
81
82 /*Allocate the tab type specific extended data*/
83 lv_tabview_ext_t * ext = lv_obj_allocate_ext_attr(tabview, sizeof(lv_tabview_ext_t));
84 LV_ASSERT_MEM(ext);
85 if(ext == NULL) {
86 lv_obj_del(tabview);
87 return NULL;
88 }
89
90 /*Initialize the allocated 'ext' */
91 ext->tab_cur = 0;
92 ext->tab_cnt = 0;
93 ext->point_last.x = 0;
94 ext->point_last.y = 0;
95 ext->content = NULL;
96 ext->indic = NULL;
97 ext->btns = NULL;
98 ext->btns_pos = LV_TABVIEW_TAB_POS_TOP;
99 #if LV_USE_ANIMATION
100 ext->anim_time = LV_TABVIEW_DEF_ANIM_TIME;
101 #endif
102
103 /*The signal and design functions are not copied so set them here*/
104 lv_obj_set_signal_cb(tabview, lv_tabview_signal);
105 /*Init the new tab tab*/
106 if(copy == NULL) {
107 ext->tab_name_ptr = lv_mem_alloc(sizeof(char *));
108 LV_ASSERT_MEM(ext->tab_name_ptr);
109 if(ext->tab_name_ptr == NULL) return NULL;
110 ext->tab_name_ptr[0] = "";
111
112 /* Set a size which fits into the parent.
113 * Don't use `par` directly because if the tabview is created on a page it is moved to the
114 * scrollable so the parent has changed */
115 lv_coord_t w;
116 lv_coord_t h;
117 if(par) {
118 w = lv_obj_get_width_fit(lv_obj_get_parent(tabview));
119 h = lv_obj_get_height_fit(lv_obj_get_parent(tabview));
120 }
121 else {
122 w = lv_disp_get_hor_res(NULL);
123 h = lv_disp_get_ver_res(NULL);
124 }
125
126 lv_obj_set_size(tabview, w, h);
127
128 ext->content = lv_page_create(tabview, NULL);
129 ext->btns = lv_btnmatrix_create(tabview, NULL);
130 ext->indic = lv_obj_create(ext->btns, NULL);
131
132 if(ancestor_scrl_signal == NULL) ancestor_scrl_signal = lv_obj_get_signal_cb(lv_page_get_scrollable(ext->content));
133 lv_obj_set_signal_cb(lv_page_get_scrollable(ext->content), tabview_scrl_signal);
134
135 lv_btnmatrix_set_map(ext->btns, tab_def);
136 lv_obj_set_event_cb(ext->btns, tab_btnm_event_cb);
137
138 lv_obj_set_click(ext->indic, false);
139 lv_obj_set_drag_dir(lv_page_get_scrollable(ext->content), LV_DRAG_DIR_ONE);
140
141 lv_page_set_scrollable_fit2(ext->content, LV_FIT_TIGHT, LV_FIT_PARENT);
142 lv_page_set_scrl_layout(ext->content, LV_LAYOUT_ROW_TOP);
143 lv_page_set_scrollbar_mode(ext->content, LV_SCROLLBAR_MODE_OFF);
144
145 lv_obj_clean_style_list(ext->content, LV_PAGE_PART_BG);
146
147 lv_theme_apply(tabview, LV_THEME_TABVIEW);
148
149 }
150 /*Copy an existing tab view*/
151 else {
152 lv_tabview_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
153 ext->point_last.x = 0;
154 ext->point_last.y = 0;
155 ext->btns = lv_btnmatrix_create(tabview, copy_ext->btns);
156 ext->indic = lv_obj_create(ext->btns, copy_ext->indic);
157 ext->content = lv_page_create(tabview, copy_ext->content);
158 #if LV_USE_ANIMATION
159 ext->anim_time = copy_ext->anim_time;
160 #endif
161
162 ext->tab_name_ptr = lv_mem_alloc(sizeof(char *));
163 LV_ASSERT_MEM(ext->tab_name_ptr);
164 if(ext->tab_name_ptr == NULL) return NULL;
165 ext->tab_name_ptr[0] = "";
166 lv_btnmatrix_set_map(ext->btns, ext->tab_name_ptr);
167
168 lv_style_list_copy(lv_obj_get_style_list(tabview, LV_TABVIEW_PART_BG_SCRLLABLE), lv_obj_get_style_list(copy,
169 LV_TABVIEW_PART_BG_SCRLLABLE));
170 lv_style_list_copy(lv_obj_get_style_list(tabview, LV_TABVIEW_PART_TAB_BG), lv_obj_get_style_list(copy,
171 LV_TABVIEW_PART_TAB_BG));
172 lv_style_list_copy(lv_obj_get_style_list(tabview, LV_TABVIEW_PART_TAB_BTN), lv_obj_get_style_list(copy,
173 LV_TABVIEW_PART_TAB_BTN));
174
175 uint16_t i;
176 for(i = 0; i < copy_ext->tab_cnt; i++) {
177 lv_obj_t * new_tab = lv_tabview_add_tab(tabview, copy_ext->tab_name_ptr[i]);
178 lv_obj_t * copy_tab = lv_tabview_get_tab(copy, i);
179 lv_style_list_copy(lv_obj_get_style_list(new_tab, LV_PAGE_PART_SCROLLABLE), lv_obj_get_style_list(copy_tab,
180 LV_PAGE_PART_SCROLLABLE));
181 lv_style_list_copy(lv_obj_get_style_list(new_tab, LV_PAGE_PART_SCROLLBAR), lv_obj_get_style_list(copy_tab,
182 LV_PAGE_PART_SCROLLBAR));
183 lv_obj_refresh_style(new_tab, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
184 }
185
186 /*Refresh the style with new signal function*/
187 lv_obj_refresh_style(tabview, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
188 }
189
190 tabview_realign(tabview);
191
192 LV_LOG_INFO("tab view created");
193
194 return tabview;
195 }
196
197 /*======================
198 * Add/remove functions
199 *=====================*/
200
201 /**
202 * Add a new tab with the given name
203 * @param tabview pointer to Tab view object where to ass the new tab
204 * @param name the text on the tab button
205 * @return pointer to the created page object (lv_page). You can create your content here
206 */
lv_tabview_add_tab(lv_obj_t * tabview,const char * name)207 lv_obj_t * lv_tabview_add_tab(lv_obj_t * tabview, const char * name)
208 {
209 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
210 LV_ASSERT_STR(name);
211
212 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
213
214 /*Create the container page*/
215 lv_obj_t * h = lv_page_create(ext->content, NULL);
216 lv_obj_set_size(h, lv_obj_get_width(tabview), lv_obj_get_height(ext->content));
217 lv_page_set_scrollbar_mode(h, LV_SCROLLBAR_MODE_AUTO);
218 lv_page_set_scroll_propagation(h, true);
219 lv_page_set_scrollable_fit4(h, LV_FIT_NONE, LV_FIT_MAX, LV_FIT_NONE, LV_FIT_MAX);
220 lv_theme_apply(h, LV_THEME_TABVIEW_PAGE);
221
222 if(page_signal == NULL) page_signal = lv_obj_get_signal_cb(h);
223
224 /*Extend the button matrix map with the new name*/
225 char * name_dm;
226 name_dm = lv_mem_alloc(strlen(name) + 1); /*+1 for the the closing '\0' */
227 LV_ASSERT_MEM(name_dm);
228 if(name_dm == NULL) return NULL;
229 strcpy(name_dm, name);
230
231 ext->tab_cnt++;
232
233 /* FIXME: It is not possible yet to switch tab button position from/to top/bottom from/to left/right at runtime.
234 * Method: clean extra \n when switch from LV_TABVIEW_BTNS_POS_LEFT or LV_TABVIEW_BTNS_POS_RIGHT
235 * to LV_TABVIEW_BTNS_POS_TOP or LV_TABVIEW_BTNS_POS_BOTTOM.
236 */
237 switch(ext->btns_pos) {
238 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
239 case LV_TABVIEW_TAB_POS_NONE:
240 case LV_TABVIEW_TAB_POS_TOP:
241 case LV_TABVIEW_TAB_POS_BOTTOM:
242 ext->tab_name_ptr = lv_mem_realloc(ext->tab_name_ptr, sizeof(char *) * (ext->tab_cnt + 1));
243
244 LV_ASSERT_MEM(ext->tab_name_ptr);
245 if(ext->tab_name_ptr == NULL) return NULL;
246
247 ext->tab_name_ptr[ext->tab_cnt - 1] = name_dm;
248 ext->tab_name_ptr[ext->tab_cnt] = "";
249
250 break;
251 case LV_TABVIEW_TAB_POS_LEFT:
252 case LV_TABVIEW_TAB_POS_RIGHT:
253 ext->tab_name_ptr = lv_mem_realloc(ext->tab_name_ptr, sizeof(char *) * (ext->tab_cnt * 2));
254
255 LV_ASSERT_MEM(ext->tab_name_ptr);
256 if(ext->tab_name_ptr == NULL) return NULL;
257
258 if(ext->tab_cnt == 1) {
259 ext->tab_name_ptr[0] = name_dm;
260 ext->tab_name_ptr[1] = "";
261 }
262 else {
263 ext->tab_name_ptr[ext->tab_cnt * 2 - 3] = "\n";
264 ext->tab_name_ptr[ext->tab_cnt * 2 - 2] = name_dm;
265 ext->tab_name_ptr[ext->tab_cnt * 2 - 1] = "";
266 }
267 break;
268 }
269
270 /* The button matrix's map still points to the old `tab_name_ptr` which might be freed by
271 * `lv_mem_realloc`. So make its current map invalid*/
272 lv_btnmatrix_ext_t * btnm_ext = lv_obj_get_ext_attr(ext->btns);
273 btnm_ext->map_p = NULL;
274
275 lv_btnmatrix_set_map(ext->btns, ext->tab_name_ptr);
276 lv_btnmatrix_set_btn_ctrl(ext->btns, ext->tab_cur, LV_BTNMATRIX_CTRL_NO_REPEAT);
277
278 /*Set the first btn as active*/
279 if(ext->tab_cnt == 1) ext->tab_cur = 0;
280
281 tabview_realign(tabview); /*Set the size of the pages, tab buttons and indicator*/
282
283 lv_tabview_set_tab_act(tabview, ext->tab_cur, false);
284
285 return h;
286 }
287
288 /**
289 * Delete all children of a tab created by `lv_tabview_add_tab`.
290 * @param tab pointer to a tab
291 */
lv_tabview_clean_tab(lv_obj_t * tab)292 void lv_tabview_clean_tab(lv_obj_t * tab)
293 {
294 LV_ASSERT_OBJ(tab, "lv_page");
295
296 lv_obj_t * scrl = lv_page_get_scrollable(tab);
297 lv_obj_clean(scrl);
298 }
299
300 /*=====================
301 * Setter functions
302 *====================*/
303
304 /**
305 * Set a new tab
306 * @param tabview pointer to Tab view object
307 * @param id index of a tab to load
308 * @param anim LV_ANIM_ON: set the value with an animation; LV_ANIM_OFF: change the value immediately
309 */
lv_tabview_set_tab_act(lv_obj_t * tabview,uint16_t id,lv_anim_enable_t anim)310 void lv_tabview_set_tab_act(lv_obj_t * tabview, uint16_t id, lv_anim_enable_t anim)
311 {
312 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
313
314 #if LV_USE_ANIMATION == 0
315 anim = LV_ANIM_OFF;
316 #endif
317 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
318
319 if(id >= ext->tab_cnt) id = ext->tab_cnt - 1;
320
321 lv_btnmatrix_clear_btn_ctrl(ext->btns, ext->tab_cur, LV_BTNMATRIX_CTRL_CHECK_STATE);
322
323 ext->tab_cur = id;
324
325 if(lv_obj_get_base_dir(tabview) == LV_BIDI_DIR_RTL) {
326 id = (ext->tab_cnt - (id + 1));
327 }
328
329 lv_coord_t cont_x;
330 lv_style_int_t scrl_inner = lv_obj_get_style_pad_inner(ext->content, LV_PAGE_PART_SCROLLABLE);
331 lv_style_int_t scrl_left = lv_obj_get_style_pad_left(ext->content, LV_PAGE_PART_SCROLLABLE);
332
333 switch(ext->btns_pos) {
334 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
335 case LV_TABVIEW_TAB_POS_NONE:
336 case LV_TABVIEW_TAB_POS_TOP:
337 case LV_TABVIEW_TAB_POS_BOTTOM:
338 cont_x = -(lv_obj_get_width(tabview) * id + scrl_inner * id + scrl_left);
339 break;
340 case LV_TABVIEW_TAB_POS_LEFT:
341 case LV_TABVIEW_TAB_POS_RIGHT:
342 cont_x = -((lv_obj_get_width(tabview) - lv_obj_get_width(ext->btns)) * id + scrl_inner * id + scrl_left);
343 break;
344 }
345
346 if(anim == LV_ANIM_OFF || lv_tabview_get_anim_time(tabview) == 0) {
347 lv_obj_set_x(lv_page_get_scrollable(ext->content), cont_x);
348 }
349 #if LV_USE_ANIMATION
350 else {
351 lv_anim_t a;
352 lv_anim_init(&a);
353 lv_anim_set_var(&a, lv_page_get_scrollable(ext->content));
354 lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_x);
355 lv_anim_set_values(&a, lv_obj_get_x(lv_page_get_scrollable(ext->content)), cont_x);
356 lv_anim_set_time(&a, ext->anim_time);
357 lv_anim_start(&a);
358 }
359 #endif
360
361 /*Move the indicator*/
362 lv_coord_t indic_size;
363 lv_coord_t indic_pos = 0; /*silence uninitialized variable warning*/;
364
365 lv_style_int_t btns_bg_inner = 0;
366 lv_style_int_t btns_bg_left = 0;
367 lv_style_int_t btns_bg_top = 0;
368
369 switch(ext->btns_pos) {
370 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
371 case LV_TABVIEW_TAB_POS_NONE:
372 break;
373 case LV_TABVIEW_TAB_POS_TOP:
374 case LV_TABVIEW_TAB_POS_BOTTOM:
375 btns_bg_inner = lv_obj_get_style_pad_inner(tabview, LV_TABVIEW_PART_TAB_BG);
376 btns_bg_left = lv_obj_get_style_pad_left(tabview, LV_TABVIEW_PART_TAB_BG);
377 indic_size = lv_obj_get_width(ext->indic);
378 indic_pos = indic_size * id + btns_bg_inner * id + btns_bg_left;
379 break;
380 case LV_TABVIEW_TAB_POS_LEFT:
381 case LV_TABVIEW_TAB_POS_RIGHT:
382 btns_bg_inner = lv_obj_get_style_pad_inner(tabview, LV_TABVIEW_PART_TAB_BG);
383 btns_bg_top = lv_obj_get_style_pad_top(tabview, LV_TABVIEW_PART_TAB_BG);
384 indic_size = lv_obj_get_height(ext->indic);
385 indic_pos = btns_bg_top + id * (indic_size + btns_bg_inner);
386 break;
387 }
388
389 #if LV_USE_ANIMATION
390 if(anim == LV_ANIM_OFF || ext->anim_time == 0)
391 #endif
392 {
393 switch(ext->btns_pos) {
394 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
395 case LV_TABVIEW_TAB_POS_NONE:
396 break;
397 case LV_TABVIEW_TAB_POS_TOP:
398 case LV_TABVIEW_TAB_POS_BOTTOM:
399 lv_obj_set_x(ext->indic, indic_pos);
400 break;
401 case LV_TABVIEW_TAB_POS_LEFT:
402 case LV_TABVIEW_TAB_POS_RIGHT:
403 lv_obj_set_y(ext->indic, indic_pos);
404 break;
405 }
406 }
407 #if LV_USE_ANIMATION
408 else {
409 lv_anim_t a;
410 lv_anim_init(&a);
411 lv_anim_set_var(&a, ext->indic);
412 lv_anim_set_time(&a, ext->anim_time);
413
414 switch(ext->btns_pos) {
415 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
416 case LV_TABVIEW_TAB_POS_NONE:
417 break;
418 case LV_TABVIEW_TAB_POS_TOP:
419 case LV_TABVIEW_TAB_POS_BOTTOM:
420 lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_x);
421 lv_anim_set_values(&a, lv_obj_get_x(ext->indic), indic_pos);
422 break;
423 case LV_TABVIEW_TAB_POS_LEFT:
424 case LV_TABVIEW_TAB_POS_RIGHT:
425 lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_y);
426 lv_anim_set_values(&a, lv_obj_get_y(ext->indic), indic_pos);
427 break;
428 }
429
430 lv_anim_start(&a);
431 }
432 #endif
433
434 lv_btnmatrix_set_btn_ctrl(ext->btns, ext->tab_cur, LV_BTNMATRIX_CTRL_CHECK_STATE);
435 }
436
437 /**
438 * Set the name of a tab.
439 * @param tabview pointer to Tab view object
440 * @param id index of the tab the name should be set
441 * @param name new tab name
442 */
lv_tabview_set_tab_name(lv_obj_t * tabview,uint16_t id,char * name)443 void lv_tabview_set_tab_name(lv_obj_t * tabview, uint16_t id, char * name)
444 {
445 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
446
447 /* get tabview's ext pointer which contains the tab name pointer list */
448 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
449
450 /* check for valid tab index */
451 if(ext->tab_cnt > id) {
452 /* reallocate memory for new tab name (use reallocate due to mostly the size didn't change much) */
453 char * str = lv_mem_realloc((void *)ext->tab_name_ptr[id], strlen(name) + 1);
454 LV_ASSERT_MEM(str);
455
456 /* store new tab name at allocated memory */
457 strcpy(str, name);
458 /* update pointer */
459 ext->tab_name_ptr[id] = str;
460
461 /* force redrawing of the tab headers */
462 lv_obj_invalidate(ext->btns);
463 }
464 }
465
466 /**
467 * Set the animation time of tab view when a new tab is loaded
468 * @param tabview pointer to Tab view object
469 * @param anim_time_ms time of animation in milliseconds
470 */
lv_tabview_set_anim_time(lv_obj_t * tabview,uint16_t anim_time)471 void lv_tabview_set_anim_time(lv_obj_t * tabview, uint16_t anim_time)
472 {
473 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
474
475 #if LV_USE_ANIMATION
476 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
477 ext->anim_time = anim_time;
478 #else
479 (void)tabview;
480 (void)anim_time;
481 #endif
482 }
483
484 /**
485 * Set the position of tab select buttons
486 * @param tabview pointer to a tan view object
487 * @param btns_pos which button position
488 */
lv_tabview_set_btns_pos(lv_obj_t * tabview,lv_tabview_btns_pos_t btns_pos)489 void lv_tabview_set_btns_pos(lv_obj_t * tabview, lv_tabview_btns_pos_t btns_pos)
490 {
491 if(btns_pos != LV_TABVIEW_TAB_POS_NONE &&
492 btns_pos != LV_TABVIEW_TAB_POS_TOP &&
493 btns_pos != LV_TABVIEW_TAB_POS_BOTTOM &&
494 btns_pos != LV_TABVIEW_TAB_POS_LEFT &&
495 btns_pos != LV_TABVIEW_TAB_POS_RIGHT) {
496 LV_LOG_WARN("lv_tabview_set_btns_pos: unexpected button position");
497 return;
498 }
499 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
500
501 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
502
503 ext->btns_pos = btns_pos;
504 tabview_realign(tabview);
505 }
506
507 /*=====================
508 * Getter functions
509 *====================*/
510
511 /**
512 * Get the index of the currently active tab
513 * @param tabview pointer to Tab view object
514 * @return the active btn index
515 */
lv_tabview_get_tab_act(const lv_obj_t * tabview)516 uint16_t lv_tabview_get_tab_act(const lv_obj_t * tabview)
517 {
518 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
519
520 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
521 return ext->tab_cur;
522 }
523
524 /**
525 * Get the number of tabs
526 * @param tabview pointer to Tab view object
527 * @return btn count
528 */
lv_tabview_get_tab_count(const lv_obj_t * tabview)529 uint16_t lv_tabview_get_tab_count(const lv_obj_t * tabview)
530 {
531 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
532
533 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
534 return ext->tab_cnt;
535 }
536
537 /**
538 * Get the page (content area) of a tab
539 * @param tabview pointer to Tab view object
540 * @param id index of the btn (>= 0)
541 * @return pointer to page (lv_page) object
542 */
lv_tabview_get_tab(const lv_obj_t * tabview,uint16_t id)543 lv_obj_t * lv_tabview_get_tab(const lv_obj_t * tabview, uint16_t id)
544 {
545 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
546
547 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
548 lv_obj_t * content_scrl = lv_page_get_scrollable(ext->content);
549 uint16_t i = 0;
550 lv_obj_t * page = lv_obj_get_child_back(content_scrl, NULL);
551
552 while(page != NULL && i != id) {
553 if(lv_obj_get_signal_cb(page) == page_signal) i++;
554 page = lv_obj_get_child_back(content_scrl, page);
555 }
556
557 if(i == id) return page;
558
559 return NULL;
560 }
561
562 /**
563 * Get the animation time of tab view when a new tab is loaded
564 * @param tabview pointer to Tab view object
565 * @return time of animation in milliseconds
566 */
lv_tabview_get_anim_time(const lv_obj_t * tabview)567 uint16_t lv_tabview_get_anim_time(const lv_obj_t * tabview)
568 {
569 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
570
571 #if LV_USE_ANIMATION
572 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
573 return ext->anim_time;
574 #else
575 (void)tabview;
576 return 0;
577 #endif
578 }
579
580 /**
581 * Get position of tab select buttons
582 * @param tabview pointer to a ab view object
583 */
lv_tabview_get_btns_pos(const lv_obj_t * tabview)584 lv_tabview_btns_pos_t lv_tabview_get_btns_pos(const lv_obj_t * tabview)
585 {
586 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
587
588 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
589 return ext->btns_pos;
590 }
591
592 /**********************
593 * STATIC FUNCTIONS
594 **********************/
595
596 /**
597 * Signal function of the Tab view
598 * @param tabview pointer to a Tab view object
599 * @param sign a signal type from lv_signal_t enum
600 * @param param pointer to a signal specific variable
601 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
602 */
lv_tabview_signal(lv_obj_t * tabview,lv_signal_t sign,void * param)603 static lv_res_t lv_tabview_signal(lv_obj_t * tabview, lv_signal_t sign, void * param)
604 {
605 lv_res_t res;
606 if(sign == LV_SIGNAL_GET_STYLE) {
607 lv_get_style_info_t * info = param;
608 info->result = lv_tabview_get_style(tabview, info->part);
609 if(info->result != NULL) return LV_RES_OK;
610 else return ancestor_signal(tabview, sign, param);
611 }
612 else if(sign == LV_SIGNAL_GET_STATE_DSC) {
613 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
614 lv_get_state_info_t * info = param;
615 if(info->part == LV_TABVIEW_PART_TAB_BG) info->result = lv_obj_get_state(ext->btns, LV_BTNMATRIX_PART_BG);
616 else if(info->part == LV_TABVIEW_PART_TAB_BTN) info->result = lv_obj_get_state(ext->btns, LV_BTNMATRIX_PART_BTN);
617 else if(info->part == LV_TABVIEW_PART_INDIC) info->result = lv_obj_get_state(ext->indic, LV_OBJ_PART_MAIN);
618 else if(info->part == LV_TABVIEW_PART_BG_SCRLLABLE) info->result = lv_obj_get_state(ext->content,
619 LV_PAGE_PART_SCROLLABLE);
620 return LV_RES_OK;
621 }
622
623 /* Include the ancient signal function */
624 res = ancestor_signal(tabview, sign, param);
625 if(res != LV_RES_OK) return res;
626 if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
627
628 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
629 if(sign == LV_SIGNAL_CLEANUP) {
630 uint8_t i;
631 for(i = 0; ext->tab_name_ptr[i][0] != '\0' && ext->tab_name_ptr[i][0] != '\n'; i++) lv_mem_free(ext->tab_name_ptr[i]);
632
633 lv_mem_free(ext->tab_name_ptr);
634 ext->tab_name_ptr = NULL;
635 ext->btns = NULL; /*These objects were children so they are already invalid*/
636 ext->content = NULL;
637 }
638 else if(sign == LV_SIGNAL_STYLE_CHG) {
639 /*Be sure the buttons are updated because correct button size is required in `tabview_realign`*/
640 lv_signal_send(ext->btns, LV_SIGNAL_STYLE_CHG, NULL);
641
642 tabview_realign(tabview);
643 }
644 else if(sign == LV_SIGNAL_COORD_CHG) {
645 if(ext->content != NULL && (lv_obj_get_width(tabview) != lv_area_get_width(param) ||
646 lv_obj_get_height(tabview) != lv_area_get_height(param))) {
647 tabview_realign(tabview);
648 }
649 }
650 else if(sign == LV_SIGNAL_RELEASED) {
651 #if LV_USE_GROUP
652 /*If released by a KEYPAD or ENCODER then really the tab buttons should be released.
653 * So simulate a CLICK on the tab buttons*/
654 lv_indev_t * indev = lv_indev_get_act();
655 lv_indev_type_t indev_type = lv_indev_get_type(indev);
656 if(indev_type == LV_INDEV_TYPE_KEYPAD ||
657 (indev_type == LV_INDEV_TYPE_ENCODER && lv_group_get_editing(lv_obj_get_group(tabview)))) {
658 lv_event_send(ext->btns, LV_EVENT_CLICKED, lv_event_get_data());
659 }
660 #endif
661 }
662 else if(sign == LV_SIGNAL_GET_EDITABLE) {
663 #if LV_USE_GROUP
664 bool * editable = (bool *)param;
665 *editable = true;
666 #endif
667 }
668
669 if(sign == LV_SIGNAL_FOCUS || sign == LV_SIGNAL_DEFOCUS ||
670 #if LV_USE_GROUP
671 sign == LV_SIGNAL_CONTROL ||
672 #endif
673 sign == LV_SIGNAL_PRESSED || sign == LV_SIGNAL_RELEASED) {
674
675 /* The button matrix is not in a group (the tab view is in it) but it should handle the
676 * group signals. So propagate the related signals to the button matrix manually*/
677 ext->btns->signal_cb(ext->btns, sign, param);
678
679 /*Make the active tab's button focused*/
680 if(sign == LV_SIGNAL_FOCUS) {
681 lv_btnmatrix_set_focused_btn(ext->btns, ext->tab_cur);
682 }
683
684 if(sign == LV_SIGNAL_FOCUS || sign == LV_SIGNAL_DEFOCUS) {
685 lv_state_t state = lv_obj_get_state(tabview, LV_TABVIEW_PART_BG);
686 if(state & LV_STATE_FOCUSED) {
687 lv_obj_set_state(ext->btns, LV_STATE_FOCUSED);
688 lv_obj_set_state(ext->indic, LV_STATE_FOCUSED);
689 }
690 else {
691 lv_obj_clear_state(ext->btns, LV_STATE_FOCUSED);
692 lv_obj_clear_state(ext->indic, LV_STATE_FOCUSED);
693
694 }
695 if(state & LV_STATE_EDITED) {
696 lv_obj_set_state(ext->btns, LV_STATE_EDITED);
697 lv_obj_set_state(ext->indic, LV_STATE_EDITED);
698 }
699 else {
700 lv_obj_clear_state(ext->btns, LV_STATE_EDITED);
701 lv_obj_clear_state(ext->indic, LV_STATE_EDITED);
702
703 }
704 }
705 }
706
707 return res;
708 }
709
710 /**
711 * Signal function of a tab views main scrollable area
712 * @param tab pointer to a tab page object
713 * @param sign a signal type from lv_signal_t enum
714 * @param param pointer to a signal specific variable
715 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
716 */
tabview_scrl_signal(lv_obj_t * tabview_scrl,lv_signal_t sign,void * param)717 static lv_res_t tabview_scrl_signal(lv_obj_t * tabview_scrl, lv_signal_t sign, void * param)
718 {
719 lv_res_t res;
720
721 /* Include the ancient signal function */
722 res = ancestor_scrl_signal(tabview_scrl, sign, param);
723 if(res != LV_RES_OK) return res;
724 if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, "");
725
726 lv_obj_t * cont = lv_obj_get_parent(tabview_scrl);
727 lv_obj_t * tabview = lv_obj_get_parent(cont);
728
729 if(sign == LV_SIGNAL_DRAG_THROW_BEGIN) {
730 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
731
732 lv_indev_t * indev = lv_indev_get_act();
733 lv_point_t point_act;
734 lv_indev_get_point(indev, &point_act);
735 lv_point_t vect;
736 lv_indev_get_vect(indev, &vect);
737 lv_coord_t x_predict = 0;
738
739 while(vect.x != 0) {
740 x_predict += vect.x;
741 vect.x = vect.x * (100 - LV_INDEV_DEF_DRAG_THROW) / 100;
742 }
743
744 res = lv_indev_finish_drag(indev);
745 if(res != LV_RES_OK) return res;
746 lv_obj_t * tab_page = lv_tabview_get_tab(tabview, ext->tab_cur);
747 if(tab_page == NULL) return LV_RES_OK;
748 lv_coord_t page_x1 = tab_page->coords.x1 - tabview->coords.x1 + x_predict;
749 lv_coord_t page_x2 = page_x1 + lv_obj_get_width(tabview);
750 lv_coord_t treshold = lv_obj_get_width(tabview) / 2;
751
752 lv_bidi_dir_t base_dir = lv_obj_get_base_dir(tabview);
753 int16_t tab_cur = ext->tab_cur;
754 if(page_x1 > treshold) {
755 if(base_dir != LV_BIDI_DIR_RTL) tab_cur--;
756 else tab_cur ++;
757 }
758 else if(page_x2 < treshold) {
759 if(base_dir != LV_BIDI_DIR_RTL) tab_cur++;
760 else tab_cur --;
761 }
762
763 if(tab_cur > ext->tab_cnt - 1) tab_cur = ext->tab_cnt - 1;
764 if(tab_cur < 0) tab_cur = 0;
765
766 uint32_t id_prev = lv_tabview_get_tab_act(tabview);
767 lv_tabview_set_tab_act(tabview, tab_cur, LV_ANIM_ON);
768 uint32_t id_new = lv_tabview_get_tab_act(tabview);
769
770 if(id_prev != id_new) res = lv_event_send(tabview, LV_EVENT_VALUE_CHANGED, &id_prev);
771 if(res != LV_RES_OK) return res;
772
773 }
774 return res;
775 }
776
777 /**
778 * Get the style descriptor of a part of the object
779 * @param page pointer the object
780 * @param part the part from `lv_tabview_part_t`. (LV_TABVIEW_PART_...)
781 * @return pointer to the style descriptor of the specified part
782 */
lv_tabview_get_style(lv_obj_t * tabview,uint8_t part)783 static lv_style_list_t * lv_tabview_get_style(lv_obj_t * tabview, uint8_t part)
784 {
785 LV_ASSERT_OBJ(tabview, LV_OBJX_NAME);
786
787 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
788 lv_style_list_t * style_dsc_p;
789
790 switch(part) {
791 case LV_TABVIEW_PART_BG:
792 style_dsc_p = &tabview->style_list;
793 break;
794 case LV_TABVIEW_PART_BG_SCRLLABLE:
795 style_dsc_p = lv_obj_get_style_list(ext->content, LV_PAGE_PART_SCROLLABLE);
796 break;
797 case LV_TABVIEW_PART_TAB_BG:
798 style_dsc_p = lv_obj_get_style_list(ext->btns, LV_BTNMATRIX_PART_BG);
799 break;
800 case LV_TABVIEW_PART_TAB_BTN:
801 style_dsc_p = lv_obj_get_style_list(ext->btns, LV_BTNMATRIX_PART_BTN);
802 break;
803 case LV_TABVIEW_PART_INDIC:
804 style_dsc_p = lv_obj_get_style_list(ext->indic, LV_OBJ_PART_MAIN);
805 break;
806 default:
807 style_dsc_p = NULL;
808 }
809
810 return style_dsc_p;
811 }
812
813
814 /**
815 * Called when a tab button is clicked
816 * @param tab_btnm pointer to the tab's button matrix object
817 * @param event type of the event
818 */
tab_btnm_event_cb(lv_obj_t * tab_btnm,lv_event_t event)819 static void tab_btnm_event_cb(lv_obj_t * tab_btnm, lv_event_t event)
820 {
821 if(event != LV_EVENT_CLICKED) return;
822
823 uint16_t btn_id = lv_btnmatrix_get_active_btn(tab_btnm);
824 if(btn_id == LV_BTNMATRIX_BTN_NONE) return;
825
826 if(lv_btnmatrix_get_btn_ctrl(tab_btnm, btn_id, LV_BTNMATRIX_CTRL_DISABLED)) return;
827
828 lv_btnmatrix_clear_btn_ctrl_all(tab_btnm, LV_BTNMATRIX_CTRL_CHECK_STATE);
829 lv_btnmatrix_set_btn_ctrl(tab_btnm, btn_id, LV_BTNMATRIX_CTRL_CHECK_STATE);
830
831 lv_obj_t * tabview = lv_obj_get_parent(tab_btnm);
832
833 uint32_t id_prev = lv_tabview_get_tab_act(tabview);
834 lv_tabview_set_tab_act(tabview, btn_id, LV_ANIM_ON);
835 uint32_t id_new = lv_tabview_get_tab_act(tabview);
836
837 lv_res_t res = LV_RES_OK;
838 if(id_prev != id_new) res = lv_event_send(tabview, LV_EVENT_VALUE_CHANGED, &id_new);
839
840 #if LV_USE_GROUP
841 if(lv_indev_get_type(lv_indev_get_act()) == LV_INDEV_TYPE_ENCODER) {
842 lv_group_set_editing(lv_obj_get_group(tabview), false);
843 }
844 #endif
845
846 if(res != LV_RES_OK) return;
847 }
848
849 /**
850 * Realign and resize the elements of Tab view
851 * @param tabview pointer to a Tab view object
852 */
tabview_realign(lv_obj_t * tabview)853 static void tabview_realign(lv_obj_t * tabview)
854 {
855 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
856
857 refr_btns_size(tabview);
858 refr_content_size(tabview);
859 refr_indic_size(tabview);
860
861 refr_align(tabview);
862
863 lv_tabview_set_tab_act(tabview, ext->tab_cur, LV_ANIM_OFF);
864 }
865
refr_indic_size(lv_obj_t * tabview)866 static void refr_indic_size(lv_obj_t * tabview)
867 {
868 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
869 lv_btnmatrix_ext_t * btnm_ext = lv_obj_get_ext_attr(ext->btns);
870
871 lv_coord_t indic_size = lv_obj_get_style_size(tabview, LV_TABVIEW_PART_INDIC);
872
873 /*Set the indicator width/height*/
874 lv_coord_t indic_w;
875 lv_coord_t indic_h;
876
877 switch(ext->btns_pos) {
878 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
879 case LV_TABVIEW_TAB_POS_NONE:
880 lv_obj_set_hidden(ext->indic, true);
881 indic_w = 0;
882 indic_h = 0;
883 break;
884 case LV_TABVIEW_TAB_POS_TOP:
885 case LV_TABVIEW_TAB_POS_BOTTOM:
886 lv_obj_set_hidden(ext->indic, false);
887 if(ext->tab_cnt) {
888 indic_h = indic_size;
889 indic_w = lv_area_get_width(&btnm_ext->button_areas[0]);
890 }
891 else {
892 indic_w = 0;
893 indic_h = 0;
894 }
895 break;
896 case LV_TABVIEW_TAB_POS_LEFT:
897 case LV_TABVIEW_TAB_POS_RIGHT:
898 lv_obj_set_hidden(ext->indic, false);
899 if(ext->tab_cnt) {
900 indic_w = indic_size;
901 indic_h = lv_area_get_height(&btnm_ext->button_areas[0]);
902 }
903 else {
904 indic_w = 0;
905 indic_h = 0;
906 }
907 break;
908 }
909
910 lv_obj_set_width(ext->indic, indic_w);
911 lv_obj_set_height(ext->indic, indic_h);
912 }
913
914
refr_btns_size(lv_obj_t * tabview)915 static void refr_btns_size(lv_obj_t * tabview)
916 {
917 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
918
919 lv_style_int_t tab_bg_left = lv_obj_get_style_pad_left(tabview, LV_TABVIEW_PART_TAB_BG);
920 lv_style_int_t tab_bg_right = lv_obj_get_style_pad_right(tabview, LV_TABVIEW_PART_TAB_BG);
921 lv_style_int_t tab_bg_top = lv_obj_get_style_pad_top(tabview, LV_TABVIEW_PART_TAB_BG);
922 lv_style_int_t tab_bg_bottom = lv_obj_get_style_pad_bottom(tabview, LV_TABVIEW_PART_TAB_BG);
923
924 lv_style_int_t tab_left = lv_obj_get_style_pad_left(tabview, LV_TABVIEW_PART_TAB_BTN);
925 lv_style_int_t tab_right = lv_obj_get_style_pad_right(tabview, LV_TABVIEW_PART_TAB_BTN);
926 lv_style_int_t tab_top = lv_obj_get_style_pad_top(tabview, LV_TABVIEW_PART_TAB_BTN);
927 lv_style_int_t tab_bottom = lv_obj_get_style_pad_bottom(tabview, LV_TABVIEW_PART_TAB_BTN);
928
929 const lv_font_t * font = lv_obj_get_style_text_font(tabview, LV_TABVIEW_PART_TAB_BTN);
930
931 /*Set the tabs height/width*/
932 lv_coord_t btns_w;
933 lv_coord_t btns_h;
934
935 switch(ext->btns_pos) {
936 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
937 case LV_TABVIEW_TAB_POS_NONE:
938 btns_w = 0;
939 btns_h = 0;
940 lv_obj_set_hidden(ext->btns, true);
941 break;
942 case LV_TABVIEW_TAB_POS_TOP:
943 case LV_TABVIEW_TAB_POS_BOTTOM:
944 lv_obj_set_hidden(ext->btns, false);
945 btns_h = lv_font_get_line_height(font) + tab_top + tab_bottom + tab_bg_top + tab_bg_bottom;
946 btns_w = lv_obj_get_width(tabview);
947
948 break;
949 case LV_TABVIEW_TAB_POS_LEFT:
950 case LV_TABVIEW_TAB_POS_RIGHT:
951 lv_obj_set_hidden(ext->btns, false);
952 btns_w = lv_font_get_glyph_width(font, 'A', '\0') +
953 tab_left + tab_right + tab_bg_left + tab_bg_right;
954 btns_h = lv_obj_get_height(tabview);
955 break;
956 }
957
958 lv_obj_set_size(ext->btns, btns_w, btns_h);
959 }
960
refr_content_size(lv_obj_t * tabview)961 static void refr_content_size(lv_obj_t * tabview)
962 {
963 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
964 lv_coord_t cont_w;
965 lv_coord_t cont_h;
966
967 switch(ext->btns_pos) {
968 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
969 case LV_TABVIEW_TAB_POS_NONE:
970 cont_w = lv_obj_get_width(tabview);
971 cont_h = lv_obj_get_height(tabview);
972 break;
973 case LV_TABVIEW_TAB_POS_TOP:
974 case LV_TABVIEW_TAB_POS_BOTTOM:
975 cont_w = lv_obj_get_width(tabview);
976 cont_h = lv_obj_get_height(tabview) - lv_obj_get_height(ext->btns);
977 break;
978 case LV_TABVIEW_TAB_POS_LEFT:
979 case LV_TABVIEW_TAB_POS_RIGHT:
980 cont_w = lv_obj_get_width(tabview) - lv_obj_get_width(ext->btns);
981 cont_h = lv_obj_get_height(tabview);
982 break;
983 }
984
985 lv_obj_set_size(ext->content, cont_w, cont_h);
986
987 /*Refresh the size of the tab pages too. `ext->content` has a layout to align the pages*/
988 lv_style_int_t bg_top = lv_obj_get_style_pad_top(tabview, LV_TABVIEW_PART_BG_SCRLLABLE);
989 lv_style_int_t bg_bottom = lv_obj_get_style_pad_bottom(tabview, LV_TABVIEW_PART_BG_SCRLLABLE);
990 cont_h -= bg_top + bg_bottom;
991 lv_obj_t * content_scrl = lv_page_get_scrollable(ext->content);
992 lv_obj_t * pages = lv_obj_get_child(content_scrl, NULL);
993 while(pages != NULL) {
994 /*Be sure adjust only the pages (user can other things)*/
995 if(lv_obj_get_signal_cb(pages) == page_signal) {
996 lv_obj_set_size(pages, cont_w, cont_h);
997 }
998 pages = lv_obj_get_child(content_scrl, pages);
999 }
1000 }
1001
refr_align(lv_obj_t * tabview)1002 static void refr_align(lv_obj_t * tabview)
1003 {
1004 lv_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
1005
1006 switch(ext->btns_pos) {
1007 default: /*default case is prevented in lv_tabview_set_btns_pos(), but here for safety*/
1008 case LV_TABVIEW_TAB_POS_NONE:
1009 lv_obj_align(ext->content, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 0);
1010 break;
1011 case LV_TABVIEW_TAB_POS_TOP:
1012 lv_obj_align(ext->btns, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 0);
1013 lv_obj_align(ext->content, ext->btns, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
1014 lv_obj_align(ext->indic, ext->btns, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
1015 break;
1016 case LV_TABVIEW_TAB_POS_BOTTOM:
1017 lv_obj_align(ext->content, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 0);
1018 lv_obj_align(ext->btns, ext->content, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
1019 lv_obj_align(ext->indic, ext->btns, LV_ALIGN_IN_TOP_LEFT, 0, 0);
1020 break;
1021 case LV_TABVIEW_TAB_POS_LEFT:
1022 lv_obj_align(ext->btns, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 0);
1023 lv_obj_align(ext->content, tabview, LV_ALIGN_IN_TOP_LEFT, lv_obj_get_width(ext->btns), 0);
1024 lv_obj_align(ext->indic, ext->btns, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
1025 break;
1026 case LV_TABVIEW_TAB_POS_RIGHT:
1027 lv_obj_align(ext->btns, NULL, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
1028 lv_obj_align(ext->content, tabview, LV_ALIGN_IN_TOP_LEFT, 0, 0);
1029 lv_obj_align(ext->indic, ext->btns, LV_ALIGN_IN_TOP_LEFT, 0, 0);
1030 break;
1031 }
1032 }
1033 #endif
1034