Button Matrix (lv_buttonmatrix)
Overview
The Button Matrix Widget is a lightweight way to display multiple Buttons in rows and columns — lightweight because the buttons are not actually created but just virtually drawn on the fly. With Button Matrix, each button uses only eight extra bytes of memory instead of the ~100-150 bytes a normal Button Widget plus the 100 or so bytes for the Label Widget.
New Button Matrix Widgets are added to the default group (if one is set). Additionally, Button Matrix is an editable Widget: it allows selecting and clicking the buttons with encoder and keyboard navigation as well.
Parts and Styles
LV_PART_MAIN
The background of the Button Matrix, uses the typical background style properties.pad_row
andpad_column
sets the space between the buttons.LV_PART_ITEMS
The buttons all use the text and typical background style properties except translations and transformations.
Usage
Button map
The number of buttons, their positions and text are controlled by a descriptor string
array, called a map, passed to
lv_buttonmatrix_set_map(btn_matrix, my_map). The declaration of a map should
look like const char * map[] = {"button1", "button2", "button3", NULL}
. Note
that the last element must be either NULL
or an empty string
(""
)!
Use "\n"
in the map to insert a line break. E.g.
{"button1", "button2", "\n", "button3", ""}
. Each line's buttons have their
width calculated automatically. So in the example the first row will
have 2 buttons each with 50% width and a second row with 1 button having
100% width.
Note
The number of buttons neither includes the newline elements nor the terminating element of the array.
Button widths
The buttons' width can be set in proportion to the width of other buttons in the same row with lv_buttonmatrix_set_button_width(btn_matrix, button_id, width) E.g. in a line with two buttons: buttonA, width = 1 and buttonB, width = 2, buttonA will have 33 % width and buttonB will have 66 % width. This is similar to how the "flex-grow" property works in CSS. The width must be in the range [1..15] with the default being 1.
Button behavior
Each button's behavior can be customized with the following control flags:
LV_BUTTONMATRIX_CTRL_HIDDEN
: Hides button; it continues to hold its space in layout.LV_BUTTONMATRIX_CTRL_NO_REPEAT
: Do not emitLV_EVENT_LONG_PRESSED_REPEAT
events while button is long-pressed.LV_BUTTONMATRIX_CTRL_DISABLED
: Disables button likeLV_STATE_DISABLED
on normal Widgets.LV_BUTTONMATRIX_CTRL_CHECKABLE
: Enable toggling ofLV_STATE_CHECKED
when clicked.LV_BUTTONMATRIX_CTRL_CHECKED
: Make the button checked. It will use theLV_STATE_CHECHKED
styles.LV_BUTTONMATRIX_CTRL_CLICK_TRIG
: 1: Enables sendingLV_EVENT_VALUE_CHANGE
onCLICK
, 0: sendsLV_EVENT_VALUE_CHANGE
onPRESS
.LV_BUTTONMATRIX_CTRL_POPOVER
: Show button text in a pop-over while being pressed.LV_BUTTONMATRIX_CTRL_RECOLOR
: Enable text recoloring with#color
LV_BUTTONMATRIX_CTRL_CUSTOM_1
: Custom free-to-use flagLV_BUTTONMATRIX_CTRL_CUSTOM_2
: Custom free-to-use flag
By default, these flags are disabled.
To set and clear a button's control flags, use
lv_buttonmatrix_set_button_ctrl(btn_matrix, button_id, LV_BUTTONMATRIX_CTRL_...) and
lv_buttonmatrix_clear_button_ctrl(btn_matrix, button_id, LV_BUTTONMATRIX_CTRL_...)
respectively. button_id
is a zero-based button index (0 = first button).
LV_BUTTONMATRIX_CTRL_...
values can be bit-wise OR-ed together when passed to
these functions.
To set and clear the same control attribute for all buttons in a Button Matrix, use
lv_buttonmatrix_set_button_ctrl_all(btn_matrix, LV_BUTTONMATRIX_CTRL_...) and
lv_buttonmatrix_clear_button_ctrl_all(btn_matrix, LV_BUTTONMATRIX_CTRL_...)
respectively.
To set a control map for a Button Matrix (similar to Button map), use
lv_buttonmatrix_set_ctrl_map(btn_matrix, ctrl_map).
An element of ctrl_map
should look like
ctrl_map[0] = width | LV_BUTTONMATRIX_CTRL_NO_REPEAT | LV_BUTTONMATRIX_CTRL_CHECHKABLE.
The number of elements must be equal to the number of buttons.
One checked
The "One-checked" feature can be enabled with lv_buttonmatrix_set_one_checked(btn_matrix, true) to allow only one button to be checked at a time.
Events
LV_EVENT_VALUE_CHANGED
: Sent when a button is pressed/released or repeated after long press. The event parameter is set to the ID of the pressed/released button.
lv_buttonmatrix_get_selected_button(btn_matrix) returns the index of the button
most recently released (the button with focus) or LV_BUTTONMATRIX_BUTTON_NONE
if no such button was found.
lv_buttonmatrix_get_button_text(btn_matrix, button_id) returns a pointer
to the text of the button specified by zero-based index button_id
.
Further Reading
Learn more about Base-Widget Events emitted by all Widgets.
Learn more about Events.
Keys
LV_KEY_RIGHT/UP/LEFT/RIGHT
To navigate among the buttons to select oneLV_KEY_ENTER
To press/release the selected button
Note that long pressing the Button Matrix with an encoder can mean to enter/leave edit mode and simply long pressing a button to make it repeat as well. To avoid this contradiction, add lv_buttonmatrix_set_button_ctrl_all(btn_matrix, LV_BUTTONMATRIX_CTRL_CLICK_TRIG | LV_BUTTONMATRIX_CTRL_NO_REPEAT) to the Button Matrix if used with an encoder. This disables the repeat feature so the button will not be activated upon leaving edit mode.
Further Reading
Learn more about Keys.
Example
Simple Button matrix
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_BUTTONMATRIX && LV_BUILD_EXAMPLES
static void event_handler(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = lv_event_get_target(e);
if(code == LV_EVENT_VALUE_CHANGED) {
uint32_t id = lv_buttonmatrix_get_selected_button(obj);
const char * txt = lv_buttonmatrix_get_button_text(obj, id);
LV_UNUSED(txt);
LV_LOG_USER("%s was pressed\n", txt);
}
}
static const char * btnm_map[] = {"1", "2", "3", "4", "5", "\n",
"6", "7", "8", "9", "0", "\n",
"Action1", "Action2", ""
};
void lv_example_buttonmatrix_1(void)
{
lv_obj_t * btnm1 = lv_buttonmatrix_create(lv_screen_active());
lv_buttonmatrix_set_map(btnm1, btnm_map);
lv_buttonmatrix_set_button_width(btnm1, 10, 2); /*Make "Action1" twice as wide as "Action2"*/
lv_buttonmatrix_set_button_ctrl(btnm1, 10, LV_BUTTONMATRIX_CTRL_CHECKABLE);
lv_buttonmatrix_set_button_ctrl(btnm1, 11, LV_BUTTONMATRIX_CTRL_CHECKED);
lv_obj_align(btnm1, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_event_cb(btnm1, event_handler, LV_EVENT_ALL, NULL);
}
#endif
Custom buttons
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_BUTTONMATRIX && LV_BUILD_EXAMPLES
static void event_cb(lv_event_t * e)
{
lv_obj_t * obj = lv_event_get_target(e);
lv_draw_task_t * draw_task = lv_event_get_draw_task(e);
lv_draw_dsc_base_t * base_dsc = lv_draw_task_get_draw_dsc(draw_task);
/*When the button matrix draws the buttons...*/
if(base_dsc->part == LV_PART_ITEMS) {
bool pressed = false;
if(lv_buttonmatrix_get_selected_button(obj) == base_dsc->id1 && lv_obj_has_state(obj, LV_STATE_PRESSED)) {
pressed = true;
}
/*Change the draw descriptor of the 2nd button*/
if(base_dsc->id1 == 1) {
lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task);
if(fill_draw_dsc) {
fill_draw_dsc->radius = 0;
if(pressed) fill_draw_dsc->color = lv_palette_darken(LV_PALETTE_BLUE, 3);
else fill_draw_dsc->color = lv_palette_main(LV_PALETTE_BLUE);
}
lv_draw_box_shadow_dsc_t * box_shadow_draw_dsc = lv_draw_task_get_box_shadow_dsc(draw_task);
if(box_shadow_draw_dsc) {
box_shadow_draw_dsc->width = 6;
box_shadow_draw_dsc->ofs_x = 3;
box_shadow_draw_dsc->ofs_y = 3;
}
lv_draw_label_dsc_t * label_draw_dsc = lv_draw_task_get_label_dsc(draw_task);
if(label_draw_dsc) {
label_draw_dsc->color = lv_color_white();
}
}
/*Change the draw descriptor of the 3rd button*/
else if(base_dsc->id1 == 2) {
lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task);
if(fill_draw_dsc) {
fill_draw_dsc->radius = LV_RADIUS_CIRCLE;
if(pressed) fill_draw_dsc->color = lv_palette_darken(LV_PALETTE_RED, 3);
else fill_draw_dsc->color = lv_palette_main(LV_PALETTE_RED);
}
}
else if(base_dsc->id1 == 3) {
lv_draw_label_dsc_t * label_draw_dsc = lv_draw_task_get_label_dsc(draw_task);
if(label_draw_dsc) {
label_draw_dsc->opa = 0;
}
if(lv_draw_task_get_type(draw_task) == LV_DRAW_TASK_TYPE_FILL) {
LV_IMAGE_DECLARE(img_star);
lv_image_header_t header;
lv_result_t res = lv_image_decoder_get_info(&img_star, &header);
if(res != LV_RESULT_OK) return;
lv_area_t a;
a.x1 = 0;
a.x2 = header.w - 1;
a.y1 = 0;
a.y2 = header.h - 1;
lv_area_t draw_task_area;
lv_draw_task_get_area(draw_task, &draw_task_area);
lv_area_align(&draw_task_area, &a, LV_ALIGN_CENTER, 0, 0);
lv_draw_image_dsc_t img_draw_dsc;
lv_draw_image_dsc_init(&img_draw_dsc);
img_draw_dsc.src = &img_star;
img_draw_dsc.recolor = lv_color_black();
if(pressed) img_draw_dsc.recolor_opa = LV_OPA_30;
lv_draw_image(base_dsc->layer, &img_draw_dsc, &a);
}
}
}
}
/**
* Add custom drawer to the button matrix to customize buttons one by one
*/
void lv_example_buttonmatrix_2(void)
{
lv_obj_t * btnm = lv_buttonmatrix_create(lv_screen_active());
lv_obj_add_event_cb(btnm, event_cb, LV_EVENT_DRAW_TASK_ADDED, NULL);
lv_obj_add_flag(btnm, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);
lv_obj_center(btnm);
}
#endif
Pagination
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_BUTTONMATRIX && LV_BUILD_EXAMPLES
static void event_cb(lv_event_t * e)
{
lv_obj_t * obj = lv_event_get_target(e);
uint32_t id = lv_buttonmatrix_get_selected_button(obj);
bool prev = id == 0;
bool next = id == 6;
if(prev || next) {
/*Find the checked button*/
uint32_t i;
for(i = 1; i < 7; i++) {
if(lv_buttonmatrix_has_button_ctrl(obj, i, LV_BUTTONMATRIX_CTRL_CHECKED)) break;
}
if(prev && i > 1) i--;
else if(next && i < 5) i++;
lv_buttonmatrix_set_button_ctrl(obj, i, LV_BUTTONMATRIX_CTRL_CHECKED);
}
}
/**
* Make a button group (pagination)
*/
void lv_example_buttonmatrix_3(void)
{
static lv_style_t style_bg;
lv_style_init(&style_bg);
lv_style_set_pad_all(&style_bg, 0);
lv_style_set_pad_gap(&style_bg, 0);
lv_style_set_clip_corner(&style_bg, true);
lv_style_set_radius(&style_bg, LV_RADIUS_CIRCLE);
lv_style_set_border_width(&style_bg, 0);
static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_radius(&style_btn, 0);
lv_style_set_border_width(&style_btn, 1);
lv_style_set_border_opa(&style_btn, LV_OPA_50);
lv_style_set_border_color(&style_btn, lv_palette_main(LV_PALETTE_GREY));
lv_style_set_border_side(&style_btn, LV_BORDER_SIDE_INTERNAL);
lv_style_set_radius(&style_btn, 0);
static const char * map[] = {LV_SYMBOL_LEFT, "1", "2", "3", "4", "5", LV_SYMBOL_RIGHT, ""};
lv_obj_t * btnm = lv_buttonmatrix_create(lv_screen_active());
lv_buttonmatrix_set_map(btnm, map);
lv_obj_add_style(btnm, &style_bg, 0);
lv_obj_add_style(btnm, &style_btn, LV_PART_ITEMS);
lv_obj_add_event_cb(btnm, event_cb, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_set_size(btnm, 225, 35);
/*Allow selecting on one number at time*/
lv_buttonmatrix_set_button_ctrl_all(btnm, LV_BUTTONMATRIX_CTRL_CHECKABLE);
lv_buttonmatrix_clear_button_ctrl(btnm, 0, LV_BUTTONMATRIX_CTRL_CHECKABLE);
lv_buttonmatrix_clear_button_ctrl(btnm, 6, LV_BUTTONMATRIX_CTRL_CHECKABLE);
lv_buttonmatrix_set_one_checked(btnm, true);
lv_buttonmatrix_set_button_ctrl(btnm, 1, LV_BUTTONMATRIX_CTRL_CHECKED);
lv_obj_center(btnm);
}
#endif