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