Menu Object Creation is Slow

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,
};