Description
Creating a menu is taking around 1200ms on my processor (64mhz Cortex M4F). The menu must use prev/next events to highlight each active element with a caret pointing to the current line.
Each line might need two labels to give the user quick info on the current sub-menu selection. Sub menus will need to be selected, so I have chosen to use buttons to allow press events.
Perhaps this is normal for a menu with this complexity, but I want to see if you have any ideas on optimizations.
What MCU/Processor/Board and compiler are you using?
64mhz ArmCortex M4F
GCC - speed optimized
What LVGL version are you using?
8.3
What do you want to achieve?
I would like to speed up the menu creation time to keep the GUI as snappy as possible.
What have you tried so far?
I have tried compiler optimizations. Perhaps I need to change my style usage or find a way to not use buttons? Perhaps break each menu up into separate object screens?
Code to reproduce
#include "main.h"
#include <lvgl.h>
static lv_style_t highlight_style;
static lv_style_t text_style;
static lv_style_t main_style;
LV_IMG_DECLARE(blank_caret);
LV_IMG_DECLARE(arrow_caret_small);
LV_IMG_DECLARE(blank_caret);
void create_styles(void)
{
static bool is_style_set = false;
if (is_style_set == false)
{
lv_style_init(&main_style);
lv_style_set_bg_color(&main_style, lv_color_black());
lv_style_set_pad_all(&main_style, 0); // Padd with 1?
lv_style_set_pad_bottom(&main_style, 1);
lv_style_set_text_color(&main_style, lv_make_grey_color(3));
lv_style_set_text_font(&main_style, &lv_font_unscii_8);
lv_style_set_border_width(&main_style, 0);
lv_style_set_arc_rounded(&main_style, false);
lv_style_set_radius(&main_style, 0);
lv_style_set_align(&main_style, LV_ALIGN_TOP_MID);
lv_style_set_outline_width(&main_style, 0);
lv_style_init(&highlight_style);
lv_style_set_text_font(&highlight_style, &lv_font_unscii_8);
lv_style_set_bg_color(&highlight_style, lv_color_black());
lv_style_set_text_color(&highlight_style, lv_color_white());
lv_style_set_pad_all(&highlight_style, 0);
lv_style_set_text_align(&highlight_style, LV_ALIGN_LEFT_MID);
lv_style_set_border_color(&highlight_style, lv_palette_darken(LV_PALETTE_GREY, 2));
lv_style_set_outline_color(&highlight_style, lv_palette_darken(LV_PALETTE_GREY, 2));
lv_style_set_outline_width(&highlight_style, 0);
lv_style_set_radius(&highlight_style, 0);
lv_style_init(&text_style);
lv_style_set_bg_color(&text_style, lv_color_black());
lv_style_set_pad_all(&text_style, 0); // Padd with 1?
lv_style_set_text_color(&text_style, lv_make_grey_color(2));
lv_style_set_text_font(&text_style, &lv_font_unscii_8);
lv_style_set_border_width(&text_style, 0);
lv_style_set_arc_rounded(&text_style, false);
lv_style_set_radius(&text_style, 0);
lv_style_set_outline_width(&text_style, 0);
is_style_set = true;
}
}
// Handle focused item, stop scrolling and change icon
static void m_focus_handler(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = lv_event_get_target(e);
LV_UNUSED(obj);
if (code == LV_EVENT_FOCUSED)
{
#if MENU_STOP_SROLL_WRAP
int child_cnt = 0;
lv_obj_t * menu = find_menu_parent(obj);
if (menu != NULL)
{
lv_obj_t * curr_page = lv_menu_get_cur_main_page(menu);
child_cnt = lv_obj_get_child_cnt(curr_page) - 1;
}
int index = lv_obj_get_index(parent); // Get index of parent menu cont
NRF_LOG_DEBUG("Index of focused element is %d or %d", index, child_cnt);
if (index >= child_cnt)
{
keyboard_disable_next();
}
else
{
keyboard_enable_next();
}
if (index == 1)
{
keyboard_disable_prev();
}
else
{
keyboard_enable_prev();
}
#endif
/*** Set arrow image to button ***/
lv_obj_t * img = lv_obj_get_child(obj, 0);
// Add arrow
if (lv_obj_check_type(img, &lv_img_class))
{
lv_img_set_src(img, &arrow_caret_small);
}
// Get label
lv_obj_t * lbl = lv_obj_get_child(obj, 1);
if (lv_obj_check_type(lbl, &lv_label_class))
{
char * lbl_text = lv_label_get_text(lbl);
NRF_LOG_INFO("highlighting button: %s", nrf_log_push(lbl_text));
// NRF_LOG_INFO("highlighting button");
}
}
else if (code == LV_EVENT_DEFOCUSED)
{
/** Button */
lv_obj_t * img = lv_obj_get_child(obj, 0);
// Remove caret arrow
if (lv_obj_check_type(img, &lv_img_class))
{
lv_img_set_src(img, &blank_caret);
}
}
}
static void menu_changed_handle(lv_event_t * e)
{
lv_obj_t * obj = lv_event_get_target(e);
LV_UNUSED(obj);
NRF_LOG_INFO("Menu changed.");
lv_obj_t * curr_page = lv_menu_get_cur_main_page(obj);
if (curr_page == NULL)
{
return;
}
lv_obj_t * back_btn = lv_menu_get_main_header_back_btn(obj);
// lv_obj_clear_flag(back_btn, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(back_btn, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(back_btn, LV_OBJ_FLAG_CLICKABLE);
}
static void back_click(lv_event_t * e)
{
// lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = lv_event_get_target(e);
// lv_obj_t * parent = lv_obj_get_parent(obj);
lv_menu_t * menu = lv_event_get_user_data(e);
bool is_menu = lv_obj_check_type((lv_obj_t *)menu, &lv_menu_class);
if (is_menu)
{
// lv_menu_set_page(menu,prev_menu);
// if (!(obj == menu->main_header_back_btn || obj == menu->sidebar_header_back_btn))
// return;
menu->prev_depth = menu->cur_depth; /* Save the previous value for user event handler */
if (lv_menu_back_btn_is_root((lv_obj_t *)menu, obj))
return;
lv_ll_t * history_ll = &(menu->history_ll);
/* The current menu */
lv_menu_history_t * act_hist = _lv_ll_get_head(history_ll);
/* The previous menu */
lv_menu_history_t * prev_hist = _lv_ll_get_next(history_ll, act_hist);
if (prev_hist != NULL)
{
/* Previous menu exists */
/* Delete the current item from the history */
_lv_ll_remove(history_ll, act_hist);
lv_mem_free(act_hist);
menu->cur_depth--;
/* Create the previous menu.
* Remove it from the history because `lv_menu_set_page` will add it again */
_lv_ll_remove(history_ll, prev_hist);
menu->cur_depth--;
lv_menu_set_page(&(menu->obj), prev_hist->page);
lv_mem_free(prev_hist);
}
}
}
lv_obj_t * create_subpage_menu(lv_obj_t * menu, char * title)
{
lv_obj_t * cont;
lv_obj_t * label;
lv_obj_t * btn;
lv_obj_t * img;
lv_obj_t * sub_page = lv_menu_page_create(menu, NULL);
lv_obj_set_scroll_snap_y(sub_page, LV_SCROLL_SNAP_CENTER);
cont = lv_menu_cont_create(sub_page);
label = lv_label_create(cont);
lv_label_set_text(label, "Subpage");
lv_obj_add_style(cont, &main_style, LV_STATE_DEFAULT);
lv_obj_add_style(cont, &main_style, LV_STATE_DEFAULT);
for (int i = 0; i < 6; i++)
{
cont = lv_menu_cont_create(sub_page);
btn = lv_btn_create(cont);
lv_obj_set_size(btn, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_ROW);
img = lv_img_create(btn);
lv_img_set_src(img, &blank_caret);
label = lv_label_create(btn);
lv_label_set_text_fmt(label, "SubItem %d", i);
lv_obj_set_style_pad_all(cont, 0, LV_STATE_DEFAULT);
lv_obj_add_style(cont, &main_style, LV_STATE_DEFAULT);
lv_obj_add_style(cont, &highlight_style, LV_STATE_FOCUS_KEY);
lv_obj_add_style(btn, &main_style, LV_STATE_DEFAULT);
lv_obj_add_style(btn, &highlight_style, LV_STATE_FOCUS_KEY);
}
cont = lv_menu_cont_create(sub_page);
btn = lv_btn_create(cont);
lv_obj_set_size(btn, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_ROW);
img = lv_img_create(btn);
lv_img_set_src(img, &blank_caret);
label = lv_label_create(btn);
lv_label_set_text(label, "Back");
lv_obj_set_style_pad_all(cont, 0, LV_STATE_DEFAULT);
lv_obj_add_style(btn, &text_style, LV_STATE_DEFAULT);
lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ONE);
lv_obj_add_event_cb(btn, m_focus_handler, LV_EVENT_FOCUSED, NULL);
lv_obj_add_event_cb(btn, m_focus_handler, LV_EVENT_DEFOCUSED, NULL);
lv_obj_add_event_cb(btn, back_click, LV_EVENT_CLICKED, menu);
return sub_page;
}
void lv_example_menu_custom(void)
{
create_styles();
uint32_t start_time = millis();
/*Create a menu object*/
lv_obj_t * menu = lv_menu_create(lv_scr_act());
lv_obj_set_size(menu, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
lv_obj_center(menu);
lv_obj_add_style(menu, &main_style, LV_PART_MAIN);
lv_obj_set_scroll_snap_y(menu, LV_SCROLL_SNAP_START);
lv_obj_add_event_cb(menu, menu_changed_handle, LV_EVENT_VALUE_CHANGED, NULL);
/*Modify the header*/
lv_obj_t * back_btn = lv_menu_get_main_header_back_btn(menu);
// Hide back button
lv_obj_add_flag(back_btn, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(back_btn, LV_OBJ_FLAG_CLICKABLE);
lv_obj_t * cont;
lv_obj_t * label;
lv_obj_t * btn;
lv_obj_t * img;
lv_obj_t * subpage;
/*Create a main page*/
lv_obj_t * main_page = lv_menu_page_create(menu, NULL);
/*** Title ***/
cont = lv_menu_cont_create(main_page);
label = lv_label_create(cont);
lv_label_set_text(label, "Main Page");
lv_obj_add_style(cont, &main_style, LV_STATE_DEFAULT);
lv_obj_add_style(cont, &main_style, LV_STATE_DEFAULT);
for (int i = 0; i < 10; i++)
{
/*Create sub pages*/
subpage = create_subpage_menu(menu, "Subpage 1");
cont = lv_menu_cont_create(main_page);
btn = lv_btn_create(cont);
lv_obj_set_size(btn, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_ROW);
img = lv_img_create(btn);
lv_img_set_src(img, &blank_caret);
label = lv_label_create(btn);
lv_label_set_text_fmt(label, "Item %d", i);
lv_obj_set_style_pad_all(cont, 0, LV_STATE_DEFAULT);
lv_obj_add_style(cont, &text_style, LV_STATE_DEFAULT);
lv_obj_add_style(btn, &text_style, LV_STATE_DEFAULT);
lv_obj_add_style(btn, &highlight_style, LV_STATE_FOCUS_KEY);
lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ONE);
lv_obj_add_event_cb(btn, m_focus_handler, LV_EVENT_FOCUSED, NULL);
lv_obj_add_event_cb(btn, m_focus_handler, LV_EVENT_DEFOCUSED, NULL);
lv_menu_set_load_page_event(menu, btn, subpage);
}
lv_obj_set_scrollbar_mode(main_page, LV_SCROLLBAR_MODE_OFF);
lv_menu_set_page(menu, main_page);
lv_obj_update_layout(main_page);
uint32_t elapsed_time = millis() - start_time;
NRF_LOG_INFO("Finished menu screen in %d ms.", elapsed_time);
}
#ifndef LV_ATTRIBUTE_MEM_ALIGN
#define LV_ATTRIBUTE_MEM_ALIGN
#endif
#ifndef LV_ATTRIBUTE_IMG_BLANK_CARET
#define LV_ATTRIBUTE_IMG_BLANK_CARET
#endif
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_BLANK_CARET uint8_t blank_caret_map[] = {
0x00, 0x00, 0x00, 0xff, /*Color of index 0*/
0x00, 0x00, 0x00, 0x00, /*Color of index 1*/
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
};
const lv_img_dsc_t blank_caret = {
.header.cf = LV_IMG_CF_INDEXED_1BIT,
.header.always_zero = 0,
.header.reserved = 0,
.header.w = 10,
.header.h = 10,
.data_size = 28,
.data = blank_caret_map,
};
#ifndef LV_ATTRIBUTE_IMG_ARROW_CARET
#define LV_ATTRIBUTE_IMG_ARROW_CARET
#endif
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_ARROW_CARET uint8_t arrow_caret_map[] = {
0xff, 0xff, 0xff, 0xff, /*Color of index 0*/
0x00, 0x00, 0x00, 0xff, /*Color of index 1*/
0xff, 0xc0,
0xf9, 0xc0,
0xf8, 0xc0,
0xfc, 0x40,
0x00, 0x00,
0x00, 0x00,
0xfc, 0x40,
0xf8, 0xc0,
0xf9, 0xc0,
0xff, 0xc0,
};
const lv_img_dsc_t arrow_caret = {
.header.cf = LV_IMG_CF_INDEXED_1BIT,
.header.always_zero = 0,
.header.reserved = 0,
.header.w = 10,
.header.h = 10,
.data_size = 28,
.data = arrow_caret_map,
};
#ifndef LV_ATTRIBUTE_IMG_ARROW_CARET_SMALL
#define LV_ATTRIBUTE_IMG_ARROW_CARET_SMALL
#endif
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_ARROW_CARET_SMALL uint8_t arrow_caret_small_map[] = {
0xff, 0xff, 0xff, 0xff, /*Color of index 0*/
0x00, 0x00, 0x00, 0xff, /*Color of index 1*/
0xff, 0xc0,
0xf7, 0xc0,
0xfb, 0xc0,
0xfd, 0xc0,
0x00, 0xc0,
0xfd, 0xc0,
0xfb, 0xc0,
0xf7, 0xc0,
0xff, 0xc0,
0xff, 0xc0,
};
const lv_img_dsc_t arrow_caret_small = {
.header.cf = LV_IMG_CF_INDEXED_1BIT,
.header.always_zero = 0,
.header.reserved = 0,
.header.w = 10,
.header.h = 10,
.data_size = 28,
.data = arrow_caret_small_map,
};