From f0cfd3c34d6d8e6820ae5d854c3d81415fc3baf8 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Wed, 27 Dec 2023 23:53:19 +0100 Subject: [PATCH] implemented gui and view_port using flipper source (adapted) disabled key input for now disabled non-fullscreen drawing for now --- .../board_2432s024/board_2432s024_display.c | 2 +- .../board_2432s024/board_2432s024_touch.c | 2 +- .../src/applications/services/gui/gui.c | 246 ++++++++++++++---- .../src/applications/services/gui/gui.h | 87 ++++++- .../src/applications/services/gui/gui_draw.c | 206 +++++++++++++++ .../src/applications/services/gui/gui_i.h | 106 +++++++- .../src/applications/services/gui/gui_input.c | 81 ++++++ .../src/applications/services/gui/view_port.c | 96 +++++++ .../src/applications/services/gui/view_port.h | 67 +++++ .../applications/services/gui/view_port_i.h | 44 ++++ .../services/gui/view_port_input.c | 91 +++++++ .../services/gui/view_port_input.h | 12 + components/nanobake/src/nanobake.c | 3 +- components/nanobake/src/nb_hardware.c | 2 +- components/nanobake/src/nb_lvgl.c | 2 +- main/src/hello_world/hello_world.c | 31 ++- sdkconfig.defaults | 2 +- 17 files changed, 993 insertions(+), 87 deletions(-) create mode 100644 components/nanobake/src/applications/services/gui/gui_draw.c create mode 100644 components/nanobake/src/applications/services/gui/gui_input.c create mode 100644 components/nanobake/src/applications/services/gui/view_port.c create mode 100644 components/nanobake/src/applications/services/gui/view_port.h create mode 100644 components/nanobake/src/applications/services/gui/view_port_i.h create mode 100644 components/nanobake/src/applications/services/gui/view_port_input.c create mode 100644 components/nanobake/src/applications/services/gui/view_port_input.h diff --git a/components/board_2432s024/board_2432s024_display.c b/components/board_2432s024/board_2432s024_display.c index 813ba02f..f517769f 100644 --- a/components/board_2432s024/board_2432s024_display.c +++ b/components/board_2432s024/board_2432s024_display.c @@ -9,7 +9,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" -static const char* TAG = "2432s024_ili9341"; +#define TAG "2432s024_ili9341" static SemaphoreHandle_t refresh_finish = NULL; diff --git a/components/board_2432s024/board_2432s024_touch.c b/components/board_2432s024/board_2432s024_touch.c index b9f32e3b..8ff0d3ba 100644 --- a/components/board_2432s024/board_2432s024_touch.c +++ b/components/board_2432s024/board_2432s024_touch.c @@ -7,7 +7,7 @@ #define CST816_I2C_PORT (0) -const char* TAG = "2432s024_cst816"; +#define TAG "2432s024_cst816" static bool prv_create_touch(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_touch_handle_t* touch_handle) { ESP_LOGI(TAG, "creating touch"); diff --git a/components/nanobake/src/applications/services/gui/gui.c b/components/nanobake/src/applications/services/gui/gui.c index 96838735..523702a7 100644 --- a/components/nanobake/src/applications/services/gui/gui.c +++ b/components/nanobake/src/applications/services/gui/gui.c @@ -3,83 +3,231 @@ #include "record.h" #include "check.h" -static NbScreenId screen_counter = 0; +#define TAG "Gui" -NbGuiHandle gui_alloc() { - struct NbGui* gui = malloc(sizeof(struct NbGui)); - ScreenDict_init(gui->screens); - gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - return gui; +// Forward declarations from gui_draw.c +bool gui_redraw_fs(NbGui* gui); +void gui_redraw(NbGui* gui); + +ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { + // Iterating backward + ViewPortArray_it_t it; + ViewPortArray_it_last(it, array); + while(!ViewPortArray_end_p(it)) { + ViewPort* view_port = *ViewPortArray_ref(it); + if(view_port_is_enabled(view_port)) { + return view_port; + } + ViewPortArray_previous(it); + } + return NULL; } -void gui_free(NbGuiHandle gui) { - ScreenDict_clear(gui->screens); - furi_mutex_free(gui->mutex); - free(gui); +size_t gui_active_view_port_count(NbGui* gui, GuiLayer layer) { + furi_assert(gui); + furi_check(layer < GuiLayerMAX); + size_t ret = 0; + + gui_lock(gui); + ViewPortArray_it_t it; + ViewPortArray_it_last(it, gui->layers[layer]); + while(!ViewPortArray_end_p(it)) { + ViewPort* view_port = *ViewPortArray_ref(it); + if(view_port_is_enabled(view_port)) { + ret++; + } + ViewPortArray_previous(it); + } + gui_unlock(gui); + + return ret; } -void gui_lock(NbGuiHandle gui) { +void gui_update(NbGui* gui) { + ESP_LOGI(TAG, "gui_update"); + furi_assert(gui); + furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_DRAW); +} + +void gui_lock(NbGui* gui) { furi_assert(gui); furi_check(furi_mutex_acquire(gui->mutex, FuriWaitForever) == FuriStatusOk); } -void gui_unlock(NbGuiHandle gui) { +void gui_unlock(NbGui* gui) { furi_assert(gui); furi_check(furi_mutex_release(gui->mutex) == FuriStatusOk); } -NbScreenId gui_screen_create(NbGuiHandle gui, InitScreen callback) { - NbScreenId id = screen_counter++; - NbScreen screen = { - .id = id, - .parent = NULL, - .callback = callback - }; +void gui_add_view_port(NbGui* gui, ViewPort* view_port, GuiLayer layer) { + furi_assert(gui); + furi_assert(view_port); + furi_check(layer < GuiLayerMAX); - ScreenDict_set_at(gui->screens, id, screen); + gui_lock(gui); + // Verify that view port is not yet added + ViewPortArray_it_t it; + for(size_t i = 0; i < GuiLayerMAX; i++) { + ViewPortArray_it(it, gui->layers[i]); + while(!ViewPortArray_end_p(it)) { + furi_assert(*ViewPortArray_ref(it) != view_port); + ViewPortArray_next(it); + } + } + // Add view port and link with gui + ViewPortArray_push_back(gui->layers[layer], view_port); + view_port_gui_set(view_port, gui); + gui_unlock(gui); - // TODO: notify desktop of change - // TODO: have desktop update views - lv_obj_t* parent = lv_scr_act(); - gui_screen_set_parent(gui, id, parent); - - // TODO: call from desktop - screen.callback(gui_screen_get_parent(gui, id), id); - - return id; + // Request redraw + gui_update(gui); } -lv_obj_t* gui_screen_get_parent(NbGuiHandle gui, NbScreenId id) { - NbScreen* screen = ScreenDict_get(gui->screens, id); - furi_check(screen != NULL); - return screen->parent; +void gui_remove_view_port(NbGui* gui, ViewPort* view_port) { + furi_assert(gui); + furi_assert(view_port); + + gui_lock(gui); + view_port_gui_set(view_port, NULL); + ViewPortArray_it_t it; + for(size_t i = 0; i < GuiLayerMAX; i++) { + ViewPortArray_it(it, gui->layers[i]); + while(!ViewPortArray_end_p(it)) { + if(*ViewPortArray_ref(it) == view_port) { + ViewPortArray_remove(gui->layers[i], it); + } else { + ViewPortArray_next(it); + } + } + } +// if(gui->ongoing_input_view_port == view_port) { +// gui->ongoing_input_view_port = NULL; +// } + gui_unlock(gui); + + // Request redraw + gui_update(gui); } -void gui_screen_set_parent(NbGuiHandle gui, NbScreenId id, lv_obj_t* parent) { - NbScreen* screen = ScreenDict_get(gui->screens, id); - furi_check(screen != NULL); - screen->parent = parent; +void gui_view_port_send_to_front(NbGui* gui, ViewPort* view_port) { + furi_assert(gui); + furi_assert(view_port); + + gui_lock(gui); + // Remove + GuiLayer layer = GuiLayerMAX; + ViewPortArray_it_t it; + for(size_t i = 0; i < GuiLayerMAX; i++) { + ViewPortArray_it(it, gui->layers[i]); + while(!ViewPortArray_end_p(it)) { + if(*ViewPortArray_ref(it) == view_port) { + ViewPortArray_remove(gui->layers[i], it); + furi_assert(layer == GuiLayerMAX); + layer = i; + } else { + ViewPortArray_next(it); + } + } + } + furi_assert(layer != GuiLayerMAX); + // Return to the top + ViewPortArray_push_back(gui->layers[layer], view_port); + gui_unlock(gui); + + // Request redraw + gui_update(gui); } -void gui_screen_free(NbGuiHandle gui, NbScreenId id) { - NbScreen* screen = ScreenDict_get(gui->screens, id); - furi_check(screen != NULL); +void gui_view_port_send_to_back(NbGui* gui, ViewPort* view_port) { + furi_assert(gui); + furi_assert(view_port); - // TODO: notify? use callback? (done from desktop service) - lv_obj_clean(screen->parent); + gui_lock(gui); + // Remove + GuiLayer layer = GuiLayerMAX; + ViewPortArray_it_t it; + for(size_t i = 0; i < GuiLayerMAX; i++) { + ViewPortArray_it(it, gui->layers[i]); + while(!ViewPortArray_end_p(it)) { + if(*ViewPortArray_ref(it) == view_port) { + ViewPortArray_remove(gui->layers[i], it); + furi_assert(layer == GuiLayerMAX); + layer = i; + } else { + ViewPortArray_next(it); + } + } + } + furi_assert(layer != GuiLayerMAX); + // Return to the top + ViewPortArray_push_at(gui->layers[layer], 0, view_port); + gui_unlock(gui); - ScreenDict_erase(gui->screens, id); + // Request redraw + gui_update(gui); } -static int32_t prv_gui_main(void* param) { - UNUSED(param); +void gui_set_lockdown(NbGui* gui, bool lockdown) { + furi_assert(gui); + + gui_lock(gui); + gui->lockdown = lockdown; + gui_unlock(gui); + + // Request redraw + gui_update(gui); +} + +NbGui* gui_alloc() { + NbGui* gui = malloc(sizeof(NbGui)); + gui->thread_id = furi_thread_get_current_id(); + gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + gui->lvgl_parent = lv_scr_act(); + gui->lockdown = false; + furi_check(gui->mutex); + for(size_t i = 0; i < GuiLayerMAX; i++) { + ViewPortArray_init(gui->layers[i]); + } + +/* + // Input + gui->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + gui->input_events = furi_record_open(RECORD_INPUT_EVENTS); + + furi_check(gui->input_events); + furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui); +*/ + return gui; +} + +__attribute((__noreturn__)) int32_t prv_gui_main(void* parameter) { + UNUSED(parameter); + NbGui* gui = gui_alloc(); - struct NbGui* gui = gui_alloc(); furi_record_create(RECORD_GUI, gui); - printf("gui app init\n"); - return 0; -} + while (1) { + uint32_t flags = furi_thread_flags_wait( + GUI_THREAD_FLAG_ALL, + FuriFlagWaitAny, + FuriWaitForever + ); + // Process and dispatch input + if (flags & GUI_THREAD_FLAG_INPUT) { +// // Process till queue become empty +// InputEvent input_event; +// while(furi_message_queue_get(gui->input_queue, &input_event, 0) == FuriStatusOk) { +// gui_input(gui, &input_event); +// } + } + // Process and dispatch draw call + if (flags & GUI_THREAD_FLAG_DRAW) { + // Clear flags that arrived on input step + furi_thread_flags_clear(GUI_THREAD_FLAG_DRAW); + gui_redraw(gui); + } + } +} const NbApp gui_app = { .id = "gui", .name = "GUI", diff --git a/components/nanobake/src/applications/services/gui/gui.h b/components/nanobake/src/applications/services/gui/gui.h index 109a0bb7..7c668040 100644 --- a/components/nanobake/src/applications/services/gui/gui.h +++ b/components/nanobake/src/applications/services/gui/gui.h @@ -1,25 +1,94 @@ #pragma once +#include "view_port.h" +#include "lvgl.h" #include "nb_app.h" #ifdef __cplusplus extern "C" { #endif +extern const NbApp gui_app; + +/** Canvas Orientation */ +typedef enum { + CanvasOrientationHorizontal, + CanvasOrientationHorizontalFlip, + CanvasOrientationVertical, + CanvasOrientationVerticalFlip, +} CanvasOrientation; + +/** Gui layers */ +typedef enum { + GuiLayerDesktop, /**< Desktop layer for internal use. Like fullscreen but with status bar */ + + GuiLayerWindow, /**< Window layer, status bar is shown */ + + GuiLayerStatusBarLeft, /**< Status bar left-side layer, auto-layout */ + GuiLayerStatusBarRight, /**< Status bar right-side layer, auto-layout */ + + GuiLayerFullscreen, /**< Fullscreen layer, no status bar */ + + GuiLayerMAX /**< Don't use or move, special value */ +} GuiLayer; + +/** Gui Canvas Commit Callback */ +typedef void (*GuiCanvasCommitCallback)( + uint8_t* data, + size_t size, + CanvasOrientation orientation, + void* context); + #define RECORD_GUI "gui" -typedef uint16_t NbScreenId; +typedef struct NbGui NbGui; -typedef struct NbGui* NbGuiHandle; -typedef void (*InitScreen)(lv_obj_t*, NbScreenId); +/** Add view_port to view_port tree + * + * @remark thread safe + * + * @param gui Gui instance + * @param view_port ViewPort instance + * @param[in] layer GuiLayer where to place view_port + */ +void gui_add_view_port(NbGui* gui, ViewPort* view_port, GuiLayer layer); -NbScreenId gui_screen_create(NbGuiHandle _Nonnull gui, InitScreen callback); -void gui_screen_free(NbGuiHandle _Nonnull gui, NbScreenId id); -// TODO make internal -void gui_screen_set_parent(NbGuiHandle _Nonnull gui, NbScreenId id, lv_obj_t* parent); -lv_obj_t* gui_screen_get_parent(NbGuiHandle _Nonnull gui, NbScreenId id); +/** Remove view_port from rendering tree + * + * @remark thread safe + * + * @param gui Gui instance + * @param view_port ViewPort instance + */ +void gui_remove_view_port(NbGui* gui, ViewPort* view_port); -extern const NbApp gui_app; +/** Send ViewPort to the front + * + * Places selected ViewPort to the top of the drawing stack + * + * @param gui Gui instance + * @param view_port ViewPort instance + */ +void gui_view_port_send_to_front(NbGui* gui, ViewPort* view_port); + +/** Send ViewPort to the back + * + * Places selected ViewPort to the bottom of the drawing stack + * + * @param gui Gui instance + * @param view_port ViewPort instance + */ +void gui_view_port_send_to_back(NbGui* gui, ViewPort* view_port); + +/** Set lockdown mode + * + * When lockdown mode is enabled, only GuiLayerDesktop is shown. + * This feature prevents services from showing sensitive information when flipper is locked. + * + * @param gui Gui instance + * @param lockdown bool, true if enabled + */ +void gui_set_lockdown(NbGui* gui, bool lockdown); #ifdef __cplusplus } diff --git a/components/nanobake/src/applications/services/gui/gui_draw.c b/components/nanobake/src/applications/services/gui/gui_draw.c new file mode 100644 index 00000000..d43f2018 --- /dev/null +++ b/components/nanobake/src/applications/services/gui/gui_draw.c @@ -0,0 +1,206 @@ +#include "gui_i.h" +#include "check.h" + +static void gui_redraw_status_bar(NbGui* gui, bool need_attention) { +// ViewPortArray_it_t it; +// uint8_t left_used = 0; +// uint8_t right_used = 0; +// uint8_t width; +// +// canvas_frame_set( +// gui->lvgl_parent, GUI_STATUS_BAR_X, GUI_STATUS_BAR_Y, GUI_DISPLAY_WIDTH, GUI_STATUS_BAR_HEIGHT); +// +// /* for support black theme - paint white area and +// * draw icon with transparent white color +// */ +// canvas_set_color(gui->canvas, ColorWhite); +// canvas_draw_box(gui->canvas, 1, 1, 9, 7); +// canvas_draw_box(gui->canvas, 7, 3, 58, 6); +// canvas_draw_box(gui->canvas, 61, 1, 32, 7); +// canvas_draw_box(gui->canvas, 89, 3, 38, 6); +// canvas_set_color(gui->canvas, ColorBlack); +// canvas_set_bitmap_mode(gui->canvas, 1); +// canvas_draw_icon(gui->canvas, 0, 0, &I_Background_128x11); +// canvas_set_bitmap_mode(gui->canvas, 0); +// +// // Right side +// uint8_t x = GUI_DISPLAY_WIDTH - 1; +// ViewPortArray_it(it, gui->layers[GuiLayerStatusBarRight]); +// while(!ViewPortArray_end_p(it) && right_used < GUI_STATUS_BAR_WIDTH) { +// ViewPort* view_port = *ViewPortArray_ref(it); +// if(view_port_is_enabled(view_port)) { +// width = view_port_get_width(view_port); +// if(!width) width = 8; +// // Recalculate next position +// right_used += (width + 2); +// x -= (width + 2); +// // Prepare work area background +// canvas_frame_set( +// gui->canvas, +// x - 1, +// GUI_STATUS_BAR_Y + 1, +// width + 2, +// GUI_STATUS_BAR_WORKAREA_HEIGHT + 2); +// canvas_set_color(gui->canvas, ColorWhite); +// canvas_draw_box( +// gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas)); +// canvas_set_color(gui->canvas, ColorBlack); +// // ViewPort draw +// canvas_frame_set( +// gui->canvas, x, GUI_STATUS_BAR_Y + 2, width, GUI_STATUS_BAR_WORKAREA_HEIGHT); +// view_port_draw(view_port, gui->canvas); +// } +// ViewPortArray_next(it); +// } +// // Draw frame around icons on the right +// if(right_used) { +// canvas_frame_set( +// gui->canvas, +// GUI_DISPLAY_WIDTH - 3 - right_used, +// GUI_STATUS_BAR_Y, +// right_used + 3, +// GUI_STATUS_BAR_HEIGHT); +// canvas_set_color(gui->canvas, ColorBlack); +// canvas_draw_rframe( +// gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas), 1); +// canvas_draw_line( +// gui->canvas, +// canvas_width(gui->canvas) - 2, +// 1, +// canvas_width(gui->canvas) - 2, +// canvas_height(gui->canvas) - 2); +// canvas_draw_line( +// gui->canvas, +// 1, +// canvas_height(gui->canvas) - 2, +// canvas_width(gui->canvas) - 2, +// canvas_height(gui->canvas) - 2); +// } +// +// // Left side +// x = 2; +// ViewPortArray_it(it, gui->layers[GuiLayerStatusBarLeft]); +// while(!ViewPortArray_end_p(it) && (right_used + left_used) < GUI_STATUS_BAR_WIDTH) { +// ViewPort* view_port = *ViewPortArray_ref(it); +// if(view_port_is_enabled(view_port)) { +// width = view_port_get_width(view_port); +// if(!width) width = 8; +// // Prepare work area background +// canvas_frame_set( +// gui->canvas, +// x - 1, +// GUI_STATUS_BAR_Y + 1, +// width + 2, +// GUI_STATUS_BAR_WORKAREA_HEIGHT + 2); +// canvas_set_color(gui->canvas, ColorWhite); +// canvas_draw_box( +// gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas)); +// canvas_set_color(gui->canvas, ColorBlack); +// // ViewPort draw +// canvas_frame_set( +// gui->canvas, x, GUI_STATUS_BAR_Y + 2, width, GUI_STATUS_BAR_WORKAREA_HEIGHT); +// view_port_draw(view_port, gui->canvas); +// // Recalculate next position +// left_used += (width + 2); +// x += (width + 2); +// } +// ViewPortArray_next(it); +// } +// // Extra notification +// if(need_attention) { +// width = icon_get_width(&I_Hidden_window_9x8); +// // Prepare work area background +// canvas_frame_set( +// gui->canvas, +// x - 1, +// GUI_STATUS_BAR_Y + 1, +// width + 2, +// GUI_STATUS_BAR_WORKAREA_HEIGHT + 2); +// canvas_set_color(gui->canvas, ColorWhite); +// canvas_draw_box(gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas)); +// canvas_set_color(gui->canvas, ColorBlack); +// // Draw Icon +// canvas_frame_set( +// gui->canvas, x, GUI_STATUS_BAR_Y + 2, width, GUI_STATUS_BAR_WORKAREA_HEIGHT); +// canvas_draw_icon(gui->canvas, 0, 0, &I_Hidden_window_9x8); +// // Recalculate next position +// left_used += (width + 2); +// x += (width + 2); +// } +// // Draw frame around icons on the left +// if(left_used) { +// canvas_frame_set(gui->canvas, 0, 0, left_used + 3, GUI_STATUS_BAR_HEIGHT); +// canvas_draw_rframe( +// gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas), 1); +// canvas_draw_line( +// gui->canvas, +// canvas_width(gui->canvas) - 2, +// 1, +// canvas_width(gui->canvas) - 2, +// canvas_height(gui->canvas) - 2); +// canvas_draw_line( +// gui->canvas, +// 1, +// canvas_height(gui->canvas) - 2, +// canvas_width(gui->canvas) - 2, +// canvas_height(gui->canvas) - 2); +// } +} + +static bool gui_redraw_window(NbGui* gui) { +// canvas_frame_set(gui->lvgl_parent, GUI_WINDOW_X, GUI_WINDOW_Y, GUI_WINDOW_WIDTH, GUI_WINDOW_HEIGHT); +// ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]); +// if(view_port) { +// view_port_draw(view_port, gui->lvgl_parent); +// return true; +// } + return false; +} + +static bool gui_redraw_desktop(NbGui* gui) { +// canvas_frame_set(gui->lvgl_parent, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT); +// ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); +// if(view_port) { +// view_port_draw(view_port, gui->lvgl_parent); +// return true; +// } + + return false; +} + +bool gui_redraw_fs(NbGui* gui) { + ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); + if (view_port) { + view_port_draw(view_port, gui->lvgl_parent); + return true; + } else { + return false; + } +} + +void gui_redraw(NbGui* gui) { + furi_assert(gui); + gui_lock(gui); + + lv_obj_clean(gui->lvgl_parent); + + if(gui->lockdown) { + ESP_LOGI("gui", "gui_redraw with lockdown"); + gui_redraw_desktop(gui); + bool need_attention = + (gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 || + gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]) != 0); + gui_redraw_status_bar(gui, need_attention); + } else { + gui_redraw_desktop(gui); + ESP_LOGI("gui", "gui_redraw"); + if (!gui_redraw_fs(gui)) { + if (!gui_redraw_window(gui)) { + gui_redraw_desktop(gui); + } + gui_redraw_status_bar(gui, false); + } + } + + gui_unlock(gui); +} diff --git a/components/nanobake/src/applications/services/gui/gui_i.h b/components/nanobake/src/applications/services/gui/gui_i.h index 59cc83bc..5a3718d2 100644 --- a/components/nanobake/src/applications/services/gui/gui_i.h +++ b/components/nanobake/src/applications/services/gui/gui_i.h @@ -1,20 +1,106 @@ #pragma once #include "gui.h" + +#include +#include +#include + +#include "view_port.h" +#include "view_port_i.h" +#include "message_queue.h" +#include "pubsub.h" #include "mutex.h" -#include "m-dict.h" -#include "m-core.h" + +#define GUI_DISPLAY_WIDTH 128 +#define GUI_DISPLAY_HEIGHT 64 + +#define GUI_STATUS_BAR_X 0 +#define GUI_STATUS_BAR_Y 0 +#define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH +/* 0-1 pixels for upper thin frame + * 2-9 pixels for icons (battery, sd card, etc) + * 10-12 pixels for lower bold line */ +#define GUI_STATUS_BAR_HEIGHT 13 +/* icon itself area (battery, sd card, etc) excluding frame. + * painted 2 pixels below GUI_STATUS_BAR_X. + */ +#define GUI_STATUS_BAR_WORKAREA_HEIGHT 8 + +#define GUI_WINDOW_X 0 +#define GUI_WINDOW_Y GUI_STATUS_BAR_HEIGHT +#define GUI_WINDOW_WIDTH GUI_DISPLAY_WIDTH +#define GUI_WINDOW_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_WINDOW_Y) + +#define GUI_THREAD_FLAG_DRAW (1 << 0) +#define GUI_THREAD_FLAG_INPUT (1 << 1) +#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT) + +ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); typedef struct { - NbScreenId id; - lv_obj_t* parent; - InitScreen _Nonnull callback; -} NbScreen; - -DICT_DEF2(ScreenDict, NbScreenId, M_BASIC_OPLIST, NbScreen, M_POD_OPLIST) + GuiCanvasCommitCallback callback; + void* context; +} CanvasCallbackPair; +/** Gui structure */ struct NbGui { - // TODO: use mutex + // Thread and lock + FuriThreadId thread_id; FuriMutex* mutex; - ScreenDict_t screens; + + // Layers and Canvas + bool lockdown; + ViewPortArray_t layers[GuiLayerMAX]; + lv_obj_t* lvgl_parent; + + // Input +/* + FuriMessageQueue* input_queue; + FuriPubSub* input_events; + uint8_t ongoing_input; + ViewPort* ongoing_input_view_port; +*/ }; + +/** Find enabled ViewPort in ViewPortArray + * + * @param[in] array The ViewPortArray instance + * + * @return ViewPort instance or NULL + */ +ViewPort* gui_view_port_find_enabled(ViewPortArray_t array); + +/** Update GUI, request redraw + * + * @param gui Gui instance + */ +void gui_update(NbGui* gui); + +///** Input event callback +// * +// * Used to receive input from input service or to inject new input events +// * +// * @param[in] value The value pointer (InputEvent*) +// * @param ctx The context (Gui instance) +// */ +//void gui_input_events_callback(const void* value, void* ctx); + +/** Get count of view ports in layer + * + * @param gui The Gui instance + * @param[in] layer GuiLayer that we want to get count of view ports + */ +size_t gui_active_view_port_count(NbGui* gui, GuiLayer layer); + +/** Lock GUI + * + * @param gui The Gui instance + */ +void gui_lock(NbGui* gui); + +/** Unlock GUI + * + * @param gui The Gui instance + */ +void gui_unlock(NbGui* gui); diff --git a/components/nanobake/src/applications/services/gui/gui_input.c b/components/nanobake/src/applications/services/gui/gui_input.c new file mode 100644 index 00000000..74707ceb --- /dev/null +++ b/components/nanobake/src/applications/services/gui/gui_input.c @@ -0,0 +1,81 @@ +#include "gui_i.h" + +/* +void gui_input_events_callback(const void* value, void* ctx) { + furi_assert(value); + furi_assert(ctx); + + Gui* gui = ctx; + + furi_message_queue_put(gui->input_queue, value, FuriWaitForever); + furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_INPUT); +} + +static void gui_input(Gui* gui, InputEvent* input_event) { + furi_assert(gui); + furi_assert(input_event); + + // Check input complementarity + uint8_t key_bit = (1 << input_event->key); + if(input_event->type == InputTypeRelease) { + gui->ongoing_input &= ~key_bit; + } else if(input_event->type == InputTypePress) { + gui->ongoing_input |= key_bit; + } else if(!(gui->ongoing_input & key_bit)) { + FURI_LOG_D( + TAG, + "non-complementary input, discarding key: %s type: %s, sequence: %p", + input_get_key_name(input_event->key), + input_get_type_name(input_event->type), + (void*)input_event->sequence); + return; + } + + gui_lock(gui); + + do { + if(gui->direct_draw && !gui->ongoing_input_view_port) { + break; + } + + ViewPort* view_port = NULL; + + if(gui->lockdown) { + view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); + } else { + view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); + if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]); + if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); + } + + if(!(gui->ongoing_input & ~key_bit) && input_event->type == InputTypePress) { + gui->ongoing_input_view_port = view_port; + } + + if(view_port && view_port == gui->ongoing_input_view_port) { + view_port_input(view_port, input_event); + } else if(gui->ongoing_input_view_port && input_event->type == InputTypeRelease) { + FURI_LOG_D( + TAG, + "ViewPort changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", + gui->ongoing_input_view_port, + view_port, + input_get_key_name(input_event->key), + input_get_type_name(input_event->type), + (void*)input_event->sequence); + view_port_input(gui->ongoing_input_view_port, input_event); + } else { + FURI_LOG_D( + TAG, + "ViewPort changed while key press %p -> %p. Discarding key: %s, type: %s, sequence: %p", + gui->ongoing_input_view_port, + view_port, + input_get_key_name(input_event->key), + input_get_type_name(input_event->type), + (void*)input_event->sequence); + } + } while(false); + + gui_unlock(gui); +} +*/ diff --git a/components/nanobake/src/applications/services/gui/view_port.c b/components/nanobake/src/applications/services/gui/view_port.c new file mode 100644 index 00000000..3ed71e95 --- /dev/null +++ b/components/nanobake/src/applications/services/gui/view_port.c @@ -0,0 +1,96 @@ +#include "view_port_i.h" + +#include "gui.h" +#include "gui_i.h" +#include "check.h" + +#define TAG "viewport" + +_Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count"); +_Static_assert( + (ViewPortOrientationHorizontal == 0 && ViewPortOrientationHorizontalFlip == 1 && + ViewPortOrientationVertical == 2 && ViewPortOrientationVerticalFlip == 3), + "Incorrect ViewPortOrientation order"); + +ViewPort* view_port_alloc() { + ViewPort* view_port = malloc(sizeof(ViewPort)); + view_port->gui = NULL; + view_port->is_enabled = true; + view_port->mutex = furi_mutex_alloc(FuriMutexTypeRecursive); + return view_port; +} + +void view_port_free(ViewPort* view_port) { + furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + furi_check(view_port->gui == NULL); + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + furi_mutex_free(view_port->mutex); + free(view_port); +} + +void view_port_enabled_set(ViewPort* view_port, bool enabled) { + furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + if(view_port->is_enabled != enabled) { + view_port->is_enabled = enabled; + if(view_port->gui) gui_update(view_port->gui); + } + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); +} + +bool view_port_is_enabled(const ViewPort* view_port) { + furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + bool is_enabled = view_port->is_enabled; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + return is_enabled; +} + +void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context) { + furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + view_port->draw_callback = callback; + view_port->draw_callback_context = context; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); +} + +void view_port_update(ViewPort* view_port) { + furi_assert(view_port); + + // We are not going to lockup system, but will notify you instead + // Make sure that you don't call viewport methods inside another mutex, especially one that is used in draw call + if(furi_mutex_acquire(view_port->mutex, 2) != FuriStatusOk) { + ESP_LOGW(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3); + } + + if(view_port->gui && view_port->is_enabled) gui_update(view_port->gui); + furi_mutex_release(view_port->mutex); +} + +void view_port_gui_set(ViewPort* view_port, NbGui* gui) { + furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + view_port->gui = gui; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); +} + +void view_port_draw(ViewPort* view_port, lv_obj_t* parent) { + furi_assert(view_port); + furi_assert(parent); + + // We are not going to lockup system, but will notify you instead + // Make sure that you don't call viewport methods inside another mutex, especially one that is used in draw call + if(furi_mutex_acquire(view_port->mutex, 2) != FuriStatusOk) { + ESP_LOGW(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3); + } + + furi_check(view_port->gui); + + if (view_port->draw_callback) { + lv_obj_clean(parent); + view_port->draw_callback(parent, view_port->draw_callback_context); + } + + furi_mutex_release(view_port->mutex); +} diff --git a/components/nanobake/src/applications/services/gui/view_port.h b/components/nanobake/src/applications/services/gui/view_port.h new file mode 100644 index 00000000..97132055 --- /dev/null +++ b/components/nanobake/src/applications/services/gui/view_port.h @@ -0,0 +1,67 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lvgl.h" + +typedef struct ViewPort ViewPort; + +typedef enum { + ViewPortOrientationHorizontal, + ViewPortOrientationHorizontalFlip, + ViewPortOrientationVertical, + ViewPortOrientationVerticalFlip, + ViewPortOrientationMAX, /**< Special value, don't use it */ +} ViewPortOrientation; + +/** ViewPort Draw callback + * @warning called from GUI thread + */ +typedef void (*ViewPortDrawCallback)(lv_obj_t* parent, void* context); + +/** ViewPort allocator + * + * always returns view_port or stops system if not enough memory. + * + * @return ViewPort instance + */ +ViewPort* view_port_alloc(); + +/** 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); + +/** Enable or disable view_port rendering. + * + * @param view_port ViewPort instance + * @param enabled Indicates if enabled + * @warning automatically dispatches update event + */ +void view_port_enabled_set(ViewPort* view_port, bool enabled); +bool view_port_is_enabled(const ViewPort* view_port); + +/** ViewPort event callbacks + * + * @param view_port ViewPort instance + * @param callback appropriate callback function + * @param context context to pass to callback + */ +void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context); +/** Emit update signal to GUI system. + * + * Rendering will happen later after GUI system process signal. + * + * @param view_port ViewPort instance + */ +void view_port_update(ViewPort* view_port); + +#ifdef __cplusplus +} +#endif + diff --git a/components/nanobake/src/applications/services/gui/view_port_i.h b/components/nanobake/src/applications/services/gui/view_port_i.h new file mode 100644 index 00000000..20bded7e --- /dev/null +++ b/components/nanobake/src/applications/services/gui/view_port_i.h @@ -0,0 +1,44 @@ +#pragma once + +#include "gui_i.h" +#include "view_port.h" +#include "mutex.h" + +struct ViewPort { + NbGui* gui; + FuriMutex* mutex; + bool is_enabled; + + ViewPortDrawCallback draw_callback; + void* draw_callback_context; + +// ViewPortInputCallback input_callback; +// void* input_callback_context; +}; + +/** Set GUI reference. + * + * To be used by GUI, called upon view_port tree insert + * + * @param view_port ViewPort instance + * @param gui gui instance pointer + */ +void view_port_gui_set(ViewPort* view_port, NbGui* gui); + +/** Process draw call. Calls draw callback. + * + * To be used by GUI, called on tree redraw. + * + * @param view_port ViewPort instance + * @param canvas canvas to draw at + */ +void view_port_draw(ViewPort* view_port, lv_obj_t* parent); + +/** Process input. Calls input callback. +// * +// * To be used by GUI, called on input dispatch. +// * +// * @param view_port ViewPort instance +// * @param event pointer to input event +// */ +//void view_port_input(ViewPort* view_port, InputEvent* event); diff --git a/components/nanobake/src/applications/services/gui/view_port_input.c b/components/nanobake/src/applications/services/gui/view_port_input.c new file mode 100644 index 00000000..144177ac --- /dev/null +++ b/components/nanobake/src/applications/services/gui/view_port_input.c @@ -0,0 +1,91 @@ +#include "view_port_input.h" +/* +_Static_assert(InputKeyMAX == 6, "Incorrect InputKey count"); +_Static_assert( + (InputKeyUp == 0 && InputKeyDown == 1 && InputKeyRight == 2 && InputKeyLeft == 3 && + InputKeyOk == 4 && InputKeyBack == 5), + "Incorrect InputKey order"); +*/ +/** InputKey directional keys mappings for different screen orientations + * + */ +/* +static const InputKey view_port_input_mapping[ViewPortOrientationMAX][InputKeyMAX] = { + {InputKeyUp, + InputKeyDown, + InputKeyRight, + InputKeyLeft, + InputKeyOk, + InputKeyBack}, //ViewPortOrientationHorizontal + {InputKeyDown, + InputKeyUp, + InputKeyLeft, + InputKeyRight, + InputKeyOk, + InputKeyBack}, //ViewPortOrientationHorizontalFlip + {InputKeyRight, + InputKeyLeft, + InputKeyDown, + InputKeyUp, + InputKeyOk, + InputKeyBack}, //ViewPortOrientationVertical + {InputKeyLeft, + InputKeyRight, + InputKeyUp, + InputKeyDown, + InputKeyOk, + InputKeyBack}, //ViewPortOrientationVerticalFlip +}; + +static const InputKey view_port_left_hand_input_mapping[InputKeyMAX] = + {InputKeyDown, InputKeyUp, InputKeyLeft, InputKeyRight, InputKeyOk, InputKeyBack}; + +static const CanvasOrientation view_port_orientation_mapping[ViewPortOrientationMAX] = { + [ViewPortOrientationHorizontal] = CanvasOrientationHorizontal, + [ViewPortOrientationHorizontalFlip] = CanvasOrientationHorizontalFlip, + [ViewPortOrientationVertical] = CanvasOrientationVertical, + [ViewPortOrientationVerticalFlip] = CanvasOrientationVerticalFlip, +}; + +//// Remaps directional pad buttons on Flipper based on ViewPort orientation +static void view_port_map_input(InputEvent* event, ViewPortOrientation orientation) { + furi_assert(orientation < ViewPortOrientationMAX && event->key < InputKeyMAX); + + if(event->sequence_source != INPUT_SEQUENCE_SOURCE_HARDWARE) { + return; + } + + if(orientation == ViewPortOrientationHorizontal || + orientation == ViewPortOrientationHorizontalFlip) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) { + event->key = view_port_left_hand_input_mapping[event->key]; + } + } + event->key = view_port_input_mapping[orientation][event->key]; +} + +void view_port_input_callback_set( + ViewPort* view_port, + ViewPortInputCallback callback, + void* context) { + furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + view_port->input_callback = callback; + view_port->input_callback_context = context; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); +} + +void view_port_input(ViewPort* view_port, InputEvent* event) { + furi_assert(view_port); + furi_assert(event); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + furi_check(view_port->gui); + + if(view_port->input_callback) { + ViewPortOrientation orientation = view_port_get_orientation(view_port); + view_port_map_input(event, orientation); + view_port->input_callback(event, view_port->input_callback_context); + } + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); +} +*/ diff --git a/components/nanobake/src/applications/services/gui/view_port_input.h b/components/nanobake/src/applications/services/gui/view_port_input.h new file mode 100644 index 00000000..67ba7d86 --- /dev/null +++ b/components/nanobake/src/applications/services/gui/view_port_input.h @@ -0,0 +1,12 @@ +#pragma once + +/** ViewPort Input callback + * @warning called from GUI thread + */ +//typedef void (*ViewPortInputCallback)(InputEvent* event, void* context); + +//void view_port_input_callback_set( +// ViewPort* view_port, +// ViewPortInputCallback callback, +// void* context); +// diff --git a/components/nanobake/src/nanobake.c b/components/nanobake/src/nanobake.c index 1e22ddfd..a76505b2 100644 --- a/components/nanobake/src/nanobake.c +++ b/components/nanobake/src/nanobake.c @@ -12,7 +12,8 @@ M_LIST_DEF(thread_ids, FuriThreadId); -static const char* TAG = "nanobake"; +#define TAG "nanobake" + thread_ids_t prv_thread_ids; static void prv_furi_init() { diff --git a/components/nanobake/src/nb_hardware.c b/components/nanobake/src/nb_hardware.c index 9f55e22d..7a3c7b1f 100644 --- a/components/nanobake/src/nb_hardware.c +++ b/components/nanobake/src/nb_hardware.c @@ -3,7 +3,7 @@ #include "esp_err.h" #include "check.h" -static const char* TAG = "nb_hardware"; +#define TAG "nb_hardware" NbHardware nb_hardware_create(NbConfig _Nonnull* config) { diff --git a/components/nanobake/src/nb_lvgl.c b/components/nanobake/src/nb_lvgl.c index 4b245e1d..053ac832 100644 --- a/components/nanobake/src/nb_lvgl.c +++ b/components/nanobake/src/nb_lvgl.c @@ -2,7 +2,7 @@ #include "esp_lvgl_port.h" #include "check.h" -static const char* TAG = "nb_lvgl"; +#define TAG "nb_lvgl" NbLvgl nb_lvgl_init(NbHardware _Nonnull* hardware) { const lvgl_port_cfg_t lvgl_cfg = { diff --git a/main/src/hello_world/hello_world.c b/main/src/hello_world/hello_world.c index 8ff82b71..b301a875 100644 --- a/main/src/hello_world/hello_world.c +++ b/main/src/hello_world/hello_world.c @@ -9,21 +9,24 @@ static const char* TAG = "app_helloworld"; +ViewPort* view_port = NULL; + static void prv_on_button_click(lv_event_t _Nonnull* event) { ESP_LOGI(TAG, "button clicked"); - // Open Gui record - struct NbGui* gui = furi_record_open(RECORD_GUI); - // Free this screen - NbScreenId screen_id = (NbScreenId)event->user_data; - gui_screen_free(gui, screen_id); + // TODO: make macro for record 'transactions' + NbGui* gui = furi_record_open(RECORD_GUI); + gui_remove_view_port(gui, view_port); + + view_port_free(view_port); + view_port = NULL; // Close Gui record furi_record_close(RECORD_GUI); gui = NULL; } -static void prv_hello_world_lvgl(lv_obj_t* parent, NbScreenId screen_id) { +static void prv_hello_world_lvgl(lv_obj_t* parent, void* context) { lvgl_port_lock(0); lv_obj_t* label = lv_label_create(parent); @@ -37,21 +40,23 @@ static void prv_hello_world_lvgl(lv_obj_t* parent, NbScreenId screen_id) { label = lv_label_create(btn); lv_label_set_text_static(label, "Exit"); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 30); - lv_obj_add_event_cb(btn, prv_on_button_click, LV_EVENT_CLICKED, (void*)screen_id); + lv_obj_add_event_cb(btn, prv_on_button_click, LV_EVENT_CLICKED, NULL); lvgl_port_unlock(); - - // TODO: on app exit, call gui_screen_destroy() } static int32_t prv_hello_world_main(void* param) { UNUSED(param); - // Open Gui record - NbGuiHandle gui = furi_record_open(RECORD_GUI); + vTaskDelay(100 / portTICK_PERIOD_MS); - // Register an lvgl screen - gui_screen_create(gui, &prv_hello_world_lvgl); + // Configure view port + view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, &prv_hello_world_lvgl, view_port); + + // Register view port in GUI + NbGui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); // Close Gui record furi_record_close(RECORD_GUI); diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 52fe03ad..6257bd63 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -1,4 +1,4 @@ CONFIG_IDF_TARGET="esp32" CONFIG_LV_COLOR_16_SWAP=y CONFIG_LV_USE_USER_DATA=y - +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2