Keyboard improvements (#240)

- Renamed various keyboard functions so it's easier to differentiate hardware versus software keyboard functionality
- Created `Tactility/lvgl/Keyboard.h` as a proxy  for the internal `Gui` service.
- Implemented `tt_lvgl_keyboard.h` in `TactilityC`.
This commit is contained in:
Ken Van Hoeylandt 2025-03-10 00:21:49 +01:00 committed by GitHub
parent 13d7e84ef3
commit 85a6ad3bbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 274 additions and 130 deletions

View File

@ -51,6 +51,7 @@
- Display app: Add toggle to display performance measurement overlay (consider showing FPS in statusbar!)
- Files app: copy/paste actions
- On crash, try to save current log to flash or SD card? (this is risky, though, so ask in Discord first)
- Support more than 1 hardware keyboard (see lvgl::hardware_keyboard_set_indev()). LVGL init currently calls keyboard init, but that part should probably be done from the KeyboardDevice base class.
# App Ideas
- Map widget:

View File

@ -0,0 +1,59 @@
#pragma once
#include <lvgl.h>
namespace tt::lvgl {
/**
* Show the on-screen keyboard.
* @param[in] textarea the textarea to focus the input for
*/
void software_keyboard_show(lv_obj_t* textarea);
/**
* Hide the on-screen keyboard.
* Has no effect when the keyboard is not visible.
*/
void software_keyboard_hide();
/**
* The on-screen keyboard is only shown when both of these conditions are true:
* - there is no hardware keyboard
* - TT_CONFIG_FORCE_ONSCREEN_KEYBOARD is set to true in tactility_config.h
* @return if we should show a on-screen keyboard for text input inside our apps
*/
bool software_keyboard_is_enabled();
/**
* Activate the keypad for a widget group.
* @param group
*/
void software_keyboard_activate(lv_group_t* group);
/**
* Deactivate the keypad for the current widget group (if any).
* You don't have to call this after calling _activate() because widget
* cleanup automatically removes itself from the group it belongs to.
*/
void software_keyboard_deactivate();
/**
* @return true if LVGL is configured with a keypad
*/
bool hardware_keyboard_is_available();
/**
* Set the keypad.
* @param device the keypad device
*/
void hardware_keyboard_set_indev(lv_indev_t* device);
/**
* Glue code for the on-screen keyboard and the hardware keyboard:
* - Attach automatic hide/show parameters for the on-screen keyboard.
* - Registers the textarea to the default lv_group_t for hardware keyboards.
* @param[in] textarea
*/
void keyboard_add_textarea(lv_obj_t* textarea);
}

View File

@ -1,34 +0,0 @@
/**
* This code relates to the hardware keyboard support also known as "keypads" in LVGL.
*/
#pragma once
#include <lvgl.h>
namespace tt::lvgl {
/**
* @return true if LVGL is configured with a keypad
*/
bool keypad_is_available();
/**
* Set the keypad.
* @param device the keypad device
*/
void keypad_set_indev(lv_indev_t* device);
/**
* Activate the keypad for a widget group.
* @param group
*/
void keypad_activate(lv_group_t* group);
/**
* Deactivate the keypad for the current widget group (if any).
* You don't have to call this after calling _activate() because widget
* cleanup automatically removes itself from the group it belongs to.
*/
void keypad_deactivate();
} // namespace

View File

@ -1,11 +1,48 @@
#pragma once
#include "Tactility/app/AppInstance.h"
#include <Tactility/MessageQueue.h>
#include <Tactility/Mutex.h>
#include <Tactility/PubSub.h>
#include "Tactility/app/AppContext.h"
#include <cstdio>
#include <lvgl.h>
namespace tt::service::gui {
typedef struct Gui Gui;
#define GUI_THREAD_FLAG_DRAW (1 << 0)
#define GUI_THREAD_FLAG_INPUT (1 << 1)
#define GUI_THREAD_FLAG_EXIT (1 << 2)
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_EXIT)
/** Gui structure */
struct Gui {
// Thread and lock
Thread* thread = nullptr;
Mutex mutex = Mutex(Mutex::Type::Recursive);
PubSub::SubscriptionHandle loader_pubsub_subscription = nullptr;
// Layers and Canvas
lv_obj_t* appRootWidget = nullptr;
lv_obj_t* statusbarWidget = nullptr;
// App-specific
std::shared_ptr<app::AppContext> appToRender = nullptr;
lv_obj_t* _Nullable keyboard = nullptr;
lv_group_t* keyboardGroup = nullptr;
};
/** Update GUI, request redraw */
void requestDraw();
/** Lock GUI */
void lock();
/** Unlock GUI */
void unlock();
/**
* Set the app viewport in the gui state and request the gui to draw it.
@ -24,13 +61,13 @@ void hideApp();
* Show the on-screen keyboard.
* @param[in] textarea the textarea to focus the input for
*/
void keyboardShow(lv_obj_t* textarea);
void softwareKeyboardShow(lv_obj_t* textarea);
/**
* Hide the on-screen keyboard.
* Has no effect when the keyboard is not visible.
*/
void keyboardHide();
void softwareKeyboardHide();
/**
* The on-screen keyboard is only shown when both of these conditions are true:
@ -38,7 +75,7 @@ void keyboardHide();
* - TT_CONFIG_FORCE_ONSCREEN_KEYBOARD is set to true in tactility_config.h
* @return if we should show a on-screen keyboard for text input inside our apps
*/
bool keyboardIsEnabled();
bool softwareKeyboardIsEnabled();
/**
* Glue code for the on-screen keyboard and the hardware keyboard:

View File

@ -1,46 +0,0 @@
#pragma once
#include <Tactility/MessageQueue.h>
#include <Tactility/Mutex.h>
#include <Tactility/PubSub.h>
#include <Tactility/service/gui/Gui.h>
#include <cstdio>
#include <lvgl.h>
namespace tt::service::gui {
#define GUI_THREAD_FLAG_DRAW (1 << 0)
#define GUI_THREAD_FLAG_INPUT (1 << 1)
#define GUI_THREAD_FLAG_EXIT (1 << 2)
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_EXIT)
/** Gui structure */
struct Gui {
// Thread and lock
Thread* thread = nullptr;
Mutex mutex = Mutex(Mutex::Type::Recursive);
PubSub::SubscriptionHandle loader_pubsub_subscription = nullptr;
// Layers and Canvas
lv_obj_t* appRootWidget = nullptr;
lv_obj_t* statusbarWidget = nullptr;
// App-specific
std::shared_ptr<app::AppContext> appToRender = nullptr;
lv_obj_t* _Nullable keyboard = nullptr;
lv_group_t* keyboardGroup = nullptr;
};
/** Update GUI, request redraw */
void requestDraw();
/** Lock GUI */
void lock();
/** Unlock GUI */
void unlock();
} // namespace

