Просмотр исходного кода

feat: implement scene manager based implementation

Alex4386 1 год назад
Родитель
Сommit
2b4ac9adf5
18 измененных файлов с 449 добавлено и 119 удалено
  1. 16 0
      README.md
  2. 6 5
      application.fam
  3. 0 8
      src/entrypoint.c
  4. 0 29
      src/events.c
  5. 0 7
      src/events.h
  6. 19 0
      src/handler.c
  7. 30 21
      src/main.c
  8. 27 1
      src/main.h
  9. 72 0
      src/scenes/about/main.c
  10. 15 0
      src/scenes/about/main.h
  11. 119 0
      src/scenes/home/main.c
  12. 16 0
      src/scenes/home/main.h
  13. 8 0
      src/scenes/import.h
  14. 15 0
      src/scenes/list.h
  15. 101 0
      src/scenes/register.c
  16. 5 0
      src/scenes/register.h
  17. 0 37
      src/utils/gui.c
  18. 0 11
      src/utils/gui.h

+ 16 - 0
README.md

@@ -50,3 +50,19 @@ Here are the resources for developing applications for Flipper Zero:
 - [Flipper Zero Firmware Docs](https://developer.flipper.net/flipperzero/doxygen/)
   - [`struct` list](https://developer.flipper.net/flipperzero/doxygen/annotated.html) ([index](https://developer.flipper.net/flipperzero/doxygen/classes.html))
 - [Flipper Zero code examples](https://github.com/m1ch3al/flipper-zero-dev-tutorial)
+- [Lopaka, Graphics Editor for Embedded Devices](https://lopaka.app/)  
+  Note that Flipper Zero has screen dimension of `128x64`.  
+  
+
+### How to use `SceneManager` with this project?
+This template implements `SceneManager`, A "Scene" based framework for programming flipper zero GUIs.
+
+Here is how you can add/modify scenes in this repository:
+1. Goto [`./src/scenes/list.h`](/src/scenes/list.h).  
+2. Add your own scene by using macro: `SCENE_ACTION`.
+   (e.g. If you want to make new scene called `Store`, You should type `SCENE_ACTION(Store)`)
+3. Implement `_on_enter`, `_on_event`, `_on_exit`, `_get_view`, `_alloc`, `_free` accordingly. Refer to [`./src/scenes/home/main.c`](/src/scenes/home/main.c) for more info.    
+   (F0 doesn't make sure the precendence of `_on_exit` and `_free`. Please make sure those two are independent by checking each other's free'd state)
+4. Add headers to export those functions.
+5. Include your header in [`./src/scenes/import.h`](/src/scenes/import.h).  
+

+ 6 - 5
application.fam

@@ -5,14 +5,15 @@ App(
     appid="demo_app",  # Must be unique
     name="Demo Application",  # Displayed in UI
     apptype=FlipperAppType.EXTERNAL,
-    entry_point="main_entrypoint",
+    entry_point="entrypoint",
     stack_size=2 * 1024, # size of memory stack it will allocate
     
     # source code settings
-    sources=["src/*.c*", "src/*/*.c*"], # Due to limitation of the fbt,
-                                        # you need to specify nested directories
-                                        # manually since it doesn't support
-                                        # recurse globbing such as "src/**/*.c*"
+    sources=[            # Due to limitation of the fbt,
+      "src/*.c*",        # you need to specify nested directories
+      "src/*/*.c*",      # manually since it doesn't support
+      "src/*/*/*.c*"     # recurse globbing such as "src/**/*.c*"
+    ], 
 
     # Dependencies
     requires=[

+ 0 - 8
src/entrypoint.c

@@ -1,8 +0,0 @@
-#include <furi.h>
-#include "main.h"
-
-int32_t main_entrypoint(void* p) {
-    UNUSED(p);
-    main();
-    return 0;
-}

+ 0 - 29
src/events.c

@@ -1,29 +0,0 @@
-#include <gui/gui.h>
-#include <demo_app_icons.h>
-
-void on_draw(Canvas* canvas, void* context) {
-    UNUSED(context);
-
-    // clear canvas
-    canvas_clear(canvas);
-
-    // Set the font
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str(canvas, 32, 13, "Hello, World!");
-
-    // draw dolphin first
-    canvas_draw_icon(canvas, 32, 34, &I_dolphin_71x25);
-
-    // write press back
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str(canvas, 15, 26, " press back to exit FAP");
-
-    canvas_draw_line(canvas, 2, 16, 126, 16);
-}
-
-void on_input(InputEvent* event, void* context) {
-    furi_assert(context);
-    FuriMessageQueue* msg_queue = (FuriMessageQueue*)context;
-
-    furi_message_queue_put(msg_queue, event, FuriWaitForever);
-}

+ 0 - 7
src/events.h

@@ -1,7 +0,0 @@
-#pragma once
-#include <stdio.h>
-#include <furi.h>
-#include <gui/gui.h>
-
-void on_draw(Canvas* canvas, void* context);
-void on_input(InputEvent* event, void* context);

+ 19 - 0
src/handler.c

@@ -0,0 +1,19 @@
+#include "main.h"
+
+bool scene_handler_event_forwarder(void* context, uint32_t event_id) {
+    App* app = (App*)context;
+    if(app == NULL || app->scene_manager == NULL) {
+        return false;
+    }
+
+    return scene_manager_handle_custom_event(app->scene_manager, event_id);
+}
+
+bool scene_handler_navigation_forwarder(void* context) {
+    App* app = (App*)context;
+    if(app == NULL || app->scene_manager == NULL) {
+        return false;
+    }
+
+    return scene_manager_handle_back_event(app->scene_manager);
+}

+ 30 - 21
src/main.c

@@ -1,27 +1,36 @@
 #include <stdio.h>
 #include <furi.h>
 #include <gui/gui.h>
-#include "events.h"
-#include "utils/gui.h"
+#include "main.h"
+#include "scenes/register.h"
 
 int main() {
-    // 1. Provision the InputHandlers
-    // Handle input event
-    InputEvent event;
-    GUISetupData* gui_setup = setup_gui(on_draw, on_input);
-
-    // 2. Main EventLoop
-    while(true) {
-        // 4.1. Read input event from the message queue
-        furi_check(
-            furi_message_queue_get(gui_setup->msg_queue, &event, FuriWaitForever) == FuriStatusOk);
-
-        // 4.2. check if the event is a quit event
-        if(event.key == InputKeyBack) {
-            break;
-        }
-    }
-
-    // 3. Free the resources
-    free_gui(gui_setup);
+    App* app = malloc(sizeof(App));
+    furi_assert(app != NULL, "Failed to allocate memory for the app");
+
+    Gui* gui = furi_record_open(RECORD_GUI);
+    furi_assert(gui != NULL, "Failed to open the GUI record");
+
+    register_scenes(app);
+    view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+
+    // The default scene is always the first one!
+    scene_manager_next_scene(app->scene_manager, 0);
+    view_dispatcher_switch_to_view(app->view_dispatcher, 0);
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    FURI_LOG_I("DemoApp", "Exiting application.");
+    free_scenes(app);
+
+    FURI_LOG_I("DemoApp", "Freed app.");
+
+    return 0;
+}
+
+// Stub entrypoint due to gcc complaining about
+// mismatching main function signature.
+int32_t entrypoint(void* p) {
+    UNUSED(p);
+    return main();
 }

+ 27 - 1
src/main.h

@@ -1,3 +1,29 @@
 #pragma once
 
-int main();
+#include <gui/scene_manager.h>
+#include <gui/view_dispatcher.h>
+#include "scenes/import.h"
+
+typedef struct App {
+    SceneManager* scene_manager;
+    ViewDispatcher* view_dispatcher;
+
+    void** allocated_scenes;
+} App;
+
+/**
+ * Enum for scenes.
+ */
+typedef enum {
+#define SCENE_ACTION(scene) scene,
+#include "scenes/list.h"
+#undef SCENE_ACTION
+
+    AppSceneNum, // This should be the last element in the enumeration.
+} AppViews;
+
+/**
+ * Header definition for handler.c
+ */
+bool scene_handler_event_forwarder(void* context, uint32_t event_id);
+bool scene_handler_navigation_forwarder(void* context);

+ 72 - 0
src/scenes/about/main.c

@@ -0,0 +1,72 @@
+#include <gui/view.h>
+#include <gui/modules/submenu.h>
+#include "../../main.h"
+#include "main.h"
+
+#define THIS_SCENE About
+
+void About_on_draw(Canvas* canvas, void* context);
+AppAbout* About_alloc() {
+    AppAbout* about = (AppAbout*)malloc(sizeof(AppAbout));
+    about->view = view_alloc();
+    view_set_draw_callback(about->view, About_on_draw);
+
+    return about;
+}
+
+void About_on_draw(Canvas* canvas, void* context) {
+    UNUSED(context);
+
+    canvas_clear(canvas);
+
+    canvas_set_bitmap_mode(canvas, true);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 67, 11, "f0-template");
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 72, 20, "by Alex4386");
+    canvas_draw_line(canvas, 54, 25, 124, 25);
+    canvas_draw_str(canvas, 71, 39, "Protected by");
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 61, 51, "Fantasy Seal");
+    canvas_draw_str(canvas, 69, 61, "Technology");
+}
+
+void About_free(void* ptr) {
+    AppAbout* home = (AppAbout*)ptr;
+    FURI_LOG_I("DemoApp", "Triggering Free for view");
+
+    view_free(home->view);
+    home->view = NULL;
+
+    free(home);
+}
+
+View* About_get_view(void* ptr) {
+    AppAbout* home = (AppAbout*)ptr;
+    return home->view;
+}
+
+void About_on_enter(void* context) {
+    UNUSED(context);
+}
+
+bool About_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+
+    if(event.type == SceneManagerEventTypeBack) {
+        return false;
+    }
+
+    return true;
+}
+
+void About_on_exit(void* context) {
+    App* app = (App*)context;
+    if(app == NULL) {
+        return;
+    }
+
+    if(app->view_dispatcher) view_dispatcher_switch_to_view(app->view_dispatcher, Home);
+    if(app->scene_manager) scene_manager_previous_scene(app->scene_manager);
+}

+ 15 - 0
src/scenes/about/main.h

@@ -0,0 +1,15 @@
+#pragma once
+#include <gui/view.h>
+#include <gui/modules/submenu.h>
+
+typedef struct AppAbout {
+    View* view;
+    Canvas* canvas;
+} AppAbout;
+
+AppAbout* About_alloc();
+void About_free(void* ptr);
+View* About_get_view(void* ptr);
+void About_on_enter(void* context);
+bool About_on_event(void* context, SceneManagerEvent event);
+void About_on_exit(void* context);

+ 119 - 0
src/scenes/home/main.c

@@ -0,0 +1,119 @@
+#include <gui/view.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#include "../../main.h"
+#include "main.h"
+
+#define THIS_SCENE Home
+
+AppHome* Home_alloc() {
+    AppHome* home = (AppHome*)malloc(sizeof(AppHome));
+    home->menu = submenu_alloc();
+    home->view = NULL;
+
+    return home;
+}
+
+void Home_free(void* ptr) {
+    AppHome* home = (AppHome*)ptr;
+
+    FURI_LOG_I("DemoApp", "Freeing Home.");
+    if(home == NULL) {
+        FURI_LOG_I("DemoApp", "Home is NULL.");
+        return;
+    }
+
+    if(home->view != NULL) {
+        FURI_LOG_I("DemoApp", "Freeing View.");
+        view_free(home->view);
+        home->view = NULL;
+    }
+
+    // I don't know the reason why
+    // but this is causing NULL pointer exception
+
+    // if(home->menu != NULL) {
+    //     FURI_LOG_I("DemoApp", "Freeing Submenu.");
+    //     submenu_free(home->menu);
+    //     home->menu = NULL;
+    // }
+
+    free(home);
+    FURI_LOG_I("DemoApp", "Home freed.");
+}
+
+View* Home_get_view(void* ptr) {
+    AppHome* home = (AppHome*)ptr;
+
+    if(home->view == NULL) {
+        home->view = submenu_get_view(home->menu);
+        furi_assert(home->view != NULL, "View is NULL");
+    }
+
+    return home->view;
+}
+
+void Home_on_submenu_item(void* context, uint32_t index) {
+    App* app = (App*)context;
+    AppHome* home = app->allocated_scenes[THIS_SCENE];
+    furi_assert(home != NULL, "Home is NULL");
+
+    switch(index) {
+    case 0:
+        FURI_LOG_I("DemoApp", "Hello World");
+        break;
+    case 1:
+        FURI_LOG_I("DemoApp", "About");
+        scene_manager_next_scene(app->scene_manager, About);
+        view_dispatcher_switch_to_view(app->view_dispatcher, About);
+        break;
+    case 2:
+        FURI_LOG_I("DemoApp", "Exit");
+        Home_on_exit(app);
+        view_dispatcher_stop(app->view_dispatcher);
+        break;
+    default:
+        break;
+    }
+}
+
+void Home_on_enter(void* context) {
+    App* app = (App*)context;
+    AppHome* home = app->allocated_scenes[THIS_SCENE];
+
+    submenu_add_item(home->menu, "Hello World", 0, Home_on_submenu_item, app);
+    submenu_add_item(home->menu, "About", 1, Home_on_submenu_item, app);
+    submenu_add_item(home->menu, "Exit", 2, Home_on_submenu_item, app);
+}
+
+bool Home_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+
+    if(event.type == SceneManagerEventTypeBack) {
+        return false;
+    }
+
+    return true;
+}
+
+void Home_on_exit(void* context) {
+    App* app = (App*)context;
+    // if the on_exit has been ran, the app will be NULL
+    if(app == NULL || app->allocated_scenes == NULL) {
+        return;
+    }
+
+    AppHome* home = app->allocated_scenes[THIS_SCENE];
+    if(home == NULL) {
+        return;
+    }
+
+    FURI_LOG_I("DemoApp", "Exiting Home.");
+
+    Submenu* menu = home->menu;
+    if(menu != NULL) {
+        submenu_reset(menu);
+    }
+
+    FURI_LOG_I("DemoApp", "Reset submenu complete.");
+}

+ 16 - 0
src/scenes/home/main.h

@@ -0,0 +1,16 @@
+#pragma once
+#include <gui/view.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+
+typedef struct AppHome {
+    Submenu* menu;
+    View* view;
+} AppHome;
+
+AppHome* Home_alloc();
+void Home_free(void* ptr);
+View* Home_get_view(void* ptr);
+void Home_on_enter(void* context);
+bool Home_on_event(void* context, SceneManagerEvent event);
+void Home_on_exit(void* context);

+ 8 - 0
src/scenes/import.h

@@ -0,0 +1,8 @@
+#pragma once
+
+/**
+ * This is the file for importing scenes in this directory
+ */
+
+#include "home/main.h"
+#include "about/main.h"

+ 15 - 0
src/scenes/list.h

@@ -0,0 +1,15 @@
+// DO NOT SET PRAGMA ONCE OR OTHER HEADER GUARDING PATTERNS.
+// IT SHOULD BE INCLUDED MULTIPLE TIMES, AND IS INTENDED.
+
+/**
+ * This is the file for defining the scenes.
+ * 
+ * Use it as following:
+ * 1. Define the scene via SCENE_ACTION macro. (e.g. SCENE_ACTION(Home))
+ * 2. Implement the scene handlers in the corresponding file.
+ *    (_on_enter, _on_event, _on_exit, _get_view, _alloc)
+ * 3. Include the scene in the list of scenes in main.h.
+ */
+
+SCENE_ACTION(Home)
+SCENE_ACTION(About)

+ 101 - 0
src/scenes/register.c

@@ -0,0 +1,101 @@
+#include "../main.h"
+
+/**
+ * SceneManagerHandlers initialization using the macro.
+ */
+void (*const scene_on_enter_handlers[])(void* context) = {
+#define SCENE_ACTION(scene) scene##_on_enter,
+#include "list.h"
+#undef SCENE_ACTION
+};
+
+bool (*const scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#define SCENE_ACTION(scene) scene##_on_event,
+#include "list.h"
+#undef SCENE_ACTION
+};
+
+void (*const scene_on_exit_handlers[])(void* context) = {
+#define SCENE_ACTION(scene) scene##_on_exit,
+#include "list.h"
+#undef SCENE_ACTION
+};
+
+const SceneManagerHandlers scene_handlers = {
+    .on_enter_handlers = scene_on_enter_handlers,
+    .on_event_handlers = scene_on_event_handlers,
+    .on_exit_handlers = scene_on_exit_handlers,
+    .scene_num = AppSceneNum,
+};
+
+/**
+ * Register all scenes.
+ */
+
+void register_scenes(App* app) {
+    app->scene_manager = scene_manager_alloc(&scene_handlers, app);
+    furi_assert(app->scene_manager != NULL, "Failed to allocate scene manager.");
+
+    app->view_dispatcher = view_dispatcher_alloc();
+    furi_assert(app->view_dispatcher != NULL, "Failed to allocate view dispatcher.");
+
+    if(app->allocated_scenes == NULL) {
+        app->allocated_scenes = (void**)malloc(sizeof(void*) * AppSceneNum);
+    }
+
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, scene_handler_event_forwarder);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, scene_handler_navigation_forwarder);
+
+    View* view = NULL;
+#define SCENE_ACTION(scene)                                                                   \
+    app->allocated_scenes[scene] = (void*)scene##_alloc();                                    \
+    furi_assert(                                                                              \
+        app->allocated_scenes[scene] != NULL, "Failed to allocate scene: " STRINGIFY(scene)); \
+    view = scene##_get_view(app->allocated_scenes[scene]);                                    \
+    furi_assert(view != NULL, "Failed to get view for scene: " STRINGIFY(scene));             \
+    view_dispatcher_add_view(app->view_dispatcher, scene, view);
+#include "list.h"
+#undef SCENE_ACTION
+}
+
+/**
+ * Free all scenes.
+ */
+void free_scenes(App* app) {
+    FURI_LOG_I("DemoApp", "Freeing scenes.");
+    furi_assert(app != NULL, "App is NULL.");
+    void* tmp;
+#define SCENE_ACTION(scene)                                                                    \
+    if(app->allocated_scenes != NULL) {                                                        \
+        tmp = app->allocated_scenes[scene];                                                    \
+        app->allocated_scenes[scene] = NULL;                                                   \
+        FURI_LOG_I("DemoApp", "Freeing scene " STRINGIFY(scene) ".");                          \
+        if(tmp != NULL) scene##_free(tmp);                                                     \
+        FURI_LOG_I("DemoApp", "Free'd scene " STRINGIFY(scene) ".");                           \
+    }                                                                                          \
+    if(app->view_dispatcher != NULL) view_dispatcher_remove_view(app->view_dispatcher, scene); \
+    FURI_LOG_I("DemoApp", "Removed from dispatcher " STRINGIFY(scene) ".");
+
+#include "list.h"
+#undef SCENE_ACTION
+
+    FURI_LOG_I("DemoApp", "Freeing allocated scenes.");
+    furi_assert(app->allocated_scenes != NULL, "Allocated scenes is NULL.");
+    free(app->allocated_scenes);
+    app->allocated_scenes = NULL;
+
+    FURI_LOG_I("DemoApp", "Freeing View dispatcher.");
+    furi_assert(app->view_dispatcher != NULL, "View dispatcher is NULL.");
+    view_dispatcher_free(app->view_dispatcher);
+
+    FURI_LOG_I("DemoApp", "Freeing SceneManager");
+    furi_assert(app->scene_manager != NULL, "Scene manager is NULL.");
+    scene_manager_free(app->scene_manager);
+
+    FURI_LOG_I("DemoApp", "Freeing App");
+    free(app);
+}

