LVGL 8.3.11 SHOW and HIDE Keyboard

Description

I’ve implemented a configuration page with text areas and a keyboard that’s supposed to appear when a text area gets focus. However, this isn’t currently happening.

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

Vaweshare LCD 7’’ with ESP32-S3

What LVGL version are you using?

8.3.11

What do you want to achieve?

I’d like the keyboard to animate in from bottom to top when a textarea is clicked or focused. If the keyboard covers the textarea, then the textarea’s container should also be moved to keep the textarea visible.

Code to reproduce

void ui_Device_Config_screen_init(uint64_t uMacSens) {

    //creo i lcontenitore degli elementi, tastiera esclusa
    ui_Device_Config = lv_obj_create(NULL);
    lv_obj_clear_flag(ui_Device_Config, LV_OBJ_FLAG_SCROLLABLE);

    //TODO se necessario aggiungere l'evento di click sullo screen per rimuovere la tastiera
    //lv_obj_add_event_cb(scr, screen_click_event_cb, LV_EVENT_CLICKED, NULL);

     //la tastiera viene creata nello screen attivo e non nel contenitore
    lv_obj_t *kb = lv_keyboard_create(NULL);
    //Occupa metà schermo in verticale mentre tutto orizzontalmente
    //lv_obj_set_size(kb, LV_HOR_RES, LV_VER_RES / 2);
    lv_obj_set_width(kb, 800); //LV_HOR_RES);
    lv_obj_set_height(kb, 240); //LV_VER_RES / 2);
    //Inizia fuori dallo schermo
    lv_obj_set_y(kb, LV_VER_RES);
    //Nascondo la tastiera
    lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN);
    //Evento per determinare quando premo a chiusura manuale della tasiera
    lv_obj_add_event_cb(kb, device_config_keyboard_click_event_cb, LV_EVENT_ALL, ui_Device_Config);
    //forzo l'aggiornamewntro della tastiera
    lv_obj_update_layout(kb);

    //stile per i bottoni
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_radius(&style, 3);
    lv_style_set_bg_opa(&style, LV_OPA_100);
    lv_style_set_bg_color(&style, lv_palette_main(LV_PALETTE_GREY));
    lv_style_set_bg_grad_color(&style, lv_palette_darken(LV_PALETTE_GREY, 2));
    lv_style_set_bg_grad_dir(&style, LV_GRAD_DIR_VER);
    lv_style_set_text_color(&style, lv_color_white());
    lv_style_set_pad_all(&style, 10);

    //recupero il device dal macSens numerico e da li ricavo il tileType
    TileType tileType = TILE_NONE;
    char deviceName[21];
    char macSens[18];
    uint16_t start = 0;
    uint16_t end = 0;
    memset(deviceName, 0, sizeof(deviceName));
    memset(macSens, 0, sizeof(macSens));

    int8_t tileIndex = -1;
    int8_t deviceIndex = -1;

    bool deviceFound = getDeviceIndex(uMacSens, &tileIndex, &deviceIndex);
    if(deviceFound) {
        lvgl_port_lock(-1);
        if(pViewData->tiles[tileIndex].devices[deviceIndex].name[0] != 0x00) sprintf(deviceName, "%s", pViewData->tiles[tileIndex].devices[deviceIndex].name);
        sprintf(macSens, "%s", pViewData->tiles[tileIndex].devices[deviceIndex].macSens);
        tileType = pViewData->tiles[tileIndex].type;
        if(tileType == RELAY) {
            start = pViewData->tiles[tileIndex].devices[deviceIndex].value.relayData.start;
            end = pViewData->tiles[tileIndex].devices[deviceIndex].value.relayData.end;
        }
        lvgl_port_unlock();

    }

    char title[101];
    switch(tileType) {
        case RELAY:
        sprintf(title, "%s", "Relay");
        break;
        case THERMOMETER_INT:
        sprintf(title, "%s", "Termomentro interno");
        break;
        case THERMOMETER_EXT:
        sprintf(title, "%s", "Termometro esterno");
        break;
        default:
        sprintf(title, "%s", "Dispositivo non riconosciuto");
        break;
    }
    if(deviceName[0] != 0x00) {
        sprintf(title + strlen(title), ": %s (%s)", deviceName, macSens);
    } else {
        sprintf(title + strlen(title), ": %s", macSens);
    }

    //Creo il titolo della pagina, child indice 1
    lv_obj_t *ui_device_view_title_label = lv_label_create(ui_Device_Config);
    lv_obj_set_height(ui_device_view_title_label, 40);
    lv_obj_set_width(ui_device_view_title_label, 800);
    lv_obj_set_pos(ui_device_view_title_label, 10, 10);
    lv_label_set_long_mode(ui_device_view_title_label, LV_LABEL_LONG_SCROLL_CIRCULAR);
    lv_obj_set_style_text_align(ui_device_view_title_label, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_set_style_text_font(ui_device_view_title_label, &lv_font_montserrat_30, 0);
    lv_label_set_text(ui_device_view_title_label, title);

    //Creo la text area per il nome del dispositivo, child index 2
    lv_obj_t *ui_Device_Name = lv_textarea_create(ui_Device_Config);
    lv_obj_set_width(ui_Device_Name, 210);
    lv_obj_set_height(ui_Device_Name, 50);
    lv_obj_set_pos(ui_Device_Name, 10, 60);
    lv_textarea_set_placeholder_text(ui_Device_Name, "Nome");
    lv_textarea_set_one_line(ui_Device_Name, true);
    lv_obj_set_style_text_font(ui_Device_Name, &lv_font_montserrat_20, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_textarea_add_text(ui_Device_Name, deviceName);
    lv_obj_add_event_cb(ui_Device_Name, device_config_textarea_click_event_cb, LV_EVENT_ALL, kb);

    lv_obj_t *ui_Device_Start_Time = NULL;
    lv_obj_t *ui_Device_End_Time = NULL;
    if(tileType == RELAY) {
        //Creo la text area per l'oraio di inizio, child indice 3
        ui_Device_Start_Time = lv_textarea_create(ui_Device_Config);
        lv_obj_set_width(ui_Device_Start_Time, 100);
        lv_obj_set_height(ui_Device_Start_Time, 50);
        lv_obj_set_pos(ui_Device_Start_Time, 10, 120);
        lv_textarea_set_placeholder_text(ui_Device_Start_Time, "Orario inizio");
        lv_textarea_set_one_line(ui_Device_Start_Time, true);
        lv_obj_set_style_text_font(ui_Device_Start_Time, &lv_font_montserrat_20, LV_PART_MAIN | LV_STATE_DEFAULT);
        char startStr[6];
        memset(startStr, 0, sizeof(startStr));
        if(deviceFound) sprintf(startStr, "%02d:%02d", start / 60, start % 60);
        lv_textarea_add_text(ui_Device_Start_Time, startStr);
        lv_obj_add_event_cb(ui_Device_Start_Time, device_config_textarea_click_event_cb, LV_EVENT_ALL, kb);

        //Creo la text area per l'oraio di fine, child indice 4
        ui_Device_End_Time = lv_textarea_create(ui_Device_Config);
        lv_obj_set_width(ui_Device_End_Time, 100);
        lv_obj_set_height(ui_Device_End_Time, 50);
        lv_obj_set_pos(ui_Device_End_Time, 120, 120);
        lv_textarea_set_placeholder_text(ui_Device_End_Time, "Orario fine");
        lv_textarea_set_one_line(ui_Device_End_Time, true);
        lv_obj_set_style_text_font(ui_Device_End_Time, &lv_font_montserrat_20, LV_PART_MAIN | LV_STATE_DEFAULT);
        char endtStr[6];
        memset(endtStr, 0, sizeof(endtStr));
        if(deviceFound) sprintf(endtStr, "%02d:%02d", end / 60, end % 60);
        lv_textarea_add_text(ui_Device_End_Time, endtStr);
        lv_obj_add_event_cb(ui_Device_End_Time, device_config_textarea_click_event_cb, LV_EVENT_ALL, kb);
    }

    //Creo il bottone di annulla, child indice 3 o 5
    lv_obj_t * ui_Device_config_Cancel = lv_btn_create(ui_Device_Config);
    lv_obj_add_style(ui_Device_config_Cancel, &style, 0);
    lv_obj_t *ui_cancle_label = lv_label_create(ui_Device_config_Cancel);
    lv_label_set_text(ui_cancle_label, "Annulla");
    lv_obj_align(ui_cancle_label, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_style_text_font(ui_cancle_label, &lv_font_montserrat_20, 0);
    lv_obj_set_width(ui_Device_config_Cancel, 140);
    lv_obj_set_height(ui_Device_config_Cancel, 50);
    lv_obj_set_pos(ui_Device_config_Cancel, 490, 420);
    lv_obj_set_user_data(ui_Device_config_Cancel, (void *)&uMacSens);
    lv_obj_add_event_cb(ui_Device_config_Cancel, device_config_btn_cancel_click_event_cb, LV_EVENT_CLICKED, NULL);

    //Creo il bottone di conferma, neast child 4
    lv_obj_t * ui_Device_config_Confirm = lv_btn_create(ui_Device_Config);
    lv_obj_add_style(ui_Device_config_Confirm, &style, 0);
    lv_obj_t *ui_confirm_label = lv_label_create(ui_Device_config_Confirm);
    lv_label_set_text(ui_confirm_label, "Conferma");
    lv_obj_align(ui_confirm_label, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_style_text_font(ui_confirm_label, &lv_font_montserrat_20, 0);
    lv_obj_set_width(ui_Device_config_Confirm, 140);
    lv_obj_set_height(ui_Device_config_Confirm, 50);
    lv_obj_set_pos(ui_Device_config_Confirm, 650, 420);
    lv_obj_set_user_data(ui_Device_config_Confirm, (void *)&uMacSens);
    lv_obj_add_event_cb(ui_Device_config_Confirm, device_config_btn_confirm_click_event_cb, LV_EVENT_CLICKED, NULL);
}

//event handler
void device_config_textarea_click_event_cb(lv_event_t * e) {

    lv_obj_t *target_ta = lv_event_get_target(e);
    if(target_ta == NULL) {
        LV_LOG_USER("device_config_textarea_click_event_cb OUT: no target_ta");
        return;
    }

    lv_obj_t * form_container = lv_obj_get_parent(target_ta);
    if(form_container == NULL) {
        LV_LOG_USER("device_config_textarea_click_event_cb OUT: no form_container");
        return;
    }

    lv_event_code_t code = lv_event_get_code(e);
    
    // Retrieve the keyboard object passed as user_data
    lv_obj_t *keyboard = (lv_obj_t *)lv_event_get_user_data(e);
    if (keyboard == NULL) {
        LV_LOG_USER("device_config_textarea_click_event_cb OUT: no keyboard");
        return;
    }

    //TODO vedere come renderlo dinamico
    lv_coord_t form_container_original_y = 0;

    // Calculate keyboard positions based on current display and keyboard dimensions
    lv_coord_t keyboard_h = lv_obj_get_height(keyboard);
    lv_coord_t screen_h = lv_disp_get_ver_res(NULL);
    lv_coord_t keyboard_visible_y = screen_h - keyboard_h;
    lv_coord_t keyboard_hidden_y = screen_h; // Off-screen, below the bottom edge

    if (code == LV_EVENT_FOCUSED || code == LV_EVENT_CLICKED) {
        LV_LOG_USER("device_config_textarea_click_event_cb: code=%d", code);

        LV_LOG_USER("device_config_textarea_click_event_cb: keyboard_h=%d, screen_h=%d, keyboard_visible_y=%d, keyboard_hidden_y=%d",
                  keyboard_h, screen_h, keyboard_visible_y, keyboard_hidden_y);

        // 1. Show and animate the keyboard using the retrieved pointer
        lv_obj_clear_flag(keyboard, LV_OBJ_FLAG_HIDDEN); // Make it visible (still at hidden_y position)
        LV_LOG_USER("device_config_textarea_click_event_cb: animating keyboard to Y=%d", keyboard_visible_y);
        animate_keyboard_to_y(keyboard, keyboard_visible_y, false); // Animate it upwards
        lv_keyboard_set_textarea(keyboard, target_ta); // Link keyboard to the focused textarea

        // 2. Calculate the desired position for the form container
        lv_coord_t ta_abs_y = lv_obj_get_y2(target_ta); // Absolute Y position of the textarea
        lv_coord_t ta_h = lv_obj_get_height(target_ta);     // Height of the textarea
        LV_LOG_USER("device_config_textarea_click_event_cb: ta_abs_y=%d, ta_h=%d", ta_abs_y, ta_h);

        // Calculate target Y for textarea to be above the keyboard with a small margin
        lv_coord_t desired_ta_y_above_kb = screen_h - keyboard_h - ta_h - 10; // 10px margin above KB

        // Calculate the target Y for the form container
        lv_coord_t target_form_y = form_container_original_y - (ta_abs_y - desired_ta_y_above_kb);

        // Clamp target_form_y to ensure it doesn't go too low (below original position)
        if (target_form_y > form_container_original_y) {
            target_form_y = form_container_original_y;
        }
        // Clamp target_form_y to ensure it doesn't go too high (above screen top)
        // Or if the form is very large, ensure its bottom is just above the keyboard
        if (target_form_y < 0 && (target_form_y + lv_obj_get_height(form_container)) < keyboard_h + 10) {
             target_form_y = screen_h - keyboard_h - lv_obj_get_height(form_container) - 10;
             if (target_form_y < 0) target_form_y = 0; // Prevent going off-screen top
        }

        // 3. Animate the form container
        LV_LOG_USER("device_config_textarea_click_event_cb: animating form container to Y=%d", target_form_y);
        animate_form_container_to_y(target_form_y, form_container);

    } else if (code == LV_EVENT_DEFOCUSED) {
        lv_obj_t *focused_obj = lv_group_get_focused(lv_group_get_default());
        // If no object is focused, or the newly focused object is not a textarea,
        // hide the keyboard and reset the form position.
        if (focused_obj == NULL || !lv_obj_check_type(focused_obj, &lv_textarea_class)) {
            animate_keyboard_to_y(keyboard, keyboard_hidden_y, true); // Animate down and hide
            animate_form_container_to_y(form_container_original_y, form_container); // Reset form position
        }
    }
}


//EVENTO DI CLICK SULLO SCREEN BOH non so se utilizzarlo devo vedere come parametrizzare gli oggetti
/*void device_config_screen_click_event_cb(lv_event_t *e) {
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED) {
        lv_obj_t *target = lv_event_get_target(e); // L'oggetto cliccato
        lv_obj_t *current_focused_obj = lv_group_get_focused(NULL); // Ottieni l'oggetto con il focus nel gruppo predefinito

        // Se la tastiera è visibile E l'oggetto cliccato NON è la tastiera E NON è una textarea
        if (!lv_obj_has_flag(keyboard, LV_OBJ_FLAG_HIDDEN) &&
            target != keyboard &&
            target != textarea_username &&
            target != textarea_password &&
            target != textarea_email &&
            // Controlla anche se il click è avvenuto all'interno della tastiera stessa ma su un'area non clickable (padding etc)
            !lv_obj_is_ancestor(keyboard, target) &&
            // E controlla se l'oggetto con il focus non è una textarea (se il focus è passato a qualcos'altro)
            (current_focused_obj != textarea_username && current_focused_obj != textarea_password && current_focused_obj != textarea_email)
            ) {
            hide_keyboard_and_restore_form();
        }
    }
}*/

//EVENTO DI CLICK SULLA TASTIERA
void device_config_keyboard_click_event_cb(lv_event_t * e) {
    lv_obj_t *keyboard = lv_event_get_target(e); // The keyboard itself is the event target
    lv_event_code_t code = lv_event_get_code(e);

    lv_obj_t *form_container = (lv_obj_t *)lv_event_get_user_data(e);
    if (form_container == NULL) {
        return;
    }

    //TODO vedere come renderlo dinamico
    lv_coord_t form_container_original_y = 0;

    if (code == LV_EVENT_VALUE_CHANGED) {
        const char *btn_txt = lv_keyboard_get_btn_text(keyboard, lv_keyboard_get_selected_btn(keyboard));
        // Check if the pressed button is the keyboard hide symbol or "Done"
        if (strcmp(btn_txt, LV_SYMBOL_KEYBOARD) == 0 || strcmp(btn_txt, "Done") == 0) {

            lv_coord_t screen_h = lv_disp_get_ver_res(NULL);
            lv_coord_t keyboard_hidden_y = screen_h; // Calculate hidden Y for this keyboard instance

            animate_keyboard_to_y(keyboard, keyboard_hidden_y, true); // Animate keyboard down and hide it

            // Remove focus from any currently focused textarea
            lv_obj_t *focused_obj = lv_group_get_focused(lv_group_get_default());
            if (focused_obj != NULL) {
                 lv_obj_clear_state(focused_obj, LV_STATE_FOCUSED); // Clear focused state
            }
            animate_form_container_to_y(form_container_original_y, form_container); // Animate form container back to original position
        }
    }
}

//animations
//animazione della tastiera in ingresso
void set_obj_y_anim_cb(void * obj, int32_t v) {
    lv_obj_set_y((lv_obj_t *)obj, (lv_coord_t)v);
}

// Callback for when form container animation is ready (optional, for debug)
void form_anim_ready_cb(lv_anim_t *a) {
    LV_LOG_USER("Form container animation completed.");
}

// Callback for when keyboard animation is ready
void keyboard_anim_ready_cb(lv_anim_t *a) {
    lv_obj_t *kb = (lv_obj_t *)a->var;
    bool hide_on_finish = (bool)(intptr_t)a->user_data; // Retrieve the hide_on_finish flag

    if (hide_on_finish) {
        lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); // Hide the keyboard after it slides down
        LV_LOG_USER("Keyboard hidden completely after animation.");
    }
    a->user_data = NULL; // Reset user_data pointer
}

// Function to start form container animation
void animate_form_container_to_y(lv_coord_t target_y, lv_obj_t *form_container) {
    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, form_container);
    lv_anim_set_exec_cb(&a, set_obj_y_anim_cb); // Use custom callback for Y
    lv_anim_set_values(&a, lv_obj_get_y(form_container), target_y);
    lv_anim_set_time(&a, 300); // Animation duration in ms
    lv_anim_set_path_cb(&a, lv_anim_path_ease_out); // Easing function
    lv_anim_set_ready_cb(&a, form_anim_ready_cb);
    lv_anim_start(&a);
}

// Function to start keyboard animation
// Takes the keyboard object as an argument
void animate_keyboard_to_y(lv_obj_t *kb_obj, lv_coord_t target_y, bool hide_on_finish) {
    if (kb_obj == NULL) {
        LV_LOG_USER("Attempted to animate a NULL keyboard object!");
        return;
    }
    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, kb_obj); // Animate the passed keyboard object
    lv_anim_set_exec_cb(&a, set_obj_y_anim_cb); // Use custom callback for Y
    lv_anim_set_values(&a, lv_obj_get_y(kb_obj), target_y);
    lv_anim_set_time(&a, 300);
    lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
    lv_anim_set_ready_cb(&a, keyboard_anim_ready_cb);
    lv_anim_set_user_data(&a, (void*)(intptr_t)hide_on_finish); // Pass flag as user_data
    lv_anim_start(&a);
}

