Touch and display driver subsystems reworked (and more) (#302)
- Refactored `TouchDevice`: it can now start/stop LVGL separately, and it has an optional `TouchDriver` interface - Refactored `DisplayDevice`: it can now start/stop LVGL separately, and it has an optional `DisplayDriver` interface - Updated all boards and drivers for above changes - LVGL can now be stopped and (re)started on the fly - Fixed issues with restarting Gui and Statusbar services - Refactored `Gui` service to be class and renamed `Gui` to `GuiService` - Fixed `Statusbar` service: forgot to deregister one of the icons - Updated `esp_lcd_st7701` to v1.1.3 - `lv_textarea_create()` now automatically registers the hardware keyboard hooks (by wrapping the function) - Fixed and updated `tactility.py` - Cleanup of a lot of code - `BootInitLvglBegin` and `BootInitLvglEnd` are replaced by `LvglStarted` and `LvglStopped`. - Introduced `tt::service::State` which is accessible via `tt::service::getState()` (and internally via `ServiceInstance`) - Started replacing `#define TAG` with `constexpr auto TAG = "..";`
This commit is contained in:
committed by
GitHub
parent
15f4fbfdc6
commit
d875ade8cb
@@ -1,7 +1,7 @@
|
||||
#include "Tactility/Tactility.h"
|
||||
|
||||
#include "Tactility/app/ManifestRegistry.h"
|
||||
#include "Tactility/lvgl/Init_i.h"
|
||||
#include "Tactility/lvgl/LvglPrivate.h"
|
||||
#include "Tactility/service/ServiceManifest.h"
|
||||
|
||||
#include <Tactility/TactilityHeadless.h>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <Tactility/Assets.h>
|
||||
#include <Tactility/service/espnow/EspNow.h>
|
||||
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
#include "Tactility/lvgl/LvglSync.h"
|
||||
|
||||
#include <cstdio>
|
||||
@@ -16,7 +15,7 @@
|
||||
namespace tt::app::chat {
|
||||
|
||||
constexpr const char* TAG = "ChatApp";
|
||||
constexpr const uint8_t BROADCAST_ADDRESS[ESP_NOW_ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
constexpr uint8_t BROADCAST_ADDRESS[ESP_NOW_ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
|
||||
class ChatApp : public App {
|
||||
|
||||
@@ -154,7 +153,6 @@ public:
|
||||
lv_obj_set_height(input_field, LV_PCT(100));
|
||||
lv_textarea_set_placeholder_text(input_field, "Type a message...");
|
||||
lv_textarea_set_one_line(input_field, true);
|
||||
service::gui::keyboardAddTextArea(input_field);
|
||||
|
||||
// Send button
|
||||
auto* send_btn = lv_btn_create(input_panel);
|
||||
|
||||
@@ -72,9 +72,7 @@ class DisplayApp : public App {
|
||||
|
||||
static void onGammaSliderEvent(lv_event_t* event) {
|
||||
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
auto* lvgl_display = lv_display_get_default();
|
||||
assert(lvgl_display != nullptr);
|
||||
auto* hal_display = static_cast<hal::display::DisplayDevice*>(lv_display_get_user_data(lvgl_display));
|
||||
auto hal_display = hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
|
||||
assert(hal_display != nullptr);
|
||||
|
||||
if (hal_display->getGammaCurveCount() > 0) {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <Tactility/service/gui/Gui.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "Tactility/service/loader/Loader.h"
|
||||
@@ -200,7 +199,6 @@ void View::init(lv_obj_t* parent, Mode mode) {
|
||||
path_textarea = lv_textarea_create(bottom_wrapper);
|
||||
lv_textarea_set_one_line(path_textarea, true);
|
||||
lv_obj_set_flex_grow(path_textarea, 1);
|
||||
service::gui::keyboardAddTextArea(path_textarea);
|
||||
lv_obj_add_event_cb(path_textarea, onPathTextChanged, LV_EVENT_VALUE_CHANGED, this);
|
||||
|
||||
select_button = lv_button_create(bottom_wrapper);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Tactility/lvgl/Toolbar.h"
|
||||
#include "Tactility/service/loader/Loader.h"
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
@@ -47,8 +46,6 @@ static std::string getTitleParameter(const std::shared_ptr<const Bundle>& bundle
|
||||
|
||||
class InputDialogApp : public App {
|
||||
|
||||
private:
|
||||
|
||||
static void createButton(lv_obj_t* parent, const std::string& text, void* callbackContext) {
|
||||
lv_obj_t* button = lv_button_create(parent);
|
||||
lv_obj_t* button_label = lv_label_create(button);
|
||||
@@ -71,9 +68,9 @@ private:
|
||||
auto bundle = std::make_unique<Bundle>();
|
||||
const char* text = lv_textarea_get_text((lv_obj_t*)user_data);
|
||||
bundle->putString(RESULT_BUNDLE_KEY_RESULT, text);
|
||||
setResult(app::Result::Ok, std::move(bundle));
|
||||
setResult(Result::Ok, std::move(bundle));
|
||||
} else {
|
||||
setResult(app::Result::Cancelled);
|
||||
setResult(Result::Cancelled);
|
||||
|
||||
}
|
||||
service::loader::stopApp();
|
||||
@@ -106,7 +103,6 @@ public:
|
||||
if (parameters->optString(PARAMETER_BUNDLE_KEY_PREFILLED, prefilled)) {
|
||||
lv_textarea_set_text(textarea, prefilled.c_str());
|
||||
}
|
||||
service::gui::keyboardAddTextArea(textarea);
|
||||
|
||||
auto* button_wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_flex_flow(button_wrapper, LV_FLEX_FLOW_ROW);
|
||||
|
||||
@@ -172,8 +172,6 @@ class NotesApp : public App {
|
||||
if (!file::findOrCreateDirectory(context.getPaths()->getDataDirectory(), 0777)) {
|
||||
TT_LOG_E(TAG, "Failed to find or create path %s", context.getPaths()->getDataDirectory().c_str());
|
||||
}
|
||||
|
||||
lvgl::keyboard_add_textarea(uiNoteText);
|
||||
}
|
||||
|
||||
void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr<Bundle> resultData) override {
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include "Tactility/app/AppManifest.h"
|
||||
#include "Tactility/lvgl/LvglSync.h"
|
||||
#include "Tactility/lvgl/Toolbar.h"
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
#include "Tactility/service/loader/Loader.h"
|
||||
#include "Tactility/service/screenshot/Screenshot.h"
|
||||
|
||||
#include <Tactility/TactilityHeadless.h>
|
||||
@@ -39,7 +37,7 @@ class ScreenshotApp final : public App {
|
||||
public:
|
||||
|
||||
ScreenshotApp();
|
||||
~ScreenshotApp() final;
|
||||
~ScreenshotApp();
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override;
|
||||
void onStartPressed();
|
||||
@@ -271,9 +269,6 @@ void ScreenshotApp::onShow(AppContext& appContext, lv_obj_t* parent) {
|
||||
createFilePathWidgets(wrapper);
|
||||
createTimerSettingsWidgets(wrapper);
|
||||
|
||||
service::gui::keyboardAddTextArea(delayTextArea);
|
||||
service::gui::keyboardAddTextArea(pathTextArea);
|
||||
|
||||
updateScreenshotMode();
|
||||
|
||||
if (!updateTimer->isRunning()) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "Tactility/app/timezone/TimeZone.h"
|
||||
#include "Tactility/lvgl/Toolbar.h"
|
||||
#include "Tactility/lvgl/LvglSync.h"
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
#include "Tactility/service/loader/Loader.h"
|
||||
|
||||
#include <Tactility/Partitions.h>
|
||||
@@ -66,8 +65,6 @@ void setResultCode(Bundle& bundle, const std::string& code) {
|
||||
|
||||
class TimeZoneApp : public App {
|
||||
|
||||
private:
|
||||
|
||||
Mutex mutex;
|
||||
std::vector<TimeZoneEntry> entries;
|
||||
std::unique_ptr<Timer> updateTimer;
|
||||
@@ -107,7 +104,7 @@ private:
|
||||
setResultName(*bundle, entry.name);
|
||||
setResultCode(*bundle, entry.code);
|
||||
|
||||
setResult(app::Result::Ok, std::move(bundle));
|
||||
setResult(Result::Ok, std::move(bundle));
|
||||
|
||||
service::loader::stopApp();
|
||||
}
|
||||
@@ -221,7 +218,6 @@ public:
|
||||
lv_obj_add_event_cb(textarea, onTextareaValueChangedCallback, LV_EVENT_VALUE_CHANGED, this);
|
||||
filterTextareaWidget = textarea;
|
||||
lv_obj_set_flex_grow(textarea, 1);
|
||||
service::gui::keyboardAddTextArea(textarea);
|
||||
|
||||
auto* list = lv_list_create(parent);
|
||||
lv_obj_set_width(list, LV_PCT(100));
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#include "Tactility/app/wificonnect/View.h"
|
||||
#include "Tactility/app/wificonnect/WifiConnect.h"
|
||||
|
||||
#include "Tactility/lvgl/Style.h"
|
||||
#include "Tactility/lvgl/Toolbar.h"
|
||||
#include "Tactility/lvgl/Spinner.h"
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
|
||||
#include <Tactility/TactilityCore.h>
|
||||
#include <Tactility/service/wifi/WifiSettings.h>
|
||||
@@ -187,10 +185,6 @@ void View::init(AppContext& app, lv_obj_t* parent) {
|
||||
// Bottom buttons
|
||||
createBottomButtons(wrapper);
|
||||
|
||||
// Keyboard bindings
|
||||
service::gui::keyboardAddTextArea(ssid_textarea);
|
||||
service::gui::keyboardAddTextArea(password_textarea);
|
||||
|
||||
// Init from app parameters
|
||||
auto bundle = app.getParameters();
|
||||
if (bundle != nullptr) {
|
||||
|
||||
@@ -39,12 +39,12 @@ void init(const Configuration& configuration) {
|
||||
if (!configuration.sdcard->mount(TT_SDCARD_MOUNT_POINT)) {
|
||||
TT_LOG_W(TAG, "SD card mount failed (init can continue)");
|
||||
}
|
||||
hal::registerDevice(configuration.sdcard);
|
||||
registerDevice(configuration.sdcard);
|
||||
}
|
||||
|
||||
if (configuration.power != nullptr) {
|
||||
std::shared_ptr<tt::hal::power::PowerDevice> power = configuration.power();
|
||||
hal::registerDevice(power);
|
||||
std::shared_ptr<power::PowerDevice> power = configuration.power();
|
||||
registerDevice(power);
|
||||
}
|
||||
|
||||
kernel::publishSystemEvent(kernel::SystemEvent::BootInitHalEnd);
|
||||
|
||||
@@ -38,16 +38,16 @@ static const char* getEventName(SystemEvent event) {
|
||||
return TT_STRINGIFY(BootInitUartBegin);
|
||||
case BootInitUartEnd:
|
||||
return TT_STRINGIFY(BootInitUartEnd);
|
||||
case BootInitLvglBegin:
|
||||
return TT_STRINGIFY(BootInitLvglBegin);
|
||||
case BootInitLvglEnd:
|
||||
return TT_STRINGIFY(BootInitLvglEnd);
|
||||
case BootSplash:
|
||||
return TT_STRINGIFY(BootSplash);
|
||||
case NetworkConnected:
|
||||
return TT_STRINGIFY(NetworkConnected);
|
||||
case NetworkDisconnected:
|
||||
return TT_STRINGIFY(NetworkDisconnected);
|
||||
case LvglStarted:
|
||||
return TT_STRINGIFY(LvglStarted);
|
||||
case LvglStopped:
|
||||
return TT_STRINGIFY(LvglStopped);
|
||||
case Time:
|
||||
return TT_STRINGIFY(Time);
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
#include "Tactility/app/display/DisplaySettings.h"
|
||||
#include "Tactility/lvgl/Keyboard.h"
|
||||
|
||||
#include "Tactility/hal/display/DisplayDevice.h"
|
||||
#include "Tactility/hal/touch/TouchDevice.h"
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
#include <Tactility/hal/keyboard/KeyboardDevice.h>
|
||||
#include <Tactility/kernel/SystemEvents.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "Tactility/lvgl/EspLvglPort.h"
|
||||
#endif
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
#define TAG "lvgl_init"
|
||||
|
||||
static std::shared_ptr<tt::hal::display::DisplayDevice> initDisplay(const hal::Configuration& config) {
|
||||
assert(config.createDisplay);
|
||||
auto display = config.createDisplay();
|
||||
assert(display != nullptr);
|
||||
|
||||
if (!display->start()) {
|
||||
TT_LOG_E(TAG, "Display start failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
lv_display_t* lvgl_display = display->getLvglDisplay();
|
||||
assert(lvgl_display);
|
||||
|
||||
if (display->supportsBacklightDuty()) {
|
||||
display->setBacklightDuty(0);
|
||||
}
|
||||
|
||||
void* existing_display_user_data = lv_display_get_user_data(lvgl_display);
|
||||
// esp_lvgl_port users user_data by default, so we have to modify the source
|
||||
// this is a check for when we upgrade esp_lvgl_port and forget to modify it again
|
||||
assert(existing_display_user_data == nullptr);
|
||||
lv_display_set_user_data(lvgl_display, display.get());
|
||||
|
||||
lv_display_rotation_t rotation = app::display::getRotation();
|
||||
if (rotation != lv_disp_get_rotation(lv_disp_get_default())) {
|
||||
lv_disp_set_rotation(lv_disp_get_default(), static_cast<lv_display_rotation_t>(rotation));
|
||||
}
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
static bool initTouch(const std::shared_ptr<hal::display::DisplayDevice>& display, const std::shared_ptr<hal::touch::TouchDevice>& touch) {
|
||||
TT_LOG_I(TAG, "Touch init");
|
||||
assert(display);
|
||||
assert(touch);
|
||||
if (touch->start(display->getLvglDisplay())) {
|
||||
return true;
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Touch init failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool initKeyboard(const std::shared_ptr<hal::display::DisplayDevice>& display, const std::shared_ptr<hal::keyboard::KeyboardDevice>& keyboard) {
|
||||
TT_LOG_I(TAG, "Keyboard init");
|
||||
assert(display);
|
||||
assert(keyboard);
|
||||
if (keyboard->isAttached()) {
|
||||
if (keyboard->start(display->getLvglDisplay())) {
|
||||
lv_indev_t* keyboard_indev = keyboard->getLvglIndev();
|
||||
lv_indev_set_user_data(keyboard_indev, keyboard.get());
|
||||
tt::lvgl::hardware_keyboard_set_indev(keyboard_indev);
|
||||
TT_LOG_I(TAG, "Keyboard started");
|
||||
return true;
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Keyboard start failed");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Keyboard attach failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void init(const hal::Configuration& config) {
|
||||
TT_LOG_I(TAG, "Starting");
|
||||
|
||||
kernel::publishSystemEvent(kernel::SystemEvent::BootInitLvglBegin);
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
if (config.lvglInit == hal::LvglInit::Default && !initEspLvglPort()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto display = initDisplay(config);
|
||||
if (display == nullptr) {
|
||||
return;
|
||||
}
|
||||
hal::registerDevice(display);
|
||||
|
||||
auto touch = display->createTouch();
|
||||
if (touch != nullptr) {
|
||||
hal::registerDevice(touch);
|
||||
initTouch(display, touch);
|
||||
}
|
||||
|
||||
if (config.createKeyboard) {
|
||||
auto keyboard = config.createKeyboard();
|
||||
if (keyboard != nullptr) {
|
||||
hal::registerDevice(keyboard);
|
||||
initKeyboard(display, keyboard);
|
||||
}
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Finished");
|
||||
|
||||
kernel::publishSystemEvent(kernel::SystemEvent::BootInitLvglEnd);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1,20 +1,33 @@
|
||||
#include "Tactility/lvgl/Keyboard.h"
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
#include "Tactility/service/gui/GuiService.h"
|
||||
|
||||
#include <Tactility/service/espnow/EspNowService.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
static lv_indev_t* keyboard_device = nullptr;
|
||||
|
||||
void software_keyboard_show(lv_obj_t* textarea) {
|
||||
service::gui::softwareKeyboardShow(textarea);
|
||||
auto gui_service = service::gui::findService();
|
||||
if (gui_service != nullptr) {
|
||||
gui_service->softwareKeyboardShow(textarea);
|
||||
}
|
||||
}
|
||||
|
||||
void software_keyboard_hide() {
|
||||
service::gui::softwareKeyboardHide();
|
||||
auto gui_service = service::gui::findService();
|
||||
if (gui_service != nullptr) {
|
||||
gui_service->softwareKeyboardHide();
|
||||
}
|
||||
}
|
||||
|
||||
bool software_keyboard_is_enabled() {
|
||||
return service::gui::softwareKeyboardIsEnabled();
|
||||
auto gui_service = service::gui::findService();
|
||||
if (gui_service != nullptr) {
|
||||
return gui_service->softwareKeyboardIsEnabled();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void software_keyboard_activate(lv_group_t* group) {
|
||||
@@ -37,8 +50,4 @@ void hardware_keyboard_set_indev(lv_indev_t* device) {
|
||||
keyboard_device = device;
|
||||
}
|
||||
|
||||
void keyboard_add_textarea(lv_obj_t* textarea) {
|
||||
service::gui::keyboardAddTextArea(textarea);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
#include "Tactility/app/display/DisplaySettings.h"
|
||||
#include "Tactility/lvgl/Keyboard.h"
|
||||
#include "Tactility/lvgl/Lvgl.h"
|
||||
|
||||
#include "Tactility/hal/display/DisplayDevice.h"
|
||||
#include "Tactility/hal/touch/TouchDevice.h"
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
#include <Tactility/hal/keyboard/KeyboardDevice.h>
|
||||
#include <Tactility/kernel/SystemEvents.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "Tactility/lvgl/EspLvglPort.h"
|
||||
#endif
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/TactilityHeadless.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
#include <Tactility/service/ServiceRegistry.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
#define TAG "Lvgl"
|
||||
|
||||
static bool started = false;
|
||||
|
||||
static std::shared_ptr<hal::display::DisplayDevice> createDisplay(const hal::Configuration& config) {
|
||||
assert(config.createDisplay);
|
||||
auto display = config.createDisplay();
|
||||
assert(display != nullptr);
|
||||
|
||||
if (!display->start()) {
|
||||
TT_LOG_E(TAG, "Display start failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (display->supportsBacklightDuty()) {
|
||||
display->setBacklightDuty(0);
|
||||
}
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
void init(const hal::Configuration& config) {
|
||||
TT_LOG_I(TAG, "Init started");
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
if (config.lvglInit == hal::LvglInit::Default && !initEspLvglPort()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto display = createDisplay(config);
|
||||
if (display == nullptr) {
|
||||
return;
|
||||
}
|
||||
hal::registerDevice(display);
|
||||
|
||||
auto touch = display->getTouchDevice();
|
||||
if (touch != nullptr) {
|
||||
touch->start();
|
||||
hal::registerDevice(touch);
|
||||
}
|
||||
|
||||
auto configuration = hal::getConfiguration();
|
||||
if (configuration->createKeyboard) {
|
||||
auto keyboard = configuration->createKeyboard();
|
||||
if (keyboard != nullptr) {
|
||||
hal::registerDevice(keyboard);
|
||||
}
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
TT_LOG_I(TAG, "Init finished");
|
||||
}
|
||||
|
||||
bool isStarted() {
|
||||
return started;
|
||||
}
|
||||
|
||||
void start() {
|
||||
TT_LOG_I(TAG, "Start LVGL");
|
||||
|
||||
if (started) {
|
||||
TT_LOG_W(TAG, "Can't start LVGL twice");
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = getSyncLock()->asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
// Start displays (their related touch devices start automatically within)
|
||||
|
||||
auto displays = hal::findDevices<hal::display::DisplayDevice>(hal::Device::Type::Display);
|
||||
for (auto display : displays) {
|
||||
if (display->supportsLvgl() && display->startLvgl()) {
|
||||
auto lvgl_display = display->getLvglDisplay();
|
||||
assert(lvgl_display != nullptr);
|
||||
lv_display_rotation_t rotation = app::display::getRotation();
|
||||
if (rotation != lv_display_get_rotation(lvgl_display)) {
|
||||
lv_display_set_rotation(lvgl_display, rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start touch
|
||||
|
||||
auto touch_devices = hal::findDevices<hal::touch::TouchDevice>(hal::Device::Type::Touch);
|
||||
for (auto touch_device : touch_devices) {
|
||||
if (displays.size() > 0) {
|
||||
// TODO: Consider implementing support for multiple displays
|
||||
auto display = displays[0];
|
||||
// Start any touch devices that haven't been started yet
|
||||
if (touch_device->supportsLvgl() && touch_device->getLvglIndev() == nullptr) {
|
||||
touch_device->startLvgl(display->getLvglDisplay());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start keyboards
|
||||
|
||||
auto keyboards = hal::findDevices<hal::keyboard::KeyboardDevice>(hal::Device::Type::Keyboard);
|
||||
for (auto keyboard : keyboards) {
|
||||
if (displays.size() > 0) {
|
||||
// TODO: Consider implementing support for multiple displays
|
||||
auto display = displays[0];
|
||||
if (keyboard->isAttached()) {
|
||||
if (keyboard->startLvgl(display->getLvglDisplay())) {
|
||||
lv_indev_t* keyboard_indev = keyboard->getLvglIndev();
|
||||
hardware_keyboard_set_indev(keyboard_indev);
|
||||
TT_LOG_I(TAG, "Keyboard started");
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Keyboard start failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restart services
|
||||
|
||||
if (service::getState("Gui") == service::State::Stopped) {
|
||||
service::startService("Gui");
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Gui service is not in Stopped state");
|
||||
}
|
||||
|
||||
if (service::getState("Statusbar") == service::State::Stopped) {
|
||||
service::startService("Statusbar");
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Statusbar service is not in Stopped state");
|
||||
}
|
||||
|
||||
// Finalize
|
||||
|
||||
kernel::publishSystemEvent(kernel::SystemEvent::LvglStarted);
|
||||
|
||||
started = true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
TT_LOG_I(TAG, "Stop LVGL");
|
||||
|
||||
if (!started) {
|
||||
TT_LOG_W(TAG, "Can't stop LVGL: not started");
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = getSyncLock()->asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
// Stop services that highly depend on LVGL
|
||||
|
||||
service::stopService("Statusbar");
|
||||
service::stopService("Gui");
|
||||
|
||||
// Stop keyboards
|
||||
|
||||
auto keyboards = hal::findDevices<hal::keyboard::KeyboardDevice>(hal::Device::Type::Keyboard);
|
||||
for (auto keyboard : keyboards) {
|
||||
if (keyboard->getLvglIndev() != nullptr) {
|
||||
keyboard->stopLvgl();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop touch
|
||||
|
||||
// The display generally stops their own touch devices, but we'll clean up anything that didn't
|
||||
auto touch_devices = hal::findDevices<hal::touch::TouchDevice>(hal::Device::Type::Touch);
|
||||
for (auto touch_device : touch_devices) {
|
||||
if (touch_device->getLvglIndev() != nullptr) {
|
||||
touch_device->stopLvgl();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop displays (and their touch devices)
|
||||
|
||||
auto displays = hal::findDevices<hal::display::DisplayDevice>(hal::Device::Type::Display);
|
||||
for (auto display : displays) {
|
||||
if (display->supportsLvgl() && display->getLvglDisplay() != nullptr && !display->stopLvgl()) {
|
||||
TT_LOG_E("HelloWorld", "Failed to detach display from LVGL");
|
||||
}
|
||||
}
|
||||
|
||||
started = false;
|
||||
|
||||
kernel::publishSystemEvent(kernel::SystemEvent::LvglStopped);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include "Tactility/app/App.h"
|
||||
#include "Tactility/service/gui/GuiService.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <Tactility/service/gui/GuiService.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern lv_obj_t * __real_lv_textarea_create(lv_obj_t * parent);
|
||||
|
||||
lv_obj_t * __wrap_lv_textarea_create(lv_obj_t * parent) {
|
||||
auto textarea = __real_lv_textarea_create(parent);
|
||||
|
||||
auto gui_service = tt::service::gui::findService();
|
||||
if (gui_service != nullptr) {
|
||||
gui_service->keyboardAddTextArea(textarea);
|
||||
}
|
||||
|
||||
return textarea;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <Tactility/app/AppInstance.h>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
@@ -76,7 +77,9 @@ bool startService(const std::string& id) {
|
||||
service_instance_map[manifest->id] = service_instance;
|
||||
instance_mutex.unlock();
|
||||
|
||||
service_instance->setState(State::Starting);
|
||||
service_instance->getService()->onStart(*service_instance);
|
||||
service_instance->setState(State::Started);
|
||||
|
||||
TT_LOG_I(TAG, "Started %s", id.c_str());
|
||||
|
||||
@@ -96,11 +99,13 @@ bool stopService(const std::string& id) {
|
||||
TT_LOG_I(TAG, "Stopping %s", id.c_str());
|
||||
auto service_instance = findServiceInstanceById(id);
|
||||
if (service_instance == nullptr) {
|
||||
TT_LOG_W(TAG, "service not running: %s", id.c_str());
|
||||
TT_LOG_W(TAG, "Service not running: %s", id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
service_instance->setState(State::Stopping);
|
||||
service_instance->getService()->onStop(*service_instance);
|
||||
service_instance->setState(State::Stopped);
|
||||
|
||||
instance_mutex.lock();
|
||||
service_instance_map.erase(id);
|
||||
@@ -115,4 +120,12 @@ bool stopService(const std::string& id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
State getState(const std::string& id) {
|
||||
auto service_instance = findServiceInstanceById(id);
|
||||
if (service_instance == nullptr) {
|
||||
return State::Stopped;
|
||||
}
|
||||
return service_instance->getState();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
#include "Tactility/lvgl/LvglSync.h"
|
||||
#include "Tactility/lvgl/Statusbar.h"
|
||||
#include "Tactility/lvgl/Style.h"
|
||||
#include "Tactility/service/loader/Loader.h"
|
||||
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/RtosCompat.h>
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
#define TAG "gui"
|
||||
|
||||
// Forward declarations
|
||||
void redraw(Gui*);
|
||||
static int32_t guiMain();
|
||||
|
||||
Gui* gui = nullptr;
|
||||
|
||||
void onLoaderMessage(const void* message, TT_UNUSED void* context) {
|
||||
auto* event = static_cast<const loader::LoaderEvent*>(message);
|
||||
if (event->type == loader::LoaderEventTypeApplicationShowing) {
|
||||
auto app_instance = app::getCurrentAppContext();
|
||||
showApp(app_instance);
|
||||
} else if (event->type == loader::LoaderEventTypeApplicationHiding) {
|
||||
hideApp();
|
||||
}
|
||||
}
|
||||
|
||||
Gui* gui_alloc() {
|
||||
auto* instance = new Gui();
|
||||
tt_check(instance != nullptr);
|
||||
instance->thread = new Thread(
|
||||
"gui",
|
||||
4096, // Last known minimum was 2800 for launching desktop
|
||||
[]() { return guiMain(); }
|
||||
);
|
||||
instance->loader_pubsub_subscription = loader::getPubsub()->subscribe(&onLoaderMessage, instance);
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
instance->keyboardGroup = lv_group_create();
|
||||
auto* screen_root = lv_scr_act();
|
||||
assert(screen_root != nullptr);
|
||||
|
||||
lvgl::obj_set_style_bg_blacken(screen_root);
|
||||
|
||||
lv_obj_t* vertical_container = lv_obj_create(screen_root);
|
||||
lv_obj_set_size(vertical_container, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_flex_flow(vertical_container, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_all(vertical_container, 0, 0);
|
||||
lv_obj_set_style_pad_gap(vertical_container, 0, 0);
|
||||
lvgl::obj_set_style_bg_blacken(vertical_container);
|
||||
|
||||
instance->statusbarWidget = lvgl::statusbar_create(vertical_container);
|
||||
|
||||
auto* app_container = lv_obj_create(vertical_container);
|
||||
lv_obj_set_style_pad_all(app_container, 0, 0);
|
||||
lv_obj_set_style_border_width(app_container, 0, 0);
|
||||
lvgl::obj_set_style_bg_blacken(app_container);
|
||||
lv_obj_set_width(app_container, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(app_container, 1);
|
||||
lv_obj_set_flex_flow(app_container, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
instance->appRootWidget = app_container;
|
||||
|
||||
lvgl::unlock();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void gui_free(Gui* instance) {
|
||||
assert(instance != nullptr);
|
||||
delete instance->thread;
|
||||
|
||||
lv_group_delete(instance->keyboardGroup);
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
lv_group_del(instance->keyboardGroup);
|
||||
lvgl::unlock();
|
||||
|
||||
delete instance;
|
||||
}
|
||||
|
||||
void lock() {
|
||||
assert(gui);
|
||||
tt_check(gui->mutex.lock(configTICK_RATE_HZ));
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
assert(gui);
|
||||
tt_check(gui->mutex.unlock());
|
||||
}
|
||||
|
||||
void requestDraw() {
|
||||
assert(gui);
|
||||
ThreadId thread_id = gui->thread->getId();
|
||||
Thread::setFlags(thread_id, GUI_THREAD_FLAG_DRAW);
|
||||
}
|
||||
|
||||
void showApp(std::shared_ptr<app::AppContext> app) {
|
||||
lock();
|
||||
tt_check(gui->appToRender == nullptr);
|
||||
gui->appToRender = std::move(app);
|
||||
unlock();
|
||||
requestDraw();
|
||||
}
|
||||
|
||||
void hideApp() {
|
||||
lock();
|
||||
tt_check(gui->appToRender != nullptr);
|
||||
|
||||
// We must lock the LVGL port, because the viewport hide callbacks
|
||||
// might call LVGL APIs (e.g. to remove the keyboard from the screen root)
|
||||
tt_check(lvgl::lock(configTICK_RATE_HZ));
|
||||
gui->appToRender->getApp()->onHide(*gui->appToRender);
|
||||
lvgl::unlock();
|
||||
|
||||
gui->appToRender = nullptr;
|
||||
unlock();
|
||||
}
|
||||
|
||||
static int32_t guiMain() {
|
||||
tt_check(gui);
|
||||
Gui* local_gui = gui;
|
||||
|
||||
while (true) {
|
||||
uint32_t flags = Thread::awaitFlags(GUI_THREAD_FLAG_ALL, EventFlag::WaitAny, (uint32_t)portMAX_DELAY);
|
||||
|
||||
// Process and dispatch draw call
|
||||
if (flags & GUI_THREAD_FLAG_DRAW) {
|
||||
Thread::clearFlags(GUI_THREAD_FLAG_DRAW);
|
||||
redraw(local_gui);
|
||||
}
|
||||
|
||||
if (flags & GUI_THREAD_FLAG_EXIT) {
|
||||
Thread::clearFlags(GUI_THREAD_FLAG_EXIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// region AppManifest
|
||||
|
||||
class GuiService : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(TT_UNUSED ServiceContext& service) override {
|
||||
assert(gui == nullptr);
|
||||
gui = gui_alloc();
|
||||
|
||||
gui->thread->setPriority(THREAD_PRIORITY_SERVICE);
|
||||
gui->thread->start();
|
||||
}
|
||||
|
||||
void onStop(TT_UNUSED ServiceContext& service) override {
|
||||
assert(gui != nullptr);
|
||||
lock();
|
||||
|
||||
ThreadId thread_id = gui->thread->getId();
|
||||
Thread::setFlags(thread_id, GUI_THREAD_FLAG_EXIT);
|
||||
gui->thread->join();
|
||||
delete gui->thread;
|
||||
|
||||
unlock();
|
||||
|
||||
gui_free(gui);
|
||||
}
|
||||
};
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "Gui",
|
||||
.createService = create<GuiService>
|
||||
};
|
||||
|
||||
// endregion
|
||||
|
||||
} // namespace
|
||||
@@ -1,75 +0,0 @@
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
|
||||
#include "Tactility/app/AppInstance.h"
|
||||
#include "Tactility/lvgl/LvglSync.h"
|
||||
#include "Tactility/lvgl/Style.h"
|
||||
|
||||
#include <Tactility/Check.h>
|
||||
#include <Tactility/Log.h>
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
#define TAG "gui"
|
||||
|
||||
static lv_obj_t* createAppViews(Gui* gui, lv_obj_t* parent) {
|
||||
lv_obj_send_event(gui->statusbarWidget, LV_EVENT_DRAW_MAIN, nullptr);
|
||||
lv_obj_t* child_container = lv_obj_create(parent);
|
||||
lv_obj_set_style_pad_all(child_container, 0, 0);
|
||||
lv_obj_set_width(child_container, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(child_container, 1);
|
||||
|
||||
if (softwareKeyboardIsEnabled()) {
|
||||
gui->keyboard = lv_keyboard_create(parent);
|
||||
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
gui->keyboard = nullptr;
|
||||
}
|
||||
|
||||
return child_container;
|
||||
}
|
||||
|
||||
void redraw(Gui* gui) {
|
||||
assert(gui);
|
||||
|
||||
// Lock GUI and LVGL
|
||||
lock();
|
||||
|
||||
if (lvgl::lock(1000)) {
|
||||
lv_obj_clean(gui->appRootWidget);
|
||||
|
||||
if (gui->appToRender != nullptr) {
|
||||
|
||||
// Create a default group which adds all objects automatically,
|
||||
// and assign all indevs to it.
|
||||
// This enables navigation with limited input, such as encoder wheels.
|
||||
lv_group_t* group = lv_group_create();
|
||||
auto* indev = lv_indev_get_next(nullptr);
|
||||
while (indev) {
|
||||
lv_indev_set_group(indev, group);
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
lv_group_set_default(group);
|
||||
|
||||
app::Flags flags = std::static_pointer_cast<app::AppInstance>(gui->appToRender)->getFlags();
|
||||
if (flags.showStatusbar) {
|
||||
lv_obj_remove_flag(gui->statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(gui->statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
lv_obj_t* container = createAppViews(gui, gui->appRootWidget);
|
||||
gui->appToRender->getApp()->onShow(*gui->appToRender, container);
|
||||
} else {
|
||||
TT_LOG_W(TAG, "nothing to draw");
|
||||
}
|
||||
|
||||
// Unlock GUI and LVGL
|
||||
lvgl::unlock();
|
||||
} else {
|
||||
TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL");
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
} // namespace tt::service::gui
|
||||
@@ -0,0 +1,232 @@
|
||||
#include "Tactility/service/gui/GuiService.h"
|
||||
#include "Tactility/lvgl/LvglSync.h"
|
||||
#include "Tactility/lvgl/Statusbar.h"
|
||||
#include "Tactility/lvgl/Style.h"
|
||||
#include "Tactility/service/loader/Loader.h"
|
||||
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/app/AppInstance.h>
|
||||
#include <Tactility/service/ServiceRegistry.h>
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
extern const ServiceManifest manifest;
|
||||
|
||||
constexpr const char* TAG = "gui";
|
||||
|
||||
// region AppManifest
|
||||
|
||||
void GuiService::onLoaderMessage(const void* message, TT_UNUSED void* context) {
|
||||
auto service = findService();
|
||||
if (service == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* event = static_cast<const loader::LoaderEvent*>(message);
|
||||
if (event->type == loader::LoaderEventTypeApplicationShowing) {
|
||||
auto app_instance = app::getCurrentAppContext();
|
||||
service->showApp(app_instance);
|
||||
} else if (event->type == loader::LoaderEventTypeApplicationHiding) {
|
||||
service->hideApp();
|
||||
}
|
||||
}
|
||||
|
||||
int32_t GuiService::guiMain() {
|
||||
while (true) {
|
||||
uint32_t flags = Thread::awaitFlags(GUI_THREAD_FLAG_ALL, EventFlag::WaitAny, (uint32_t)portMAX_DELAY);
|
||||
|
||||
// When service not started or starting -> exit
|
||||
State service_state = getState(manifest.id);
|
||||
if (service_state != State::Started && service_state != State::Starting) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Process and dispatch draw call
|
||||
if (flags & GUI_THREAD_FLAG_DRAW) {
|
||||
Thread::clearFlags(GUI_THREAD_FLAG_DRAW);
|
||||
auto service = findService();
|
||||
if (service != nullptr) {
|
||||
service->redraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & GUI_THREAD_FLAG_EXIT) {
|
||||
Thread::clearFlags(GUI_THREAD_FLAG_EXIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
lv_obj_t* GuiService::createAppViews(lv_obj_t* parent) {
|
||||
lv_obj_send_event(statusbarWidget, LV_EVENT_DRAW_MAIN, nullptr);
|
||||
lv_obj_t* child_container = lv_obj_create(parent);
|
||||
lv_obj_set_style_pad_all(child_container, 0, 0);
|
||||
lv_obj_set_width(child_container, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(child_container, 1);
|
||||
|
||||
if (softwareKeyboardIsEnabled()) {
|
||||
keyboard = lv_keyboard_create(parent);
|
||||
lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
keyboard = nullptr;
|
||||
}
|
||||
|
||||
return child_container;
|
||||
}
|
||||
|
||||
void GuiService::redraw() {
|
||||
// Lock GUI and LVGL
|
||||
lock();
|
||||
|
||||
if (lvgl::lock(1000)) {
|
||||
lv_obj_clean(appRootWidget);
|
||||
|
||||
if (appToRender != nullptr) {
|
||||
|
||||
// Create a default group which adds all objects automatically,
|
||||
// and assign all indevs to it.
|
||||
// This enables navigation with limited input, such as encoder wheels.
|
||||
lv_group_t* group = lv_group_create();
|
||||
auto* indev = lv_indev_get_next(nullptr);
|
||||
while (indev) {
|
||||
lv_indev_set_group(indev, group);
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
lv_group_set_default(group);
|
||||
|
||||
app::Flags flags = std::static_pointer_cast<app::AppInstance>(appToRender)->getFlags();
|
||||
if (flags.showStatusbar) {
|
||||
lv_obj_remove_flag(statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
lv_obj_t* container = createAppViews(appRootWidget);
|
||||
appToRender->getApp()->onShow(*appToRender, container);
|
||||
} else {
|
||||
TT_LOG_W(TAG, "nothing to draw");
|
||||
}
|
||||
|
||||
// Unlock GUI and LVGL
|
||||
lvgl::unlock();
|
||||
} else {
|
||||
TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL");
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
void GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
thread = new Thread(
|
||||
"gui",
|
||||
4096, // Last known minimum was 2800 for launching desktop
|
||||
[]() { return guiMain(); }
|
||||
);
|
||||
loader_pubsub_subscription = loader::getPubsub()->subscribe(&onLoaderMessage, nullptr);
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
keyboardGroup = lv_group_create();
|
||||
auto* screen_root = lv_screen_active();
|
||||
assert(screen_root != nullptr);
|
||||
|
||||
lvgl::obj_set_style_bg_blacken(screen_root);
|
||||
|
||||
lv_obj_t* vertical_container = lv_obj_create(screen_root);
|
||||
lv_obj_set_size(vertical_container, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_flex_flow(vertical_container, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_all(vertical_container, 0, 0);
|
||||
lv_obj_set_style_pad_gap(vertical_container, 0, 0);
|
||||
lvgl::obj_set_style_bg_blacken(vertical_container);
|
||||
|
||||
statusbarWidget = lvgl::statusbar_create(vertical_container);
|
||||
|
||||
auto* app_container = lv_obj_create(vertical_container);
|
||||
lv_obj_set_style_pad_all(app_container, 0, 0);
|
||||
lv_obj_set_style_border_width(app_container, 0, 0);
|
||||
lvgl::obj_set_style_bg_blacken(app_container);
|
||||
lv_obj_set_width(app_container, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(app_container, 1);
|
||||
lv_obj_set_flex_flow(app_container, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
appRootWidget = app_container;
|
||||
|
||||
lvgl::unlock();
|
||||
|
||||
isStarted = true;
|
||||
|
||||
thread->setPriority(THREAD_PRIORITY_SERVICE);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void GuiService::onStop(TT_UNUSED ServiceContext& service) {
|
||||
lock();
|
||||
|
||||
loader::getPubsub()->unsubscribe(loader_pubsub_subscription);
|
||||
|
||||
appToRender = nullptr;
|
||||
isStarted = false;
|
||||
|
||||
ThreadId thread_id = thread->getId();
|
||||
Thread::setFlags(thread_id, GUI_THREAD_FLAG_EXIT);
|
||||
thread->join();
|
||||
delete thread;
|
||||
|
||||
unlock();
|
||||
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
lv_group_delete(keyboardGroup);
|
||||
lvgl::unlock();
|
||||
}
|
||||
|
||||
void GuiService::requestDraw() {
|
||||
ThreadId thread_id = thread->getId();
|
||||
Thread::setFlags(thread_id, GUI_THREAD_FLAG_DRAW);
|
||||
}
|
||||
|
||||
void GuiService::showApp(std::shared_ptr<app::AppContext> app) {
|
||||
lock();
|
||||
if (!isStarted) {
|
||||
TT_LOG_W(TAG, "Failed to show app %s: GUI not started", app->getManifest().id.c_str());
|
||||
} else {
|
||||
// Ensure previous app triggers onHide() logic
|
||||
if (appToRender != nullptr) {
|
||||
hideApp();
|
||||
}
|
||||
appToRender = std::move(app);
|
||||
}
|
||||
unlock();
|
||||
requestDraw();
|
||||
}
|
||||
|
||||
void GuiService::hideApp() {
|
||||
lock();
|
||||
if (!isStarted) {
|
||||
TT_LOG_W(TAG, "Failed to hide app: GUI not started");
|
||||
} else if (appToRender == nullptr) {
|
||||
TT_LOG_W(TAG, "hideApp() called but no app is currently shown");
|
||||
} else {
|
||||
// We must lock the LVGL port, because the viewport hide callbacks
|
||||
// might call LVGL APIs (e.g. to remove the keyboard from the screen root)
|
||||
tt_check(lvgl::lock(configTICK_RATE_HZ));
|
||||
appToRender->getApp()->onHide(*appToRender);
|
||||
lvgl::unlock();
|
||||
appToRender = nullptr;
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
std::shared_ptr<GuiService> findService() {
|
||||
return std::static_pointer_cast<GuiService>(
|
||||
findServiceById(manifest.id)
|
||||
);
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "Gui",
|
||||
.createService = create<GuiService>
|
||||
};
|
||||
|
||||
// endregion
|
||||
|
||||
} // namespace
|
||||
@@ -1,65 +1,74 @@
|
||||
#include "Tactility/lvgl/Keyboard.h"
|
||||
#include "Tactility/Check.h"
|
||||
#include "Tactility/lvgl/LvglSync.h"
|
||||
#include "Tactility/service/gui/Gui.h"
|
||||
#include "Tactility/service/gui/GuiService.h"
|
||||
|
||||
#include <Tactility/TactilityConfig.h>
|
||||
#include <Tactility/service/espnow/EspNowService.h>
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
extern Gui* gui;
|
||||
|
||||
static void show_keyboard(lv_event_t* event) {
|
||||
lv_obj_t* target = lv_event_get_current_target_obj(event);
|
||||
softwareKeyboardShow(target);
|
||||
lv_obj_scroll_to_view(target, LV_ANIM_ON);
|
||||
auto service = findService();
|
||||
if (service != nullptr) {
|
||||
lv_obj_t* target = lv_event_get_current_target_obj(event);
|
||||
service->softwareKeyboardShow(target);
|
||||
lv_obj_scroll_to_view(target, LV_ANIM_ON);
|
||||
}
|
||||
}
|
||||
|
||||
static void hide_keyboard(TT_UNUSED lv_event_t* event) {
|
||||
softwareKeyboardHide();
|
||||
auto service = findService();
|
||||
if (service != nullptr) {
|
||||
service->softwareKeyboardHide();
|
||||
}
|
||||
}
|
||||
|
||||
bool softwareKeyboardIsEnabled() {
|
||||
bool GuiService::softwareKeyboardIsEnabled() {
|
||||
return !lvgl::hardware_keyboard_is_available() || TT_CONFIG_FORCE_ONSCREEN_KEYBOARD;
|
||||
}
|
||||
|
||||
void softwareKeyboardShow(lv_obj_t* textarea) {
|
||||
void GuiService::softwareKeyboardShow(lv_obj_t* textarea) {
|
||||
lock();
|
||||
|
||||
if (gui->keyboard) {
|
||||
lv_obj_clear_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_keyboard_set_textarea(gui->keyboard, textarea);
|
||||
if (isStarted && keyboard != nullptr) {
|
||||
lv_obj_clear_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_keyboard_set_textarea(keyboard, textarea);
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
void softwareKeyboardHide() {
|
||||
void GuiService::softwareKeyboardHide() {
|
||||
lock();
|
||||
|
||||
if (gui->keyboard) {
|
||||
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
if (isStarted && keyboard != nullptr) {
|
||||
lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
void keyboardAddTextArea(lv_obj_t* textarea) {
|
||||
void GuiService::keyboardAddTextArea(lv_obj_t* textarea) {
|
||||
lock();
|
||||
tt_check(lvgl::lock(0), "lvgl should already be locked before calling this method");
|
||||
|
||||
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);
|
||||
if (isStarted) {
|
||||
tt_check(lvgl::lock(0), "lvgl should already be locked before calling this method");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// lv_obj_t auto-remove themselves from the group when they are destroyed (last checked in LVGL 8.3)
|
||||
lv_group_add_obj(keyboardGroup, textarea);
|
||||
|
||||
lvgl::software_keyboard_activate(keyboardGroup);
|
||||
|
||||
lvgl::unlock();
|
||||
}
|
||||
|
||||
// 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::software_keyboard_activate(gui->keyboardGroup);
|
||||
|
||||
lvgl::unlock();
|
||||
unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -130,20 +130,18 @@ static _Nullable const char* getPowerStatusIcon() {
|
||||
|
||||
class StatusbarService final : public Service {
|
||||
|
||||
private:
|
||||
|
||||
Mutex mutex;
|
||||
std::unique_ptr<Timer> updateTimer;
|
||||
int8_t gps_icon_id = lvgl::statusbar_icon_add();
|
||||
int8_t gps_icon_id;
|
||||
bool gps_last_state = false;
|
||||
int8_t wifi_icon_id = lvgl::statusbar_icon_add();
|
||||
int8_t wifi_icon_id;
|
||||
const char* wifi_last_icon = nullptr;
|
||||
int8_t sdcard_icon_id = lvgl::statusbar_icon_add();
|
||||
int8_t sdcard_icon_id;
|
||||
const char* sdcard_last_icon = nullptr;
|
||||
int8_t power_icon_id = lvgl::statusbar_icon_add();
|
||||
int8_t power_icon_id;
|
||||
const char* power_last_icon = nullptr;
|
||||
|
||||
std::unique_ptr<service::Paths> paths;
|
||||
std::unique_ptr<Paths> paths;
|
||||
|
||||
void lock() const {
|
||||
mutex.lock();
|
||||
@@ -154,7 +152,7 @@ private:
|
||||
}
|
||||
|
||||
void updateGpsIcon() {
|
||||
auto gps_state = service::gps::findGpsService()->getState();
|
||||
auto gps_state = gps::findGpsService()->getState();
|
||||
bool show_icon = (gps_state == gps::State::OnPending) || (gps_state == gps::State::On);
|
||||
if (gps_last_state != show_icon) {
|
||||
if (show_icon) {
|
||||
@@ -199,7 +197,7 @@ private:
|
||||
}
|
||||
|
||||
void updateSdCardIcon() {
|
||||
auto sdcard = tt::hal::getConfiguration()->sdcard;
|
||||
auto sdcard = hal::getConfiguration()->sdcard;
|
||||
if (sdcard != nullptr) {
|
||||
auto state = sdcard->getState();
|
||||
if (state != hal::sdcard::SdCardDevice::State::Unknown) {
|
||||
@@ -229,10 +227,18 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
~StatusbarService() final {
|
||||
StatusbarService() {
|
||||
gps_icon_id = lvgl::statusbar_icon_add();
|
||||
sdcard_icon_id = lvgl::statusbar_icon_add();
|
||||
wifi_icon_id = lvgl::statusbar_icon_add();
|
||||
power_icon_id = lvgl::statusbar_icon_add();
|
||||
}
|
||||
|
||||
~StatusbarService() override {
|
||||
lvgl::statusbar_icon_remove(wifi_icon_id);
|
||||
lvgl::statusbar_icon_remove(sdcard_icon_id);
|
||||
lvgl::statusbar_icon_remove(power_icon_id);
|
||||
lvgl::statusbar_icon_remove(gps_icon_id);
|
||||
}
|
||||
|
||||
void onStart(ServiceContext& serviceContext) override {
|
||||
@@ -245,7 +251,7 @@ public:
|
||||
assert(service);
|
||||
onUpdate(service);
|
||||
|
||||
updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, [service]() {
|
||||
updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, [service] {
|
||||
onUpdate(service);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user