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