C++ refactoring (#91)
This commit is contained in:
committed by
GitHub
parent
d06137ba76
commit
ca5d4b8226
@@ -0,0 +1,165 @@
|
||||
#include "Tactility.h"
|
||||
#include "service/gui/Gui_i.h"
|
||||
#include "service/loader/Loader.h"
|
||||
#include "ui/LvglKeypad.h"
|
||||
#include "ui/LvglSync.h"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#else
|
||||
#include "FreeRTOS.h"
|
||||
#endif
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
#define TAG "gui"
|
||||
|
||||
// Forward declarations
|
||||
void redraw(Gui*);
|
||||
static int32_t gui_main(void*);
|
||||
|
||||
Gui* gui = nullptr;
|
||||
|
||||
typedef void (*PubSubCallback)(const void* message, void* context);
|
||||
void loader_callback(const void* message, TT_UNUSED void* context) {
|
||||
auto* event = static_cast<const loader::LoaderEvent*>(message);
|
||||
if (event->type == loader::LoaderEventTypeApplicationShowing) {
|
||||
app::App* app = event->app_showing.app;
|
||||
const app::Manifest& app_manifest = app::tt_app_get_manifest(app);
|
||||
show_app(app, app_manifest.on_show, app_manifest.on_hide);
|
||||
} else if (event->type == loader::LoaderEventTypeApplicationHiding) {
|
||||
hide_app();
|
||||
}
|
||||
}
|
||||
|
||||
Gui* gui_alloc() {
|
||||
auto* instance = static_cast<Gui*>(malloc(sizeof(Gui)));
|
||||
memset(instance, 0, sizeof(Gui));
|
||||
tt_check(instance != nullptr);
|
||||
instance->thread = new Thread(
|
||||
"gui",
|
||||
4096, // Last known minimum was 2800 for launching desktop
|
||||
&gui_main,
|
||||
nullptr
|
||||
);
|
||||
instance->mutex = tt_mutex_alloc(MutexTypeRecursive);
|
||||
instance->keyboard = nullptr;
|
||||
instance->loader_pubsub_subscription = tt_pubsub_subscribe(loader::get_pubsub(), &loader_callback, instance);
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
instance->keyboard_group = lv_group_create();
|
||||
instance->lvgl_parent = lv_scr_act();
|
||||
lvgl::unlock();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void gui_free(Gui* instance) {
|
||||
tt_assert(instance != nullptr);
|
||||
delete instance->thread;
|
||||
tt_mutex_free(instance->mutex);
|
||||
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
lv_group_del(instance->keyboard_group);
|
||||
lvgl::unlock();
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void lock() {
|
||||
tt_assert(gui);
|
||||
tt_assert(gui->mutex);
|
||||
tt_check(tt_mutex_acquire(gui->mutex, configTICK_RATE_HZ) == TtStatusOk);
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
tt_assert(gui);
|
||||
tt_assert(gui->mutex);
|
||||
tt_check(tt_mutex_release(gui->mutex) == TtStatusOk);
|
||||
}
|
||||
|
||||
void request_draw() {
|
||||
tt_assert(gui);
|
||||
ThreadId thread_id = gui->thread->getId();
|
||||
thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW);
|
||||
}
|
||||
|
||||
void show_app(app::App app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide) {
|
||||
lock();
|
||||
tt_check(gui->app_view_port == nullptr);
|
||||
gui->app_view_port = view_port_alloc(app, on_show, on_hide);
|
||||
unlock();
|
||||
request_draw();
|
||||
}
|
||||
|
||||
void hide_app() {
|
||||
lock();
|
||||
ViewPort* view_port = gui->app_view_port;
|
||||
tt_check(view_port != 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));
|
||||
view_port_hide(view_port);
|
||||
lvgl::unlock();
|
||||
|
||||
view_port_free(view_port);
|
||||
gui->app_view_port = nullptr;
|
||||
unlock();
|
||||
}
|
||||
|
||||
static int32_t gui_main(TT_UNUSED void* p) {
|
||||
tt_check(gui);
|
||||
Gui* local_gui = gui;
|
||||
|
||||
while (true) {
|
||||
uint32_t flags = thread_flags_wait(
|
||||
GUI_THREAD_FLAG_ALL,
|
||||
TtFlagWaitAny,
|
||||
TtWaitForever
|
||||
);
|
||||
// Process and dispatch draw call
|
||||
if (flags & GUI_THREAD_FLAG_DRAW) {
|
||||
thread_flags_clear(GUI_THREAD_FLAG_DRAW);
|
||||
redraw(local_gui);
|
||||
}
|
||||
|
||||
if (flags & GUI_THREAD_FLAG_EXIT) {
|
||||
thread_flags_clear(GUI_THREAD_FLAG_EXIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// region AppManifest
|
||||
|
||||
static void start(TT_UNUSED Service& service) {
|
||||
gui = gui_alloc();
|
||||
|
||||
gui->thread->setPriority(THREAD_PRIORITY_SERVICE);
|
||||
gui->thread->start();
|
||||
}
|
||||
|
||||
static void stop(TT_UNUSED Service& service) {
|
||||
lock();
|
||||
|
||||
ThreadId thread_id = gui->thread->getId();
|
||||
thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT);
|
||||
gui->thread->join();
|
||||
delete gui->thread;
|
||||
|
||||
unlock();
|
||||
|
||||
gui_free(gui);
|
||||
}
|
||||
|
||||
extern const Manifest manifest = {
|
||||
.id = "Gui",
|
||||
.onStart = &start,
|
||||
.onStop = &stop
|
||||
};
|
||||
|
||||
// endregion
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/App.h"
|
||||
#include "ViewPort.h"
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
typedef struct Gui Gui;
|
||||
|
||||
/**
|
||||
* Set the app viewport in the gui state and request the gui to draw it.
|
||||
*
|
||||
* @param app
|
||||
* @param on_show
|
||||
* @param on_hide
|
||||
*/
|
||||
void show_app(app::App app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide);
|
||||
|
||||
/**
|
||||
* 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 hide_app();
|
||||
|
||||
/**
|
||||
* Show the on-screen keyboard.
|
||||
* @param textarea the textarea to focus the input for
|
||||
*/
|
||||
void keyboard_show(lv_obj_t* textarea);
|
||||
|
||||
/**
|
||||
* Hide the on-screen keyboard.
|
||||
* Has no effect when the keyboard is not visible.
|
||||
*/
|
||||
void keyboard_hide();
|
||||
|
||||
/**
|
||||
* The on-screen keyboard is only shown when both of these conditions are true:
|
||||
* - there is no hardware keyboard
|
||||
* - TT_CONFIG_FORCE_ONSCREEN_KEYBOARD is set to true in tactility_config.h
|
||||
* @return if we should show a on-screen keyboard for text input inside our apps
|
||||
*/
|
||||
bool keyboard_is_enabled();
|
||||
|
||||
/**
|
||||
* 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 textarea
|
||||
*/
|
||||
void keyboard_add_textarea(lv_obj_t* textarea);
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "Check.h"
|
||||
#include "Log.h"
|
||||
#include "service/gui/Gui_i.h"
|
||||
#include "ui/LvglSync.h"
|
||||
#include "ui/Statusbar.h"
|
||||
#include "ui/Style.h"
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
#define TAG "gui"
|
||||
|
||||
static lv_obj_t* create_app_views(Gui* gui, lv_obj_t* parent, app::App app) {
|
||||
lvgl::obj_set_style_bg_blacken(parent);
|
||||
|
||||
lv_obj_t* vertical_container = lv_obj_create(parent);
|
||||
lv_obj_set_size(vertical_container, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_flex_flow(vertical_container, LV_FLEX_FLOW_COLUMN);
|
||||
lvgl::obj_set_style_no_padding(vertical_container);
|
||||
lvgl::obj_set_style_bg_blacken(vertical_container);
|
||||
|
||||
// TODO: Move statusbar into separate ViewPort
|
||||
app::Flags flags = app::tt_app_get_flags(app);
|
||||
if (flags.show_statusbar) {
|
||||
lvgl::statusbar_create(vertical_container);
|
||||
}
|
||||
|
||||
lv_obj_t* child_container = lv_obj_create(vertical_container);
|
||||
lv_obj_set_width(child_container, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(child_container, 1);
|
||||
|
||||
if (keyboard_is_enabled()) {
|
||||
gui->keyboard = lv_keyboard_create(vertical_container);
|
||||
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
gui->keyboard = nullptr;
|
||||
}
|
||||
|
||||
return child_container;
|
||||
}
|
||||
|
||||
void redraw(Gui* gui) {
|
||||
tt_assert(gui);
|
||||
|
||||
// Lock GUI and LVGL
|
||||
lock();
|
||||
|
||||
if (lvgl::lock(1000)) {
|
||||
lv_obj_clean(gui->lvgl_parent);
|
||||
|
||||
ViewPort* view_port = gui->app_view_port;
|
||||
if (view_port != nullptr) {
|
||||
app::App app = view_port->app;
|
||||
lv_obj_t* container = create_app_views(gui, gui->lvgl_parent, app);
|
||||
view_port_show(view_port, container);
|
||||
} else {
|
||||
TT_LOG_W(TAG, "nothing to draw");
|
||||
}
|
||||
|
||||
// Unlock GUI and LVGL
|
||||
lvgl::unlock();
|
||||
} else {
|
||||
TT_LOG_E(TAG, "failed to lock lvgl");
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "Check.h"
|
||||
#include "TactilityConfig.h"
|
||||
#include "service/gui/Gui_i.h"
|
||||
#include "ui/LvglKeypad.h"
|
||||
#include "ui/LvglSync.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);
|
||||
keyboard_show(target);
|
||||
lv_obj_scroll_to_view(target, LV_ANIM_ON);
|
||||
}
|
||||
|
||||
static void hide_keyboard(TT_UNUSED lv_event_t* event) {
|
||||
keyboard_hide();
|
||||
}
|
||||
|
||||
bool keyboard_is_enabled() {
|
||||
return !lvgl::keypad_is_available() || TT_CONFIG_FORCE_ONSCREEN_KEYBOARD;
|
||||
}
|
||||
|
||||
void keyboard_show(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);
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
void keyboard_hide() {
|
||||
lock();
|
||||
|
||||
if (gui->keyboard) {
|
||||
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
void keyboard_add_textarea(lv_obj_t* textarea) {
|
||||
lock();
|
||||
tt_check(lvgl::lock(0), "lvgl should already be locked before calling this method");
|
||||
|
||||
if (keyboard_is_enabled()) {
|
||||
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(gui->keyboard_group, textarea);
|
||||
|
||||
lvgl::keypad_activate(gui->keyboard_group);
|
||||
|
||||
lvgl::unlock();
|
||||
unlock();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "ViewPort.h"
|
||||
|
||||
#include "Check.h"
|
||||
#include "service/gui/ViewPort_i.h"
|
||||
#include "ui/Style.h"
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
#define TAG "viewport"
|
||||
|
||||
ViewPort* view_port_alloc(
|
||||
app::App app,
|
||||
ViewPortShowCallback on_show,
|
||||
ViewPortHideCallback on_hide
|
||||
) {
|
||||
auto* view_port = static_cast<ViewPort*>(malloc(sizeof(ViewPort)));
|
||||
view_port->app = app;
|
||||
view_port->on_show = on_show;
|
||||
view_port->on_hide = on_hide;
|
||||
return view_port;
|
||||
}
|
||||
|
||||
void view_port_free(ViewPort* view_port) {
|
||||
tt_assert(view_port);
|
||||
free(view_port);
|
||||
}
|
||||
|
||||
void view_port_show(ViewPort* view_port, lv_obj_t* parent) {
|
||||
tt_assert(view_port);
|
||||
tt_assert(parent);
|
||||
if (view_port->on_show) {
|
||||
lvgl::obj_set_style_no_padding(parent);
|
||||
view_port->on_show(view_port->app, parent);
|
||||
}
|
||||
}
|
||||
|
||||
void view_port_hide(ViewPort* view_port) {
|
||||
tt_assert(view_port);
|
||||
if (view_port->on_hide) {
|
||||
view_port->on_hide(view_port->app);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/App.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
/** ViewPort Draw callback
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewPortShowCallback)(app::App app, lv_obj_t* parent);
|
||||
typedef void (*ViewPortHideCallback)(app::App app);
|
||||
|
||||
// TODO: Move internally, use handle publicly
|
||||
|
||||
typedef struct {
|
||||
app::App app;
|
||||
ViewPortShowCallback on_show;
|
||||
ViewPortHideCallback _Nullable on_hide;
|
||||
bool app_toolbar;
|
||||
} ViewPort;
|
||||
|
||||
/** ViewPort allocator
|
||||
*
|
||||
* always returns view_port or stops system if not enough memory.
|
||||
* @param app
|
||||
* @param on_show Called to create LVGL widgets
|
||||
* @param on_hide Called before clearing the LVGL widget parent
|
||||
*
|
||||
* @return ViewPort instance
|
||||
*/
|
||||
ViewPort* view_port_alloc(
|
||||
app::App app,
|
||||
ViewPortShowCallback on_show,
|
||||
ViewPortHideCallback on_hide
|
||||
);
|
||||
|
||||
/** ViewPort deallocator
|
||||
*
|
||||
* Ensure that view_port was unregistered in GUI system before use.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void view_port_free(ViewPort* view_port);
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user