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