View File

@ -1,5 +1,5 @@
#include "Tactility/app/display/DisplaySettings.h"
#include "Tactility/lvgl/LvglKeypad.h"
#include "Tactility/lvgl/Keyboard.h"
#include "Tactility/hal/display/DisplayDevice.h"
#include "Tactility/hal/touch/TouchDevice.h"
@ -68,7 +68,7 @@ static bool initKeyboard(const std::shared_ptr<hal::display::DisplayDevice>& dis
if (keyboard->start(display->getLvglDisplay())) {
lv_indev_t* keyboard_indev = keyboard->getLvglIndev();
lv_indev_set_user_data(keyboard_indev, keyboard.get());
tt::lvgl::keypad_set_indev(keyboard_indev);
tt::lvgl::hardware_keyboard_set_indev(keyboard_indev);
TT_LOG_I(TAG, "Keyboard started");
return true;
} else {

View File

@ -0,0 +1,44 @@
#include "Tactility/lvgl/Keyboard.h"
#include "Tactility/service/gui/Gui.h"
namespace tt::lvgl {
static lv_indev_t* keyboard_device = nullptr;
void software_keyboard_show(lv_obj_t* textarea) {
service::gui::softwareKeyboardShow(textarea);
}
void software_keyboard_hide() {
service::gui::softwareKeyboardHide();
}
bool software_keyboard_is_enabled() {
return service::gui::softwareKeyboardIsEnabled();
}
void software_keyboard_activate(lv_group_t* group) {
if (keyboard_device != nullptr) {
lv_indev_set_group(keyboard_device, group);
}
}
void software_keyboard_deactivate() {
if (keyboard_device != nullptr) {
lv_indev_set_group(keyboard_device, nullptr);
}
}
bool hardware_keyboard_is_available() {
return keyboard_device != nullptr;
}
void hardware_keyboard_set_indev(lv_indev_t* device) {
keyboard_device = device;
}
void keyboard_add_textarea(lv_obj_t* textarea) {
service::gui::keyboardAddTextArea(textarea);
}
}

View File

@ -1,27 +0,0 @@
#include "Tactility/lvgl/LvglKeypad.h"
namespace tt::lvgl {
static lv_indev_t* keyboard_device = nullptr;
bool keypad_is_available() {
return keyboard_device != nullptr;
}
void keypad_set_indev(lv_indev_t* device) {
keyboard_device = device;
}
void keypad_activate(lv_group_t* group) {
if (keyboard_device != nullptr) {
lv_indev_set_group(keyboard_device, group);
}
}
void keypad_deactivate() {
if (keyboard_device != nullptr) {
lv_indev_set_group(keyboard_device, nullptr);
}
}
} // namespace

View File

@ -1,8 +1,8 @@
#include "Tactility/service/gui/Gui_i.h"
#include "Tactility/service/loader/Loader_i.h"
#include "Tactility/service/gui/Gui.h"
#include "Tactility/lvgl/LvglSync.h"
#include "Tactility/lvgl/Style.h"
#include "Tactility/lvgl/Statusbar.h"
#include "Tactility/lvgl/Style.h"
#include "Tactility/service/loader/Loader_i.h"
#include <Tactility/Tactility.h>
#include <Tactility/RtosCompat.h>

View File

@ -1,6 +1,7 @@
#include "Tactility/service/gui/Gui_i.h"
#include "Tactility/service/gui/Gui.h"
#include "Tactility/app/AppInstance.h"
#include "Tactility/lvgl/LvglSync.h"
#include "Tactility/lvgl/Statusbar.h"
#include "Tactility/lvgl/Style.h"
#include <Tactility/Check.h>
@ -17,7 +18,7 @@ static lv_obj_t* createAppViews(Gui* gui, lv_obj_t* parent) {
lv_obj_set_width(child_container, LV_PCT(100));
lv_obj_set_flex_grow(child_container, 1);
if (keyboardIsEnabled()) {
if (softwareKeyboardIsEnabled()) {
gui->keyboard = lv_keyboard_create(parent);
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
} else {

View File

@ -1,7 +1,7 @@
#include "Tactility/lvgl/Keyboard.h"
#include "Tactility/Check.h"
#include "Tactility/service/gui/Gui_i.h"
#include "Tactility/lvgl/LvglKeypad.h"
#include "Tactility/lvgl/LvglSync.h"
#include "Tactility/service/gui/Gui.h"
#include <Tactility/TactilityConfig.h>
@ -11,19 +11,19 @@ extern Gui* gui;
static void show_keyboard(lv_event_t* event) {
lv_obj_t* target = lv_event_get_current_target_obj(event);
keyboardShow(target);
softwareKeyboardShow(target);
lv_obj_scroll_to_view(target, LV_ANIM_ON);
}
static void hide_keyboard(TT_UNUSED lv_event_t* event) {
keyboardHide();
softwareKeyboardHide();
}
bool keyboardIsEnabled() {
return !lvgl::keypad_is_available() || TT_CONFIG_FORCE_ONSCREEN_KEYBOARD;
bool softwareKeyboardIsEnabled() {
return !lvgl::hardware_keyboard_is_available() || TT_CONFIG_FORCE_ONSCREEN_KEYBOARD;
}
void keyboardShow(lv_obj_t* textarea) {
void softwareKeyboardShow(lv_obj_t* textarea) {
lock();
if (gui->keyboard) {
@ -34,7 +34,7 @@ void keyboardShow(lv_obj_t* textarea) {
unlock();
}
void keyboardHide() {
void softwareKeyboardHide() {
lock();
if (gui->keyboard) {
@ -48,7 +48,7 @@ void keyboardAddTextArea(lv_obj_t* textarea) {
lock();
tt_check(lvgl::lock(0), "lvgl should already be locked before calling this method");
if (keyboardIsEnabled()) {
if (softwareKeyboardIsEnabled()) {
lv_obj_add_event_cb(textarea, show_keyboard, LV_EVENT_FOCUSED, nullptr);
lv_obj_add_event_cb(textarea, hide_keyboard, LV_EVENT_DEFOCUSED, nullptr);
lv_obj_add_event_cb(textarea, hide_keyboard, LV_EVENT_READY, nullptr);
@ -57,7 +57,7 @@ void keyboardAddTextArea(lv_obj_t* textarea) {
// lv_obj_t auto-remove themselves from the group when they are destroyed (last checked in LVGL 8.3)
lv_group_add_obj(gui->keyboardGroup, textarea);
lvgl::keypad_activate(gui->keyboardGroup);
lvgl::software_keyboard_activate(gui->keyboardGroup);
lvgl::unlock();
unlock();

View File

@ -0,0 +1,63 @@
#pragma once
#include <lvgl.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Show the on-screen keyboard.
* @param[in] textarea the textarea to focus the input for
*/
void tt_lvgl_software_keyboard_show(lv_obj_t* textarea);
/**
* Hide the on-screen keyboard.
* Has no effect when the keyboard is not visible.
*/
void tt_lvgl_software_keyboard_hide();
/**
* The on-screen keyboard is only shown when both of these conditions are true:
* - there is no hardware keyboard
* - TT_CONFIG_FORCE_ONSCREEN_KEYBOARD is set to true in tactility_config.h
* @return if we should show a on-screen keyboard for text input inside our apps
*/
bool tt_lvgl_software_keyboard_is_enabled();
/**
* Activate the keypad for a widget group.
* @param group
*/
void tt_lvgl_software_keyboard_activate(lv_group_t* group);
/**
* Deactivate the keypad for the current widget group (if any).
* You don't have to call this after calling _activate() because widget
* cleanup automatically removes itself from the group it belongs to.
*/
void tt_lvgl_software_keyboard_deactivate();
/**
* @return true if LVGL is configured with a keypad
*/
bool tt_lvgl_hardware_keyboard_is_available();
/**
* Set the keypad.
* @param device the keypad device
*/
void tt_lvgl_hardware_keyboard_set_indev(lv_indev_t* device);
/**
* Glue code for the on-screen keyboard and the hardware keyboard:
* - Attach automatic hide/show parameters for the on-screen keyboard.
* - Registers the textarea to the default lv_group_t for hardware keyboards.
* @param[in] textarea
*/
void tt_lvgl_keyboard_add_textarea(lv_obj_t* textarea);
#ifdef __cplusplus
}
#endif

View File

@ -6,6 +6,7 @@
#include "tt_app_selectiondialog.h"
#include "tt_bundle.h"
#include "tt_hal_i2c.h"
#include "tt_lvgl_keyboard.h"
#include "tt_lvgl_spinner.h"
#include "tt_lvgl_toolbar.h"
#include "tt_message_queue.h"
@ -49,6 +50,14 @@ const struct esp_elfsym elf_symbols[] {
ESP_ELFSYM_EXPORT(tt_hal_i2c_master_has_device_at_address),
ESP_ELFSYM_EXPORT(tt_hal_i2c_lock),
ESP_ELFSYM_EXPORT(tt_hal_i2c_unlock),
ESP_ELFSYM_EXPORT(tt_lvgl_software_keyboard_show),
ESP_ELFSYM_EXPORT(tt_lvgl_software_keyboard_hide),
ESP_ELFSYM_EXPORT(tt_lvgl_software_keyboard_is_enabled),
ESP_ELFSYM_EXPORT(tt_lvgl_software_keyboard_activate),
ESP_ELFSYM_EXPORT(tt_lvgl_software_keyboard_deactivate),
ESP_ELFSYM_EXPORT(tt_lvgl_hardware_keyboard_is_available),
ESP_ELFSYM_EXPORT(tt_lvgl_hardware_keyboard_set_indev),
ESP_ELFSYM_EXPORT(tt_lvgl_keyboard_add_textarea),
ESP_ELFSYM_EXPORT(tt_lvgl_toolbar_create),
ESP_ELFSYM_EXPORT(tt_lvgl_toolbar_create_for_app),
ESP_ELFSYM_EXPORT(tt_message_queue_alloc),

View File

@ -0,0 +1,37 @@
#include "Tactility/lvgl/Keyboard.h"
extern "C" {
void tt_lvgl_software_keyboard_show(lv_obj_t* textarea) {
tt::lvgl::software_keyboard_show(textarea);
}
void tt_lvgl_software_keyboard_hide() {
tt::lvgl::software_keyboard_hide();
}
bool tt_lvgl_software_keyboard_is_enabled() {
return tt::lvgl::software_keyboard_is_enabled();
}
void tt_lvgl_software_keyboard_activate(lv_group_t* group) {
tt::lvgl::software_keyboard_activate(group);
}
void tt_lvgl_software_keyboard_deactivate() {
tt::lvgl::software_keyboard_deactivate();
}
bool tt_lvgl_hardware_keyboard_is_available() {
return tt::lvgl::hardware_keyboard_is_available();
}
void tt_lvgl_hardware_keyboard_set_indev(lv_indev_t* device) {
tt::lvgl::hardware_keyboard_set_indev(device);
}
void tt_lvgl_keyboard_add_textarea(lv_obj_t* textarea) {
tt::lvgl::keyboard_add_textarea(textarea);
}
}