Explorar o código

Setup Main Menu

[Problem]
Currently the application does nothing.

[Solution]
This updates the app to have a simple main menu, with the options
that will be available in the final version of the app. It also
sets up some directory structures, dividing the code up into
pieces that are more easily digestible. Specifically, different
scenes can be managed on their own, without being as dependent on
a single file.

[Testing]
Built and confirmed working on device. Should probably investigate
unit testing but that can come later.
Gerald McAlister %!s(int64=2) %!d(string=hai) anos
pai
achega
c442af4493
Modificáronse 10 ficheiros con 423 adicións e 12 borrados
  1. 1 0
      application.fam
  2. 92 0
      src/app_context.c
  3. 52 0
      src/app_context.h
  4. 100 0
      src/menus/main_menu.c
  5. 11 0
      src/menus/main_menu.h
  6. 57 0
      src/tone_gen.c
  7. 16 0
      src/tone_gen.h
  8. 71 0
      src/utils/linked_list.c
  9. 23 0
      src/utils/linked_list.h
  10. 0 12
      tone_gen.c

+ 1 - 0
application.fam

@@ -13,4 +13,5 @@ App(
     fap_author="Gerald McAlister",
     fap_author="Gerald McAlister",
     fap_weburl="https://github.com/GEMISIS/tone_gen",
     fap_weburl="https://github.com/GEMISIS/tone_gen",
     fap_icon_assets="images",  # Image assets to compile for this application
     fap_icon_assets="images",  # Image assets to compile for this application
+    sources=["src/*.c", "src/menus/*.c", "src/utils/*.c"],
 )
 )

+ 92 - 0
src/app_context.c

@@ -0,0 +1,92 @@
+#include <gui/modules/menu.h>
+#include <gui/modules/popup.h>
+
+#include "tone_gen.h"
+#include "app_context.h"
+
+/** custom event handler - passes the event to the scene manager */
+bool viewDispatcherCustomCallback(void* context, uint32_t custom_event) {
+    furi_assert(context);
+    struct AppContext_t* appContext = context;
+    return scene_manager_handle_custom_event(appContext->scene_manager, custom_event);
+}
+
+/** navigation event handler - passes the event to the scene manager */
+bool viewDispatcherNavigationCallback(void* context) {
+    furi_assert(context);
+    struct AppContext_t* appContext = context;
+    return scene_manager_handle_back_event(appContext->scene_manager);
+}
+
+AppContextStatus initializeAppContext(
+    struct AppContext_t** context,
+    const SceneManagerHandlers* sceneManagerHandlers) {
+    FURI_LOG_I(TAG, "Allocating memory for app context");
+
+    *context = malloc(sizeof(struct AppContext_t));
+    if(*context == NULL) {
+        FURI_LOG_E(TAG, "Failed to allocate memory for app context");
+        return APP_CONTEXT_CANT_ALLOCATE;
+    }
+
+    // Allocate our scene manager with the handlers provided
+    FURI_LOG_I(TAG, "Setting up the scene manager");
+    (*context)->scene_manager = scene_manager_alloc(sceneManagerHandlers, *context);
+
+    // Now setup our view dispatchers
+    FURI_LOG_I(TAG, "Setting up the view dispatcher");
+    (*context)->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue((*context)->view_dispatcher);
+
+    FURI_LOG_I(TAG, "Setting view dispatcher callbacks");
+    view_dispatcher_set_event_callback_context((*context)->view_dispatcher, (*context));
+    FURI_LOG_I(TAG, "Setting view dispatcher custom event callbacks");
+    view_dispatcher_set_custom_event_callback(
+        (*context)->view_dispatcher, viewDispatcherCustomCallback);
+    FURI_LOG_I(TAG, "Setting view dispatcher navigation event callbacks");
+    view_dispatcher_set_navigation_event_callback(
+        (*context)->view_dispatcher, viewDispatcherNavigationCallback);
+    FURI_LOG_I(TAG, "Setting view dispatcher callbacks done");
+
+    return APP_CONTEXT_OK;
+}
+
+AppContextStatus freeAppContextViews(struct AppContext_t** context) {
+    FURI_LOG_I(TAG, "Freeing views");
+    struct ListNode_t* root = (*context)->activeViews;
+    while(root) {
+        struct View_t* view = root->data;
+        view_dispatcher_remove_view((*context)->view_dispatcher, view->viewId);
+
+        switch(view->type) {
+        case MENU:
+            menu_free(view->viewData);
+            break;
+        case POPUP:
+            popup_free(view->viewData);
+            break;
+        }
+        root = root->next;
+    }
+    FURI_LOG_I(TAG, "Removing all views from list");
+    LinkedListStatus result = removeAllNodes(&(*context)->activeViews);
+    if(result != LIST_OK) {
+        return APP_CONTEXT_UNKNOWN_ERROR;
+    }
+    return APP_CONTEXT_OK;
+}
+
+AppContextStatus freeAppContext(struct AppContext_t** context) {
+    FURI_LOG_I(TAG, "Ensuring views are free'd");
+    AppContextStatus result = freeAppContextViews(context);
+    if(result != APP_CONTEXT_OK) {
+        return APP_CONTEXT_CANT_FREE_VIEWS;
+    }
+    FURI_LOG_I(TAG, "Freeing the scene");
+    scene_manager_free((*context)->scene_manager);
+    FURI_LOG_I(TAG, "Freeing the view dispatcher");
+    view_dispatcher_free((*context)->view_dispatcher);
+    FURI_LOG_I(TAG, "Freeing the app context");
+    free((*context));
+    return APP_CONTEXT_OK;
+}