+ 5 - 0
src/scenes/register.h

@@ -0,0 +1,5 @@
+#pragma once
+#include "../main.h"
+
+void register_scenes(App* app);
+void free_scenes(App* app);

+ 0 - 37
src/utils/gui.c

@@ -1,37 +0,0 @@
-#include <furi.h>
-#include <gui/gui.h>
-#include "gui.h"
-
-GUISetupData* setup_gui(ViewPortDrawCallback on_draw, ViewPortInputCallback on_input) {
-    GUISetupData* data = malloc(sizeof(GUISetupData));
-
-    data->msg_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
-    data->viewport = view_port_alloc();
-
-    view_port_draw_callback_set(data->viewport, on_draw, NULL);
-    view_port_input_callback_set(data->viewport, on_input, data->msg_queue);
-    data->gui = furi_record_open(RECORD_GUI);
-    gui_add_view_port(data->gui, data->viewport, GuiLayerFullscreen);
-    return data;
-}
-
-void free_gui(GUISetupData* data) {
-    // nullguard!
-    if(data == NULL) return;
-
-    if(data->msg_queue != NULL) {
-        furi_message_queue_free(data->msg_queue);
-    }
-
-    if(data->viewport != NULL) {
-        if(data->gui != NULL) {
-            gui_remove_view_port(data->gui, data->viewport);
-        }
-
-        view_port_enabled_set(data->viewport, false);
-        view_port_free(data->viewport);
-    }
-
-    furi_record_close(RECORD_GUI);
-    free(data);
-}

+ 0 - 11
src/utils/gui.h

@@ -1,11 +0,0 @@
-#pragma once
-#include <gui/gui.h>
-
-typedef struct GUISetupData {
-    FuriMessageQueue* msg_queue;
-    ViewPort* viewport;
-    Gui* gui;
-} GUISetupData;
-
-GUISetupData* setup_gui(ViewPortDrawCallback on_draw, ViewPortInputCallback on_input);
-void free_gui(GUISetupData* data);