Using the keyboard in the menu

Description

In my current project I am working on a menu structure. The menu is based on the complex menu sample.
The idea is that the user gets a pop-up to enter the data in textboxes.
The keyboard should open at the bottom of the screen (similar to the example) but somehow it opens at the top of the submenu.

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

LPC1877 with a custom toolchain

What LVGL version are you using?

8.4.0

What do you want to achieve?

A “normal” working menu. With the constraint that I have to load-unload pages since I don’t have enough RAM in my controller.

What have you tried so far?

multiple options as argument to lv_keyboard_create(). Some produce the keyboard at the top of the sub screen, others a hard crash.

Code to reproduce

static lv_obj_t * create_text(lv_obj_t * parent, const char * icon, const char * txt, lv_menu_builder_variant_t builder_variant);
static lv_obj_t * create_slider(lv_obj_t * parent, const char * icon, const char * txt, int32_t min, int32_t max, int32_t val);
static lv_obj_t * create_switch(lv_obj_t * parent, const char * icon, const char * txt, bool chk);

static lv_obj_t * create_sub_page(lv_obj_t * menu);
static lv_obj_t * create_module_page(lv_obj_t * menu);

lv_obj_t * keyboard = NULL;
lv_obj_t * menu;
lv_obj_t * root_page;

static void ta_event_cb(lv_event_t * e)
{
    lv_event_code_t eventCode = lv_event_get_code(e);
    lv_obj_t * targetArea = lv_event_get_target(e);
    
    if(eventCode == LV_EVENT_FOCUSED) 
    {
        //Create the keyboard if required
        if(keyboard == NULL)
            keyboard = lv_keyboard_create(menu);
    
        lv_keyboard_set_textarea(keyboard, targetArea);
    }

    if(eventCode == LV_EVENT_DEFOCUSED) 
    {
        lv_keyboard_set_textarea(keyboard, NULL);
        lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
    }
}