+ 52 - 0
src/app_context.h

@@ -0,0 +1,52 @@
+#ifndef _APP_CONTEXT_H_
+
+#define _APP_CONTEXT_H_
+
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+
+#include "utils/linked_list.h"
+
+typedef enum {
+    MENU,
+    POPUP,
+} ViewType;
+
+struct View_t {
+    ViewType type;
+    int viewId;
+    void* viewData;
+};
+
+typedef enum {
+    APP_CONTEXT_OK = 0,
+    APP_CONTEXT_CANT_ALLOCATE = -1,
+    APP_CONTEXT_CANT_FREE_VIEWS = -2,
+    APP_CONTEXT_UNKNOWN_ERROR = -3,
+} AppContextStatus;
+
+struct AppContext_t {
+    SceneManager* scene_manager;
+    ViewDispatcher* view_dispatcher;
+    struct ListNode_t* activeViews;
+};
+
+/// @brief Creates an app context with the desired scene handlers.
+/// @param context The app context to populate. Will be passed through to the supplied scene handlers.
+/// @param sceneManagerHandlers The scene handlers to use for each scene in your app.
+/// @return Returns APP_CONTEXT_OK on success, APP_CONTEXT_CANT_ALLOCATE if there is an error.
+AppContextStatus initializeAppContext(
+    struct AppContext_t** context,
+    const SceneManagerHandlers* sceneManagerHandlers);
+
+/// @brief Frees and removes all views attached to the app context.
+/// @param context The app context to remove all of the views from.
+/// @return Returns APP_CONTEXT_OK on success. Should not error.
+AppContextStatus freeAppContextViews(struct AppContext_t** context);
+
+/// @brief Frees the app context entirely, cleaning it up from usage.
+/// @param context The app context to clean up.
+/// @return Returns APP_CONTEXT_OK on success. Should not error.
+AppContextStatus freeAppContext(struct AppContext_t** context);
+
+#endif

+ 100 - 0
src/menus/main_menu.c

