acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

lv_btnmatrix.c (38154B)

      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 0x0007
     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 
     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 
     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 
    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 
    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 
    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     btnm->ctrl_bits[btn_id] |= ctrl;
    220     invalidate_button_area(obj, btn_id);
    221 
    222     if(ctrl & LV_BTNMATRIX_CTRL_POPOVER) {
    223         lv_obj_refresh_ext_draw_size(obj);
    224     }
    225 }
    226 
    227 void lv_btnmatrix_clear_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
    228 {
    229     LV_ASSERT_OBJ(obj, MY_CLASS);
    230 
    231     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    232 
    233     if(btn_id >= btnm->btn_cnt) return;
    234 
    235     btnm->ctrl_bits[btn_id] &= (~ctrl);
    236     invalidate_button_area(obj, btn_id);
    237 
    238     if(ctrl & LV_BTNMATRIX_CTRL_POPOVER) {
    239         lv_obj_refresh_ext_draw_size(obj);
    240     }
    241 }
    242 
    243 void lv_btnmatrix_set_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl)
    244 {
    245     LV_ASSERT_OBJ(obj, MY_CLASS);
    246 
    247     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    248     uint16_t i;
    249     for(i = 0; i < btnm->btn_cnt; i++) {
    250         lv_btnmatrix_set_btn_ctrl(obj, i, ctrl);
    251     }
    252 }
    253 
    254 void lv_btnmatrix_clear_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl)
    255 {
    256     LV_ASSERT_OBJ(obj, MY_CLASS);
    257 
    258     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    259     uint16_t i;
    260     for(i = 0; i < btnm->btn_cnt; i++) {
    261         lv_btnmatrix_clear_btn_ctrl(obj, i, ctrl);
    262     }
    263 }
    264 
    265 void lv_btnmatrix_set_btn_width(lv_obj_t * obj, uint16_t btn_id, uint8_t width)
    266 {
    267     LV_ASSERT_OBJ(obj, MY_CLASS);
    268 
    269     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    270     if(btn_id >= btnm->btn_cnt) return;
    271     btnm->ctrl_bits[btn_id] &= (~LV_BTNMATRIX_WIDTH_MASK);
    272     btnm->ctrl_bits[btn_id] |= (LV_BTNMATRIX_WIDTH_MASK & width);
    273 
    274     lv_btnmatrix_set_map(obj, btnm->map_p);
    275 }
    276 
    277 void lv_btnmatrix_set_one_checked(lv_obj_t * obj, bool en)
    278 {
    279     LV_ASSERT_OBJ(obj, MY_CLASS);
    280 
    281     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    282     btnm->one_check     = en;
    283 
    284     /*If more than one button is toggled only the first one should be*/
    285     make_one_button_checked(obj, 0);
    286 }
    287 
    288 /*=====================
    289  * Getter functions
    290  *====================*/
    291 
    292 const char ** lv_btnmatrix_get_map(const lv_obj_t * obj)
    293 {
    294     LV_ASSERT_OBJ(obj, MY_CLASS);
    295 
    296     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    297     return btnm->map_p;
    298 }
    299 
    300 uint16_t lv_btnmatrix_get_selected_btn(const lv_obj_t * obj)
    301 {
    302     LV_ASSERT_OBJ(obj, MY_CLASS);
    303 
    304     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    305     return btnm->btn_id_sel;
    306 }
    307 
    308 const char * lv_btnmatrix_get_btn_text(const lv_obj_t * obj, uint16_t btn_id)
    309 {
    310     LV_ASSERT_OBJ(obj, MY_CLASS);
    311 
    312     if(btn_id == LV_BTNMATRIX_BTN_NONE) return NULL;
    313 
    314     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
    315     if(btn_id > btnm->btn_cnt) return NULL;
    316 
    317     uint16_t txt_i = 0;
    318     uint16_t btn_i = 0;
    319 
    320     /*Search the text of btnm->btn_pr the buttons text in the map
    321      *Skip "\n"-s*/
    322     while(btn_i != btn_id) {
    323         btn_i++;
    324         txt_i++;
    325         if(strcmp(btnm->map_p[txt_i], "\n") == 0) txt_i++;
    326     }
    327 
    328     if(btn_i == btnm->btn_cnt) return NULL;
    329 
    330     return btnm->map_p[txt_i];
    331 }
    332 
    333 bool lv_btnmatrix_has_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
    334 {
    335     LV_ASSERT_OBJ(obj, MY_CLASS);
    336 
    337     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    338     if(btn_id >= btnm->btn_cnt) return false;
    339 
    340     return ((btnm->ctrl_bits[btn_id] & ctrl) == ctrl) ? true : false;
    341 }
    342 
    343 bool lv_btnmatrix_get_one_checked(const lv_obj_t * obj)
    344 {
    345     LV_ASSERT_OBJ(obj, MY_CLASS);
    346 
    347     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    348 
    349     return btnm->one_check;
    350 }
    351 
    352 /**********************
    353  *   STATIC FUNCTIONS
    354  **********************/
    355 
    356 static void lv_btnmatrix_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
    357 {
    358     LV_UNUSED(class_p);
    359     LV_TRACE_OBJ_CREATE("begin");
    360     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
    361     btnm->btn_cnt        = 0;
    362     btnm->row_cnt        = 0;
    363     btnm->btn_id_sel     = LV_BTNMATRIX_BTN_NONE;
    364     btnm->button_areas   = NULL;
    365     btnm->ctrl_bits      = NULL;
    366     btnm->map_p          = NULL;
    367     btnm->one_check      = 0;
    368 
    369     lv_btnmatrix_set_map(obj, lv_btnmatrix_def_map);
    370 
    371     LV_TRACE_OBJ_CREATE("finished");
    372 }
    373 
    374 static void lv_btnmatrix_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
    375 {
    376     LV_TRACE_OBJ_CREATE("begin");
    377     LV_UNUSED(class_p);
    378     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
    379     lv_mem_free(btnm->button_areas);
    380     lv_mem_free(btnm->ctrl_bits);
    381     btnm->button_areas = NULL;
    382     btnm->ctrl_bits = NULL;
    383     LV_TRACE_OBJ_CREATE("finished");
    384 }
    385 
    386 static void lv_btnmatrix_event(const lv_obj_class_t * class_p, lv_event_t * e)
    387 {
    388     LV_UNUSED(class_p);
    389 
    390     lv_res_t res;
    391 
    392     /*Call the ancestor's event handler*/
    393     res = lv_obj_event_base(MY_CLASS, e);
    394     if(res != LV_RES_OK) return;
    395 
    396     lv_event_code_t code = lv_event_get_code(e);
    397     lv_obj_t * obj = lv_event_get_target(e);
    398     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
    399     lv_point_t p;
    400 
    401     if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
    402         lv_coord_t * s = lv_event_get_param(e);
    403         if(has_popovers_in_top_row(obj)) {
    404             /*reserve one row worth of extra space to account for popovers in the top row*/
    405             *s = btnm->row_cnt > 0 ? lv_obj_get_content_height(obj) / btnm->row_cnt : 0;
    406         }
    407         else {
    408             *s = 0;
    409         }
    410     }
    411     if(code == LV_EVENT_STYLE_CHANGED) {
    412         lv_btnmatrix_set_map(obj, btnm->map_p);
    413     }
    414     else if(code == LV_EVENT_SIZE_CHANGED) {
    415         lv_btnmatrix_set_map(obj, btnm->map_p);
    416     }
    417     else if(code == LV_EVENT_PRESSED) {
    418         void * param = lv_event_get_param(e);
    419         invalidate_button_area(obj, btnm->btn_id_sel);
    420 
    421         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
    422         if(indev_type == LV_INDEV_TYPE_POINTER || indev_type == LV_INDEV_TYPE_BUTTON) {
    423             uint16_t btn_pr;
    424             /*Search the pressed area*/
    425             lv_indev_get_point(param, &p);
    426             btn_pr = get_button_from_point(obj, &p);
    427             /*Handle the case where there is no button there*/
    428             if(btn_pr != LV_BTNMATRIX_BTN_NONE) {
    429                 if(button_is_inactive(btnm->ctrl_bits[btn_pr]) == false &&
    430                    button_is_hidden(btnm->ctrl_bits[btn_pr]) == false) {
    431                     btnm->btn_id_sel = btn_pr;
    432                     invalidate_button_area(obj, btnm->btn_id_sel); /*Invalidate the new area*/
    433                 }
    434             }
    435         }
    436 
    437         if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
    438             if(button_is_click_trig(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
    439                button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
    440                button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
    441                button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
    442                 uint32_t b = btnm->btn_id_sel;
    443                 res        = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
    444                 if(res != LV_RES_OK) return;
    445             }
    446         }
    447     }
    448     else if(code == LV_EVENT_PRESSING) {
    449         void * param = lv_event_get_param(e);
    450         uint16_t btn_pr = LV_BTNMATRIX_BTN_NONE;
    451         /*Search the pressed area*/
    452         lv_indev_t * indev = lv_indev_get_act();
    453         lv_indev_type_t indev_type = lv_indev_get_type(indev);
    454         if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) return;
    455 
    456         lv_indev_get_point(indev, &p);
    457         btn_pr = get_button_from_point(obj, &p);
    458         /*Invalidate to old and the new areas*/
    459         if(btn_pr != btnm->btn_id_sel) {
    460             if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
    461                 invalidate_button_area(obj, btnm->btn_id_sel);
    462             }
    463 
    464             btnm->btn_id_sel = btn_pr;
    465 
    466             lv_indev_reset_long_press(param); /*Start the log press time again on the new button*/
    467             if(btn_pr != LV_BTNMATRIX_BTN_NONE &&
    468                button_is_inactive(btnm->ctrl_bits[btn_pr]) == false &&
    469                button_is_hidden(btnm->ctrl_bits[btn_pr]) == false) {
    470                 invalidate_button_area(obj, btn_pr);
    471                 /*Send VALUE_CHANGED for the newly pressed button*/
    472                 if(button_is_click_trig(btnm->ctrl_bits[btn_pr]) == false &&
    473                    button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
    474                     uint32_t b = btn_pr;
    475                     res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
    476                     if(res != LV_RES_OK) return;
    477                 }
    478             }
    479         }
    480     }
    481     else if(code == LV_EVENT_RELEASED) {
    482         if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
    483             /*Toggle the button if enabled*/
    484             if(button_is_checkable(btnm->ctrl_bits[btnm->btn_id_sel]) &&
    485                !button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
    486                 if(button_get_checked(btnm->ctrl_bits[btnm->btn_id_sel]) && !btnm->one_check) {
    487                     btnm->ctrl_bits[btnm->btn_id_sel] &= (~LV_BTNMATRIX_CTRL_CHECKED);
    488                 }
    489                 else {
    490                     btnm->ctrl_bits[btnm->btn_id_sel] |= LV_BTNMATRIX_CTRL_CHECKED;
    491                 }
    492                 if(btnm->one_check) make_one_button_checked(obj, btnm->btn_id_sel);
    493             }
    494 
    495 
    496             if((button_is_click_trig(btnm->ctrl_bits[btnm->btn_id_sel]) == true ||
    497                 button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == true) &&
    498                button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
    499                button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
    500                 uint32_t b = btnm->btn_id_sel;
    501                 res        = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
    502                 if(res != LV_RES_OK) return;
    503             }
    504         }
    505 
    506         /*Invalidate to old pressed area*/;
    507         invalidate_button_area(obj, btnm->btn_id_sel);
    508 
    509     }
    510     else if(code == LV_EVENT_LONG_PRESSED_REPEAT) {
    511         if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
    512             if(button_is_repeat_disabled(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
    513                button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
    514                button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
    515                 uint32_t b = btnm->btn_id_sel;
    516                 res        = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
    517                 if(res != LV_RES_OK) return;
    518             }
    519         }
    520     }
    521     else if(code == LV_EVENT_PRESS_LOST) {
    522         invalidate_button_area(obj, btnm->btn_id_sel);
    523         btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
    524     }
    525     else if(code == LV_EVENT_FOCUSED) {
    526         lv_indev_t * indev = lv_event_get_param(e);
    527         lv_indev_type_t indev_type = lv_indev_get_type(indev);
    528 
    529         /*If not focused by an input device assume the last input device*/
    530         if(indev == NULL) {
    531             indev = lv_indev_get_next(NULL);
    532             indev_type = lv_indev_get_type(indev);
    533         }
    534 
    535         bool editing = lv_group_get_editing(lv_obj_get_group(obj));
    536         /*Focus the first button if there is not selected button*/
    537         if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
    538             if(indev_type == LV_INDEV_TYPE_KEYPAD || (indev_type == LV_INDEV_TYPE_ENCODER && editing)) {
    539                 uint32_t b = 0;
    540                 if(btnm->one_check) {
    541                     while(button_is_hidden(btnm->ctrl_bits[b]) || button_is_inactive(btnm->ctrl_bits[b]) ||
    542                           button_is_checked(btnm->ctrl_bits[b]) == false) b++;
    543                 }
    544                 else {
    545                     while(button_is_hidden(btnm->ctrl_bits[b]) || button_is_inactive(btnm->ctrl_bits[b])) b++;
    546                 }
    547 
    548                 btnm->btn_id_sel = b;
    549             }
    550             else {
    551                 btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
    552             }
    553         }
    554     }
    555     else if(code == LV_EVENT_DEFOCUSED || code == LV_EVENT_LEAVE) {
    556         if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) invalidate_button_area(obj, btnm->btn_id_sel);
    557         btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
    558     }
    559     else if(code == LV_EVENT_KEY) {
    560 
    561         invalidate_button_area(obj, btnm->btn_id_sel);
    562 
    563         char c = *((char *)lv_event_get_param(e));
    564         if(c == LV_KEY_RIGHT) {
    565             if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE)  btnm->btn_id_sel = 0;
    566             else btnm->btn_id_sel++;
    567             if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
    568 
    569             while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
    570                 btnm->btn_id_sel++;
    571                 if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
    572             }
    573         }
    574         else if(c == LV_KEY_LEFT) {
    575             if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) btnm->btn_id_sel = 0;
    576 
    577             if(btnm->btn_id_sel == 0) btnm->btn_id_sel = btnm->btn_cnt - 1;
    578             else if(btnm->btn_id_sel > 0) btnm->btn_id_sel--;
    579 
    580             while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
    581                 if(btnm->btn_id_sel > 0) btnm->btn_id_sel--;
    582                 else btnm->btn_id_sel = btnm->btn_cnt - 1;
    583             }
    584         }
    585         else if(c == LV_KEY_DOWN) {
    586             lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
    587             /*Find the area below the current*/
    588             if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
    589                 btnm->btn_id_sel = 0;
    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             }
    595             else {
    596                 uint16_t area_below;
    597                 lv_coord_t pr_center =
    598                     btnm->button_areas[btnm->btn_id_sel].x1 + (lv_area_get_width(&btnm->button_areas[btnm->btn_id_sel]) >> 1);
    599 
    600                 for(area_below = btnm->btn_id_sel; area_below < btnm->btn_cnt; area_below++) {
    601                     if(btnm->button_areas[area_below].y1 > btnm->button_areas[btnm->btn_id_sel].y1 &&
    602                        pr_center >= btnm->button_areas[area_below].x1 &&
    603                        pr_center <= btnm->button_areas[area_below].x2 + col_gap &&
    604                        button_is_inactive(btnm->ctrl_bits[area_below]) == false &&
    605                        button_is_hidden(btnm->ctrl_bits[area_below]) == false) {
    606                         break;
    607                     }
    608                 }
    609 
    610                 if(area_below < btnm->btn_cnt) btnm->btn_id_sel = area_below;
    611             }
    612         }
    613         else if(c == LV_KEY_UP) {
    614             lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
    615             /*Find the area below the current*/
    616             if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
    617                 btnm->btn_id_sel = 0;
    618                 while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
    619                     btnm->btn_id_sel++;
    620                     if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
    621                 }
    622             }
    623             else {
    624                 int16_t area_above;
    625                 lv_coord_t pr_center =
    626                     btnm->button_areas[btnm->btn_id_sel].x1 + (lv_area_get_width(&btnm->button_areas[btnm->btn_id_sel]) >> 1);
    627 
    628                 for(area_above = btnm->btn_id_sel; area_above >= 0; area_above--) {
    629                     if(btnm->button_areas[area_above].y1 < btnm->button_areas[btnm->btn_id_sel].y1 &&
    630                        pr_center >= btnm->button_areas[area_above].x1 - col_gap &&
    631                        pr_center <= btnm->button_areas[area_above].x2 &&
    632                        button_is_inactive(btnm->ctrl_bits[area_above]) == false &&
    633                        button_is_hidden(btnm->ctrl_bits[area_above]) == false) {
    634                         break;
    635                     }
    636                 }
    637                 if(area_above >= 0) btnm->btn_id_sel = area_above;
    638             }
    639         }
    640 
    641         invalidate_button_area(obj, btnm->btn_id_sel);
    642     }
    643     else if(code == LV_EVENT_DRAW_MAIN) {
    644         draw_main(e);
    645     }
    646 
    647 }
    648 
    649 static void draw_main(lv_event_t * e)
    650 {
    651     lv_obj_t * obj = lv_event_get_target(e);
    652     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
    653     if(btnm->btn_cnt == 0) return;
    654 
    655     lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
    656     obj->skip_trans = 1;
    657 
    658     lv_area_t area_obj;
    659     lv_obj_get_coords(obj, &area_obj);
    660 
    661     lv_area_t btn_area;
    662 
    663     uint16_t btn_i = 0;
    664     uint16_t txt_i = 0;
    665 
    666     lv_draw_rect_dsc_t draw_rect_dsc_act;
    667     lv_draw_label_dsc_t draw_label_dsc_act;
    668 
    669     lv_draw_rect_dsc_t draw_rect_dsc_def;
    670     lv_draw_label_dsc_t draw_label_dsc_def;
    671 
    672     lv_state_t state_ori = obj->state;
    673     obj->state = LV_STATE_DEFAULT;
    674     obj->skip_trans = 1;
    675     lv_draw_rect_dsc_init(&draw_rect_dsc_def);
    676     lv_draw_label_dsc_init(&draw_label_dsc_def);
    677     lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &draw_rect_dsc_def);
    678     lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &draw_label_dsc_def);
    679     obj->skip_trans = 0;
    680     obj->state = state_ori;
    681 
    682     lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
    683     lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
    684     lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    685     lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
    686 
    687 #if LV_USE_ARABIC_PERSIAN_CHARS
    688     const size_t txt_ap_size = 256 ;
    689     char * txt_ap = lv_mem_buf_get(txt_ap_size);
    690 #endif
    691 
    692     lv_obj_draw_part_dsc_t part_draw_dsc;
    693     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
    694     part_draw_dsc.part = LV_PART_ITEMS;
    695     part_draw_dsc.class_p = MY_CLASS;
    696     part_draw_dsc.type = LV_BTNMATRIX_DRAW_PART_BTN;
    697     part_draw_dsc.rect_dsc = &draw_rect_dsc_act;
    698     part_draw_dsc.label_dsc = &draw_label_dsc_act;
    699 
    700     for(btn_i = 0; btn_i < btnm->btn_cnt; btn_i++, txt_i++) {
    701         /*Search the next valid text in the map*/
    702         while(strcmp(btnm->map_p[txt_i], "\n") == 0) {
    703             txt_i++;
    704         }
    705 
    706         /*Skip hidden buttons*/
    707         if(button_is_hidden(btnm->ctrl_bits[btn_i])) continue;
    708 
    709         /*Get the state of the button*/
    710         lv_state_t btn_state = LV_STATE_DEFAULT;
    711         if(button_get_checked(btnm->ctrl_bits[btn_i])) btn_state |= LV_STATE_CHECKED;
    712 
    713         if(button_is_inactive(btnm->ctrl_bits[btn_i])) btn_state |= LV_STATE_DISABLED;
    714         else if(btn_i == btnm->btn_id_sel) {
    715             if(state_ori & LV_STATE_PRESSED) btn_state |= LV_STATE_PRESSED;
    716             if(state_ori & LV_STATE_FOCUSED) btn_state |= LV_STATE_FOCUSED;
    717             if(state_ori & LV_STATE_FOCUS_KEY) btn_state |= LV_STATE_FOCUS_KEY;
    718             if(state_ori & LV_STATE_EDITED) btn_state |= LV_STATE_EDITED;
    719         }
    720 
    721         /*Get the button's area*/
    722         lv_area_copy(&btn_area, &btnm->button_areas[btn_i]);
    723         btn_area.x1 += area_obj.x1;
    724         btn_area.y1 += area_obj.y1;
    725         btn_area.x2 += area_obj.x1;
    726         btn_area.y2 += area_obj.y1;
    727 
    728         /*Set up the draw descriptors*/
    729         if(btn_state == LV_STATE_DEFAULT) {
    730             lv_memcpy(&draw_rect_dsc_act, &draw_rect_dsc_def, sizeof(lv_draw_rect_dsc_t));
    731             lv_memcpy(&draw_label_dsc_act, &draw_label_dsc_def, sizeof(lv_draw_label_dsc_t));
    732         }
    733         /*In other cases get the styles directly without caching them*/
    734         else {
    735             obj->state = btn_state;
    736             obj->skip_trans = 1;
    737             lv_draw_rect_dsc_init(&draw_rect_dsc_act);
    738             lv_draw_label_dsc_init(&draw_label_dsc_act);
    739             lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &draw_rect_dsc_act);
    740             lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &draw_label_dsc_act);
    741             obj->state = state_ori;
    742             obj->skip_trans = 0;
    743         }
    744 
    745         bool recolor = button_is_recolor(btnm->ctrl_bits[btn_i]);
    746         if(recolor) draw_label_dsc_act.flag |= LV_TEXT_FLAG_RECOLOR;
    747         else draw_label_dsc_act.flag &= ~LV_TEXT_FLAG_RECOLOR;
    748 
    749 
    750         part_draw_dsc.draw_area = &btn_area;
    751         part_draw_dsc.id = btn_i;
    752         lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
    753 
    754         /*Remove borders on the edges if `LV_BORDER_SIDE_INTERNAL`*/
    755         if(draw_rect_dsc_act.border_side & LV_BORDER_SIDE_INTERNAL) {
    756             draw_rect_dsc_act.border_side = LV_BORDER_SIDE_FULL;
    757             if(btn_area.x1 == obj->coords.x1 + pleft) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_LEFT;
    758             if(btn_area.x2 == obj->coords.x2 - pright) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_RIGHT;
    759             if(btn_area.y1 == obj->coords.y1 + ptop) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_TOP;
    760             if(btn_area.y2 == obj->coords.y2 - pbottom) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_BOTTOM;
    761         }
    762 
    763         lv_coord_t btn_height = lv_area_get_height(&btn_area);
    764 
    765         if((btn_state & LV_STATE_PRESSED) && (btnm->ctrl_bits[btn_i] & LV_BTNMATRIX_CTRL_POPOVER)) {
    766             /*Push up the upper boundary of the btn area to create the popover*/
    767             btn_area.y1 -= btn_height;
    768         }
    769 
    770         /*Draw the background*/
    771         lv_draw_rect(draw_ctx, &draw_rect_dsc_act, &btn_area);
    772 
    773         /*Calculate the size of the text*/
    774         const lv_font_t * font = draw_label_dsc_act.font;
    775         lv_coord_t letter_space = draw_label_dsc_act.letter_space;
    776         lv_coord_t line_space = draw_label_dsc_act.line_space;
    777         const char * txt = btnm->map_p[txt_i];
    778 
    779 #if LV_USE_ARABIC_PERSIAN_CHARS
    780         /*Get the size of the Arabic text and process it*/
    781         size_t len_ap = _lv_txt_ap_calc_bytes_cnt(txt);
    782         if(len_ap < txt_ap_size) {
    783             _lv_txt_ap_proc(txt, txt_ap);
    784             txt = txt_ap;
    785         }
    786 #endif
    787         lv_point_t txt_size;
    788         lv_txt_get_size(&txt_size, txt, font, letter_space,
    789                         line_space, lv_area_get_width(&area_obj), draw_label_dsc_act.flag);
    790 
    791         btn_area.x1 += (lv_area_get_width(&btn_area) - txt_size.x) / 2;
    792         btn_area.y1 += (lv_area_get_height(&btn_area) - txt_size.y) / 2;
    793         btn_area.x2 = btn_area.x1 + txt_size.x;
    794         btn_area.y2 = btn_area.y1 + txt_size.y;
    795 
    796         if((btn_state & LV_STATE_PRESSED) && (btnm->ctrl_bits[btn_i] & LV_BTNMATRIX_CTRL_POPOVER)) {
    797             /*Push up the button text into the popover*/
    798             btn_area.y1 -= btn_height / 2;
    799             btn_area.y2 -= btn_height / 2;
    800         }
    801 
    802         /*Draw the text*/
    803         lv_draw_label(draw_ctx, &draw_label_dsc_act, &btn_area, txt, NULL);
    804 
    805         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
    806     }
    807 
    808     obj->skip_trans = 0;
    809 #if LV_USE_ARABIC_PERSIAN_CHARS
    810     lv_mem_buf_release(txt_ap);
    811 #endif
    812 }
    813 /**
    814  * Create the required number of buttons and control bytes according to a map
    815  * @param obj pointer to button matrix object
    816  * @param map_p pointer to a string array
    817  */
    818 static void allocate_btn_areas_and_controls(const lv_obj_t * obj, const char ** map)
    819 {
    820     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
    821     btnm->row_cnt = 1;
    822     /*Count the buttons in the map*/
    823     uint16_t btn_cnt = 0;
    824     uint16_t i       = 0;
    825     while(map[i] && map[i][0] != '\0') {
    826         if(strcmp(map[i], "\n") != 0) { /*Do not count line breaks*/
    827             btn_cnt++;
    828         }
    829         else {
    830             btnm->row_cnt++;
    831         }
    832         i++;
    833     }
    834 
    835     /*Do not allocate memory for the same amount of buttons*/
    836     if(btn_cnt == btnm->btn_cnt) return;
    837 
    838     if(btnm->button_areas != NULL) {
    839         lv_mem_free(btnm->button_areas);
    840         btnm->button_areas = NULL;
    841     }
    842     if(btnm->ctrl_bits != NULL) {
    843         lv_mem_free(btnm->ctrl_bits);
    844         btnm->ctrl_bits = NULL;
    845     }
    846 
    847     btnm->button_areas = lv_mem_alloc(sizeof(lv_area_t) * btn_cnt);
    848     LV_ASSERT_MALLOC(btnm->button_areas);
    849     btnm->ctrl_bits = lv_mem_alloc(sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
    850     LV_ASSERT_MALLOC(btnm->ctrl_bits);
    851     if(btnm->button_areas == NULL || btnm->ctrl_bits == NULL) btn_cnt = 0;
    852 
    853     lv_memset_00(btnm->ctrl_bits, sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
    854 
    855     btnm->btn_cnt = btn_cnt;
    856 }
    857 
    858 /**
    859  * Get the width of a button in units (default is 1).
    860  * @param ctrl_bits least significant 3 bits used (1..7 valid values)
    861  * @return the width of the button in units
    862  */
    863 static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits)
    864 {
    865     uint8_t w = ctrl_bits & LV_BTNMATRIX_WIDTH_MASK;
    866     return w != 0 ? w : 1;
    867 }
    868 
    869 static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits)
    870 {
    871     return (ctrl_bits & LV_BTNMATRIX_CTRL_HIDDEN) ? true : false;
    872 }
    873 
    874 static bool button_is_checked(lv_btnmatrix_ctrl_t ctrl_bits)
    875 {
    876     return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKED) ? true : false;
    877 }
    878 
    879 static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits)
    880 {
    881     return (ctrl_bits & LV_BTNMATRIX_CTRL_NO_REPEAT) ? true : false;
    882 }
    883 
    884 static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits)
    885 {
    886     return (ctrl_bits & LV_BTNMATRIX_CTRL_DISABLED) ? true : false;
    887 }
    888 
    889 static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits)
    890 {
    891     return (ctrl_bits & LV_BTNMATRIX_CTRL_CLICK_TRIG) ? true : false;
    892 }
    893 
    894 static bool button_is_popover(lv_btnmatrix_ctrl_t ctrl_bits)
    895 {
    896     return (ctrl_bits & LV_BTNMATRIX_CTRL_POPOVER) ? true : false;
    897 }
    898 
    899 static bool button_is_checkable(lv_btnmatrix_ctrl_t ctrl_bits)
    900 {
    901     return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKABLE) ? true : false;
    902 }
    903 
    904 static bool button_get_checked(lv_btnmatrix_ctrl_t ctrl_bits)
    905 {
    906     return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKED) ? true : false;
    907 }
    908 
    909 static bool button_is_recolor(lv_btnmatrix_ctrl_t ctrl_bits)
    910 {
    911     return (ctrl_bits & LV_BTNMATRIX_CTRL_RECOLOR) ? true : false;
    912 }
    913 /**
    914  * Gives the button id of a button under a given point
    915  * @param obj pointer to a button matrix object
    916  * @param p a point with absolute coordinates
    917  * @return the id of the button or LV_BTNMATRIX_BTN_NONE.
    918  */
    919 static uint16_t get_button_from_point(lv_obj_t * obj, lv_point_t * p)
    920 {
    921     lv_area_t obj_cords;
    922     lv_area_t btn_area;
    923     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    924     uint16_t i;
    925     lv_obj_get_coords(obj, &obj_cords);
    926 
    927     lv_coord_t w = lv_obj_get_width(obj);
    928     lv_coord_t h = lv_obj_get_height(obj);
    929     lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    930     lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
    931     lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
    932     lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
    933     lv_coord_t prow = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
    934     lv_coord_t pcol = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
    935 
    936     /*Get the half gap. Button look larger with this value. (+1 for rounding error)*/
    937     prow = (prow / 2) + 1 + (prow & 1);
    938     pcol = (pcol / 2) + 1 + (pcol & 1);
    939 
    940     prow = LV_MIN(prow, BTN_EXTRA_CLICK_AREA_MAX);
    941     pcol = LV_MIN(pcol, BTN_EXTRA_CLICK_AREA_MAX);
    942     pright = LV_MIN(pright, BTN_EXTRA_CLICK_AREA_MAX);
    943     ptop = LV_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
    944     pbottom = LV_MIN(pbottom, BTN_EXTRA_CLICK_AREA_MAX);
    945 
    946     for(i = 0; i < btnm->btn_cnt; i++) {
    947         lv_area_copy(&btn_area, &btnm->button_areas[i]);
    948         if(btn_area.x1 <= pleft) btn_area.x1 += obj_cords.x1 - LV_MIN(pleft, BTN_EXTRA_CLICK_AREA_MAX);
    949         else btn_area.x1 += obj_cords.x1 - pcol;
    950 
    951         if(btn_area.y1 <= ptop) btn_area.y1 += obj_cords.y1 - LV_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
    952         else btn_area.y1 += obj_cords.y1 - prow;
    953 
    954         if(btn_area.x2 >= w - pright - 2) btn_area.x2 += obj_cords.x1 + LV_MIN(pright,
    955                                                                                    BTN_EXTRA_CLICK_AREA_MAX);  /*-2 for rounding error*/
    956         else btn_area.x2 += obj_cords.x1 + pcol;
    957 
    958         if(btn_area.y2 >= h - pbottom - 2) btn_area.y2 += obj_cords.y1 + LV_MIN(pbottom,
    959                                                                                     BTN_EXTRA_CLICK_AREA_MAX); /*-2 for rounding error*/
    960         else btn_area.y2 += obj_cords.y1 + prow;
    961 
    962         if(_lv_area_is_point_on(&btn_area, p, 0) != false) {
    963             break;
    964         }
    965     }
    966 
    967     if(i == btnm->btn_cnt) i = LV_BTNMATRIX_BTN_NONE;
    968 
    969     return i;
    970 }
    971 
    972 static void invalidate_button_area(const lv_obj_t * obj, uint16_t btn_idx)
    973 {
    974     if(btn_idx == LV_BTNMATRIX_BTN_NONE) return;
    975 
    976     lv_area_t btn_area;
    977     lv_area_t obj_area;
    978 
    979     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
    980     if(btn_idx >= btnm->btn_cnt) return;
    981 
    982     lv_area_copy(&btn_area, &btnm->button_areas[btn_idx]);
    983     lv_obj_get_coords(obj, &obj_area);
    984 
    985     /*The buttons might have outline and shadow so make the invalidation larger with the gaps between the buttons.
    986      *It assumes that the outline or shadow is smaller than the gaps*/
    987     lv_coord_t row_gap = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
    988     lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
    989 
    990     /*Be sure to have a minimal extra space if row/col_gap is small*/
    991     lv_coord_t dpi = lv_disp_get_dpi(lv_obj_get_disp(obj));
    992     row_gap = LV_MAX(row_gap, dpi / 10);
    993     col_gap = LV_MAX(col_gap, dpi / 10);
    994 
    995     /*Convert relative coordinates to absolute*/
    996     btn_area.x1 += obj_area.x1 - row_gap;
    997     btn_area.y1 += obj_area.y1 - col_gap;
    998     btn_area.x2 += obj_area.x1 + row_gap;
    999     btn_area.y2 += obj_area.y1 + col_gap;
   1000 
   1001     if((btn_idx == btnm->btn_id_sel) && (btnm->ctrl_bits[btn_idx] & LV_BTNMATRIX_CTRL_POPOVER)) {
   1002         /*Push up the upper boundary of the btn area to also invalidate the popover*/
   1003         btn_area.y1 -= lv_area_get_height(&btn_area);
   1004     }
   1005 
   1006     lv_obj_invalidate_area(obj, &btn_area);
   1007 }
   1008 
   1009 /**
   1010  * Enforces a single button being toggled on the button matrix.
   1011  * It simply clears the toggle flag on other buttons.
   1012  * @param obj Button matrix object
   1013  * @param btn_idx Button that should remain toggled
   1014  */
   1015 static void make_one_button_checked(lv_obj_t * obj, uint16_t btn_idx)
   1016 {
   1017     /*Save whether the button was toggled*/
   1018     bool was_toggled = lv_btnmatrix_has_btn_ctrl(obj, btn_idx, LV_BTNMATRIX_CTRL_CHECKED);
   1019 
   1020     lv_btnmatrix_clear_btn_ctrl_all(obj, LV_BTNMATRIX_CTRL_CHECKED);
   1021 
   1022     if(was_toggled) lv_btnmatrix_set_btn_ctrl(obj, btn_idx, LV_BTNMATRIX_CTRL_CHECKED);
   1023 }
   1024 
   1025 /**
   1026  * Check if any of the buttons in the first row has the LV_BTNMATRIX_CTRL_POPOVER control flag set.
   1027  * @param obj Button matrix object
   1028  * @return true if at least one button has the flag, false otherwise
   1029  */
   1030 static bool has_popovers_in_top_row(lv_obj_t * obj)
   1031 {
   1032     lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
   1033 
   1034     if(btnm->row_cnt <= 0) {
   1035         return false;
   1036     }
   1037 
   1038     const char ** map_row = btnm->map_p;
   1039     uint16_t btn_cnt = 0;
   1040 
   1041     while(map_row[btn_cnt] && strcmp(map_row[btn_cnt], "\n") != 0 && map_row[btn_cnt][0] != '\0') {
   1042         if(button_is_popover(btnm->ctrl_bits[btn_cnt])) {
   1043             return true;
   1044         }
   1045         btn_cnt++;
   1046     }
   1047 
   1048     return false;
   1049 }
   1050 
   1051 #endif