/// @brief Loads the main screen into memory. The screen is only loaded, not shown.
/// @return An object thar represents the full screen.
lv_obj_t* ScreenLoad(void * arg)
{
    menu = lv_menu_create(NULL);

    lv_color_t bg_color = lv_obj_get_style_bg_color(menu, 0);
    if(lv_color_brightness(bg_color) > 127) 
    {
        lv_obj_set_style_bg_color(menu, lv_color_darken(bg_color, 10), 0);
    }
    else 
    {
        lv_obj_set_style_bg_color(menu, lv_color_darken(bg_color, 50), 0);
    }

    lv_menu_set_mode_root_back_btn(menu, LV_MENU_ROOT_BACK_BTN_ENABLED);
    lv_obj_set_size(menu, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
    lv_obj_center(menu);

    lv_obj_t * sub_page = create_sub_page(menu);
	
	
	lv_obj_t * root_page = lv_menu_page_create(menu, "Menu");
    lv_obj_set_style_pad_hor(root_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
    lv_obj_t * section = lv_menu_section_create(root_page);
    
    lv_obj_t * cont = create_text(section, LV_SYMBOL_SETTINGS, "sub_page", LV_MENU_ITEM_BUILDER_VARIANT_1);
    lv_menu_set_load_page_event(menu, cont, sub_page);
    
    lv_menu_set_sidebar_page(menu, root_page);

    lv_event_send(lv_obj_get_child(lv_obj_get_child(lv_menu_get_cur_sidebar_page(menu), 0), 0), LV_EVENT_CLICKED, NULL);

    return menu;
}

static lv_obj_t * create_sub_page(lv_obj_t * menu)
{
    lv_obj_t * page = lv_menu_page_create(menu, "sub page");
    lv_obj_set_style_pad_hor(page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
    lv_menu_separator_create(page);
    lv_obj_t * section = lv_menu_section_create(page);
    
    lv_obj_t * cont = create_text(section, LV_SYMBOL_AUDIO, "Module", LV_MENU_ITEM_BUILDER_VARIANT_1);
    lv_obj_t * module_page = create_module_page(menu);
    lv_menu_set_load_page_event(menu, cont, module_page);

    return page;
}

static lv_obj_t * create_module_page(lv_obj_t * menu)
{
    lv_obj_t * page = lv_menu_page_create(menu, "Module");
    lv_obj_set_style_pad_hor(page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
    lv_menu_separator_create(page);
    lv_obj_t * section = lv_menu_section_create(page);

    // Analog input header
    lv_obj_t * analog_header = create_text(section, NULL, "Analog input", LV_MENU_ITEM_BUILDER_VARIANT_1);

    {
        // Channel 1 sub-header
        lv_obj_t * channel_header = create_text(section, NULL, "Channel 1", LV_MENU_ITEM_BUILDER_VARIANT_1);
        lv_obj_set_style_pad_left(channel_header, 20, 0); // Indent Channel 1

        // Name input
        lv_obj_t * name_cont = create_text(section, NULL, "Name", LV_MENU_ITEM_BUILDER_VARIANT_1);
        lv_obj_set_style_pad_left(name_cont, 40, 0);
        lv_obj_t * name_input = lv_textarea_create(name_cont);
        lv_textarea_set_one_line(name_input, true);
        lv_obj_set_width(name_input, lv_pct(60));
        lv_obj_align(name_input, LV_ALIGN_RIGHT_MID, 0, 0);
        lv_obj_add_event_cb(name_input, ta_event_cb, LV_EVENT_ALL, NULL);

        // Input min
        lv_obj_t * input_min_cont = create_text(section, NULL, "Input min", LV_MENU_ITEM_BUILDER_VARIANT_1);
        lv_obj_set_style_pad_left(input_min_cont, 40, 0);
        lv_obj_t * input_min = lv_textarea_create(input_min_cont);
        lv_textarea_set_one_line(input_min, true);
        lv_textarea_set_accepted_chars(input_min, "0123456789.-");
        lv_obj_set_width(input_min, lv_pct(30));
        lv_obj_align(input_min, LV_ALIGN_RIGHT_MID, 0, 0);
        lv_obj_add_event_cb(input_min, ta_event_cb, LV_EVENT_ALL, NULL);

        // Input max
        lv_obj_t * input_max_cont = create_text(section, NULL, "Input max", LV_MENU_ITEM_BUILDER_VARIANT_1);
        lv_obj_set_style_pad_left(input_max_cont, 40, 0);
        lv_obj_t * input_max = lv_textarea_create(input_max_cont);
        lv_textarea_set_one_line(input_max, true);
        lv_textarea_set_accepted_chars(input_max, "0123456789.-");
        lv_obj_set_width(input_max, lv_pct(30));
        lv_obj_align(input_max, LV_ALIGN_RIGHT_MID, 0, 0);
        lv_obj_add_event_cb(input_max, ta_event_cb, LV_EVENT_ALL, NULL);

        // Output min
        lv_obj_t * output_min_cont = create_text(section, NULL, "Output min", LV_MENU_ITEM_BUILDER_VARIANT_1);
        lv_obj_set_style_pad_left(output_min_cont, 40, 0);
        lv_obj_t * output_min = lv_textarea_create(output_min_cont);
        lv_textarea_set_one_line(output_min, true);
        lv_textarea_set_accepted_chars(output_min, "0123456789.-");
        lv_obj_set_width(output_min, lv_pct(30));
        lv_obj_align(output_min, LV_ALIGN_RIGHT_MID, 0, 0);
        lv_obj_add_event_cb(output_min, ta_event_cb, LV_EVENT_ALL, NULL);

        // Output max
        lv_obj_t * output_max_cont = create_text(section, NULL, "Output max", LV_MENU_ITEM_BUILDER_VARIANT_1);
        lv_obj_set_style_pad_left(output_max_cont, 40, 0);
        lv_obj_t * output_max = lv_textarea_create(output_max_cont);
        lv_textarea_set_one_line(output_max, true);
        lv_textarea_set_accepted_chars(output_max, "0123456789.-");
        lv_obj_set_width(output_max, lv_pct(30));
        lv_obj_align(output_max, LV_ALIGN_RIGHT_MID, 0, 0);
        lv_obj_add_event_cb(output_max, ta_event_cb, LV_EVENT_ALL, NULL);
    }

    return page;
}

static void back_event_handler(lv_event_t * e)
{
    lv_obj_t * obj = lv_event_get_target(e);
    lv_obj_t * menu = lv_event_get_user_data(e);

    if(lv_menu_back_btn_is_root(menu, obj)) {
        lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Hello", "Root back btn click.", NULL, true);
        lv_obj_center(mbox1);
    }
}

static lv_obj_t * create_text(lv_obj_t * parent, const char * icon, const char * txt,
                              lv_menu_builder_variant_t builder_variant)
{
    lv_obj_t * obj = lv_menu_cont_create(parent);

    lv_obj_t * img = NULL;
    lv_obj_t * label = NULL;

    if(icon) {
        img = lv_img_create(obj);
        lv_img_set_src(img, icon);
    }

    if(txt) {
        label = lv_label_create(obj);
        lv_label_set_text(label, txt);
        lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
        lv_obj_set_flex_grow(label, 1);
    }

    if(builder_variant == LV_MENU_ITEM_BUILDER_VARIANT_2 && icon && txt) {
        lv_obj_add_flag(img, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK);
        lv_obj_swap(img, label);
    }

    return obj;
}

static lv_obj_t * create_slider(lv_obj_t * parent, const char * icon, const char * txt, int32_t min, int32_t max, int32_t val)
{
    lv_obj_t * obj = create_text(parent, icon, txt, LV_MENU_ITEM_BUILDER_VARIANT_2);

    lv_obj_t * slider = lv_slider_create(obj);
    lv_obj_set_flex_grow(slider, 1);
    lv_slider_set_range(slider, min, max);
    lv_slider_set_value(slider, val, LV_ANIM_OFF);

    if(icon == NULL) {
        lv_obj_add_flag(slider, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK);
    }

    return obj;
}

static lv_obj_t * create_switch(lv_obj_t * parent, const char * icon, const char * txt, bool chk)
{
    lv_obj_t * obj = create_text(parent, icon, txt, LV_MENU_ITEM_BUILDER_VARIANT_1);

    lv_obj_t * sw = lv_switch_create(obj);
    lv_obj_add_state(sw, chk ? LV_STATE_CHECKED : 0);

    return obj;
}

This code get’s called from the main() in the following way:

//Load the graphScreen into RAM and display it
lv_obj_t *configurationScreen = ConfigurationScreenLoad(arg);
lv_scr_load_anim(configurationScreen, LV_SCR_LOAD_ANIM_NONE, 0, 0, true);

Screenshot and/or video

The current state with the strange keyboard (menu entry’s are blacked out).

I seem to have stumbled across the solution. Just placing it here for reference (but if I get an explanation why it works, that would be great.

So all that seems required for this to work, is to replace the menu pointer with lv_layer_top()

//Create the keyboard if required
if(keyboard == NULL)
    keyboard = lv_keyboard_create(lv_layer_top());

I have no clue how this works, but it works.

Hello,

If I am reading this right, in the code you posted, you don’t set the position for the keyboard anywhere. Default position will be 0,0 inside the parent object, which is the top left.

Could you try

keyboard = lv_keyboard_create(menu);
lv_obj_align(keyboard, LV_ALIGN_BOTTOM_MID, 0, 0); 

I am puzzled as to why lv_layer_top() origin position for the keyboard is at the bottom, but alas.

It shouldn’t be placed in the top-left.
If you look at the constructor the keyboard is placed at bottom mid automatically.

1 Like

Nice catch. That explains why it works on lv_layer_top().
I can only imagine that the menu automatically overwrites the positioning somehow, I figure it has to do with the FLEX layout used by the menu.

From: lvgl/src/widgets/menu/lv_menu.c at master · lvgl/lvgl · GitHub (line 546)

    lv_obj_t * main_cont = lv_obj_class_create_obj(&lv_menu_main_cont_class, obj);
    lv_obj_class_init_obj(main_cont);
    lv_obj_set_height(main_cont, LV_PCT(100));
    lv_obj_set_flex_grow(main_cont, 1);
    lv_obj_set_flex_flow(main_cont, LV_FLEX_FLOW_COLUMN);
    lv_obj_add_flag(main_cont, LV_OBJ_FLAG_EVENT_BUBBLE);
    lv_obj_remove_flag(main_cont, LV_OBJ_FLAG_CLICKABLE);
    menu->main = main_cont;

Could you try this for the keyboard:

lv_obj_add_flag(keyboard, LV_OBJ_FLAG_IGNORE_LAYOUT);
lv_obj_align(keyboard, LV_ALIGN_BOTTOM_MID, 0, 0); 

If that doesn’t work, I bet @kisvegabor probably knows…

I can’t see what ConfigurationScreenLoad returns, but does it create the base object that is the basis for any screen in LVGL 8?

lv_obj_t * screen = lv_obj_create(NULL);

This should be the root parent of menu, etc, the target of lv_scr_load_anim and also the parent of the keyboard object - all the examples in the docs use the active screen object, for instance. Using the menu object as the parent of the keyboard probably constrains the layout to the menu objects size, layout and styles, and causes your issues. lv_layer_top() is a special layer on top of the loaded screen and its bounds would be similar to the screen, which is probably why it works.

ConfigurationScreenLoad loads the result from lv_menu_create(NULL).

So that might be the issue. I need to start with lv_obj_create(NULL) as the root.