this log of LV_LOG_USER

[User]  (42.042, +42042)         device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: code=14  (in ui_events.c line #590)

[User]  (42.054, +12)    device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: keyboard_h=480, screen_h=480, keyboard_visible_y=0, keyboard_hidden_y=480       (in ui_events.c line #592)

[User]  (42.064, +10)    device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: animating keyboard to Y=0        (in ui_events.c line #597)

[User]  (42.084, +20)    device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: ta_abs_y=108, ta_h=48    (in ui_events.c line #604)

[User]  (42.094, +10)    device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: animating form container to Y=0  (in ui_events.c line #624)

[User]  (42.208, +114)   device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: code=7   (in ui_events.c line #590)

[User]  (42.220, +12)    device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: keyboard_h=480, screen_h=480, keyboard_visible_y=0, keyboard_hidden_y=480       (in ui_events.c line #592)

[User]  (42.230, +10)    device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: animating keyboard to Y=0        (in ui_events.c line #597)

[User]  (42.250, +20)    device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: ta_abs_y=108, ta_h=48    (in ui_events.c line #604)

[User]  (42.262, +12)    device_config_textarea_click_event_cb: device_config_textarea_click_event_cb: animating form container to Y=0  (in ui_events.c line #624)     

[User]  (42.560, +298)   form_anim_ready_cb: Form container animation completed.        (in ui_animations.c line #95)

thanks for support