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