You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
ndg/lib/lvgl/src/widgets/lv_btnmatrix.c

1049 lines
37 KiB
C

/**
* @file lv_btnmatrix.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_btnmatrix.h"
#if LV_USE_BTNMATRIX != 0
#include "../misc/lv_assert.h"
#include "../core/lv_indev.h"
#include "../core/lv_group.h"
#include "../draw/lv_draw.h"
#include "../core/lv_refr.h"
#include "../misc/lv_txt.h"
#include "../misc/lv_txt_ap.h"
/*********************
* DEFINES
*********************/
#define MY_CLASS &lv_btnmatrix_class
#define BTN_EXTRA_CLICK_AREA_MAX (LV_DPI_DEF / 10)
#define LV_BTNMATRIX_WIDTH_MASK 0x0007
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_btnmatrix_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_btnmatrix_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_btnmatrix_event(const lv_obj_class_t * class_p, lv_event_t * e);
static void draw_main(lv_event_t * e);
static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_is_checked(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_is_popover(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_is_checkable(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_is_recolor(lv_btnmatrix_ctrl_t ctrl_bits);
static bool button_get_checked(lv_btnmatrix_ctrl_t ctrl_bits);
static uint16_t get_button_from_point(lv_obj_t * obj, lv_point_t * p);
static void allocate_btn_areas_and_controls(const lv_obj_t * obj, const char ** map);
static void invalidate_button_area(const lv_obj_t * obj, uint16_t btn_idx);
static void make_one_button_checked(lv_obj_t * obj, uint16_t btn_idx);
static bool has_popovers_in_top_row(lv_obj_t * obj);
/**********************
* STATIC VARIABLES
**********************/
static const char * lv_btnmatrix_def_map[] = {"Btn1", "Btn2", "Btn3", "\n", "Btn4", "Btn5", ""};
const lv_obj_class_t lv_btnmatrix_class = {
.constructor_cb = lv_btnmatrix_constructor,
.destructor_cb = lv_btnmatrix_destructor,
.event_cb = lv_btnmatrix_event,
.width_def = LV_DPI_DEF * 2,
.height_def = LV_DPI_DEF,
.instance_size = sizeof(lv_btnmatrix_t),
.editable = LV_OBJ_CLASS_EDITABLE_TRUE,
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
.base_class = &lv_obj_class
};
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_obj_t * lv_btnmatrix_create(lv_obj_t * parent)
{
LV_LOG_INFO("begin");
lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
lv_obj_class_init_obj(obj);
return obj;
}
/*=====================
* Setter functions
*====================*/
void lv_btnmatrix_set_map(lv_obj_t * obj, const char * map[])
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(map == NULL) return;
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
/*Analyze the map and create the required number of buttons*/
allocate_btn_areas_and_controls(obj, map);
btnm->map_p = map;
lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
/*Set size and positions of the buttons*/
lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
lv_coord_t prow = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
lv_coord_t pcol = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
lv_coord_t max_w = lv_obj_get_content_width(obj);
lv_coord_t max_h = lv_obj_get_content_height(obj);
/*Calculate the position of each row*/
lv_coord_t max_h_no_gap = max_h - (prow * (btnm->row_cnt - 1));
/*Count the units and the buttons in a line
*(A button can be 1,2,3... unit wide)*/
uint32_t txt_tot_i = 0; /*Act. index in the str map*/
uint32_t btn_tot_i = 0; /*Act. index of button areas*/
const char ** map_row = map;
/*Count the units and the buttons in a line*/
uint32_t row;
for(row = 0; row < btnm->row_cnt; row++) {
uint16_t unit_cnt = 0; /*Number of units in a row*/
uint16_t btn_cnt = 0; /*Number of buttons in a row*/
/*Count the buttons and units in this row*/
while(map_row[btn_cnt] && strcmp(map_row[btn_cnt], "\n") != 0 && map_row[btn_cnt][0] != '\0') {
unit_cnt += get_button_width(btnm->ctrl_bits[btn_tot_i + btn_cnt]);
btn_cnt++;
}
/*Only deal with the non empty lines*/
if(btn_cnt == 0) {
map_row = &map_row[btn_cnt + 1]; /*Set the map to the next row*/
continue;
}
lv_coord_t row_y1 = ptop + (max_h_no_gap * row) / btnm->row_cnt + row * prow;
lv_coord_t row_y2 = ptop + (max_h_no_gap * (row + 1)) / btnm->row_cnt + row * prow - 1;
/*Set the button size and positions*/
lv_coord_t max_w_no_gap = max_w - (pcol * (btn_cnt - 1));
if(max_w_no_gap < 0) max_w_no_gap = 0;
uint32_t row_unit_cnt = 0; /*The current unit position in the row*/
uint32_t btn;
for(btn = 0; btn < btn_cnt; btn++, btn_tot_i++, txt_tot_i++) {
uint32_t btn_u = get_button_width(btnm->ctrl_bits[btn_tot_i]);
lv_coord_t btn_x1 = (max_w_no_gap * row_unit_cnt) / unit_cnt + btn * pcol;
lv_coord_t btn_x2 = (max_w_no_gap * (row_unit_cnt + btn_u)) / unit_cnt + btn * pcol - 1;
/*If RTL start from the right*/
if(base_dir == LV_BASE_DIR_RTL) {
lv_coord_t tmp = btn_x1;
btn_x1 = btn_x2;
btn_x2 = tmp;
btn_x1 = max_w - btn_x1;
btn_x2 = max_w - btn_x2;
}
btn_x1 += pleft;
btn_x2 += pleft;
lv_area_set(&btnm->button_areas[btn_tot_i], btn_x1, row_y1, btn_x2, row_y2);
row_unit_cnt += btn_u;
}
map_row = &map_row[btn_cnt + 1]; /*Set the map to the next line*/
}
/*Popovers in the top row will draw outside the widget and the extended draw size depends on
*the row height which may have changed when setting the new map*/
lv_obj_refresh_ext_draw_size(obj);
lv_obj_invalidate(obj);
}
void lv_btnmatrix_set_ctrl_map(lv_obj_t * obj, const lv_btnmatrix_ctrl_t ctrl_map[])
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
lv_memcpy(btnm->ctrl_bits, ctrl_map, sizeof(lv_btnmatrix_ctrl_t) * btnm->btn_cnt);
lv_btnmatrix_set_map(obj, btnm->map_p);
}
void lv_btnmatrix_set_selected_btn(lv_obj_t * obj, uint16_t btn_id)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
if(btn_id >= btnm->btn_cnt && btn_id != LV_BTNMATRIX_BTN_NONE) return;
invalidate_button_area(obj, btnm->btn_id_sel);
btnm->btn_id_sel = btn_id;
invalidate_button_area(obj, btn_id);
}
void lv_btnmatrix_set_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
if(btn_id >= btnm->btn_cnt) return;
if(btnm->one_check && (ctrl & LV_BTNMATRIX_CTRL_CHECKED)) {
lv_btnmatrix_clear_btn_ctrl_all(obj, LV_BTNMATRIX_CTRL_CHECKED);
}
btnm->ctrl_bits[btn_id] |= ctrl;
invalidate_button_area(obj, btn_id);
if(ctrl & LV_BTNMATRIX_CTRL_POPOVER) {
lv_obj_refresh_ext_draw_size(obj);
}
}
void lv_btnmatrix_clear_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
if(btn_id >= btnm->btn_cnt) return;
btnm->ctrl_bits[btn_id] &= (~ctrl);
invalidate_button_area(obj, btn_id);
if(ctrl & LV_BTNMATRIX_CTRL_POPOVER) {
lv_obj_refresh_ext_draw_size(obj);
}
}
void lv_btnmatrix_set_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
uint16_t i;
for(i = 0; i < btnm->btn_cnt; i++) {
lv_btnmatrix_set_btn_ctrl(obj, i, ctrl);
}
}
void lv_btnmatrix_clear_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
uint16_t i;
for(i = 0; i < btnm->btn_cnt; i++) {
lv_btnmatrix_clear_btn_ctrl(obj, i, ctrl);
}
}
void lv_btnmatrix_set_btn_width(lv_obj_t * obj, uint16_t btn_id, uint8_t width)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
if(btn_id >= btnm->btn_cnt) return;
btnm->ctrl_bits[btn_id] &= (~LV_BTNMATRIX_WIDTH_MASK);
btnm->ctrl_bits[btn_id] |= (LV_BTNMATRIX_WIDTH_MASK & width);
lv_btnmatrix_set_map(obj, btnm->map_p);
}
void lv_btnmatrix_set_one_checked(lv_obj_t * obj, bool en)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
btnm->one_check = en;
/*If more than one button is toggled only the first one should be*/
make_one_button_checked(obj, 0);
}
/*=====================
* Getter functions
*====================*/
const char ** lv_btnmatrix_get_map(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
return btnm->map_p;
}
uint16_t lv_btnmatrix_get_selected_btn(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
return btnm->btn_id_sel;
}
const char * lv_btnmatrix_get_btn_text(const lv_obj_t * obj, uint16_t btn_id)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(btn_id == LV_BTNMATRIX_BTN_NONE) return NULL;
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
if(btn_id > btnm->btn_cnt) return NULL;
uint16_t txt_i = 0;
uint16_t btn_i = 0;
/*Search the text of btnm->btn_pr the buttons text in the map
*Skip "\n"-s*/
while(btn_i != btn_id) {
btn_i++;
txt_i++;
if(strcmp(btnm->map_p[txt_i], "\n") == 0) txt_i++;
}
if(btn_i == btnm->btn_cnt) return NULL;
return btnm->map_p[txt_i];
}
bool lv_btnmatrix_has_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
if(btn_id >= btnm->btn_cnt) return false;
return ((btnm->ctrl_bits[btn_id] & ctrl) == ctrl) ? true : false;
}
bool lv_btnmatrix_get_one_checked(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
return btnm->one_check;
}
/**********************
* STATIC FUNCTIONS
**********************/
static void lv_btnmatrix_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
btnm->btn_cnt = 0;
btnm->row_cnt = 0;
btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
btnm->button_areas = NULL;
btnm->ctrl_bits = NULL;
btnm->map_p = NULL;
btnm->one_check = 0;
lv_btnmatrix_set_map(obj, lv_btnmatrix_def_map);
LV_TRACE_OBJ_CREATE("finished");
}
static void lv_btnmatrix_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_TRACE_OBJ_CREATE("begin");
LV_UNUSED(class_p);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
lv_mem_free(btnm->button_areas);
lv_mem_free(btnm->ctrl_bits);
btnm->button_areas = NULL;
btnm->ctrl_bits = NULL;
LV_TRACE_OBJ_CREATE("finished");
}
static void lv_btnmatrix_event(const lv_obj_class_t * class_p, lv_event_t * e)
{
LV_UNUSED(class_p);
lv_res_t res;
/*Call the ancestor's event handler*/
res = lv_obj_event_base(MY_CLASS, e);
if(res != LV_RES_OK) return;
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = lv_event_get_target(e);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
lv_point_t p;
if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
if(has_popovers_in_top_row(obj)) {
/*reserve one row worth of extra space to account for popovers in the top row*/
lv_coord_t s = btnm->row_cnt > 0 ? lv_obj_get_content_height(obj) / btnm->row_cnt : 0;
lv_event_set_ext_draw_size(e, s);
}
}
if(code == LV_EVENT_STYLE_CHANGED) {
lv_btnmatrix_set_map(obj, btnm->map_p);
}
else if(code == LV_EVENT_SIZE_CHANGED) {
lv_btnmatrix_set_map(obj, btnm->map_p);
}
else if(code == LV_EVENT_PRESSED) {
void * param = lv_event_get_param(e);
invalidate_button_area(obj, btnm->btn_id_sel);
lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
if(indev_type == LV_INDEV_TYPE_POINTER || indev_type == LV_INDEV_TYPE_BUTTON) {
uint16_t btn_pr;
/*Search the pressed area*/
lv_indev_get_point(param, &p);
btn_pr = get_button_from_point(obj, &p);
/*Handle the case where there is no button there*/
if(btn_pr != LV_BTNMATRIX_BTN_NONE) {
if(button_is_inactive(btnm->ctrl_bits[btn_pr]) == false &&
button_is_hidden(btnm->ctrl_bits[btn_pr]) == false) {
btnm->btn_id_sel = btn_pr;
invalidate_button_area(obj, btnm->btn_id_sel); /*Invalidate the new area*/
}
}
}
if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
if(button_is_click_trig(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
uint32_t b = btnm->btn_id_sel;
res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
if(res != LV_RES_OK) return;
}
}
}
else if(code == LV_EVENT_PRESSING) {
void * param = lv_event_get_param(e);
uint16_t btn_pr = LV_BTNMATRIX_BTN_NONE;
/*Search the pressed area*/
lv_indev_t * indev = lv_indev_get_act();
lv_indev_type_t indev_type = lv_indev_get_type(indev);
if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) return;
lv_indev_get_point(indev, &p);
btn_pr = get_button_from_point(obj, &p);
/*Invalidate to old and the new areas*/
if(btn_pr != btnm->btn_id_sel) {
if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
invalidate_button_area(obj, btnm->btn_id_sel);
}
btnm->btn_id_sel = btn_pr;
lv_indev_reset_long_press(param); /*Start the log press time again on the new button*/
if(btn_pr != LV_BTNMATRIX_BTN_NONE &&
button_is_inactive(btnm->ctrl_bits[btn_pr]) == false &&
button_is_hidden(btnm->ctrl_bits[btn_pr]) == false) {
invalidate_button_area(obj, btn_pr);
/*Send VALUE_CHANGED for the newly pressed button*/
if(button_is_click_trig(btnm->ctrl_bits[btn_pr]) == false &&
button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
uint32_t b = btn_pr;
res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
if(res != LV_RES_OK) return;
}
}
}
}
else if(code == LV_EVENT_RELEASED) {
if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
/*Toggle the button if enabled*/
if(button_is_checkable(btnm->ctrl_bits[btnm->btn_id_sel]) &&
!button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
if(button_get_checked(btnm->ctrl_bits[btnm->btn_id_sel]) && !btnm->one_check) {
btnm->ctrl_bits[btnm->btn_id_sel] &= (~LV_BTNMATRIX_CTRL_CHECKED);
}
else {
btnm->ctrl_bits[btnm->btn_id_sel] |= LV_BTNMATRIX_CTRL_CHECKED;
}
if(btnm->one_check) make_one_button_checked(obj, btnm->btn_id_sel);
}
if((button_is_click_trig(btnm->ctrl_bits[btnm->btn_id_sel]) == true ||
button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == true) &&
button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
uint32_t b = btnm->btn_id_sel;
res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
if(res != LV_RES_OK) return;
}
}
/*Invalidate to old pressed area*/;
invalidate_button_area(obj, btnm->btn_id_sel);
}
else if(code == LV_EVENT_LONG_PRESSED_REPEAT) {
if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
if(button_is_repeat_disabled(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
uint32_t b = btnm->btn_id_sel;
res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
if(res != LV_RES_OK) return;
}
}
}
else if(code == LV_EVENT_PRESS_LOST) {
invalidate_button_area(obj, btnm->btn_id_sel);
btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
}
else if(code == LV_EVENT_FOCUSED) {
lv_indev_t * indev = lv_event_get_param(e);
lv_indev_type_t indev_type = lv_indev_get_type(indev);
/*If not focused by an input device assume the last input device*/
if(indev == NULL) {
indev = lv_indev_get_next(NULL);
indev_type = lv_indev_get_type(indev);
}
bool editing = lv_group_get_editing(lv_obj_get_group(obj));
/*Focus the first button if there is not selected button*/
if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
if(indev_type == LV_INDEV_TYPE_KEYPAD || (indev_type == LV_INDEV_TYPE_ENCODER && editing)) {
uint32_t b = 0;
if(btnm->one_check) {
while(button_is_hidden(btnm->ctrl_bits[b]) || button_is_inactive(btnm->ctrl_bits[b]) ||
button_is_checked(btnm->ctrl_bits[b]) == false) b++;
}
else {
while(button_is_hidden(btnm->ctrl_bits[b]) || button_is_inactive(btnm->ctrl_bits[b])) b++;
}
btnm->btn_id_sel = b;
}
else {
btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
}
}
}
else if(code == LV_EVENT_DEFOCUSED || code == LV_EVENT_LEAVE) {
if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) invalidate_button_area(obj, btnm->btn_id_sel);
btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
}
else if(code == LV_EVENT_KEY) {
invalidate_button_area(obj, btnm->btn_id_sel);
char c = *((char *)lv_event_get_param(e));
if(c == LV_KEY_RIGHT) {
if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) btnm->btn_id_sel = 0;
else btnm->btn_id_sel++;
if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
btnm->btn_id_sel++;
if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
}
}
else if(c == LV_KEY_LEFT) {
if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) btnm->btn_id_sel = 0;
if(btnm->btn_id_sel == 0) btnm->btn_id_sel = btnm->btn_cnt - 1;
else if(btnm->btn_id_sel > 0) btnm->btn_id_sel--;
while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
if(btnm->btn_id_sel > 0) btnm->btn_id_sel--;
else btnm->btn_id_sel = btnm->btn_cnt - 1;
}
}
else if(c == LV_KEY_DOWN) {
lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
/*Find the area below the current*/
if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
btnm->btn_id_sel = 0;
while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
btnm->btn_id_sel++;
if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
}
}
else {
uint16_t area_below;
lv_coord_t pr_center =
btnm->button_areas[btnm->btn_id_sel].x1 + (lv_area_get_width(&btnm->button_areas[btnm->btn_id_sel]) >> 1);
for(area_below = btnm->btn_id_sel; area_below < btnm->btn_cnt; area_below++) {
if(btnm->button_areas[area_below].y1 > btnm->button_areas[btnm->btn_id_sel].y1 &&
pr_center >= btnm->button_areas[area_below].x1 &&
pr_center <= btnm->button_areas[area_below].x2 + col_gap &&
button_is_inactive(btnm->ctrl_bits[area_below]) == false &&
button_is_hidden(btnm->ctrl_bits[area_below]) == false) {
break;
}
}
if(area_below < btnm->btn_cnt) btnm->btn_id_sel = area_below;
}
}
else if(c == LV_KEY_UP) {
lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
/*Find the area below the current*/
if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
btnm->btn_id_sel = 0;
while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
btnm->btn_id_sel++;
if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
}
}
else {
int16_t area_above;
lv_coord_t pr_center =
btnm->button_areas[btnm->btn_id_sel].x1 + (lv_area_get_width(&btnm->button_areas[btnm->btn_id_sel]) >> 1);
for(area_above = btnm->btn_id_sel; area_above >= 0; area_above--) {
if(btnm->button_areas[area_above].y1 < btnm->button_areas[btnm->btn_id_sel].y1 &&
pr_center >= btnm->button_areas[area_above].x1 - col_gap &&
pr_center <= btnm->button_areas[area_above].x2 &&
button_is_inactive(btnm->ctrl_bits[area_above]) == false &&
button_is_hidden(btnm->ctrl_bits[area_above]) == false) {
break;
}
}
if(area_above >= 0) btnm->btn_id_sel = area_above;
}
}
invalidate_button_area(obj, btnm->btn_id_sel);
}
else if(code == LV_EVENT_DRAW_MAIN) {
draw_main(e);
}
}
static void draw_main(lv_event_t * e)
{
lv_obj_t * obj = lv_event_get_target(e);
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
if(btnm->btn_cnt == 0) return;
lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
obj->skip_trans = 1;
lv_area_t area_obj;
lv_obj_get_coords(obj, &area_obj);
lv_area_t btn_area;
uint16_t btn_i = 0;
uint16_t txt_i = 0;
lv_draw_rect_dsc_t draw_rect_dsc_act;
lv_draw_label_dsc_t draw_label_dsc_act;
lv_draw_rect_dsc_t draw_rect_dsc_def;
lv_draw_label_dsc_t draw_label_dsc_def;
lv_state_t state_ori = obj->state;
obj->state = LV_STATE_DEFAULT;
obj->skip_trans = 1;
lv_draw_rect_dsc_init(&draw_rect_dsc_def);
lv_draw_label_dsc_init(&draw_label_dsc_def);
lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &draw_rect_dsc_def);
lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &draw_label_dsc_def);
obj->skip_trans = 0;
obj->state = state_ori;
lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
#if LV_USE_ARABIC_PERSIAN_CHARS
const size_t txt_ap_size = 256 ;
char * txt_ap = lv_mem_buf_get(txt_ap_size);
#endif
lv_obj_draw_part_dsc_t part_draw_dsc;
lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
part_draw_dsc.part = LV_PART_ITEMS;
part_draw_dsc.class_p = MY_CLASS;
part_draw_dsc.type = LV_BTNMATRIX_DRAW_PART_BTN;
part_draw_dsc.rect_dsc = &draw_rect_dsc_act;
part_draw_dsc.label_dsc = &draw_label_dsc_act;
for(btn_i = 0; btn_i < btnm->btn_cnt; btn_i++, txt_i++) {
/*Search the next valid text in the map*/
while(strcmp(btnm->map_p[txt_i], "\n") == 0) {
txt_i++;
}
/*Skip hidden buttons*/
if(button_is_hidden(btnm->ctrl_bits[btn_i])) continue;
/*Get the state of the button*/
lv_state_t btn_state = LV_STATE_DEFAULT;
if(button_get_checked(btnm->ctrl_bits[btn_i])) btn_state |= LV_STATE_CHECKED;
if(button_is_inactive(btnm->ctrl_bits[btn_i])) btn_state |= LV_STATE_DISABLED;
else if(btn_i == btnm->btn_id_sel) {
if(state_ori & LV_STATE_PRESSED) btn_state |= LV_STATE_PRESSED;
if(state_ori & LV_STATE_FOCUSED) btn_state |= LV_STATE_FOCUSED;
if(state_ori & LV_STATE_FOCUS_KEY) btn_state |= LV_STATE_FOCUS_KEY;
if(state_ori & LV_STATE_EDITED) btn_state |= LV_STATE_EDITED;
}
/*Get the button's area*/
lv_area_copy(&btn_area, &btnm->button_areas[btn_i]);
btn_area.x1 += area_obj.x1;
btn_area.y1 += area_obj.y1;
btn_area.x2 += area_obj.x1;
btn_area.y2 += area_obj.y1;
/*Set up the draw descriptors*/
if(btn_state == LV_STATE_DEFAULT) {
lv_memcpy(&draw_rect_dsc_act, &draw_rect_dsc_def, sizeof(lv_draw_rect_dsc_t));
lv_memcpy(&draw_label_dsc_act, &draw_label_dsc_def, sizeof(lv_draw_label_dsc_t));
}
/*In other cases get the styles directly without caching them*/
else {
obj->state = btn_state;
obj->skip_trans = 1;
lv_draw_rect_dsc_init(&draw_rect_dsc_act);
lv_draw_label_dsc_init(&draw_label_dsc_act);
lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &draw_rect_dsc_act);
lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &draw_label_dsc_act);
obj->state = state_ori;
obj->skip_trans = 0;
}
bool recolor = button_is_recolor(btnm->ctrl_bits[btn_i]);
if(recolor) draw_label_dsc_act.flag |= LV_TEXT_FLAG_RECOLOR;
else draw_label_dsc_act.flag &= ~LV_TEXT_FLAG_RECOLOR;
part_draw_dsc.draw_area = &btn_area;
part_draw_dsc.id = btn_i;
lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
/*Remove borders on the edges if `LV_BORDER_SIDE_INTERNAL`*/
if(draw_rect_dsc_act.border_side & LV_BORDER_SIDE_INTERNAL) {
draw_rect_dsc_act.border_side = LV_BORDER_SIDE_FULL;
if(btn_area.x1 == obj->coords.x1 + pleft) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_LEFT;
if(btn_area.x2 == obj->coords.x2 - pright) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_RIGHT;
if(btn_area.y1 == obj->coords.y1 + ptop) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_TOP;
if(btn_area.y2 == obj->coords.y2 - pbottom) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_BOTTOM;
}
lv_coord_t btn_height = lv_area_get_height(&btn_area);
if((btn_state & LV_STATE_PRESSED) && (btnm->ctrl_bits[btn_i] & LV_BTNMATRIX_CTRL_POPOVER)) {
/*Push up the upper boundary of the btn area to create the popover*/
btn_area.y1 -= btn_height;
}
/*Draw the background*/
lv_draw_rect(draw_ctx, &draw_rect_dsc_act, &btn_area);
/*Calculate the size of the text*/
const lv_font_t * font = draw_label_dsc_act.font;
lv_coord_t letter_space = draw_label_dsc_act.letter_space;
lv_coord_t line_space = draw_label_dsc_act.line_space;
const char * txt = btnm->map_p[txt_i];
#if LV_USE_ARABIC_PERSIAN_CHARS
/*Get the size of the Arabic text and process it*/
size_t len_ap = _lv_txt_ap_calc_bytes_cnt(txt);
if(len_ap < txt_ap_size) {
_lv_txt_ap_proc(txt, txt_ap);
txt = txt_ap;
}
#endif
lv_point_t txt_size;
lv_txt_get_size(&txt_size, txt, font, letter_space,
line_space, lv_area_get_width(&area_obj), draw_label_dsc_act.flag);
btn_area.x1 += (lv_area_get_width(&btn_area) - txt_size.x) / 2;
btn_area.y1 += (lv_area_get_height(&btn_area) - txt_size.y) / 2;
btn_area.x2 = btn_area.x1 + txt_size.x;
btn_area.y2 = btn_area.y1 + txt_size.y;
if((btn_state & LV_STATE_PRESSED) && (btnm->ctrl_bits[btn_i] & LV_BTNMATRIX_CTRL_POPOVER)) {
/*Push up the button text into the popover*/
btn_area.y1 -= btn_height / 2;
btn_area.y2 -= btn_height / 2;
}
/*Draw the text*/
lv_draw_label(draw_ctx, &draw_label_dsc_act, &btn_area, txt, NULL);
lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
}
obj->skip_trans = 0;
#if LV_USE_ARABIC_PERSIAN_CHARS
lv_mem_buf_release(txt_ap);
#endif
}
/**
* Create the required number of buttons and control bytes according to a map
* @param obj pointer to button matrix object
* @param map_p pointer to a string array
*/
static void allocate_btn_areas_and_controls(const lv_obj_t * obj, const char ** map)
{
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
btnm->row_cnt = 1;
/*Count the buttons in the map*/
uint16_t btn_cnt = 0;
uint16_t i = 0;
while(map[i] && map[i][0] != '\0') {
if(strcmp(map[i], "\n") != 0) { /*Do not count line breaks*/
btn_cnt++;
}
else {
btnm->row_cnt++;
}
i++;
}
/*Do not allocate memory for the same amount of buttons*/
if(btn_cnt == btnm->btn_cnt) return;
if(btnm->button_areas != NULL) {
lv_mem_free(btnm->button_areas);
btnm->button_areas = NULL;
}
if(btnm->ctrl_bits != NULL) {
lv_mem_free(btnm->ctrl_bits);
btnm->ctrl_bits = NULL;
}
btnm->button_areas = lv_mem_alloc(sizeof(lv_area_t) * btn_cnt);
LV_ASSERT_MALLOC(btnm->button_areas);
btnm->ctrl_bits = lv_mem_alloc(sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
LV_ASSERT_MALLOC(btnm->ctrl_bits);
if(btnm->button_areas == NULL || btnm->ctrl_bits == NULL) btn_cnt = 0;
lv_memset_00(btnm->ctrl_bits, sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
btnm->btn_cnt = btn_cnt;
}
/**
* Get the width of a button in units (default is 1).
* @param ctrl_bits least significant 3 bits used (1..7 valid values)
* @return the width of the button in units
*/
static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits)
{
uint8_t w = ctrl_bits & LV_BTNMATRIX_WIDTH_MASK;
return w != 0 ? w : 1;
}
static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_HIDDEN) ? true : false;
}
static bool button_is_checked(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKED) ? true : false;
}
static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_NO_REPEAT) ? true : false;
}
static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_DISABLED) ? true : false;
}
static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_CLICK_TRIG) ? true : false;
}
static bool button_is_popover(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_POPOVER) ? true : false;
}
static bool button_is_checkable(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKABLE) ? true : false;
}
static bool button_get_checked(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKED) ? true : false;
}
static bool button_is_recolor(lv_btnmatrix_ctrl_t ctrl_bits)
{
return (ctrl_bits & LV_BTNMATRIX_CTRL_RECOLOR) ? true : false;
}
/**
* Gives the button id of a button under a given point
* @param obj pointer to a button matrix object
* @param p a point with absolute coordinates
* @return the id of the button or LV_BTNMATRIX_BTN_NONE.
*/
static uint16_t get_button_from_point(lv_obj_t * obj, lv_point_t * p)
{
lv_area_t obj_cords;
lv_area_t btn_area;
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
uint16_t i;
lv_obj_get_coords(obj, &obj_cords);
lv_coord_t w = lv_obj_get_width(obj);
lv_coord_t h = lv_obj_get_height(obj);
lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
lv_coord_t prow = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
lv_coord_t pcol = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
/*Get the half gap. Button look larger with this value. (+1 for rounding error)*/
prow = (prow / 2) + 1 + (prow & 1);
pcol = (pcol / 2) + 1 + (pcol & 1);
prow = LV_MIN(prow, BTN_EXTRA_CLICK_AREA_MAX);
pcol = LV_MIN(pcol, BTN_EXTRA_CLICK_AREA_MAX);
pright = LV_MIN(pright, BTN_EXTRA_CLICK_AREA_MAX);
ptop = LV_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
pbottom = LV_MIN(pbottom, BTN_EXTRA_CLICK_AREA_MAX);
for(i = 0; i < btnm->btn_cnt; i++) {
lv_area_copy(&btn_area, &btnm->button_areas[i]);
if(btn_area.x1 <= pleft) btn_area.x1 += obj_cords.x1 - LV_MIN(pleft, BTN_EXTRA_CLICK_AREA_MAX);
else btn_area.x1 += obj_cords.x1 - pcol;
if(btn_area.y1 <= ptop) btn_area.y1 += obj_cords.y1 - LV_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
else btn_area.y1 += obj_cords.y1 - prow;
if(btn_area.x2 >= w - pright - 2) btn_area.x2 += obj_cords.x1 + LV_MIN(pright,
BTN_EXTRA_CLICK_AREA_MAX); /*-2 for rounding error*/
else btn_area.x2 += obj_cords.x1 + pcol;
if(btn_area.y2 >= h - pbottom - 2) btn_area.y2 += obj_cords.y1 + LV_MIN(pbottom,
BTN_EXTRA_CLICK_AREA_MAX); /*-2 for rounding error*/
else btn_area.y2 += obj_cords.y1 + prow;
if(_lv_area_is_point_on(&btn_area, p, 0) != false) {
break;
}
}
if(i == btnm->btn_cnt) i = LV_BTNMATRIX_BTN_NONE;
return i;
}
static void invalidate_button_area(const lv_obj_t * obj, uint16_t btn_idx)
{
if(btn_idx == LV_BTNMATRIX_BTN_NONE) return;
lv_area_t btn_area;
lv_area_t obj_area;
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
if(btn_idx >= btnm->btn_cnt) return;
lv_area_copy(&btn_area, &btnm->button_areas[btn_idx]);
lv_obj_get_coords(obj, &obj_area);
/*The buttons might have outline and shadow so make the invalidation larger with the gaps between the buttons.
*It assumes that the outline or shadow is smaller than the gaps*/
lv_coord_t row_gap = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
/*Be sure to have a minimal extra space if row/col_gap is small*/
lv_coord_t dpi = lv_disp_get_dpi(lv_obj_get_disp(obj));
row_gap = LV_MAX(row_gap, dpi / 10);
col_gap = LV_MAX(col_gap, dpi / 10);
/*Convert relative coordinates to absolute*/
btn_area.x1 += obj_area.x1 - row_gap;
btn_area.y1 += obj_area.y1 - col_gap;
btn_area.x2 += obj_area.x1 + row_gap;
btn_area.y2 += obj_area.y1 + col_gap;
if((btn_idx == btnm->btn_id_sel) && (btnm->ctrl_bits[btn_idx] & LV_BTNMATRIX_CTRL_POPOVER)) {
/*Push up the upper boundary of the btn area to also invalidate the popover*/
btn_area.y1 -= lv_area_get_height(&btn_area);
}
lv_obj_invalidate_area(obj, &btn_area);
}
/**
* Enforces a single button being toggled on the button matrix.
* It simply clears the toggle flag on other buttons.
* @param obj Button matrix object
* @param btn_idx Button that should remain toggled
*/
static void make_one_button_checked(lv_obj_t * obj, uint16_t btn_idx)
{
/*Save whether the button was toggled*/
bool was_toggled = lv_btnmatrix_has_btn_ctrl(obj, btn_idx, LV_BTNMATRIX_CTRL_CHECKED);
lv_btnmatrix_clear_btn_ctrl_all(obj, LV_BTNMATRIX_CTRL_CHECKED);
if(was_toggled) lv_btnmatrix_set_btn_ctrl(obj, btn_idx, LV_BTNMATRIX_CTRL_CHECKED);
}
/**
* Check if any of the buttons in the first row has the LV_BTNMATRIX_CTRL_POPOVER control flag set.
* @param obj Button matrix object
* @return true if at least one button has the flag, false otherwise
*/
static bool has_popovers_in_top_row(lv_obj_t * obj)
{
lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
if(btnm->row_cnt <= 0) {
return false;
}
const char ** map_row = btnm->map_p;
uint16_t btn_cnt = 0;
while(map_row[btn_cnt] && strcmp(map_row[btn_cnt], "\n") != 0 && map_row[btn_cnt][0] != '\0') {
if(button_is_popover(btnm->ctrl_bits[btn_cnt])) {
return true;
}
btn_cnt++;
}
return false;
}
#endif