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!) - Display app: Add toggle to display performance measurement overlay (consider showing FPS in statusbar!)
- Files app: copy/paste actions - 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) - 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 # App Ideas
- Map widget: - 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 #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 "Tactility/app/AppContext.h"
#include <cstdio>
#include <lvgl.h>
namespace tt::service::gui { 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. * 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. * Show the on-screen keyboard.
* @param[in] textarea the textarea to focus the input for * @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. * Hide the on-screen keyboard.
* Has no effect when the keyboard is not visible. * 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: * 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 * - 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 * @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: * 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/app/display/DisplaySettings.h"
#include "Tactility/lvgl/LvglKeypad.h" #include "Tactility/lvgl/Keyboard.h"
#include "Tactility/hal/display/DisplayDevice.h" #include "Tactility/hal/display/DisplayDevice.h"
#include "Tactility/hal/touch/TouchDevice.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())) { if (keyboard->start(display->getLvglDisplay())) {
lv_indev_t* keyboard_indev = keyboard->getLvglIndev(); lv_indev_t* keyboard_indev = keyboard->getLvglIndev();
lv_indev_set_user_data(keyboard_indev, keyboard.get()); 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"); TT_LOG_I(TAG, "Keyboard started");
return true; return true;
} else { } 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/gui/Gui.h"
#include "Tactility/service/loader/Loader_i.h"
#include "Tactility/lvgl/LvglSync.h" #include "Tactility/lvgl/LvglSync.h"
#include "Tactility/lvgl/Style.h"
#include "Tactility/lvgl/Statusbar.h" #include "Tactility/lvgl/Statusbar.h"
#include "Tactility/lvgl/Style.h"
#include "Tactility/service/loader/Loader_i.h"
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <Tactility/RtosCompat.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/LvglSync.h"
#include "Tactility/lvgl/Statusbar.h"
#include "Tactility/lvgl/Style.h" #include "Tactility/lvgl/Style.h"
#include <Tactility/Check.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_width(child_container, LV_PCT(100));
lv_obj_set_flex_grow(child_container, 1); lv_obj_set_flex_grow(child_container, 1);
if (keyboardIsEnabled()) { if (softwareKeyboardIsEnabled()) {
gui->keyboard = lv_keyboard_create(parent); gui->keyboard = lv_keyboard_create(parent);
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
} else { } else {

View File

@ -1,7 +1,7 @@
#include "Tactility/lvgl/Keyboard.h"
#include "Tactility/Check.h" #include "Tactility/Check.h"
#include "Tactility/service/gui/Gui_i.h"
#include "Tactility/lvgl/LvglKeypad.h"
#include "Tactility/lvgl/LvglSync.h" #include "Tactility/lvgl/LvglSync.h"
#include "Tactility/service/gui/Gui.h"
#include <Tactility/TactilityConfig.h> #include <Tactility/TactilityConfig.h>
@ -11,19 +11,19 @@ extern Gui* gui;
static void show_keyboard(lv_event_t* event) { static void show_keyboard(lv_event_t* event) {
lv_obj_t* target = lv_event_get_current_target_obj(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); lv_obj_scroll_to_view(target, LV_ANIM_ON);
} }
static void hide_keyboard(TT_UNUSED lv_event_t* event) { static void hide_keyboard(TT_UNUSED lv_event_t* event) {
keyboardHide(); softwareKeyboardHide();
} }
bool keyboardIsEnabled() { bool softwareKeyboardIsEnabled() {
return !lvgl::keypad_is_available() || TT_CONFIG_FORCE_ONSCREEN_KEYBOARD; return !lvgl::hardware_keyboard_is_available() || TT_CONFIG_FORCE_ONSCREEN_KEYBOARD;
} }
void keyboardShow(lv_obj_t* textarea) { void softwareKeyboardShow(lv_obj_t* textarea) {
lock(); lock();
if (gui->keyboard) { if (gui->keyboard) {
@ -34,7 +34,7 @@ void keyboardShow(lv_obj_t* textarea) {
unlock(); unlock();
} }
void keyboardHide() { void softwareKeyboardHide() {
lock(); lock();
if (gui->keyboard) { if (gui->keyboard) {
@ -48,7 +48,7 @@ void keyboardAddTextArea(lv_obj_t* textarea) {
lock(); lock();
tt_check(lvgl::lock(0), "lvgl should already be locked before calling this method"); 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, 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_DEFOCUSED, nullptr);
lv_obj_add_event_cb(textarea, hide_keyboard, LV_EVENT_READY, 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_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); lv_group_add_obj(gui->keyboardGroup, textarea);
lvgl::keypad_activate(gui->keyboardGroup); lvgl::software_keyboard_activate(gui->keyboardGroup);
lvgl::unlock(); lvgl::unlock();
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_app_selectiondialog.h"
#include "tt_bundle.h" #include "tt_bundle.h"
#include "tt_hal_i2c.h" #include "tt_hal_i2c.h"
#include "tt_lvgl_keyboard.h"
#include "tt_lvgl_spinner.h" #include "tt_lvgl_spinner.h"
#include "tt_lvgl_toolbar.h" #include "tt_lvgl_toolbar.h"
#include "tt_message_queue.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_master_has_device_at_address),
ESP_ELFSYM_EXPORT(tt_hal_i2c_lock), ESP_ELFSYM_EXPORT(tt_hal_i2c_lock),
ESP_ELFSYM_EXPORT(tt_hal_i2c_unlock), 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),
ESP_ELFSYM_EXPORT(tt_lvgl_toolbar_create_for_app), ESP_ELFSYM_EXPORT(tt_lvgl_toolbar_create_for_app),
ESP_ELFSYM_EXPORT(tt_message_queue_alloc), 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);
}
}