Create a lv_menu that responds to keypad input

Description

What MCU/Processor/Board and compiler are you using?

Raspberry Pi Pico board (RP2040), building using gcc version 14.2.1 20241119 (Arm GNU Toolchain 14.2.Rel1 (Build arm-14.52)).

What LVGL version are you using?

9cc9e7b50309715ebcb3ca851fda647b4605d333

What do you want to achieve?

I’d like to create an lv_menu based menu that is navigable with a keypad input device. The active item of the menu should be highlighted, and menu items should change when KEY_UP/KEY_DOWN are received.

What have you tried so far?

I tried creating an input group, affecting menu cont to the group. Nothing happened.

Code to reproduce

lv_obj_set_style_bg_color(screen, lv_color_hex(0x000000), LV_PART_MAIN);

lv_obj_t * settings_menu = lv_menu_create(screen);
lv_obj_set_size(settings_menu, 240, 240);
lv_obj_center(settings_menu);

lv_obj_t * label;
lv_obj_t * cont;

lv_group_t * group = lv_group_get_default();
lv_obj_t * settings_main_page = lv_menu_page_create(settings_menu, NULL);
cont = lv_menu_cont_create(settings_main_page);
lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
label = lv_label_create(cont);
lv_label_set_text(label, "Item 1");
lv_group_add_obj(group, cont);

cont = lv_menu_cont_create(settings_main_page);
lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
label = lv_label_create(cont);
lv_label_set_text(label, "Item 2");
lv_group_add_obj(group, cont);

cont = lv_menu_cont_create(settings_main_page);
lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
label = lv_label_create(cont);
lv_label_set_text(label, "Item 3");
lv_group_add_obj(group, cont);

lv_menu_set_page(settings_menu, settings_main_page);

I made a bit of progress. It turns out, in order to make a menu with keypad interactions, you need to:

  • Put all your menu cont’s into a lv_group_t.
  • Add some theming to visually distinguish the active item from all the disabled items.
  • Emit the right keycode (LV_KEY_PREV/LV_KEY_NEXT).

Regarding that last aspect, this is causing me a bit of trouble since all my keypad keys are mapped to LV_KEY_{UP,DOWN,LEFT,RIGHT}. docs/overview/indev.rst says that there is a translation mechanism. I could not make it work nor find where it was implemented. Is the documentation out of date here?

Hello,

I am struggling with the same thing. How hard can it be??

I have a button handler (this works).

static void settings_menu_button_handler(int button_id, int press_type) {
    if (press_type != SHORT_PRESS) return;

    uint32_t key;

    switch (button_id) {
        case 0:  // LEFT = UP
            key = LV_KEY_UP;
            lv_event_send(menu, LV_EVENT_KEY, &key);
            LOG_INF("Pressed UP");
            break;

        case 1:  // RIGHT = DOWN
            key = LV_KEY_DOWN;
            lv_event_send(menu, LV_EVENT_KEY, &key);
            LOG_INF("Pressed DOWN");
            break;

        case 2:  // ENTER
            key = LV_KEY_ENTER;
            lv_event_send(menu, LV_EVENT_KEY, &key);
            LOG_INF("Pressed ENTER");
    }
}

But nothing happens when I send it down to the menu

// Create menu
    menu = lv_menu_create(settings_screen);
    lv_obj_set_style_bg_color(menu, lv_color_black(), 0);
    lv_obj_set_size(menu, 200, 200);
    lv_obj_center(menu);


    // Create sub-page
    lv_obj_t * sub_page = lv_menu_page_create(menu, NULL);

    lv_obj_t * cont = lv_menu_cont_create(sub_page);
    lv_obj_add_style(cont, &style_default, LV_STATE_DEFAULT);  // Apply always
    lv_obj_t * label = lv_label_create(cont);
    lv_label_set_text(label, "Hello, I am hiding here");

    // Create main page
    lv_obj_t * main_page = lv_menu_page_create(menu, NULL);

    cont = lv_menu_cont_create(main_page);
    lv_obj_add_style(cont, &style_focused, LV_STATE_FOCUSED); // <- Apply to Item 1
    lv_obj_add_style(cont, &style_default, LV_STATE_DEFAULT);  // Apply always
    label = lv_label_create(cont);
    lv_label_set_text(label, LV_SYMBOL_BLUETOOTH " Communication");

did you work it out in the end

Hi,

I went with a dodgy workaround: my keypad inputs are handled by a custom lv_indev_t. This lv_indev_t implements two input modes. The first mode outputs direction keys (LV_KEY_UP, etc.) whereas the second outputs widget focus keys (LV_KEY_PREV).