Skip to content

Commit

Permalink
Setup Main Menu
Browse files Browse the repository at this point in the history
[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.
  • Loading branch information
GEMISIS committed Dec 26, 2023
1 parent 6cc8608 commit c442af4
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 12 deletions.
1 change: 1 addition & 0 deletions application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ App(
fap_author="Gerald McAlister",
fap_weburl="https://github.com/GEMISIS/tone_gen",
fap_icon_assets="images", # Image assets to compile for this application
sources=["src/*.c", "src/menus/*.c", "src/utils/*.c"],
)
92 changes: 92 additions & 0 deletions src/app_context.c
Original file line number Diff line number Diff line change
@@ -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 changes: 52 additions & 0 deletions src/app_context.h
Original file line number Diff line number Diff line change
@@ -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 changes: 100 additions & 0 deletions src/menus/main_menu.c
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions src/menus/main_menu.h
Original file line number Diff line number Diff line change
@@ -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 changes: 57 additions & 0 deletions src/tone_gen.c
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions src/tone_gen.h
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit c442af4

Please sign in to comment.