1 /**
2  * @file lv_btnmatrix.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_btnmatrix.h"
10 #if LV_USE_BTNMATRIX != 0
11 
12 #include "../misc/lv_assert.h"
13 #include "../core/lv_indev.h"
14 #include "../core/lv_group.h"
15 #include "../draw/lv_draw.h"
16 #include "../core/lv_refr.h"
17 #include "../misc/lv_txt.h"
18 #include "../misc/lv_txt_ap.h"
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 #define MY_CLASS &lv_btnmatrix_class
24 
25 #define BTN_EXTRA_CLICK_AREA_MAX (LV_DPI_DEF / 10)
26 #define LV_BTNMATRIX_WIDTH_MASK 0x000F
27 
28 /**********************
29  *      TYPEDEFS
30  **********************/
31 
32 /**********************
33  *  STATIC PROTOTYPES
34  **********************/
35 static void lv_btnmatrix_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
36 static void lv_btnmatrix_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
37 static void lv_btnmatrix_event(const lv_obj_class_t * class_p, lv_event_t * e);
38 static void draw_main(lv_event_t * e);
39 
40 static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits);
41 static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits);
42 static bool button_is_checked(lv_btnmatrix_ctrl_t ctrl_bits);
43 static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits);
44 static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits);
45 static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits);
46 static bool button_is_popover(lv_btnmatrix_ctrl_t ctrl_bits);
47 static bool button_is_checkable(lv_btnmatrix_ctrl_t ctrl_bits);
48 static bool button_is_recolor(lv_btnmatrix_ctrl_t ctrl_bits);
49 static bool button_get_checked(lv_btnmatrix_ctrl_t ctrl_bits);
50 static uint16_t get_button_from_point(lv_obj_t * obj, lv_point_t * p);
51 static void allocate_btn_areas_and_controls(const lv_obj_t * obj, const char ** map);
52 static void invalidate_button_area(const lv_obj_t * obj, uint16_t btn_idx);
53 static void make_one_button_checked(lv_obj_t * obj, uint16_t btn_idx);
54 static bool has_popovers_in_top_row(lv_obj_t * obj);
55 
56 /**********************
57  *  STATIC VARIABLES
58  **********************/
59 static const char * lv_btnmatrix_def_map[] = {"Btn1", "Btn2", "Btn3", "\n", "Btn4", "Btn5", ""};
60 
61 const lv_obj_class_t lv_btnmatrix_class = {
62     .constructor_cb = lv_btnmatrix_constructor,
63     .destructor_cb = lv_btnmatrix_destructor,
64     .event_cb = lv_btnmatrix_event,
65     .width_def = LV_DPI_DEF * 2,
66     .height_def = LV_DPI_DEF,
67     .instance_size = sizeof(lv_btnmatrix_t),
68     .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
69     .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
70     .base_class = &lv_obj_class
71 };
72 
73 /**********************
74  *      MACROS
75  **********************/
76 
77 /**********************
78  *   GLOBAL FUNCTIONS
79  **********************/
80 
lv_btnmatrix_create(lv_obj_t * parent)81 lv_obj_t * lv_btnmatrix_create(lv_obj_t * parent)
82 {
83     LV_LOG_INFO("begin");
84     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
85     lv_obj_class_init_obj(obj);
86     return obj;
87 }
88 
89 /*=====================
90  * Setter functions
91  *====================*/
92 
lv_btnmatrix_set_map(lv_obj_t * obj,const char * map[])93 void lv_btnmatrix_set_map(lv_obj_t * obj, const char * map[])
94 {
95     LV_ASSERT_OBJ(obj, MY_CLASS);
96     if(map == NULL) return;
97 
98     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
99 
100     /*Analyze the map and create the required number of buttons*/
101     allocate_btn_areas_and_controls(obj, map);
102     btnm->map_p = map;
103 
104     lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
105 
106     /*Set size and positions of the buttons*/
107     lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
108     lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
109     lv_coord_t prow = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
110     lv_coord_t pcol = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
111 
112     lv_coord_t max_w            = lv_obj_get_content_width(obj);
113     lv_coord_t max_h            = lv_obj_get_content_height(obj);
114 
115     /*Calculate the position of each row*/
116     lv_coord_t max_h_no_gap = max_h - (prow * (btnm->row_cnt - 1));
117 
118     /*Count the units and the buttons in a line
119      *(A button can be 1,2,3... unit wide)*/
120     uint32_t txt_tot_i = 0; /*Act. index in the str map*/
121     uint32_t btn_tot_i = 0; /*Act. index of button areas*/
122     const char ** map_row = map;
123 
124     /*Count the units and the buttons in a line*/
125     uint32_t row;
126     for(row = 0; row < btnm->row_cnt; row++) {
127         uint16_t unit_cnt = 0;           /*Number of units in a row*/
128         uint16_t btn_cnt = 0;            /*Number of buttons in a row*/
129         /*Count the buttons and units in this row*/
130         while(map_row[btn_cnt] && strcmp(map_row[btn_cnt], "\n") != 0 && map_row[btn_cnt][0] != '\0') {
131             unit_cnt += get_button_width(btnm->ctrl_bits[btn_tot_i + btn_cnt]);
132             btn_cnt++;
133         }
134 
135         /*Only deal with the non empty lines*/
136         if(btn_cnt == 0) {
137             map_row = &map_row[btn_cnt + 1];       /*Set the map to the next row*/
138             continue;
139         }
140 
141         lv_coord_t row_y1 = ptop + (max_h_no_gap * row) / btnm->row_cnt + row * prow;
142         lv_coord_t row_y2 = ptop + (max_h_no_gap * (row + 1)) / btnm->row_cnt + row * prow - 1;
143 
144         /*Set the button size and positions*/
145         lv_coord_t max_w_no_gap = max_w - (pcol * (btn_cnt - 1));
146         if(max_w_no_gap < 0) max_w_no_gap = 0;
147 
148         uint32_t row_unit_cnt = 0;  /*The current unit position in the row*/
149         uint32_t btn;
150         for(btn = 0; btn < btn_cnt; btn++, btn_tot_i++, txt_tot_i++) {
151             uint32_t btn_u = get_button_width(btnm->ctrl_bits[btn_tot_i]);
152 
153             lv_coord_t btn_x1 = (max_w_no_gap * row_unit_cnt) / unit_cnt + btn * pcol;
154             lv_coord_t btn_x2 = (max_w_no_gap * (row_unit_cnt + btn_u)) / unit_cnt + btn * pcol - 1;
155 
156             /*If RTL start from the right*/
157             if(base_dir == LV_BASE_DIR_RTL) {
158                 lv_coord_t tmp = btn_x1;
159                 btn_x1 = btn_x2;
160                 btn_x2 = tmp;
161 
162                 btn_x1 = max_w - btn_x1;
163                 btn_x2 = max_w - btn_x2;
164             }
165 
166             btn_x1 += pleft;
167             btn_x2 += pleft;
168 
169             lv_area_set(&btnm->button_areas[btn_tot_i], btn_x1, row_y1, btn_x2, row_y2);
170 
171             row_unit_cnt += btn_u;
172         }
173 
174         map_row = &map_row[btn_cnt + 1];       /*Set the map to the next line*/
175     }
176 
177     /*Popovers in the top row will draw outside the widget and the extended draw size depends on
178      *the row height which may have changed when setting the new map*/
179     lv_obj_refresh_ext_draw_size(obj);
180 
181     lv_obj_invalidate(obj);
182 }
183 
lv_btnmatrix_set_ctrl_map(lv_obj_t * obj,const lv_btnmatrix_ctrl_t ctrl_map[])184 void lv_btnmatrix_set_ctrl_map(lv_obj_t * obj, const lv_btnmatrix_ctrl_t ctrl_map[])
185 {
186     LV_ASSERT_OBJ(obj, MY_CLASS);
187 
188     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
189     lv_memcpy(btnm->ctrl_bits, ctrl_map, sizeof(lv_btnmatrix_ctrl_t) * btnm->btn_cnt);
190 
191     lv_btnmatrix_set_map(obj, btnm->map_p);
192 }
193 
lv_btnmatrix_set_selected_btn(lv_obj_t * obj,uint16_t btn_id)194 void lv_btnmatrix_set_selected_btn(lv_obj_t * obj, uint16_t btn_id)
195 {
196     LV_ASSERT_OBJ(obj, MY_CLASS);
197 
198     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
199 
200     if(btn_id >= btnm->btn_cnt && btn_id != LV_BTNMATRIX_BTN_NONE) return;
201 
202     invalidate_button_area(obj, btnm->btn_id_sel);
203     btnm->btn_id_sel = btn_id;
204     invalidate_button_area(obj, btn_id);
205 }
206 
lv_btnmatrix_set_btn_ctrl(lv_obj_t * obj,uint16_t btn_id,lv_btnmatrix_ctrl_t ctrl)207 void lv_btnmatrix_set_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
208 {
209     LV_ASSERT_OBJ(obj, MY_CLASS);
210 
211     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
212 
213     if(btn_id >= btnm->btn_cnt) return;
214 
215     if(btnm->one_check && (ctrl & LV_BTNMATRIX_CTRL_CHECKED)) {
216         lv_btnmatrix_clear_btn_ctrl_all(obj, LV_BTNMATRIX_CTRL_CHECKED);
217     }
218 
219     /* If we hide a button if all buttons are now hidden hide the whole button matrix to make focus behave correctly */
220     if(ctrl & LV_BTNMATRIX_CTRL_HIDDEN) {
221         bool all_buttons_hidden = true;
222         if(btnm->btn_cnt > 1) {
223             for(uint16_t btn_idx = 0; btn_idx < btnm->btn_cnt; btn_idx++) {
224                 if(btn_idx == btn_id) continue;
225                 if(!(btnm->ctrl_bits[btn_idx] & LV_BTNMATRIX_CTRL_HIDDEN)) all_buttons_hidden = false;
226             }
227 
228         }
229         if(all_buttons_hidden) lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
230     }
231 
232     btnm->ctrl_bits[btn_id] |= ctrl;
233     invalidate_button_area(obj, btn_id);
234 
235     if(ctrl & LV_BTNMATRIX_CTRL_POPOVER) {
236         lv_obj_refresh_ext_draw_size(obj);
237     }
238 }
239 
lv_btnmatrix_clear_btn_ctrl(lv_obj_t * obj,uint16_t btn_id,lv_btnmatrix_ctrl_t ctrl)240 void lv_btnmatrix_clear_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
241 {
242     LV_ASSERT_OBJ(obj, MY_CLASS);
243 
244     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
245 
246     if(btn_id >= btnm->btn_cnt) return;
247 
248     /* If all buttons were hidden the whole button matrix is hidden so we need to check and remove hidden flag if present */
249     if(ctrl & LV_BTNMATRIX_CTRL_HIDDEN) {
250         if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) lv_obj_clear_flag(obj, LV_OBJ_FLAG_HIDDEN);
251     }
252 
253     btnm->ctrl_bits[btn_id] &= (~ctrl);
254     invalidate_button_area(obj, btn_id);
255 
256     if(ctrl & LV_BTNMATRIX_CTRL_POPOVER) {
257         lv_obj_refresh_ext_draw_size(obj);
258     }
259 }
260 
lv_btnmatrix_set_btn_ctrl_all(lv_obj_t * obj,lv_btnmatrix_ctrl_t ctrl)261 void lv_btnmatrix_set_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl)
262 {
263     LV_ASSERT_OBJ(obj, MY_CLASS);
264 
265     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
266     uint16_t i;
267     for(i = 0; i < btnm->btn_cnt; i++) {
268         lv_btnmatrix_set_btn_ctrl(obj, i, ctrl);
269     }
270 }
271 
lv_btnmatrix_clear_btn_ctrl_all(lv_obj_t * obj,lv_btnmatrix_ctrl_t ctrl)272 void lv_btnmatrix_clear_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl)
273 {
274     LV_ASSERT_OBJ(obj, MY_CLASS);
275 
276     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
277     uint16_t i;
278     for(i = 0; i < btnm->btn_cnt; i++) {
279         lv_btnmatrix_clear_btn_ctrl(obj, i, ctrl);
280     }
281 }
282 
lv_btnmatrix_set_btn_width(lv_obj_t * obj,uint16_t btn_id,uint8_t width)283 void lv_btnmatrix_set_btn_width(lv_obj_t * obj, uint16_t btn_id, uint8_t width)
284 {
285     LV_ASSERT_OBJ(obj, MY_CLASS);
286 
287     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
288     if(btn_id >= btnm->btn_cnt) return;
289     btnm->ctrl_bits[btn_id] &= (~LV_BTNMATRIX_WIDTH_MASK);
290     btnm->ctrl_bits[btn_id] |= (LV_BTNMATRIX_WIDTH_MASK & width);
291 
292     lv_btnmatrix_set_map(obj, btnm->map_p);
293 }
294 
lv_btnmatrix_set_one_checked(lv_obj_t * obj,bool en)295 void lv_btnmatrix_set_one_checked(lv_obj_t * obj, bool en)
296 {
297     LV_ASSERT_OBJ(obj, MY_CLASS);
298 
299     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
300     btnm->one_check     = en;
301 
302     /*If more than one button is toggled only the first one should be*/
303     make_one_button_checked(obj, 0);
304 }
305 
306 /*=====================
307  * Getter functions
308  *====================*/
309 
lv_btnmatrix_get_map(const lv_obj_t * obj)310 const char ** lv_btnmatrix_get_map(const lv_obj_t * obj)
311 {
312     LV_ASSERT_OBJ(obj, MY_CLASS);
313 
314     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
315     return btnm->map_p;
316 }
317 
lv_btnmatrix_get_selected_btn(const lv_obj_t * obj)318 uint16_t lv_btnmatrix_get_selected_btn(const lv_obj_t * obj)
319 {
320     LV_ASSERT_OBJ(obj, MY_CLASS);
321 
322     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
323     return btnm->btn_id_sel;
324 }
325 
lv_btnmatrix_get_btn_text(const lv_obj_t * obj,uint16_t btn_id)326 const char * lv_btnmatrix_get_btn_text(const lv_obj_t * obj, uint16_t btn_id)
327 {
328     LV_ASSERT_OBJ(obj, MY_CLASS);
329 
330     if(btn_id == LV_BTNMATRIX_BTN_NONE) return NULL;
331 
332     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
333     if(btn_id > btnm->btn_cnt) return NULL;
334 
335     uint16_t txt_i = 0;
336     uint16_t btn_i = 0;
337 
338     /*Search the text of btnm->btn_pr the buttons text in the map
339      *Skip "\n"-s*/
340     while(btn_i != btn_id) {
341         btn_i++;
342         txt_i++;
343         if(strcmp(btnm->map_p[txt_i], "\n") == 0) txt_i++;
344     }
345 
346     if(btn_i == btnm->btn_cnt) return NULL;
347 
348     return btnm->map_p[txt_i];
349 }
350 
lv_btnmatrix_has_btn_ctrl(lv_obj_t * obj,uint16_t btn_id,lv_btnmatrix_ctrl_t ctrl)351 bool lv_btnmatrix_has_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
352 {
353     LV_ASSERT_OBJ(obj, MY_CLASS);
354 
355     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
356     if(btn_id >= btnm->btn_cnt) return false;
357 
358     return ((btnm->ctrl_bits[btn_id] & ctrl) == ctrl) ? true : false;
359 }
360 
lv_btnmatrix_get_one_checked(const lv_obj_t * obj)361 bool lv_btnmatrix_get_one_checked(const lv_obj_t * obj)
362 {
363     LV_ASSERT_OBJ(obj, MY_CLASS);
364 
365     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
366 
367     return btnm->one_check;
368 }
369 
370 /**********************
371  *   STATIC FUNCTIONS
372  **********************/
373 
lv_btnmatrix_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)374 static void lv_btnmatrix_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
375 {
376     LV_UNUSED(class_p);
377     LV_TRACE_OBJ_CREATE("begin");
378     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
379     btnm->btn_cnt        = 0;
380     btnm->row_cnt        = 0;
381     btnm->btn_id_sel     = LV_BTNMATRIX_BTN_NONE;
382     btnm->button_areas   = NULL;
383     btnm->ctrl_bits      = NULL;
384     btnm->map_p          = NULL;
385     btnm->one_check      = 0;
386 
387     lv_btnmatrix_set_map(obj, lv_btnmatrix_def_map);
388 
389     LV_TRACE_OBJ_CREATE("finished");
390 }
391 
lv_btnmatrix_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)392 static void lv_btnmatrix_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
393 {
394     LV_TRACE_OBJ_CREATE("begin");
395     LV_UNUSED(class_p);
396     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
397     lv_mem_free(btnm->button_areas);
398     lv_mem_free(btnm->ctrl_bits);
399     btnm->button_areas = NULL;
400     btnm->ctrl_bits = NULL;
401     LV_TRACE_OBJ_CREATE("finished");
402 }
403 
lv_btnmatrix_event(const lv_obj_class_t * class_p,lv_event_t * e)404 static void lv_btnmatrix_event(const lv_obj_class_t * class_p, lv_event_t * e)
405 {
406     LV_UNUSED(class_p);
407 
408     lv_res_t res;
409 
410     /*Call the ancestor's event handler*/
411     res = lv_obj_event_base(MY_CLASS, e);
412     if(res != LV_RES_OK) return;
413 
414     lv_event_code_t code = lv_event_get_code(e);
415     lv_obj_t * obj = lv_event_get_target(e);
416     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
417     lv_point_t p;
418 
419     if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
420         if(has_popovers_in_top_row(obj)) {
421             /*reserve one row worth of extra space to account for popovers in the top row*/
422             lv_coord_t s = btnm->row_cnt > 0 ? lv_obj_get_content_height(obj) / btnm->row_cnt : 0;
423             lv_event_set_ext_draw_size(e, s);
424         }
425     }
426     if(code == LV_EVENT_STYLE_CHANGED) {
427         lv_btnmatrix_set_map(obj, btnm->map_p);
428     }
429     else if(code == LV_EVENT_SIZE_CHANGED) {
430         lv_btnmatrix_set_map(obj, btnm->map_p);
431     }
432     else if(code == LV_EVENT_PRESSED) {
433         void * param = lv_event_get_param(e);
434         invalidate_button_area(obj, btnm->btn_id_sel);
435 
436         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
437         if(indev_type == LV_INDEV_TYPE_POINTER || indev_type == LV_INDEV_TYPE_BUTTON) {
438             uint16_t btn_pr;
439             /*Search the pressed area*/
440             lv_indev_get_point(param, &p);
441             btn_pr = get_button_from_point(obj, &p);
442             /*Handle the case where there is no button there*/
443             btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
444             if(btn_pr != LV_BTNMATRIX_BTN_NONE) {
445                 if(button_is_inactive(btnm->ctrl_bits[btn_pr]) == false &&
446                    button_is_hidden(btnm->ctrl_bits[btn_pr]) == false) {
447                     btnm->btn_id_sel = btn_pr;
448                     invalidate_button_area(obj, btnm->btn_id_sel); /*Invalidate the new area*/
449                 }
450             }
451             else {
452                 btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
453             }
454         }
455 
456         if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
457             if(button_is_click_trig(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
458                button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
459                button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
460                button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
461                 uint32_t b = btnm->btn_id_sel;
462                 res        = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
463                 if(res != LV_RES_OK) return;
464             }
465         }
466     }
467     else if(code == LV_EVENT_PRESSING) {
468         void * param = lv_event_get_param(e);
469         uint16_t btn_pr = LV_BTNMATRIX_BTN_NONE;
470         /*Search the pressed area*/
471         lv_indev_t * indev = lv_indev_get_act();
472         lv_indev_type_t indev_type = lv_indev_get_type(indev);
473         if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) return;
474 
475         lv_indev_get_point(indev, &p);
476         btn_pr = get_button_from_point(obj, &p);
477         /*Invalidate to old and the new areas*/
478         if(btn_pr != btnm->btn_id_sel) {
479             if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
480                 invalidate_button_area(obj, btnm->btn_id_sel);
481             }
482 
483             btnm->btn_id_sel = btn_pr;
484 
485             lv_indev_reset_long_press(param); /*Start the log press time again on the new button*/
486             if(btn_pr != LV_BTNMATRIX_BTN_NONE &&
487                button_is_inactive(btnm->ctrl_bits[btn_pr]) == false &&
488                button_is_hidden(btnm->ctrl_bits[btn_pr]) == false) {
489                 invalidate_button_area(obj, btn_pr);
490                 /*Send VALUE_CHANGED for the newly pressed button*/
491                 if(button_is_click_trig(btnm->ctrl_bits[btn_pr]) == false &&
492                    button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
493                     uint32_t b = btn_pr;
494                     res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
495                     if(res != LV_RES_OK) return;
496                 }
497             }
498         }
499     }
500     else if(code == LV_EVENT_RELEASED) {
501         if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
502             /*Toggle the button if enabled*/
503             if(button_is_checkable(btnm->ctrl_bits[btnm->btn_id_sel]) &&
504                !button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
505                 if(button_get_checked(btnm->ctrl_bits[btnm->btn_id_sel]) && !btnm->one_check) {
506                     btnm->ctrl_bits[btnm->btn_id_sel] &= (~LV_BTNMATRIX_CTRL_CHECKED);
507                 }
508                 else {
509                     btnm->ctrl_bits[btnm->btn_id_sel] |= LV_BTNMATRIX_CTRL_CHECKED;
510                 }
511                 if(btnm->one_check) make_one_button_checked(obj, btnm->btn_id_sel);
512             }
513 
514             if((button_is_click_trig(btnm->ctrl_bits[btnm->btn_id_sel]) == true ||
515                 button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == true) &&
516                button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
517                button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
518                 uint32_t b = btnm->btn_id_sel;
519                 res        = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
520                 if(res != LV_RES_OK) return;
521             }
522         }
523 
524         /*Invalidate to old pressed area*/;
525         invalidate_button_area(obj, btnm->btn_id_sel);
526 
527     }
528     else if(code == LV_EVENT_LONG_PRESSED_REPEAT) {
529         if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
530             if(button_is_repeat_disabled(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
531                button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
532                button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
533                 uint32_t b = btnm->btn_id_sel;
534                 res        = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
535                 if(res != LV_RES_OK) return;
536             }
537         }
538     }
539     else if(code == LV_EVENT_PRESS_LOST) {
540         invalidate_button_area(obj, btnm->btn_id_sel);
541         btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
542     }
543     else if(code == LV_EVENT_FOCUSED) {
544         if(btnm->btn_cnt == 0) return;
545 
546         lv_indev_t * indev = lv_event_get_param(e);
547         lv_indev_type_t indev_type = lv_indev_get_type(indev);
548 
549         /*If not focused by an input device assume the last input device*/
550         if(indev == NULL) {
551             indev = lv_indev_get_next(NULL);
552             indev_type = lv_indev_get_type(indev);
553         }
554 
555         bool editing = lv_group_get_editing(lv_obj_get_group(obj));
556         /*Focus the first button if there is not selected button*/
557         if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
558             if(indev_type == LV_INDEV_TYPE_KEYPAD || (indev_type == LV_INDEV_TYPE_ENCODER && editing)) {
559                 uint32_t b = 0;
560                 if(btnm->one_check) {
561                     while(button_is_hidden(btnm->ctrl_bits[b]) || button_is_inactive(btnm->ctrl_bits[b]) ||
562                           button_is_checked(btnm->ctrl_bits[b]) == false) b++;
563                 }
564                 else {
565                     while(button_is_hidden(btnm->ctrl_bits[b]) || button_is_inactive(btnm->ctrl_bits[b])) b++;
566                 }
567 
568                 btnm->btn_id_sel = b;
569             }
570             else {
571                 btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
572             }
573         }
574     }
575     else if(code == LV_EVENT_DEFOCUSED || code == LV_EVENT_LEAVE) {
576         if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) invalidate_button_area(obj, btnm->btn_id_sel);
577         btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
578     }
579     else if(code == LV_EVENT_KEY) {
580 
581         invalidate_button_area(obj, btnm->btn_id_sel);
582 
583         char c = *((char *)lv_event_get_param(e));
584         if(c == LV_KEY_RIGHT) {
585             if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE)  btnm->btn_id_sel = 0;
586             else btnm->btn_id_sel++;
587             if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
588 
589             uint16_t btn_id_start = btnm->btn_id_sel;
590             while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
591                 btnm->btn_id_sel++;
592                 if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
593 
594                 if(btnm->btn_id_sel == btn_id_start) {
595                     btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
596                     break;
597                 }
598             }
599         }
600         else if(c == LV_KEY_LEFT) {
601             if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) btnm->btn_id_sel = 0;
602 
603             if(btnm->btn_id_sel == 0) btnm->btn_id_sel = btnm->btn_cnt - 1;
604             else if(btnm->btn_id_sel > 0) btnm->btn_id_sel--;
605 
606             uint16_t btn_id_start = btnm->btn_id_sel;
607             while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
608                 if(btnm->btn_id_sel > 0) btnm->btn_id_sel--;
609                 else btnm->btn_id_sel = btnm->btn_cnt - 1;
610 
611                 if(btnm->btn_id_sel == btn_id_start) {
612                     btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
613                     break;
614                 }
615             }
616         }
617         else if(c == LV_KEY_DOWN) {
618             lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
619             /*Find the area below the current*/
620             if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
621                 btnm->btn_id_sel = 0;
622                 while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
623                     btnm->btn_id_sel++;
624                     if(btnm->btn_id_sel >= btnm->btn_cnt) {
625                         btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
626                         break;
627                     }
628                 }
629             }
630             else {
631                 uint16_t area_below;
632                 lv_coord_t pr_center =
633                     btnm->button_areas[btnm->btn_id_sel].x1 + (lv_area_get_width(&btnm->button_areas[btnm->btn_id_sel]) >> 1);
634 
635                 for(area_below = btnm->btn_id_sel; area_below < btnm->btn_cnt; area_below++) {
636                     if(btnm->button_areas[area_below].y1 > btnm->button_areas[btnm->btn_id_sel].y1 &&
637                        pr_center >= btnm->button_areas[area_below].x1 &&
638                        pr_center <= btnm->button_areas[area_below].x2 + col_gap &&
639                        button_is_inactive(btnm->ctrl_bits[area_below]) == false &&
640                        button_is_hidden(btnm->ctrl_bits[area_below]) == false) {
641                         break;
642                     }
643                 }
644 
645                 if(area_below < btnm->btn_cnt) btnm->btn_id_sel = area_below;
646             }
647         }
648         else if(c == LV_KEY_UP) {
649             lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
650             /*Find the area below the current*/
651             if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
652                 btnm->btn_id_sel = 0;
653                 while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
654                     btnm->btn_id_sel++;
655                     if(btnm->btn_id_sel >= btnm->btn_cnt) {
656                         btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
657                         break;
658                     }
659                 }
660             }
661             else {
662                 int16_t area_above;
663                 lv_coord_t pr_center =
664                     btnm->button_areas[btnm->btn_id_sel].x1 + (lv_area_get_width(&btnm->button_areas[btnm->btn_id_sel]) >> 1);
665 
666                 for(area_above = btnm->btn_id_sel; area_above >= 0; area_above--) {
667                     if(btnm->button_areas[area_above].y1 < btnm->button_areas[btnm->btn_id_sel].y1 &&
668                        pr_center >= btnm->button_areas[area_above].x1 - col_gap &&
669                        pr_center <= btnm->button_areas[area_above].x2 &&
670                        button_is_inactive(btnm->ctrl_bits[area_above]) == false &&
671                        button_is_hidden(btnm->ctrl_bits[area_above]) == false) {
672                         break;
673                     }
674                 }
675                 if(area_above >= 0) btnm->btn_id_sel = area_above;
676             }
677         }
678 
679         invalidate_button_area(obj, btnm->btn_id_sel);
680     }
681     else if(code == LV_EVENT_DRAW_MAIN) {
682         draw_main(e);
683     }
684 
685 }
686 
draw_main(lv_event_t * e)687 static void draw_main(lv_event_t * e)
688 {
689     lv_obj_t * obj = lv_event_get_target(e);
690     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
691     if(btnm->btn_cnt == 0) return;
692 
693     lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
694     obj->skip_trans = 1;
695 
696     lv_area_t area_obj;
697     lv_obj_get_coords(obj, &area_obj);
698 
699     lv_area_t btn_area;
700 
701     uint16_t btn_i = 0;
702     uint16_t txt_i = 0;
703 
704     lv_draw_rect_dsc_t draw_rect_dsc_act;
705     lv_draw_label_dsc_t draw_label_dsc_act;
706 
707     lv_draw_rect_dsc_t draw_rect_dsc_def;
708     lv_draw_label_dsc_t draw_label_dsc_def;
709 
710     lv_state_t state_ori = obj->state;
711     obj->state = LV_STATE_DEFAULT;
712     obj->skip_trans = 1;
713     lv_draw_rect_dsc_init(&draw_rect_dsc_def);
714     lv_draw_label_dsc_init(&draw_label_dsc_def);
715     lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &draw_rect_dsc_def);
716     lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &draw_label_dsc_def);
717     obj->skip_trans = 0;
718     obj->state = state_ori;
719 
720     lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
721     lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
722     lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
723     lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
724 
725 #if LV_USE_ARABIC_PERSIAN_CHARS
726     const size_t txt_ap_size = 256 ;
727     char * txt_ap = lv_mem_buf_get(txt_ap_size);
728 #endif
729 
730     lv_obj_draw_part_dsc_t part_draw_dsc;
731     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
732     part_draw_dsc.part = LV_PART_ITEMS;
733     part_draw_dsc.class_p = MY_CLASS;
734     part_draw_dsc.type = LV_BTNMATRIX_DRAW_PART_BTN;
735     part_draw_dsc.rect_dsc = &draw_rect_dsc_act;
736     part_draw_dsc.label_dsc = &draw_label_dsc_act;
737 
738     for(btn_i = 0; btn_i < btnm->btn_cnt; btn_i++, txt_i++) {
739         /*Search the next valid text in the map*/
740         while(strcmp(btnm->map_p[txt_i], "\n") == 0) {
741             txt_i++;
742         }
743 
744         /*Skip hidden buttons*/
745         if(button_is_hidden(btnm->ctrl_bits[btn_i])) continue;
746 
747         /*Get the state of the button*/
748         lv_state_t btn_state = LV_STATE_DEFAULT;
749         if(button_get_checked(btnm->ctrl_bits[btn_i])) btn_state |= LV_STATE_CHECKED;
750 
751         if(button_is_inactive(btnm->ctrl_bits[btn_i])) btn_state |= LV_STATE_DISABLED;
752         else if(btn_i == btnm->btn_id_sel) {
753             if(state_ori & LV_STATE_PRESSED) btn_state |= LV_STATE_PRESSED;
754             if(state_ori & LV_STATE_FOCUSED) btn_state |= LV_STATE_FOCUSED;
755             if(state_ori & LV_STATE_FOCUS_KEY) btn_state |= LV_STATE_FOCUS_KEY;
756             if(state_ori & LV_STATE_EDITED) btn_state |= LV_STATE_EDITED;
757         }
758 
759         /*Get the button's area*/
760         lv_area_copy(&btn_area, &btnm->button_areas[btn_i]);
761         btn_area.x1 += area_obj.x1;
762         btn_area.y1 += area_obj.y1;
763         btn_area.x2 += area_obj.x1;
764         btn_area.y2 += area_obj.y1;
765 
766         /*Set up the draw descriptors*/
767         if(btn_state == LV_STATE_DEFAULT) {
768             lv_memcpy(&draw_rect_dsc_act, &draw_rect_dsc_def, sizeof(lv_draw_rect_dsc_t));
769             lv_memcpy(&draw_label_dsc_act, &draw_label_dsc_def, sizeof(lv_draw_label_dsc_t));
770         }
771         /*In other cases get the styles directly without caching them*/
772         else {
773             obj->state = btn_state;
774             obj->skip_trans = 1;
775             lv_draw_rect_dsc_init(&draw_rect_dsc_act);
776             lv_draw_label_dsc_init(&draw_label_dsc_act);
777             lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &draw_rect_dsc_act);
778             lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &draw_label_dsc_act);
779             obj->state = state_ori;
780             obj->skip_trans = 0;
781         }
782 
783         bool recolor = button_is_recolor(btnm->ctrl_bits[btn_i]);
784         if(recolor) draw_label_dsc_act.flag |= LV_TEXT_FLAG_RECOLOR;
785         else draw_label_dsc_act.flag &= ~LV_TEXT_FLAG_RECOLOR;
786 
787         part_draw_dsc.draw_area = &btn_area;
788         part_draw_dsc.id = btn_i;
789         lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
790 
791         /*Remove borders on the edges if `LV_BORDER_SIDE_INTERNAL`*/
792         if(draw_rect_dsc_act.border_side & LV_BORDER_SIDE_INTERNAL) {
793             draw_rect_dsc_act.border_side = LV_BORDER_SIDE_FULL;
794             if(btn_area.x1 == obj->coords.x1 + pleft) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_LEFT;
795             if(btn_area.x2 == obj->coords.x2 - pright) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_RIGHT;
796             if(btn_area.y1 == obj->coords.y1 + ptop) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_TOP;
797             if(btn_area.y2 == obj->coords.y2 - pbottom) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_BOTTOM;
798         }
799 
800         lv_coord_t btn_height = lv_area_get_height(&btn_area);
801 
802         if((btn_state & LV_STATE_PRESSED) && (btnm->ctrl_bits[btn_i] & LV_BTNMATRIX_CTRL_POPOVER)) {
803             /*Push up the upper boundary of the btn area to create the popover*/
804             btn_area.y1 -= btn_height;
805         }
806 
807         /*Draw the background*/
808         lv_draw_rect(draw_ctx, &draw_rect_dsc_act, &btn_area);
809 
810         /*Calculate the size of the text*/
811         const lv_font_t * font = draw_label_dsc_act.font;
812         lv_coord_t letter_space = draw_label_dsc_act.letter_space;
813         lv_coord_t line_space = draw_label_dsc_act.line_space;
814         const char * txt = btnm->map_p[txt_i];
815 
816 #if LV_USE_ARABIC_PERSIAN_CHARS
817         /*Get the size of the Arabic text and process it*/
818         size_t len_ap = _lv_txt_ap_calc_bytes_cnt(txt);
819         if(len_ap < txt_ap_size) {
820             _lv_txt_ap_proc(txt, txt_ap);
821             txt = txt_ap;
822         }
823 #endif
824         lv_point_t txt_size;
825         lv_txt_get_size(&txt_size, txt, font, letter_space,
826                         line_space, lv_area_get_width(&area_obj), draw_label_dsc_act.flag);
827 
828         btn_area.x1 += (lv_area_get_width(&btn_area) - txt_size.x) / 2;
829         btn_area.y1 += (lv_area_get_height(&btn_area) - txt_size.y) / 2;
830         btn_area.x2 = btn_area.x1 + txt_size.x;
831         btn_area.y2 = btn_area.y1 + txt_size.y;
832 
833         if((btn_state & LV_STATE_PRESSED) && (btnm->ctrl_bits[btn_i] & LV_BTNMATRIX_CTRL_POPOVER)) {
834             /*Push up the button text into the popover*/
835             btn_area.y1 -= btn_height / 2;
836             btn_area.y2 -= btn_height / 2;
837         }
838 
839         /*Draw the text*/
840         lv_draw_label(draw_ctx, &draw_label_dsc_act, &btn_area, txt, NULL);
841 
842         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
843     }
844 
845     obj->skip_trans = 0;
846 #if LV_USE_ARABIC_PERSIAN_CHARS
847     lv_mem_buf_release(txt_ap);
848 #endif
849 }
850 /**
851  * Create the required number of buttons and control bytes according to a map
852  * @param obj pointer to button matrix object
853  * @param map_p pointer to a string array
854  */
allocate_btn_areas_and_controls(const lv_obj_t * obj,const char ** map)855 static void allocate_btn_areas_and_controls(const lv_obj_t * obj, const char ** map)
856 {
857     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
858     btnm->row_cnt = 1;
859     /*Count the buttons in the map*/
860     uint16_t btn_cnt = 0;
861     uint16_t i       = 0;
862     while(map[i] && map[i][0] != '\0') {
863         if(strcmp(map[i], "\n") != 0) { /*Do not count line breaks*/
864             btn_cnt++;
865         }
866         else {
867             btnm->row_cnt++;
868         }
869         i++;
870     }
871 
872     /*Do not allocate memory for the same amount of buttons*/
873     if(btn_cnt == btnm->btn_cnt) return;
874 
875     if(btnm->button_areas != NULL) {
876         lv_mem_free(btnm->button_areas);
877         btnm->button_areas = NULL;
878     }
879     if(btnm->ctrl_bits != NULL) {
880         lv_mem_free(btnm->ctrl_bits);
881         btnm->ctrl_bits = NULL;
882     }
883 
884     btnm->button_areas = lv_mem_alloc(sizeof(lv_area_t) * btn_cnt);
885     LV_ASSERT_MALLOC(btnm->button_areas);
886     btnm->ctrl_bits = lv_mem_alloc(sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
887     LV_ASSERT_MALLOC(btnm->ctrl_bits);
888     if(btnm->button_areas == NULL || btnm->ctrl_bits == NULL) btn_cnt = 0;
889 
890     lv_memset_00(btnm->ctrl_bits, sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
891 
892     btnm->btn_cnt = btn_cnt;
893 }
894 
895 /**
896  * Get the width of a button in units (default is 1).
897  * @param ctrl_bits least significant 3 bits used (1..7 valid values)
898  * @return the width of the button in units
899  */
get_button_width(lv_btnmatrix_ctrl_t ctrl_bits)900 static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits)
901 {
902     uint8_t w = ctrl_bits & LV_BTNMATRIX_WIDTH_MASK;
903     return w != 0 ? w : 1;
904 }
905 
button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits)906 static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits)
907 {
908     return (ctrl_bits & LV_BTNMATRIX_CTRL_HIDDEN) ? true : false;
909 }
910 
button_is_checked(lv_btnmatrix_ctrl_t ctrl_bits)911 static bool button_is_checked(lv_btnmatrix_ctrl_t ctrl_bits)
912 {
913     return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKED) ? true : false;
914 }
915 
button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits)916 static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits)
917 {
918     return (ctrl_bits & LV_BTNMATRIX_CTRL_NO_REPEAT) ? true : false;
919 }
920 
button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits)921 static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits)
922 {
923     return (ctrl_bits & LV_BTNMATRIX_CTRL_DISABLED) ? true : false;
924 }
925 
button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits)926 static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits)
927 {
928     return (ctrl_bits & LV_BTNMATRIX_CTRL_CLICK_TRIG) ? true : false;
929 }
930 
button_is_popover(lv_btnmatrix_ctrl_t ctrl_bits)931 static bool button_is_popover(lv_btnmatrix_ctrl_t ctrl_bits)
932 {
933     return (ctrl_bits & LV_BTNMATRIX_CTRL_POPOVER) ? true : false;
934 }
935 
button_is_checkable(lv_btnmatrix_ctrl_t ctrl_bits)936 static bool button_is_checkable(lv_btnmatrix_ctrl_t ctrl_bits)
937 {
938     return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKABLE) ? true : false;
939 }
940 
button_get_checked(lv_btnmatrix_ctrl_t ctrl_bits)941 static bool button_get_checked(lv_btnmatrix_ctrl_t ctrl_bits)
942 {
943     return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKED) ? true : false;
944 }
945 
button_is_recolor(lv_btnmatrix_ctrl_t ctrl_bits)946 static bool button_is_recolor(lv_btnmatrix_ctrl_t ctrl_bits)
947 {
948     return (ctrl_bits & LV_BTNMATRIX_CTRL_RECOLOR) ? true : false;
949 }
950 /**
951  * Gives the button id of a button under a given point
952  * @param obj pointer to a button matrix object
953  * @param p a point with absolute coordinates
954  * @return the id of the button or LV_BTNMATRIX_BTN_NONE.
955  */
get_button_from_point(lv_obj_t * obj,lv_point_t * p)956 static uint16_t get_button_from_point(lv_obj_t * obj, lv_point_t * p)
957 {
958     lv_area_t obj_cords;
959     lv_area_t btn_area;
960     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
961     uint16_t i;
962     lv_obj_get_coords(obj, &obj_cords);
963 
964     lv_coord_t w = lv_obj_get_width(obj);
965     lv_coord_t h = lv_obj_get_height(obj);
966     lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
967     lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
968     lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
969     lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
970     lv_coord_t prow = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
971     lv_coord_t pcol = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
972 
973     /*Get the half gap. Button look larger with this value. (+1 for rounding error)*/
974     prow = (prow / 2) + 1 + (prow & 1);
975     pcol = (pcol / 2) + 1 + (pcol & 1);
976 
977     prow = LV_MIN(prow, BTN_EXTRA_CLICK_AREA_MAX);
978     pcol = LV_MIN(pcol, BTN_EXTRA_CLICK_AREA_MAX);
979     pright = LV_MIN(pright, BTN_EXTRA_CLICK_AREA_MAX);
980     ptop = LV_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
981     pbottom = LV_MIN(pbottom, BTN_EXTRA_CLICK_AREA_MAX);
982 
983     for(i = 0; i < btnm->btn_cnt; i++) {
984         lv_area_copy(&btn_area, &btnm->button_areas[i]);
985         if(btn_area.x1 <= pleft) btn_area.x1 += obj_cords.x1 - LV_MIN(pleft, BTN_EXTRA_CLICK_AREA_MAX);
986         else btn_area.x1 += obj_cords.x1 - pcol;
987 
988         if(btn_area.y1 <= ptop) btn_area.y1 += obj_cords.y1 - LV_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
989         else btn_area.y1 += obj_cords.y1 - prow;
990 
991         if(btn_area.x2 >= w - pright - 2) btn_area.x2 += obj_cords.x1 + LV_MIN(pright,
992                                                                                    BTN_EXTRA_CLICK_AREA_MAX);  /*-2 for rounding error*/
993         else btn_area.x2 += obj_cords.x1 + pcol;
994 
995         if(btn_area.y2 >= h - pbottom - 2) btn_area.y2 += obj_cords.y1 + LV_MIN(pbottom,
996                                                                                     BTN_EXTRA_CLICK_AREA_MAX); /*-2 for rounding error*/
997         else btn_area.y2 += obj_cords.y1 + prow;
998 
999         if(_lv_area_is_point_on(&btn_area, p, 0) != false) {
1000             break;
1001         }
1002     }
1003 
1004     if(i == btnm->btn_cnt) i = LV_BTNMATRIX_BTN_NONE;
1005 
1006     return i;
1007 }
1008 
invalidate_button_area(const lv_obj_t * obj,uint16_t btn_idx)1009 static void invalidate_button_area(const lv_obj_t * obj, uint16_t btn_idx)
1010 {
1011     if(btn_idx == LV_BTNMATRIX_BTN_NONE) return;
1012 
1013     lv_area_t btn_area;
1014     lv_area_t obj_area;
1015 
1016     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
1017     if(btn_idx >= btnm->btn_cnt) return;
1018 
1019     lv_area_copy(&btn_area, &btnm->button_areas[btn_idx]);
1020     lv_obj_get_coords(obj, &obj_area);
1021 
1022     /*The buttons might have outline and shadow so make the invalidation larger with the gaps between the buttons.
1023      *It assumes that the outline or shadow is smaller than the gaps*/
1024     lv_coord_t row_gap = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
1025     lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
1026 
1027     /*Be sure to have a minimal extra space if row/col_gap is small*/
1028     lv_coord_t dpi = lv_disp_get_dpi(lv_obj_get_disp(obj));
1029     row_gap = LV_MAX(row_gap, dpi / 10);
1030     col_gap = LV_MAX(col_gap, dpi / 10);
1031 
1032     /*Convert relative coordinates to absolute*/
1033     btn_area.x1 += obj_area.x1 - row_gap;
1034     btn_area.y1 += obj_area.y1 - col_gap;
1035     btn_area.x2 += obj_area.x1 + row_gap;
1036     btn_area.y2 += obj_area.y1 + col_gap;
1037 
1038     if((btn_idx == btnm->btn_id_sel) && (btnm->ctrl_bits[btn_idx] & LV_BTNMATRIX_CTRL_POPOVER)) {
1039         /*Push up the upper boundary of the btn area to also invalidate the popover*/
1040         btn_area.y1 -= lv_area_get_height(&btn_area);
1041     }
1042 
1043     lv_obj_invalidate_area(obj, &btn_area);
1044 }
1045 
1046 /**
1047  * Enforces a single button being toggled on the button matrix.
1048  * It simply clears the toggle flag on other buttons.
1049  * @param obj Button matrix object
1050  * @param btn_idx Button that should remain toggled
1051  */
make_one_button_checked(lv_obj_t * obj,uint16_t btn_idx)1052 static void make_one_button_checked(lv_obj_t * obj, uint16_t btn_idx)
1053 {
1054     /*Save whether the button was toggled*/
1055     bool was_toggled = lv_btnmatrix_has_btn_ctrl(obj, btn_idx, LV_BTNMATRIX_CTRL_CHECKED);
1056 
1057     lv_btnmatrix_clear_btn_ctrl_all(obj, LV_BTNMATRIX_CTRL_CHECKED);
1058 
1059     if(was_toggled) lv_btnmatrix_set_btn_ctrl(obj, btn_idx, LV_BTNMATRIX_CTRL_CHECKED);
1060 }
1061 
1062 /**
1063  * Check if any of the buttons in the first row has the LV_BTNMATRIX_CTRL_POPOVER control flag set.
1064  * @param obj Button matrix object
1065  * @return true if at least one button has the flag, false otherwise
1066  */
has_popovers_in_top_row(lv_obj_t * obj)1067 static bool has_popovers_in_top_row(lv_obj_t * obj)
1068 {
1069     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
1070 
1071     if(btnm->row_cnt <= 0) {
1072         return false;
1073     }
1074 
1075     const char ** map_row = btnm->map_p;
1076     uint16_t btn_cnt = 0;
1077 
1078     while(map_row[btn_cnt] && strcmp(map_row[btn_cnt], "\n") != 0 && map_row[btn_cnt][0] != '\0') {
1079         if(button_is_popover(btnm->ctrl_bits[btn_cnt])) {
1080             return true;
1081         }
1082         btn_cnt++;
1083     }
1084 
1085     return false;
1086 }
1087 
1088 #endif
1089