Button matrix (lv_btnmatrix)
Overview
The Button Matrix object 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. This way, one button use only eight extra bytes of memory instead of the ~100-150 bytes a normal Button object plus the 100 or so bytes for the Label object.
The Button matrix is added to the default group (if one is set). Besides the Button matrix is an editable object to allow selecting and clicking the buttons with encoder navigation too.
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's text
There is a text on each button. To specify them a descriptor string
array, called map, needs to be used. The map can be set with
lv_btnmatrix_set_map(btnm, my_map). The declaration of a map should
look like const char * map[] = {"btn1", "btn2", "btn3", NULL}
. Note
that the last element has to be either NULL
or an empty string
(""
)!
Use "\n"
in the map to insert a line break. E.g.
{"btn1", "btn2", "\n", "btn3", ""}
. 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.
Control buttons
The buttons' width can be set relative to the other button in the same
row with lv_btnmatrix_set_btn_width(btnm, btn_id, width) E.g. in a
line with two buttons: btnA, width = 1 and btnB, width = 2, btnA
will have 33 % width and btnB will have 66 % width. It's similar to
how the
`flex-grow
<https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow>`__
property works in CSS. The width must be in the [1..15] range and the
default width is 1.
In addition to the width, each button can be customized with the following parameters:
LV_BTNMATRIX_CTRL_HIDDEN
: Makes a button hidden (hidden buttons still take up space in the layout, they are just not visible or clickable)LV_BTNMATRIX_CTRL_NO_REPEAT
: Disable repeating when the button is long pressedLV_BTNMATRIX_CTRL_DISABLED
: Makes a button disabled LikeLV_STATE_DISABLED
on normal objectsLV_BTNMATRIX_CTRL_CHECKABLE
: Enable toggling of a button. I.e.LV_STATE_CHECKED
will be added/removed as the button is clickedLV_BTNMATRIX_CTRL_CHECKED
: Make the button checked. It will use theLV_STATE_CHECHKED
styles.LV_BTNMATRIX_CTRL_CLICK_TRIG
: Enabled: send LV_EVENT_VALUE_CHANGE on CLICK, Disabled: sendLV_EVENT_VALUE_CHANGE
on PRESSLV_BTNMATRIX_CTRL_POPOVER
: Show the button label in a popover when pressing this keyLV_BTNMATRIX_CTRL_RECOLOR
: Enable recoloring of button texts with#
. E.g."It's #ff0000 red#"
LV_BTNMATRIX_CTRL_CUSTOM_1
: Custom free to use flagLV_BTNMATRIX_CTRL_CUSTOM_2
: Custom free to use flag
By default, all flags are disabled.
To set or clear a button's control attribute, use
lv_btnmatrix_set_btn_ctrl(btnm, btn_id, LV_BTNM_CTRL_...)
and
lv_btnmatrix_clear_btn_ctrl(btnm, btn_id, LV_BTNMATRIX_CTRL_...)
respectively. More LV_BTNM_CTRL_...
values can be OR-ed
To set/clear the same control attribute for all buttons of a button
matrix, use lv_btnmatrix_set_btn_ctrl_all(btnm, LV_BTNM_CTRL_...)
and lv_btnmatrix_clear_btn_ctrl_all(btnm, LV_BTNMATRIX_CTRL_...)
.
The set a control map for a button matrix (similarly to the map for the
text), use lv_btnmatrix_set_ctrl_map(btnm, ctrl_map)
. An element of
ctrl_map
should look like
ctrl_map[0] = width | LV_BTNM_CTRL_NO_REPEAT | LV_BTNM_CTRL_CHECHKABLE
.
The number of elements should be equal to the number of buttons
(excluding newlines characters).
One check
The "One check" feature can be enabled with lv_btnmatrix_set_one_checked(btnm, 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_EVENT_DRAW_PART_BEGIN
andLV_EVENT_DRAW_PART_END
are sent for the following types:LV_BTNMATRIX_DRAW_PART_BTN
The individual buttons.part
:LV_PART_ITEMS
id
:index of the button being drawndraw_area
: the area of the buttonrect_dsc
See the events of the Base object too.
lv_btnmatrix_get_selected_btn(btnm) returns the index of the most
recently released or focused button or LV_BTNMATRIX_BTN_NONE
if no
such button.
lv_btnmatrix_get_btn_text(btnm, btn_id) returns a pointer to the
text of btn_id
th button.
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 it's suggested to add lv_btnmatrix_set_btn_ctrl_all(btnm, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT) to the button matrix if used with encoder. This way, the pressed button repeat feature is disabled and on leaving edit mode the selected button won't be activated.
Learn more about Keys.
Example
Simple Button matrix
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_BTNMATRIX && 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(btnm1, event_handler, LV_EVENT_ALL, NULL);
}
#endif
def event_handler(e):
code = e.get_code()
obj = e.get_target_obj()
if code == lv.EVENT.VALUE_CHANGED :
id = obj.get_selected_button()
txt = obj.get_button_text(id)
print("%s was pressed"%txt)
buttonm_map = ["1", "2", "3", "4", "5", "\n",
"6", "7", "8", "9", "0", "\n",
"Action1", "Action2", ""]
buttonm1 = lv.buttonmatrix(lv.screen_active())
buttonm1.set_map(buttonm_map)
buttonm1.set_button_width(10, 2) # Make "Action1" twice as wide as "Action2"
buttonm1.set_button_ctrl(10, lv.buttonmatrix.CTRL.CHECKABLE)
buttonm1.set_button_ctrl(11, lv.buttonmatrix.CTRL.CHECKED)
buttonm1.align(lv.ALIGN.CENTER, 0, 0)
buttonm1.add_event(event_handler, lv.EVENT.ALL, None)
#endif
Custom buttons
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_BTNMATRIX && 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 = draw_task->draw_dsc;
/*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) {
if(draw_task->type == LV_DRAW_TASK_TYPE_FILL) {
lv_draw_rect_dsc_t * rect_draw_dsc = draw_task->draw_dsc;
rect_draw_dsc->radius = 0;
if(pressed) rect_draw_dsc->bg_color = lv_palette_darken(LV_PALETTE_BLUE, 3);
else rect_draw_dsc->bg_color = lv_palette_main(LV_PALETTE_BLUE);
rect_draw_dsc->shadow_width = 6;
rect_draw_dsc->shadow_ofs_x = 3;
rect_draw_dsc->shadow_ofs_y = 3;
}
if(draw_task->type == LV_DRAW_TASK_TYPE_LABEL) {
lv_draw_label_dsc_t * label_draw_dsc = draw_task->draw_dsc;
label_draw_dsc->color = lv_color_white();
}
}
/*Change the draw descriptor of the 3rd button*/
else if(base_dsc->id1 == 2) {
if(draw_task->type == LV_DRAW_TASK_TYPE_FILL) {
lv_draw_rect_dsc_t * rect_draw_dsc = draw_task->draw_dsc;
rect_draw_dsc->radius = LV_RADIUS_CIRCLE;
if(pressed) rect_draw_dsc->bg_color = lv_palette_darken(LV_PALETTE_RED, 3);
else rect_draw_dsc->bg_color = lv_palette_main(LV_PALETTE_RED);
}
}
else if(base_dsc->id1 == 3) {
if(draw_task->type == LV_DRAW_TASK_TYPE_LABEL) {
lv_draw_label_dsc_t * label_draw_dsc = draw_task->draw_dsc;
label_draw_dsc->opa = 0;
}
if(draw_task->type == 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_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(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_BTNMATRIX && 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(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
def event_cb(e):
obj = e.get_target_obj()
id = obj.get_selected_button()
if id == 0:
prev = True
else:
prev = False
if id == 6:
next = True
else:
next = False
if prev or next:
# Find the checked butto
for i in range(7):
if obj.has_button_ctrl(i, lv.buttonmatrix.CTRL.CHECKED):
break
if prev and i > 1:
i-=1
elif next and i < 5:
i+=1
obj.set_button_ctrl(i, lv.buttonmatrix.CTRL.CHECKED)
#
# Make a button group
#
style_bg = lv.style_t()
style_bg.init()
style_bg.set_pad_all(0)
style_bg.set_pad_gap(0)
style_bg.set_clip_corner(True)
style_bg.set_radius(lv.RADIUS_CIRCLE)
style_bg.set_border_width(0)
style_button = lv.style_t()
style_button.init()
style_button.set_radius(0)
style_button.set_border_width(1)
style_button.set_border_opa(lv.OPA._50)
style_button.set_border_color(lv.palette_main(lv.PALETTE.GREY))
style_button.set_border_side(lv.BORDER_SIDE.INTERNAL)
style_button.set_radius(0)
map = [lv.SYMBOL.LEFT,"1","2", "3", "4", "5",lv.SYMBOL.RIGHT, ""]
buttonm = lv.buttonmatrix(lv.screen_active())
buttonm.set_map(map)
buttonm.add_style(style_bg, 0)
buttonm.add_style(style_button, lv.PART.ITEMS)
buttonm.add_event(event_cb, lv.EVENT.VALUE_CHANGED, None)
buttonm.set_size(225, 35)
# Allow selecting on one number at time
buttonm.set_button_ctrl_all(lv.buttonmatrix.CTRL.CHECKABLE)
buttonm.clear_button_ctrl(0, lv.buttonmatrix.CTRL.CHECKABLE)
buttonm.clear_button_ctrl(6, lv.buttonmatrix.CTRL.CHECKABLE)
buttonm.set_one_checked(True)
buttonm.set_button_ctrl(1, lv.buttonmatrix.CTRL.CHECKED)
buttonm.center()