mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-02-20 15:35:05 +00:00
Refactor GUI service
And much more
This commit is contained in:
parent
e63d9d9a56
commit
db4c0ac6fc
@ -1,18 +1,51 @@
|
|||||||
#include <Tactility/app/AppManifest.h>
|
#include <Tactility/app/AppManifest.h>
|
||||||
#include <Tactility/lvgl/Toolbar.h>
|
#include <Tactility/lvgl/Toolbar.h>
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
|
#include <Tactility/hal/Device.h>
|
||||||
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
#include <Tactility/service/ServiceRegistry.h>
|
||||||
|
|
||||||
using namespace tt::app;
|
using namespace tt::app;
|
||||||
|
|
||||||
class HelloWorldApp : public App {
|
class HelloWorldApp : public App {
|
||||||
|
|
||||||
void onShow(AppContext& context, lv_obj_t* parent) override {
|
std::shared_ptr<tt::hal::display::DisplayDevice> displayDevice;
|
||||||
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
|
// void onShow(AppContext& context, lv_obj_t* parent) override {
|
||||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
// 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);
|
void onCreate(AppContext& appContext) override {
|
||||||
lv_label_set_text(label, "Hello, world!");
|
tt::service::stopService("Statusbar");
|
||||||
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
|
tt::service::stopService("Gui");
|
||||||
|
|
||||||
|
tt::service::startService("Gui");
|
||||||
|
tt::service::startService("Statusbar");
|
||||||
|
|
||||||
|
// using namespace tt::hal;
|
||||||
|
// displayDevice = findFirstDevice<display::DisplayDevice>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#include "tt_init.h"
|
#include "tt_init.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// extern const tt::app::AppManifest hello_world_app;
|
extern const tt::app::AppManifest hello_world_app;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ void app_main() {
|
|||||||
*/
|
*/
|
||||||
.hardware = TT_BOARD_HARDWARE,
|
.hardware = TT_BOARD_HARDWARE,
|
||||||
.apps = {
|
.apps = {
|
||||||
// &hello_world_app,
|
&hello_world_app,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -88,7 +88,7 @@ bool St7789Display::start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Finished");
|
TT_LOG_I(TAG, "Finished");
|
||||||
return displayHandle != nullptr;
|
return panelHandle != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool St7789Display::stop() {
|
bool St7789Display::stop() {
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include <EspLcdNativeDisplay.h>
|
#include <EspLcdNativeDisplay.h>
|
||||||
|
|
||||||
#include <driver/spi_common.h>
|
|
||||||
#include <driver/gpio.h>
|
#include <driver/gpio.h>
|
||||||
#include <esp_lcd_panel_io.h>
|
#include <esp_lcd_panel_io.h>
|
||||||
#include <esp_lcd_types.h>
|
#include <esp_lcd_types.h>
|
||||||
|
|||||||
@ -8,10 +8,8 @@
|
|||||||
#include "Tactility/hal/uart/Uart.h"
|
#include "Tactility/hal/uart/Uart.h"
|
||||||
#include "Tactility/lvgl/LvglSync.h"
|
#include "Tactility/lvgl/LvglSync.h"
|
||||||
#include "Tactility/lvgl/Style.h"
|
#include "Tactility/lvgl/Style.h"
|
||||||
#include "Tactility/service/gui/Gui.h"
|
|
||||||
|
|
||||||
#include <Tactility/StringUtils.h>
|
#include <Tactility/StringUtils.h>
|
||||||
#include <array>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace tt::app::serialconsole {
|
namespace tt::app::serialconsole {
|
||||||
@ -129,7 +127,7 @@ public:
|
|||||||
lv_obj_add_event_cb(connect_button, onConnectCallback, LV_EVENT_SHORT_CLICKED, this);
|
lv_obj_add_event_cb(connect_button, onConnectCallback, LV_EVENT_SHORT_CLICKED, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onStop() final {
|
void onStop() {
|
||||||
int speed = getSpeedInput();
|
int speed = getSpeedInput();
|
||||||
if (speed > 0) {
|
if (speed > 0) {
|
||||||
preferences.putInt32("speed", speed);
|
preferences.putInt32("speed", speed);
|
||||||
|
|||||||
@ -4,6 +4,13 @@
|
|||||||
|
|
||||||
namespace tt::service {
|
namespace tt::service {
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
Starting,
|
||||||
|
Started,
|
||||||
|
Stopping,
|
||||||
|
Stopped
|
||||||
|
};
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class ServiceContext;
|
class ServiceContext;
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,8 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/** @return a reference ot the service's manifest */
|
/** @return a reference to the service's manifest */
|
||||||
virtual const service::ServiceManifest& getManifest() const = 0;
|
virtual const ServiceManifest& getManifest() const = 0;
|
||||||
|
|
||||||
/** Retrieve the paths that are relevant to this service */
|
/** Retrieve the paths that are relevant to this service */
|
||||||
virtual std::unique_ptr<Paths> getPaths() const = 0;
|
virtual std::unique_ptr<Paths> getPaths() const = 0;
|
||||||
|
|||||||
@ -29,6 +29,13 @@ bool startService(const std::string& id);
|
|||||||
*/
|
*/
|
||||||
bool stopService(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.
|
/** Find a service manifest by its id.
|
||||||
* @param[in] id the id as defined in the manifest
|
* @param[in] id the id as defined in the manifest
|
||||||
* @return the matching manifest or nullptr when it wasn't found
|
* @return the matching manifest or nullptr when it wasn't found
|
||||||
|
|||||||
@ -5,26 +5,29 @@
|
|||||||
|
|
||||||
namespace tt::service {
|
namespace tt::service {
|
||||||
|
|
||||||
class ServiceInstance : public ServiceContext {
|
class ServiceInstance final : public ServiceContext {
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
Mutex mutex = Mutex(Mutex::Type::Normal);
|
Mutex mutex = Mutex(Mutex::Type::Normal);
|
||||||
std::shared_ptr<const ServiceManifest> manifest;
|
std::shared_ptr<const ServiceManifest> manifest;
|
||||||
std::shared_ptr<Service> service;
|
std::shared_ptr<Service> service;
|
||||||
|
State state = State::Stopped;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit ServiceInstance(std::shared_ptr<const service::ServiceManifest> manifest);
|
explicit ServiceInstance(std::shared_ptr<const ServiceManifest> manifest);
|
||||||
~ServiceInstance() override = default;
|
~ServiceInstance() override = default;
|
||||||
|
|
||||||
/** @return a reference ot the service's manifest */
|
/** @return a reference to the service's manifest */
|
||||||
const service::ServiceManifest& getManifest() const override;
|
const ServiceManifest& getManifest() const override;
|
||||||
|
|
||||||
/** Retrieve the paths that are relevant to this service */
|
/** Retrieve the paths that are relevant to this service */
|
||||||
std::unique_ptr<Paths> getPaths() const override;
|
std::unique_ptr<Paths> getPaths() const override;
|
||||||
|
|
||||||
std::shared_ptr<Service> getService() const { return service; }
|
std::shared_ptr<Service> getService() const { return service; }
|
||||||
|
|
||||||
|
State getState() const { return state; }
|
||||||
|
|
||||||
|
void setState(State newState) { state = newState; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,88 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/MessageQueue.h>
|
|
||||||
#include <Tactility/Mutex.h>
|
|
||||||
#include <Tactility/PubSub.h>
|
|
||||||
|
|
||||||
#include "Tactility/app/AppContext.h"
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
namespace tt::service::gui {
|
|
||||||
|
|
||||||
#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.
|
|
||||||
* @param[in] app
|
|
||||||
*/
|
|
||||||
void showApp(std::shared_ptr<app::AppContext> 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
|
|
||||||
101
Tactility/Private/Tactility/service/gui/GuiService.h
Normal file
101
Tactility/Private/Tactility/service/gui/GuiService.h
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/MessageQueue.h>
|
||||||
|
#include <Tactility/Mutex.h>
|
||||||
|
#include <Tactility/PubSub.h>
|
||||||
|
#include <Tactility/service/Service.h>
|
||||||
|
|
||||||
|
#include "Tactility/app/AppContext.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)
|
||||||
|
|
||||||
|
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<app::AppContext> 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::AppContext> 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<GuiService> findService();
|
||||||
|
|
||||||
|
} // namespace
|
||||||
@ -5,7 +5,6 @@
|
|||||||
#include <Tactility/Assets.h>
|
#include <Tactility/Assets.h>
|
||||||
#include <Tactility/service/espnow/EspNow.h>
|
#include <Tactility/service/espnow/EspNow.h>
|
||||||
|
|
||||||
#include "Tactility/service/gui/Gui.h"
|
|
||||||
#include "Tactility/lvgl/LvglSync.h"
|
#include "Tactility/lvgl/LvglSync.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@ -16,7 +15,7 @@
|
|||||||
namespace tt::app::chat {
|
namespace tt::app::chat {
|
||||||
|
|
||||||
constexpr const char* TAG = "ChatApp";
|
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 {
|
class ChatApp : public App {
|
||||||
|
|
||||||
|
|||||||
@ -72,8 +72,6 @@ class DisplayApp : public App {
|
|||||||
|
|
||||||
static void onGammaSliderEvent(lv_event_t* event) {
|
static void onGammaSliderEvent(lv_event_t* event) {
|
||||||
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(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 = hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
|
auto hal_display = hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
|
||||||
assert(hal_display != nullptr);
|
assert(hal_display != nullptr);
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <Tactility/service/gui/Gui.h>
|
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
#include "Tactility/service/loader/Loader.h"
|
#include "Tactility/service/loader/Loader.h"
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Tactility/lvgl/Toolbar.h"
|
#include "Tactility/lvgl/Toolbar.h"
|
||||||
#include "Tactility/service/loader/Loader.h"
|
#include "Tactility/service/loader/Loader.h"
|
||||||
#include "Tactility/service/gui/Gui.h"
|
|
||||||
|
|
||||||
#include <Tactility/TactilityCore.h>
|
#include <Tactility/TactilityCore.h>
|
||||||
|
|
||||||
@ -47,8 +46,6 @@ static std::string getTitleParameter(const std::shared_ptr<const Bundle>& bundle
|
|||||||
|
|
||||||
class InputDialogApp : public App {
|
class InputDialogApp : public App {
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
static void createButton(lv_obj_t* parent, const std::string& text, void* callbackContext) {
|
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 = lv_button_create(parent);
|
||||||
lv_obj_t* button_label = lv_label_create(button);
|
lv_obj_t* button_label = lv_label_create(button);
|
||||||
@ -71,9 +68,9 @@ private:
|
|||||||
auto bundle = std::make_unique<Bundle>();
|
auto bundle = std::make_unique<Bundle>();
|
||||||
const char* text = lv_textarea_get_text((lv_obj_t*)user_data);
|
const char* text = lv_textarea_get_text((lv_obj_t*)user_data);
|
||||||
bundle->putString(RESULT_BUNDLE_KEY_RESULT, text);
|
bundle->putString(RESULT_BUNDLE_KEY_RESULT, text);
|
||||||
setResult(app::Result::Ok, std::move(bundle));
|
setResult(Result::Ok, std::move(bundle));
|
||||||
} else {
|
} else {
|
||||||
setResult(app::Result::Cancelled);
|
setResult(Result::Cancelled);
|
||||||
|
|
||||||
}
|
}
|
||||||
service::loader::stopApp();
|
service::loader::stopApp();
|
||||||
|
|||||||
@ -9,8 +9,6 @@
|
|||||||
#include "Tactility/app/AppManifest.h"
|
#include "Tactility/app/AppManifest.h"
|
||||||
#include "Tactility/lvgl/LvglSync.h"
|
#include "Tactility/lvgl/LvglSync.h"
|
||||||
#include "Tactility/lvgl/Toolbar.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/service/screenshot/Screenshot.h"
|
||||||
|
|
||||||
#include <Tactility/TactilityHeadless.h>
|
#include <Tactility/TactilityHeadless.h>
|
||||||
@ -39,7 +37,7 @@ class ScreenshotApp final : public App {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
ScreenshotApp();
|
ScreenshotApp();
|
||||||
~ScreenshotApp() final;
|
~ScreenshotApp();
|
||||||
|
|
||||||
void onShow(AppContext& app, lv_obj_t* parent) override;
|
void onShow(AppContext& app, lv_obj_t* parent) override;
|
||||||
void onStartPressed();
|
void onStartPressed();
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
#include "Tactility/app/timezone/TimeZone.h"
|
#include "Tactility/app/timezone/TimeZone.h"
|
||||||
#include "Tactility/lvgl/Toolbar.h"
|
#include "Tactility/lvgl/Toolbar.h"
|
||||||
#include "Tactility/lvgl/LvglSync.h"
|
#include "Tactility/lvgl/LvglSync.h"
|
||||||
#include "Tactility/service/gui/Gui.h"
|
|
||||||
#include "Tactility/service/loader/Loader.h"
|
#include "Tactility/service/loader/Loader.h"
|
||||||
|
|
||||||
#include <Tactility/Partitions.h>
|
#include <Tactility/Partitions.h>
|
||||||
@ -66,8 +65,6 @@ void setResultCode(Bundle& bundle, const std::string& code) {
|
|||||||
|
|
||||||
class TimeZoneApp : public App {
|
class TimeZoneApp : public App {
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
std::vector<TimeZoneEntry> entries;
|
std::vector<TimeZoneEntry> entries;
|
||||||
std::unique_ptr<Timer> updateTimer;
|
std::unique_ptr<Timer> updateTimer;
|
||||||
@ -107,7 +104,7 @@ private:
|
|||||||
setResultName(*bundle, entry.name);
|
setResultName(*bundle, entry.name);
|
||||||
setResultCode(*bundle, entry.code);
|
setResultCode(*bundle, entry.code);
|
||||||
|
|
||||||
setResult(app::Result::Ok, std::move(bundle));
|
setResult(Result::Ok, std::move(bundle));
|
||||||
|
|
||||||
service::loader::stopApp();
|
service::loader::stopApp();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
#include "Tactility/app/wificonnect/View.h"
|
#include "Tactility/app/wificonnect/View.h"
|
||||||
#include "Tactility/app/wificonnect/WifiConnect.h"
|
#include "Tactility/app/wificonnect/WifiConnect.h"
|
||||||
|
|
||||||
#include "Tactility/lvgl/Style.h"
|
|
||||||
#include "Tactility/lvgl/Toolbar.h"
|
#include "Tactility/lvgl/Toolbar.h"
|
||||||
#include "Tactility/lvgl/Spinner.h"
|
#include "Tactility/lvgl/Spinner.h"
|
||||||
#include "Tactility/service/gui/Gui.h"
|
|
||||||
|
|
||||||
#include <Tactility/TactilityCore.h>
|
#include <Tactility/TactilityCore.h>
|
||||||
#include <Tactility/service/wifi/WifiSettings.h>
|
#include <Tactility/service/wifi/WifiSettings.h>
|
||||||
|
|||||||
@ -32,9 +32,11 @@ static std::shared_ptr<hal::display::DisplayDevice> initDisplay(const hal::Confi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (display->supportsLvgl() && display->startLvgl()) {
|
if (display->supportsLvgl() && display->startLvgl()) {
|
||||||
|
auto lvgl_display = display->getLvglDisplay();
|
||||||
|
assert(lvgl_display != nullptr);
|
||||||
lv_display_rotation_t rotation = app::display::getRotation();
|
lv_display_rotation_t rotation = app::display::getRotation();
|
||||||
if (rotation != lv_display_get_rotation(lv_display_get_default())) {
|
if (rotation != lv_display_get_rotation(lvgl_display)) {
|
||||||
lv_display_set_rotation(lv_display_get_default(), rotation);
|
lv_display_set_rotation(lvgl_display, rotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,33 @@
|
|||||||
#include "Tactility/lvgl/Keyboard.h"
|
#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 {
|
namespace tt::lvgl {
|
||||||
|
|
||||||
static lv_indev_t* keyboard_device = nullptr;
|
static lv_indev_t* keyboard_device = nullptr;
|
||||||
|
|
||||||
void software_keyboard_show(lv_obj_t* textarea) {
|
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() {
|
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() {
|
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) {
|
void software_keyboard_activate(lv_group_t* group) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include <Tactility/service/gui/Gui.h>
|
#include <Tactility/service/gui/GuiService.h>
|
||||||
|
|
||||||
extern "C" {
|
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) {
|
lv_obj_t * __wrap_lv_textarea_create(lv_obj_t * parent) {
|
||||||
auto textarea = __real_lv_textarea_create(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;
|
return textarea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,7 +76,9 @@ bool startService(const std::string& id) {
|
|||||||
service_instance_map[manifest->id] = service_instance;
|
service_instance_map[manifest->id] = service_instance;
|
||||||
instance_mutex.unlock();
|
instance_mutex.unlock();
|
||||||
|
|
||||||
|
service_instance->setState(State::Starting);
|
||||||
service_instance->getService()->onStart(*service_instance);
|
service_instance->getService()->onStart(*service_instance);
|
||||||
|
service_instance->setState(State::Started);
|
||||||
|
|
||||||
TT_LOG_I(TAG, "Started %s", id.c_str());
|
TT_LOG_I(TAG, "Started %s", id.c_str());
|
||||||
|
|
||||||
@ -100,7 +102,9 @@ bool stopService(const std::string& id) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service_instance->setState(State::Stopping);
|
||||||
service_instance->getService()->onStop(*service_instance);
|
service_instance->getService()->onStop(*service_instance);
|
||||||
|
service_instance->setState(State::Stopped);
|
||||||
|
|
||||||
instance_mutex.lock();
|
instance_mutex.lock();
|
||||||
service_instance_map.erase(id);
|
service_instance_map.erase(id);
|
||||||
@ -115,4 +119,15 @@ bool stopService(const std::string& id) {
|
|||||||
return true;
|
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
|
} // 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
|
|
||||||
237
Tactility/Source/service/gui/GuiService.cpp
Normal file
237
Tactility/Source/service/gui/GuiService.cpp
Normal file
@ -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 <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() {
|
||||||
|
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<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/lvgl/Keyboard.h"
|
||||||
#include "Tactility/Check.h"
|
#include "Tactility/Check.h"
|
||||||
#include "Tactility/lvgl/LvglSync.h"
|
#include "Tactility/lvgl/LvglSync.h"
|
||||||
#include "Tactility/service/gui/Gui.h"
|
#include "Tactility/service/gui/GuiService.h"
|
||||||
|
|
||||||
#include <Tactility/TactilityConfig.h>
|
#include <Tactility/TactilityConfig.h>
|
||||||
|
#include <Tactility/service/espnow/EspNowService.h>
|
||||||
|
|
||||||
namespace tt::service::gui {
|
namespace tt::service::gui {
|
||||||
|
|
||||||
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);
|
auto service = findService();
|
||||||
softwareKeyboardShow(target);
|
if (service != nullptr) {
|
||||||
lv_obj_scroll_to_view(target, LV_ANIM_ON);
|
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) {
|
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;
|
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();
|
lock();
|
||||||
|
|
||||||
if (gui->keyboard) {
|
if (isStarted && keyboard != nullptr) {
|
||||||
lv_obj_clear_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_clear_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||||
lv_keyboard_set_textarea(gui->keyboard, textarea);
|
lv_keyboard_set_textarea(keyboard, textarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void softwareKeyboardHide() {
|
void GuiService::softwareKeyboardHide() {
|
||||||
lock();
|
lock();
|
||||||
|
|
||||||
if (gui->keyboard) {
|
if (isStarted && keyboard != nullptr) {
|
||||||
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void keyboardAddTextArea(lv_obj_t* textarea) {
|
void GuiService::keyboardAddTextArea(lv_obj_t* textarea) {
|
||||||
lock();
|
lock();
|
||||||
tt_check(lvgl::lock(0), "lvgl should already be locked before calling this method");
|
|
||||||
|
|
||||||
if (softwareKeyboardIsEnabled()) {
|
if (isStarted) {
|
||||||
lv_obj_add_event_cb(textarea, show_keyboard, LV_EVENT_FOCUSED, nullptr);
|
tt_check(lvgl::lock(0), "lvgl should already be locked before calling this method");
|
||||||
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 (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();
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user