diff --git a/App/Source/HelloWorld/HelloWorld.cpp b/App/Source/HelloWorld/HelloWorld.cpp index f02e3fc4..c5dce570 100644 --- a/App/Source/HelloWorld/HelloWorld.cpp +++ b/App/Source/HelloWorld/HelloWorld.cpp @@ -1,18 +1,51 @@ #include #include #include +#include +#include +#include using namespace tt::app; class HelloWorldApp : public App { - void onShow(AppContext& context, lv_obj_t* parent) override { - lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context); - lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + std::shared_ptr displayDevice; + // void onShow(AppContext& context, lv_obj_t* parent) override { + // lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context); + // lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + // + // lv_obj_t* label = lv_label_create(parent); + // lv_label_set_text(label, "Hello, world!"); + // lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); + // } - lv_obj_t* label = lv_label_create(parent); - lv_label_set_text(label, "Hello, world!"); - lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); + void onCreate(AppContext& appContext) override { + tt::service::stopService("Statusbar"); + tt::service::stopService("Gui"); + + tt::service::startService("Gui"); + tt::service::startService("Statusbar"); + + // using namespace tt::hal; + // displayDevice = findFirstDevice(Device::Type::Display); + // if (displayDevice == nullptr) { + // TT_LOG_E("HelloWorld", "Display device not found"); + // stop(); + // } else { + // if (displayDevice->supportsLvgl() && displayDevice->getLvglDisplay() != nullptr) { + // if (!displayDevice->stopLvgl()) { + // TT_LOG_E("HelloWorld", "Failed to detach display from LVGL"); + // } + // } + // } + } + + void onDestroy(AppContext& appContext) override { + if (displayDevice != nullptr) { + if (displayDevice->supportsLvgl() && displayDevice->getLvglDisplay() == nullptr) { + displayDevice->startLvgl(); + } + } } }; diff --git a/App/Source/Main.cpp b/App/Source/Main.cpp index 5586930d..5f9f7f80 100644 --- a/App/Source/Main.cpp +++ b/App/Source/Main.cpp @@ -5,7 +5,7 @@ #include "tt_init.h" #endif -// extern const tt::app::AppManifest hello_world_app; +extern const tt::app::AppManifest hello_world_app; extern "C" { @@ -17,7 +17,7 @@ void app_main() { */ .hardware = TT_BOARD_HARDWARE, .apps = { - // &hello_world_app, + &hello_world_app, } }; diff --git a/Drivers/ST7789/Source/St7789Display.cpp b/Drivers/ST7789/Source/St7789Display.cpp index 27006e07..edd65af6 100644 --- a/Drivers/ST7789/Source/St7789Display.cpp +++ b/Drivers/ST7789/Source/St7789Display.cpp @@ -88,7 +88,7 @@ bool St7789Display::start() { } TT_LOG_I(TAG, "Finished"); - return displayHandle != nullptr; + return panelHandle != nullptr; } bool St7789Display::stop() { diff --git a/Drivers/ST7789/Source/St7789Display.h b/Drivers/ST7789/Source/St7789Display.h index 92079e95..bde9513e 100644 --- a/Drivers/ST7789/Source/St7789Display.h +++ b/Drivers/ST7789/Source/St7789Display.h @@ -4,7 +4,6 @@ #include -#include #include #include #include diff --git a/Tactility/Include/Tactility/app/serialconsole/ConnectView.h b/Tactility/Include/Tactility/app/serialconsole/ConnectView.h index 6a45b06c..b34ec561 100644 --- a/Tactility/Include/Tactility/app/serialconsole/ConnectView.h +++ b/Tactility/Include/Tactility/app/serialconsole/ConnectView.h @@ -8,10 +8,8 @@ #include "Tactility/hal/uart/Uart.h" #include "Tactility/lvgl/LvglSync.h" #include "Tactility/lvgl/Style.h" -#include "Tactility/service/gui/Gui.h" #include -#include #include namespace tt::app::serialconsole { @@ -129,7 +127,7 @@ public: lv_obj_add_event_cb(connect_button, onConnectCallback, LV_EVENT_SHORT_CLICKED, this); } - void onStop() final { + void onStop() { int speed = getSpeedInput(); if (speed > 0) { preferences.putInt32("speed", speed); diff --git a/Tactility/Include/Tactility/service/Service.h b/Tactility/Include/Tactility/service/Service.h index c5983c4a..3c772d55 100644 --- a/Tactility/Include/Tactility/service/Service.h +++ b/Tactility/Include/Tactility/service/Service.h @@ -4,6 +4,13 @@ namespace tt::service { +enum class State { + Starting, + Started, + Stopping, + Stopped +}; + // Forward declaration class ServiceContext; diff --git a/Tactility/Include/Tactility/service/ServiceContext.h b/Tactility/Include/Tactility/service/ServiceContext.h index 58127c77..fc1cd86a 100644 --- a/Tactility/Include/Tactility/service/ServiceContext.h +++ b/Tactility/Include/Tactility/service/ServiceContext.h @@ -22,8 +22,8 @@ protected: public: - /** @return a reference ot the service's manifest */ - virtual const service::ServiceManifest& getManifest() const = 0; + /** @return a reference to the service's manifest */ + virtual const ServiceManifest& getManifest() const = 0; /** Retrieve the paths that are relevant to this service */ virtual std::unique_ptr getPaths() const = 0; diff --git a/Tactility/Include/Tactility/service/ServiceRegistry.h b/Tactility/Include/Tactility/service/ServiceRegistry.h index fbae2292..2fef6f4f 100644 --- a/Tactility/Include/Tactility/service/ServiceRegistry.h +++ b/Tactility/Include/Tactility/service/ServiceRegistry.h @@ -29,6 +29,13 @@ bool startService(const std::string& id); */ bool stopService(const std::string& id); +/** Get the state of a service. + * @param[in] the service id as defined in its manifest + * @param[out] the variable to store the resulting state in + * @return true if the service was found and "state" was set + */ +bool getState(const std::string& id, State& state); + /** Find a service manifest by its id. * @param[in] id the id as defined in the manifest * @return the matching manifest or nullptr when it wasn't found diff --git a/Tactility/Private/Tactility/service/ServiceInstance.h b/Tactility/Private/Tactility/service/ServiceInstance.h index dc7e0cab..5d4da957 100644 --- a/Tactility/Private/Tactility/service/ServiceInstance.h +++ b/Tactility/Private/Tactility/service/ServiceInstance.h @@ -5,26 +5,29 @@ namespace tt::service { -class ServiceInstance : public ServiceContext { - -private: +class ServiceInstance final : public ServiceContext { Mutex mutex = Mutex(Mutex::Type::Normal); std::shared_ptr manifest; std::shared_ptr service; + State state = State::Stopped; public: - explicit ServiceInstance(std::shared_ptr manifest); + explicit ServiceInstance(std::shared_ptr manifest); ~ServiceInstance() override = default; - /** @return a reference ot the service's manifest */ - const service::ServiceManifest& getManifest() const override; + /** @return a reference to the service's manifest */ + const ServiceManifest& getManifest() const override; /** Retrieve the paths that are relevant to this service */ std::unique_ptr getPaths() const override; std::shared_ptr getService() const { return service; } + + State getState() const { return state; } + + void setState(State newState) { state = newState; } }; } diff --git a/Tactility/Private/Tactility/service/gui/Gui.h b/Tactility/Private/Tactility/service/gui/Gui.h deleted file mode 100644 index 4aa6c98d..00000000 --- a/Tactility/Private/Tactility/service/gui/Gui.h +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "Tactility/app/AppContext.h" - -#include - -#include - -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 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. - * @param[in] app - */ -void showApp(std::shared_ptr app); - -/** - * Hide the current app's viewport. - * Does not request a re-draw because after hiding the current app, - * we always show the previous app, and there is always at least 1 app running. - */ -void hideApp(); - -/** - * Show the on-screen keyboard. - * @param[in] textarea the textarea to focus the input for - */ -void softwareKeyboardShow(lv_obj_t* textarea); - -/** - * Hide the on-screen keyboard. - * Has no effect when the keyboard is not visible. - */ -void softwareKeyboardHide(); - -/** - * 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 softwareKeyboardIsEnabled(); - -/** - * 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 keyboardAddTextArea(lv_obj_t* textarea); - -} // namespace diff --git a/Tactility/Private/Tactility/service/gui/GuiService.h b/Tactility/Private/Tactility/service/gui/GuiService.h new file mode 100644 index 00000000..addec094 --- /dev/null +++ b/Tactility/Private/Tactility/service/gui/GuiService.h @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include +#include + +#include "Tactility/app/AppContext.h" + +#include + +#include + +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) + +class GuiService : public Service { + + // 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 appToRender = nullptr; + + lv_obj_t* _Nullable keyboard = nullptr; + lv_group_t* keyboardGroup = nullptr; + + bool isStarted = false; + + static void onLoaderMessage(const void* message, TT_UNUSED void* context); + + static int32_t guiMain(); + + lv_obj_t* createAppViews(lv_obj_t* parent); + + void redraw(); + + void lock() const { + tt_check(mutex.lock(pdMS_TO_TICKS(1000))); + } + + void unlock() const { + tt_check(mutex.unlock()); + } + +public: + + void onStart(TT_UNUSED ServiceContext& service) override; + + void onStop(TT_UNUSED ServiceContext& service) override; + + void requestDraw(); + + void showApp(std::shared_ptr app); + + void hideApp(); + + + /** + * Show the on-screen keyboard. + * @param[in] textarea the textarea to focus the input for + */ + void softwareKeyboardShow(lv_obj_t* textarea); + + /** + * Hide the on-screen keyboard. + * Has no effect when the keyboard is not visible. + */ + void softwareKeyboardHide(); + + /** + * 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 softwareKeyboardIsEnabled(); + + /** + * 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 keyboardAddTextArea(lv_obj_t* textarea); + +}; + +std::shared_ptr findService(); + +} // namespace diff --git a/Tactility/Source/app/chat/ChatApp.cpp b/Tactility/Source/app/chat/ChatApp.cpp index 9567d6cc..726e83bb 100644 --- a/Tactility/Source/app/chat/ChatApp.cpp +++ b/Tactility/Source/app/chat/ChatApp.cpp @@ -5,7 +5,6 @@ #include #include -#include "Tactility/service/gui/Gui.h" #include "Tactility/lvgl/LvglSync.h" #include @@ -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 { diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp index 28842810..51169d5b 100644 --- a/Tactility/Source/app/display/Display.cpp +++ b/Tactility/Source/app/display/Display.cpp @@ -72,8 +72,6 @@ class DisplayApp : public App { static void onGammaSliderEvent(lv_event_t* event) { auto* slider = static_cast(lv_event_get_target(event)); - auto* lvgl_display = lv_display_get_default(); - assert(lvgl_display != nullptr); auto hal_display = hal::findFirstDevice(hal::Device::Type::Display); assert(hal_display != nullptr); diff --git a/Tactility/Source/app/fileselection/View.cpp b/Tactility/Source/app/fileselection/View.cpp index 87376ed7..0d5043f2 100644 --- a/Tactility/Source/app/fileselection/View.cpp +++ b/Tactility/Source/app/fileselection/View.cpp @@ -10,7 +10,6 @@ #include #include -#include #ifdef ESP_PLATFORM #include "Tactility/service/loader/Loader.h" diff --git a/Tactility/Source/app/inputdialog/InputDialog.cpp b/Tactility/Source/app/inputdialog/InputDialog.cpp index ebdc7f65..44d6e63e 100644 --- a/Tactility/Source/app/inputdialog/InputDialog.cpp +++ b/Tactility/Source/app/inputdialog/InputDialog.cpp @@ -2,7 +2,6 @@ #include "Tactility/lvgl/Toolbar.h" #include "Tactility/service/loader/Loader.h" -#include "Tactility/service/gui/Gui.h" #include @@ -47,8 +46,6 @@ static std::string getTitleParameter(const std::shared_ptr& 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(); 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(); diff --git a/Tactility/Source/app/screenshot/Screenshot.cpp b/Tactility/Source/app/screenshot/Screenshot.cpp index 6ced9a23..a87b22c2 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -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 @@ -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(); diff --git a/Tactility/Source/app/timezone/TimeZone.cpp b/Tactility/Source/app/timezone/TimeZone.cpp index e770ac17..c8372060 100644 --- a/Tactility/Source/app/timezone/TimeZone.cpp +++ b/Tactility/Source/app/timezone/TimeZone.cpp @@ -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 @@ -66,8 +65,6 @@ void setResultCode(Bundle& bundle, const std::string& code) { class TimeZoneApp : public App { -private: - Mutex mutex; std::vector entries; std::unique_ptr 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(); } diff --git a/Tactility/Source/app/wificonnect/View.cpp b/Tactility/Source/app/wificonnect/View.cpp index d4d4e586..21f08976 100644 --- a/Tactility/Source/app/wificonnect/View.cpp +++ b/Tactility/Source/app/wificonnect/View.cpp @@ -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 #include diff --git a/Tactility/Source/lvgl/Init.cpp b/Tactility/Source/lvgl/Init.cpp index b032c19b..e517bb58 100644 --- a/Tactility/Source/lvgl/Init.cpp +++ b/Tactility/Source/lvgl/Init.cpp @@ -32,9 +32,11 @@ static std::shared_ptr initDisplay(const hal::Confi } 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(lv_display_get_default())) { - lv_display_set_rotation(lv_display_get_default(), rotation); + if (rotation != lv_display_get_rotation(lvgl_display)) { + lv_display_set_rotation(lvgl_display, rotation); } } diff --git a/Tactility/Source/lvgl/Keyboard.cpp b/Tactility/Source/lvgl/Keyboard.cpp index abd20004..ecf7a0cb 100644 --- a/Tactility/Source/lvgl/Keyboard.cpp +++ b/Tactility/Source/lvgl/Keyboard.cpp @@ -1,20 +1,33 @@ #include "Tactility/lvgl/Keyboard.h" -#include "Tactility/service/gui/Gui.h" +#include "Tactility/service/gui/GuiService.h" + +#include 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) { diff --git a/Tactility/Source/lvgl/Wrappers.cpp b/Tactility/Source/lvgl/Wrappers.cpp index 27e88cba..f3f4c728 100644 --- a/Tactility/Source/lvgl/Wrappers.cpp +++ b/Tactility/Source/lvgl/Wrappers.cpp @@ -1,5 +1,5 @@ #include -#include +#include extern "C" { @@ -7,7 +7,12 @@ 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); - tt::service::gui::keyboardAddTextArea(textarea); + + auto gui_service = tt::service::gui::findService(); + if (gui_service != nullptr) { + gui_service->keyboardAddTextArea(textarea); + } + return textarea; } diff --git a/Tactility/Source/service/ServiceRegistry.cpp b/Tactility/Source/service/ServiceRegistry.cpp index 57ef8e2e..a2885922 100644 --- a/Tactility/Source/service/ServiceRegistry.cpp +++ b/Tactility/Source/service/ServiceRegistry.cpp @@ -76,7 +76,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()); @@ -100,7 +102,9 @@ bool stopService(const std::string& id) { 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 +119,15 @@ bool stopService(const std::string& id) { return true; } +bool getState(const std::string& id, State& state) { + auto service_instance = findServiceInstanceById(id); + if (service_instance == nullptr) { + TT_LOG_W(TAG, "service not running: %s", id.c_str()); + return false; + } else { + state = service_instance->getState(); + return true; + } +} + } // namespace diff --git a/Tactility/Source/service/gui/Gui.cpp b/Tactility/Source/service/gui/Gui.cpp deleted file mode 100644 index 43ca3ddb..00000000 --- a/Tactility/Source/service/gui/Gui.cpp +++ /dev/null @@ -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 -#include - -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(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) { - 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 -}; - -// endregion - -} // namespace diff --git a/Tactility/Source/service/gui/GuiDraw.cpp b/Tactility/Source/service/gui/GuiDraw.cpp deleted file mode 100644 index a3ba2333..00000000 --- a/Tactility/Source/service/gui/GuiDraw.cpp +++ /dev/null @@ -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 -#include - -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(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 diff --git a/Tactility/Source/service/gui/GuiService.cpp b/Tactility/Source/service/gui/GuiService.cpp new file mode 100644 index 00000000..35050427 --- /dev/null +++ b/Tactility/Source/service/gui/GuiService.cpp @@ -0,0 +1,237 @@ +#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 +#include +#include + +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(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() { + State service_state; + while (true) { + uint32_t flags = Thread::awaitFlags(GUI_THREAD_FLAG_ALL, EventFlag::WaitAny, (uint32_t)portMAX_DELAY); + + // When service (state) not found -> exit + if (!getState(manifest.id, service_state)) { + break; + } + + // When service not started or starting -> exit + 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(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) { + 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 findService() { + return std::static_pointer_cast( + findServiceById(manifest.id) + ); +} + +extern const ServiceManifest manifest = { + .id = "Gui", + .createService = create +}; + +// endregion + +} // namespace diff --git a/Tactility/Source/service/gui/Keyboard.cpp b/Tactility/Source/service/gui/Keyboard.cpp index 0aede061..ae754848 100644 --- a/Tactility/Source/service/gui/Keyboard.cpp +++ b/Tactility/Source/service/gui/Keyboard.cpp @@ -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 +#include 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(); }