@@ -0,0 +1,100 @@
+#include <gui/modules/menu.h>
+#include <gui/modules/popup.h>
+
+#include "main_menu.h"
+#include "../app_context.h"
+#include "../tone_gen.h"
+#include "../utils/linked_list.h"
+
+/** indices for menu items */
+typedef enum {
+    ToneGenAppMenuSelection_Play,
+    ToneGenAppMenuSelection_Adjust
+} ToneGenAppMenuSelection;
+
+/** main menu callback - sends a custom event to the scene manager based on the menu selection */
+void menu_callback_main_menu(void* context, uint32_t index) {
+    FURI_LOG_I(TAG, "menu_callback_main_menu");
+    UNUSED(context);
+    // struct AppContext_t* app = context;
+    switch(index) {
+    case ToneGenAppMenuSelection_Play:
+        FURI_LOG_I(TAG, "selection one");
+        // scene_manager_handle_custom_event(app->scene_manager, ToneGenAppEvent_StartPlayback);
+        break;
+    case ToneGenAppMenuSelection_Adjust:
+        FURI_LOG_I(TAG, "selection two");
+        // scene_manager_handle_custom_event(app->scene_manager, ToneGenAppEvent_AdjustTone);
+        break;
+    }
+}
+
+/** resets the menu, gives it content, callbacks and selection enums */
+void scene_on_enter_main_menu(void* context) {
+    FURI_LOG_I(TAG, "scene_on_enter_main_menu");
+    struct AppContext_t* app = (struct AppContext_t*)context;
+    // Setup our menu
+    FURI_LOG_D(TAG, "Adding view menu");
+    struct View_t* menuView = malloc(sizeof(struct View_t));
+    menuView->viewData = menu_alloc();
+    menuView->viewId = ToneGenAppView_Menu;
+    menuView->type = MENU;
+    view_dispatcher_add_view(
+        app->view_dispatcher, ToneGenAppView_Menu, menu_get_view(menuView->viewData));
+
+    // Set the currently active view
+    addNode(&app->activeViews, menuView);
+    menu_reset(menuView->viewData);
+
+    // NB. icons are specified as NULL below, because:
+    // * icons are _always_ animated by the menu
+    // * the icons provided (&I_one, &I_two) are generated by the build process
+    // * these icons do not have a framerate (resulting in a division by zero)
+    menu_add_item(
+        menuView->viewData,
+        "Play Tone",
+        NULL,
+        ToneGenAppMenuSelection_Play,
+        menu_callback_main_menu,
+        app);
+    menu_add_item(
+        menuView->viewData,
+        "Adjust Tone",
+        NULL,
+        ToneGenAppMenuSelection_Adjust,
+        menu_callback_main_menu,
+        app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, ToneGenAppView_Menu);
+}
+
+/** main menu event handler - switches scene based on the event */
+bool scene_on_event_main_menu(void* context, SceneManagerEvent event) {
+    FURI_LOG_I(TAG, "scene_on_event_main_menu");
+    UNUSED(context);
+    // struct AppContext_t* app = context;
+    bool consumed = false;
+    switch(event.type) {
+    case SceneManagerEventTypeCustom:
+        // switch(event.event) {
+        // case ToneGenAppEvent_StartPlayback:
+        //     scene_manager_next_scene(app->scene_manager, ToneGenAppScene_Playback);
+        //     consumed = true;
+        //     break;
+        // case ToneGenAppEvent_AdjustTone:
+        //     scene_manager_next_scene(app->scene_manager, ToneGenAppScene_AdjustTone);
+        //     consumed = true;
+        //     break;
+        // }
+        break;
+    default: // eg. SceneManagerEventTypeBack, SceneManagerEventTypeTick
+        consumed = false;
+        break;
+    }
+    return consumed;
+}
+
+void scene_on_exit_main_menu(void* context) {
+    FURI_LOG_I(TAG, "scene_on_exit_main_menu");
+    struct AppContext_t* app = context;
+    freeAppContextViews(&app);
+}

+ 11 - 0
src/menus/main_menu.h

@@ -0,0 +1,11 @@
+#ifndef _MAIN_MENU_H_
+
+#define _MAIN_MENU_H_
+
+#include <gui/scene_manager.h>
+
+void scene_on_enter_main_menu(void* context);
+bool scene_on_event_main_menu(void* context, SceneManagerEvent event);
+void scene_on_exit_main_menu(void* context);
+
+#endif

+ 57 - 0
src/tone_gen.c

