1 /**
2  * @file lv_tabview.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_tabview_private.h"
10 #include "../../core/lv_obj_class_private.h"
11 #include "../../lvgl.h"
12 
13 #if LV_USE_TABVIEW
14 
15 #include "../../misc/lv_assert.h"
16 #include "../../indev/lv_indev_private.h"
17 
18 /*********************
19  *      DEFINES
20  *********************/
21 #define MY_CLASS (&lv_tabview_class)
22 
23 /**********************
24  *      TYPEDEFS
25  **********************/
26 
27 /**********************
28  *  STATIC PROTOTYPES
29  **********************/
30 static void lv_tabview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
31 static void lv_tabview_event(const lv_obj_class_t * class_p, lv_event_t * e);
32 static void button_clicked_event_cb(lv_event_t * e);
33 static void cont_scroll_end_event_cb(lv_event_t * e);
34 
35 /**********************
36  *  STATIC VARIABLES
37  **********************/
38 const lv_obj_class_t lv_tabview_class = {
39     .constructor_cb = lv_tabview_constructor,
40     .event_cb = lv_tabview_event,
41     .width_def = LV_PCT(100),
42     .height_def = LV_PCT(100),
43     .base_class = &lv_obj_class,
44     .instance_size = sizeof(lv_tabview_t),
45     .name = "tabview",
46 };
47 
48 typedef struct {
49     lv_dir_t tab_pos;
50     int32_t tab_size;
51 } lv_tabview_create_info_t;
52 
53 /**********************
54  *      MACROS
55  **********************/
56 
57 /**********************
58  *   GLOBAL FUNCTIONS
59  **********************/
60 
lv_tabview_create(lv_obj_t * parent)61 lv_obj_t * lv_tabview_create(lv_obj_t * parent)
62 {
63     LV_LOG_INFO("begin");
64 
65     lv_obj_t * obj = lv_obj_class_create_obj(&lv_tabview_class, parent);
66     lv_obj_class_init_obj(obj);
67     return obj;
68 }
69 
lv_tabview_add_tab(lv_obj_t * obj,const char * name)70 lv_obj_t * lv_tabview_add_tab(lv_obj_t * obj, const char * name)
71 {
72     LV_ASSERT_OBJ(obj, MY_CLASS);
73     lv_obj_t * cont = lv_tabview_get_content(obj);
74 
75     lv_obj_t * page = lv_obj_create(cont);
76     lv_obj_set_size(page, lv_pct(100), lv_pct(100));
77 
78     lv_obj_t * tab_bar = lv_tabview_get_tab_bar(obj);
79 
80     lv_obj_t * button = lv_button_create(tab_bar);
81     lv_obj_set_flex_grow(button, 1);
82     lv_obj_set_size(button, lv_pct(100), lv_pct(100));
83     lv_obj_add_event_cb(button, button_clicked_event_cb, LV_EVENT_CLICKED, NULL);
84     lv_group_t * g = lv_group_get_default();
85     if(g) lv_group_add_obj(g, button);
86 
87     lv_obj_t * label = lv_label_create(button);
88     lv_label_set_text(label, name);
89     lv_obj_center(label);
90 
91     uint32_t tab_idx = lv_obj_get_child_count(cont) - 1;
92     lv_tabview_t * tabview = (lv_tabview_t *)obj;
93     if(tab_idx == tabview->tab_cur) {
94         lv_tabview_set_active(obj, tab_idx, LV_ANIM_OFF);
95     }
96 
97     return page;
98 }
99 
lv_tabview_rename_tab(lv_obj_t * obj,uint32_t idx,const char * new_name)100 void lv_tabview_rename_tab(lv_obj_t * obj, uint32_t idx, const char * new_name)
101 {
102     LV_ASSERT_OBJ(obj, MY_CLASS);
103 
104     lv_obj_t * tab_bar = lv_tabview_get_tab_bar(obj);
105     lv_obj_t * button = lv_obj_get_child_by_type(tab_bar, idx, &lv_button_class);
106     lv_obj_t * label = lv_obj_get_child_by_type(button, 0, &lv_label_class);
107     lv_label_set_text(label, new_name);
108 }
109 
lv_tabview_set_active(lv_obj_t * obj,uint32_t idx,lv_anim_enable_t anim_en)110 void lv_tabview_set_active(lv_obj_t * obj, uint32_t idx, lv_anim_enable_t anim_en)
111 {
112     LV_ASSERT_OBJ(obj, MY_CLASS);
113     lv_tabview_t * tabview = (lv_tabview_t *)obj;
114 
115     tabview->tab_cur = idx;
116 
117     lv_obj_t * cont = lv_tabview_get_content(obj);
118     lv_obj_t * tab_bar = lv_tabview_get_tab_bar(obj);
119 
120     uint32_t tab_cnt = lv_tabview_get_tab_count(obj);
121     if(idx >= tab_cnt) return;
122 
123     /*To be sure lv_obj_get_content_width will return valid value*/
124     if(cont == NULL) return;
125 
126     lv_obj_update_layout(obj);
127 
128     if((tabview->tab_pos & LV_DIR_VER) != 0) {
129         int32_t gap = lv_obj_get_style_pad_column(cont, LV_PART_MAIN);
130         int32_t w = lv_obj_get_content_width(cont);
131         if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL) {
132             lv_obj_scroll_to_x(cont, idx * (gap + w), anim_en);
133         }
134         else {
135             int32_t id_rtl = -(int32_t)idx;
136             lv_obj_scroll_to_x(cont, (gap + w) * id_rtl, anim_en);
137         }
138     }
139     else {
140         int32_t gap = lv_obj_get_style_pad_row(cont, LV_PART_MAIN);
141         int32_t h = lv_obj_get_content_height(cont);
142         lv_obj_scroll_to_y(cont, idx * (gap + h), anim_en);
143     }
144 
145     uint32_t i = 0;
146     lv_obj_t * button = lv_obj_get_child_by_type(tab_bar, i, &lv_button_class);
147     while(button) {
148         lv_obj_set_state(button, LV_STATE_CHECKED, i == idx);
149         i++;
150         button = lv_obj_get_child_by_type(tab_bar, (int32_t)i, &lv_button_class);
151     }
152 
153 }
154 
lv_tabview_set_tab_bar_position(lv_obj_t * obj,lv_dir_t dir)155 void lv_tabview_set_tab_bar_position(lv_obj_t * obj, lv_dir_t dir)
156 {
157     LV_ASSERT_OBJ(obj, MY_CLASS);
158     lv_tabview_t * tabview = (lv_tabview_t *)obj;
159 
160     switch(dir) {
161         case LV_DIR_TOP:
162             lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
163             break;
164         case LV_DIR_BOTTOM:
165             lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN_REVERSE);
166             break;
167         case LV_DIR_LEFT:
168             lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
169             break;
170         case LV_DIR_RIGHT:
171             lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW_REVERSE);
172             break;
173         case LV_DIR_HOR:
174         case LV_DIR_VER:
175         case LV_DIR_ALL:
176         case LV_DIR_NONE:
177             break;
178     }
179 
180     lv_obj_t * tab_bar = lv_tabview_get_tab_bar(obj);
181     lv_obj_t * cont = lv_tabview_get_content(obj);
182 
183     switch(dir) {
184         case LV_DIR_TOP:
185         case LV_DIR_BOTTOM:
186             lv_obj_set_width(cont, LV_PCT(100));
187             lv_obj_set_flex_grow(cont, 1);
188             lv_obj_set_flex_flow(tab_bar, LV_FLEX_FLOW_ROW);
189             lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW);
190             lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_CENTER);
191             lv_obj_set_scroll_snap_y(cont, LV_SCROLL_SNAP_NONE);
192             break;
193         case LV_DIR_LEFT:
194         case LV_DIR_RIGHT:
195             lv_obj_set_height(cont, LV_PCT(100));
196             lv_obj_set_flex_grow(cont, 1);
197             lv_obj_set_flex_flow(tab_bar, LV_FLEX_FLOW_COLUMN);
198             lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
199             lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_NONE);
200             lv_obj_set_scroll_snap_y(cont, LV_SCROLL_SNAP_CENTER);
201             break;
202         case LV_DIR_HOR:
203         case LV_DIR_VER:
204         case LV_DIR_ALL:
205         case LV_DIR_NONE:
206             break;
207     }
208 
209     bool was_ver = tabview->tab_pos & LV_DIR_VER;
210     bool now_ver = dir & LV_DIR_VER;
211 
212     if(was_ver != now_ver) {
213         int32_t dpi = lv_display_get_dpi(lv_obj_get_display(obj));
214         if(now_ver) {
215             lv_obj_set_size(tab_bar, lv_pct(100), dpi / 2);
216         }
217         else {
218             lv_obj_set_size(tab_bar, dpi, lv_pct(100));
219         }
220     }
221     tabview->tab_pos = dir;
222 }
223 
lv_tabview_set_tab_bar_size(lv_obj_t * obj,int32_t size)224 void lv_tabview_set_tab_bar_size(lv_obj_t * obj, int32_t size)
225 {
226     LV_ASSERT_OBJ(obj, MY_CLASS);
227     lv_tabview_t * tabview = (lv_tabview_t *)obj;
228 
229     lv_obj_t * tab_bar = lv_tabview_get_tab_bar(obj);
230     if(tabview->tab_pos & LV_DIR_VER) {
231         lv_obj_set_height(tab_bar, size);
232     }
233     else {
234         lv_obj_set_width(tab_bar, size);
235     }
236 }
237 
lv_tabview_get_tab_active(lv_obj_t * obj)238 uint32_t lv_tabview_get_tab_active(lv_obj_t * obj)
239 {
240     LV_ASSERT_OBJ(obj, MY_CLASS);
241     lv_tabview_t * tabview = (lv_tabview_t *)obj;
242     return tabview->tab_cur;
243 }
244 
lv_tabview_get_tab_count(lv_obj_t * obj)245 uint32_t lv_tabview_get_tab_count(lv_obj_t * obj)
246 {
247     LV_ASSERT_OBJ(obj, MY_CLASS);
248     lv_obj_t * tab_bar = lv_tabview_get_tab_bar(obj);
249     return lv_obj_get_child_count_by_type(tab_bar, &lv_button_class);
250 }
251 
lv_tabview_get_content(lv_obj_t * tv)252 lv_obj_t * lv_tabview_get_content(lv_obj_t * tv)
253 {
254     return lv_obj_get_child(tv, 1);
255 }
256 
lv_tabview_get_tab_bar(lv_obj_t * tv)257 lv_obj_t * lv_tabview_get_tab_bar(lv_obj_t * tv)
258 {
259     return lv_obj_get_child(tv, 0);
260 }
261 
262 /**********************
263  *   STATIC FUNCTIONS
264  **********************/
265 
lv_tabview_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)266 static void lv_tabview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
267 {
268     LV_UNUSED(class_p);
269     lv_tabview_t * tabview = (lv_tabview_t *)obj;
270     tabview->tab_pos = LV_DIR_NONE;  /*Invalid value to apply the default TOP direction correctly*/
271 
272     lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
273 
274     lv_obj_t * cont;
275 
276     lv_obj_create(obj);
277     cont = lv_obj_create(obj);
278     lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW);
279 
280     lv_obj_add_event_cb(cont, cont_scroll_end_event_cb, LV_EVENT_ALL, NULL);
281     lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF);
282     lv_tabview_set_tab_bar_position(obj, LV_DIR_TOP);
283 
284     lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ONE);
285     lv_obj_remove_flag(cont, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
286 }
287 
lv_tabview_event(const lv_obj_class_t * class_p,lv_event_t * e)288 static void lv_tabview_event(const lv_obj_class_t * class_p, lv_event_t * e)
289 {
290     LV_UNUSED(class_p);
291     lv_result_t res = lv_obj_event_base(&lv_tabview_class, e);
292     if(res != LV_RESULT_OK) return;
293 
294     lv_event_code_t code = lv_event_get_code(e);
295     lv_obj_t * target = lv_event_get_current_target(e);
296 
297     if(code == LV_EVENT_SIZE_CHANGED) {
298         lv_tabview_set_active(target, lv_tabview_get_tab_active(target), LV_ANIM_OFF);
299     }
300 }
301 
button_clicked_event_cb(lv_event_t * e)302 static void button_clicked_event_cb(lv_event_t * e)
303 {
304     lv_obj_t * button = lv_event_get_current_target(e);
305 
306     lv_obj_t * tv = lv_obj_get_parent(lv_obj_get_parent(button));
307     int32_t idx = lv_obj_get_index_by_type(button, &lv_button_class);
308     lv_tabview_set_active(tv, idx, LV_ANIM_OFF);
309 }
310 
cont_scroll_end_event_cb(lv_event_t * e)311 static void cont_scroll_end_event_cb(lv_event_t * e)
312 {
313     lv_obj_t * cont = lv_event_get_current_target(e);
314     lv_event_code_t code = lv_event_get_code(e);
315 
316     lv_obj_t * tv = lv_obj_get_parent(cont);
317     lv_tabview_t * tv_obj = (lv_tabview_t *)tv;
318     if(code == LV_EVENT_LAYOUT_CHANGED) {
319         lv_tabview_set_active(tv, lv_tabview_get_tab_active(tv), LV_ANIM_OFF);
320     }
321     else if(code == LV_EVENT_SCROLL_END) {
322         lv_indev_t * indev = lv_indev_active();
323         if(indev && indev->state == LV_INDEV_STATE_PRESSED) {
324             return;
325         }
326 
327         lv_point_t p;
328         lv_obj_get_scroll_end(cont, &p);
329 
330         int32_t t;
331         if((tv_obj->tab_pos & LV_DIR_VER) != 0) {
332             int32_t w = lv_obj_get_content_width(cont);
333             if(lv_obj_get_style_base_dir(tv, LV_PART_MAIN) == LV_BASE_DIR_RTL)  t = -(p.x - w / 2) / w;
334             else t = (p.x + w / 2) / w;
335         }
336         else {
337             int32_t h = lv_obj_get_content_height(cont);
338             t = (p.y + h / 2) / h;
339         }
340 
341         if(t < 0) t = 0;
342         bool new_tab = false;
343         if(t != (int32_t)lv_tabview_get_tab_active(tv)) new_tab = true;
344 
345         /*If not scrolled by an indev set the tab immediately*/
346         if(lv_indev_active()) {
347             lv_tabview_set_active(tv, t, LV_ANIM_ON);
348         }
349         else {
350             lv_tabview_set_active(tv, t, LV_ANIM_OFF);
351         }
352 
353         if(new_tab) lv_obj_send_event(tv, LV_EVENT_VALUE_CHANGED, NULL);
354     }
355 }
356 #endif /*LV_USE_TABVIEW*/
357