@@ -0,0 +1,57 @@
+#include <gui/modules/menu.h>
+
+/* generated by fbt from .png files in images folder */
+#include <tone_gen_icons.h>
+
+#include "app_context.h"
+#include "tone_gen.h"
+
+#include "menus/main_menu.h"
+
+/** collection of all scene on_enter handlers - in the same order as their enum */
+void (*const scene_on_enter_handlers[])(void*) = {
+    scene_on_enter_main_menu,
+};
+
+/** collection of all scene on event handlers - in the same order as their enum */
+bool (*const scene_on_event_handlers[])(void*, SceneManagerEvent) = {
+    scene_on_event_main_menu,
+};
+
+/** collection of all scene on exit handlers - in the same order as their enum */
+void (*const scene_on_exit_handlers[])(void*) = {
+    scene_on_exit_main_menu,
+};
+
+const SceneManagerHandlers scene_event_handlers = {
+    .on_enter_handlers = scene_on_enter_handlers,
+    .on_event_handlers = scene_on_event_handlers,
+    .on_exit_handlers = scene_on_exit_handlers,
+    .scene_num = ToneGenAppScene_count};
+
+int32_t tone_gen_app(void* p) {
+    UNUSED(p);
+
+    FURI_LOG_I(TAG, "Tone gen app starting...");
+
+    struct AppContext_t* appContext;
+    AppContextStatus result = initializeAppContext(&appContext, &scene_event_handlers);
+
+    if(result == APP_CONTEXT_OK) {
+        // set the scene and launch the main loop
+        FURI_LOG_D(TAG, "Setting the scene");
+        Gui* gui = furi_record_open(RECORD_GUI);
+        view_dispatcher_attach_to_gui(
+            appContext->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+        scene_manager_next_scene(appContext->scene_manager, ToneGenAppScene_MainMenu);
+        FURI_LOG_D(TAG, "Starting the view dispatcher");
+        view_dispatcher_run(appContext->view_dispatcher);
+
+        // free all memory
+        FURI_LOG_D(TAG, "Ending the app");
+        furi_record_close(RECORD_GUI);
+        freeAppContext(&appContext);
+        return 0;
+    }
+    return -1;
+}

+ 16 - 0
src/tone_gen.h

@@ -0,0 +1,16 @@
+#ifndef _TONE_GEN_H_
+
+#define _TONE_GEN_H_
+
+#define TAG "tone-gen"
+
+#include <furi.h>
+#include <music_worker/music_worker.h>
+
+// ids for all scenes used by the app
+typedef enum { ToneGenAppScene_MainMenu, ToneGenAppScene_count } ToneGenAppScene;
+
+// ids for the 2 types of view used by the app
+typedef enum { ToneGenAppView_Menu, ToneGenAppView_Popup } ToneGenAppView;
+
+#endif

+ 71 - 0
src/utils/linked_list.c

@@ -0,0 +1,71 @@
+#include <stdlib.h>
+
+#include "linked_list.h"
+
+int getLength(const struct ListNode_t* root) {
+    int count = 0;
+    while(root) {
+        root = root->next;
+        count += 1;
+    }
+
+    return count;
+}
+
+LinkedListStatus
+    createNode(struct ListNode_t** emptyNode, struct ListNode_t* previousNode, void* data) {
+    *emptyNode = malloc(sizeof(struct ListNode_t));
+    if(*emptyNode == 0) {
+        return LIST_CANT_ALLOCATE;
+    }
+    (*emptyNode)->data = data;
+    (*emptyNode)->previous = previousNode;
+    return LIST_OK;
+}
+
+LinkedListStatus addNode(struct ListNode_t** root, void* data) {
+    // If there is no root node, add that first
+    if(*root == 0) {
+        return createNode(root, 0, data);
+    }
+
+    // Iterate until we find an empty node
+    struct ListNode_t* base = *root;
+    while(base->next) {
+        base = base->next;
+    }
+    return createNode(&base->next, base, data);
+}
+
+LinkedListStatus removeNode(struct ListNode_t** root, const void* data) {
+    if(*root == 0) {
+        return LIST_NO_NODE;
+    }
+
+    struct ListNode_t* base = *root;
+    while(base->data != data && base->next) {
+        base = base->next;
+    }
+
+    // Delete node if data is matching
+    if(base->data == data) {
+        (base->previous)->next = base->next;
+        (base->next)->previous = base->previous;
+        free(base);
+        return LIST_OK;
+    }
+    return LIST_NO_NODE;
+}
+
+LinkedListStatus removeAllNodes(struct ListNode_t** root) {
+    struct ListNode_t* base = *root;
+    struct ListNode_t* temp;
+    while(base) {
+        temp = base;
+        base = base->next;
+        free(temp);
+    }
+    (*root) = NULL;
+
+    return LIST_OK;
+}

+ 23 - 0
src/utils/linked_list.h

@@ -0,0 +1,23 @@
+#ifndef _LINKED_LIST_H_
+
+#define _LINKED_LIST_H_
+
+typedef enum {
+    LIST_OK = 0,
+    LIST_CANT_ALLOCATE = -1,
+    LIST_NO_NODE = -2,
+} LinkedListStatus;
+
+struct ListNode_t {
+    struct ListNode_t* previous;
+    struct ListNode_t* next;
+
+    void* data;
+};
+
+int getLength(const struct ListNode_t* root);
+LinkedListStatus addNode(struct ListNode_t** root, void* data);
+LinkedListStatus removeNode(struct ListNode_t** root, const void* data);
+LinkedListStatus removeAllNodes(struct ListNode_t** root);
+
+#endif

+ 0 - 12
tone_gen.c

@@ -1,12 +0,0 @@
-#include <furi.h>
-
-/* generated by fbt from .png files in images folder */
-#include <tone_gen_icons.h>
-
-int32_t tone_gen_app(void* p) {
-    UNUSED(p);
-    FURI_LOG_I("TEST", "Hello world");
-    FURI_LOG_I("TEST", "I'm tone_gen!");
-
-    return 0;
-}