mirror of
https://github.com/pionaiki/fz-tarot.git
synced 2026-02-13 10:24:36 +01:00
569 lines
19 KiB
C
569 lines
19 KiB
C
#include <furi.h>
|
|
#include <gui/gui.h>
|
|
#include <gui/icon_i.h>
|
|
#include <gui/view_dispatcher.h>
|
|
#include <gui/scene_manager.h>
|
|
#include <gui/modules/submenu.h>
|
|
#include <gui/modules/popup.h>
|
|
#include <gui/modules/widget.h>
|
|
#include "cards.h"
|
|
|
|
#define TAG "tarot"
|
|
|
|
/* generated by fbt from .png files in images folder */
|
|
#include <tarot_icons.h>
|
|
|
|
/* ids for all scenes used by the app */
|
|
typedef enum {
|
|
AppScene_MainMenu,
|
|
AppScene_About,
|
|
AppScene_Game,
|
|
AppScene_DeckBrowser,
|
|
AppScene_Settings,
|
|
AppScene_count
|
|
} AppScene;
|
|
|
|
/* ids for the 2 types of view used by the app */
|
|
typedef enum {
|
|
AppView_Submenu,
|
|
AppView_Popup,
|
|
AppView_Widget
|
|
} AppView;
|
|
|
|
/* the app context struct */
|
|
typedef struct {
|
|
SceneManager* scene_manager;
|
|
ViewDispatcher* view_dispatcher;
|
|
Submenu* submenu; // Submenu for the main menu
|
|
Popup* popup; // Popup for about
|
|
Widget* widget; // Widget for game
|
|
bool allow_reversed; // NEW: Toggle for reversed cards
|
|
} App;
|
|
|
|
/* all custom events */
|
|
typedef enum {
|
|
AppEvent_ShowGame,
|
|
AppEvent_ShowAbout,
|
|
AppEvent_ShowDeckBrowser,
|
|
AppEvent_ShowSettings
|
|
} AppEvent;
|
|
|
|
/* main menu scene */
|
|
|
|
/* indices for menu items */
|
|
typedef enum {
|
|
AppMenuSelection_Run,
|
|
AppMenuSelection_About,
|
|
AppMenuSelection_BrowseDeck,
|
|
AppMenuSelection_Settings
|
|
} AppMenuSelection;
|
|
|
|
/* main menu callback - sends a custom event to the scene manager based on the menu selection */
|
|
void tarot_app_menu_callback_main_menu(void* context, uint32_t index) {
|
|
FURI_LOG_T(TAG, "tarot_app_menu_callback_main_menu");
|
|
App* app = context;
|
|
switch(index) {
|
|
case AppMenuSelection_Run:
|
|
scene_manager_handle_custom_event(app->scene_manager, AppEvent_ShowGame);
|
|
break;
|
|
case AppMenuSelection_About:
|
|
scene_manager_handle_custom_event(app->scene_manager, AppEvent_ShowAbout);
|
|
break;
|
|
case AppMenuSelection_BrowseDeck:
|
|
scene_manager_handle_custom_event(app->scene_manager, AppEvent_ShowDeckBrowser);
|
|
break;
|
|
case AppMenuSelection_Settings:
|
|
scene_manager_handle_custom_event(app->scene_manager, AppEvent_ShowSettings);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* resets the submenu, gives it content, callbacks and selection enums */
|
|
void tarot_app_scene_on_enter_main_menu(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_enter_main_menu");
|
|
App* app = context;
|
|
submenu_reset(app->submenu);
|
|
|
|
submenu_add_item(
|
|
app->submenu, "Run", AppMenuSelection_Run, tarot_app_menu_callback_main_menu, app);
|
|
submenu_add_item(
|
|
app->submenu,
|
|
"Browse Deck",
|
|
AppMenuSelection_BrowseDeck,
|
|
tarot_app_menu_callback_main_menu,
|
|
app);
|
|
submenu_add_item(
|
|
app->submenu, "About", AppMenuSelection_About, tarot_app_menu_callback_main_menu, app);
|
|
submenu_add_item(
|
|
app->submenu,
|
|
"Settings",
|
|
AppMenuSelection_Settings,
|
|
tarot_app_menu_callback_main_menu,
|
|
app);
|
|
view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Submenu);
|
|
}
|
|
|
|
/* main menu event handler - switches scene based on the event */
|
|
bool tarot_app_scene_on_event_main_menu(void* context, SceneManagerEvent event) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_event_main_menu");
|
|
App* app = context;
|
|
bool consumed = false;
|
|
switch(event.type) {
|
|
case SceneManagerEventTypeCustom:
|
|
switch(event.event) {
|
|
case AppEvent_ShowGame:
|
|
scene_manager_next_scene(app->scene_manager, AppScene_Game);
|
|
consumed = true;
|
|
break;
|
|
case AppEvent_ShowAbout:
|
|
scene_manager_next_scene(app->scene_manager, AppScene_About);
|
|
consumed = true;
|
|
break;
|
|
case AppEvent_ShowDeckBrowser:
|
|
scene_manager_next_scene(app->scene_manager, AppScene_DeckBrowser);
|
|
consumed = true;
|
|
break;
|
|
case AppEvent_ShowSettings:
|
|
scene_manager_next_scene(app->scene_manager, AppScene_Settings);
|
|
consumed = true;
|
|
break;
|
|
}
|
|
break;
|
|
default: // eg. SceneManagerEventTypeBack, SceneManagerEventTypeTick
|
|
consumed = false;
|
|
break;
|
|
}
|
|
return consumed;
|
|
}
|
|
|
|
void tarot_app_scene_on_exit_main_menu(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_exit_main_menu");
|
|
App* app = context;
|
|
submenu_reset(app->submenu);
|
|
}
|
|
|
|
/* About scene */
|
|
|
|
void tarot_app_scene_on_enter_about(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_enter_about");
|
|
App* app = context;
|
|
popup_reset(app->popup);
|
|
popup_set_context(app->popup, app);
|
|
popup_set_header(app->popup, "About", 64, 1, AlignCenter, AlignTop);
|
|
popup_set_icon(app->popup, 16, 64 - 13, &I_github_icon);
|
|
popup_set_text(
|
|
app->popup,
|
|
"\n\nCode: pionaiki, OrionW06\nArt: tihyltew\n\n /pionaiki/fz-tarot",
|
|
64,
|
|
0,
|
|
AlignCenter,
|
|
AlignTop);
|
|
view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Popup);
|
|
}
|
|
|
|
bool tarot_app_scene_on_event_about(void* context, SceneManagerEvent event) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_event_about");
|
|
UNUSED(context);
|
|
UNUSED(event);
|
|
return false; // don't handle any events
|
|
}
|
|
|
|
void tarot_app_scene_on_exit_about(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_exit_about");
|
|
App* app = context;
|
|
popup_reset(app->popup);
|
|
}
|
|
|
|
/* Game scene */
|
|
|
|
struct Spread {
|
|
int card[3];
|
|
bool selected[3];
|
|
};
|
|
|
|
struct Spread spread;
|
|
|
|
// Card selection and card size globals
|
|
int card_selected = 0;
|
|
const int card_x = 23;
|
|
const int card_y = 32;
|
|
|
|
void draw_tarot(void* context) {
|
|
App* app = context;
|
|
widget_reset(app->widget);
|
|
int n = 3; // Always 3 cards
|
|
for(int i = 0; i < 3; ++i)
|
|
spread.selected[i] = 0;
|
|
if(card_selected >= n) card_selected = 0;
|
|
spread.selected[card_selected] = 1;
|
|
int x_offsets[3] = {(128 - card_x) / 2 - 32, (128 - card_x) / 2, (128 - card_x) / 2 + 32};
|
|
|
|
for(int i = 0; i < n; ++i) {
|
|
widget_add_icon_element(
|
|
app->widget, x_offsets[i], 10 - 2 * spread.selected[i], card[spread.card[i]].icon);
|
|
}
|
|
|
|
// Adjusted cursor position (moved 8px to the right)
|
|
widget_add_icon_element(app->widget, x_offsets[card_selected] - 2 + card_x / 2, 41, &I_cursor);
|
|
|
|
widget_add_string_element(
|
|
app->widget,
|
|
64,
|
|
60,
|
|
AlignCenter,
|
|
AlignBottom,
|
|
FontPrimary,
|
|
card[spread.card[card_selected]].name);
|
|
}
|
|
|
|
static bool widget_input_callback(InputEvent* input_event, void* context) {
|
|
App* app = context;
|
|
int n = 3; // Always 3 cards
|
|
bool consumed = false;
|
|
if(input_event->type == InputTypeShort) {
|
|
switch(input_event->key) {
|
|
case InputKeyRight:
|
|
card_selected++;
|
|
if(card_selected >= n) {
|
|
card_selected = 0;
|
|
}
|
|
consumed = true;
|
|
break;
|
|
case InputKeyLeft:
|
|
card_selected--;
|
|
if(card_selected < 0) {
|
|
card_selected = n - 1;
|
|
}
|
|
consumed = true;
|
|
break;
|
|
default:
|
|
consumed = false;
|
|
break;
|
|
}
|
|
}
|
|
if(consumed) draw_tarot(app);
|
|
return consumed;
|
|
}
|
|
|
|
void tarot_app_scene_on_enter_game(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_enter_game");
|
|
App* app = context;
|
|
int n = 3; // Always 3 cards
|
|
for(int i = 0; i < n; ++i) {
|
|
int unique = 0;
|
|
do {
|
|
spread.card[i] = unbiased_rand(card_number);
|
|
unique = 1;
|
|
for(int j = 0; j < i; ++j) {
|
|
if(spread.card[i] == spread.card[j]) unique = 0;
|
|
}
|
|
} while(!unique);
|
|
// Reversed logic based on setting
|
|
if(app->allow_reversed && unbiased_rand(2))
|
|
spread.card[i] += card_number; // 50% chance reversed
|
|
}
|
|
draw_tarot(app);
|
|
view_set_context(widget_get_view(app->widget), app);
|
|
view_set_input_callback(widget_get_view(app->widget), widget_input_callback);
|
|
view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Widget);
|
|
}
|
|
|
|
bool tarot_app_scene_on_event_game(void* context, SceneManagerEvent event) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_event_game");
|
|
UNUSED(context);
|
|
UNUSED(event);
|
|
return false; // don't handle any events
|
|
}
|
|
|
|
void tarot_app_scene_on_exit_game(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_exit_game");
|
|
App* app = context;
|
|
widget_reset(app->widget);
|
|
}
|
|
|
|
// Forward declarations for settings scene (must be before handler arrays)
|
|
void tarot_app_scene_on_enter_settings(void* context);
|
|
bool tarot_app_scene_on_event_settings(void* context, SceneManagerEvent event);
|
|
void tarot_app_scene_on_exit_settings(void* context);
|
|
|
|
// Forward declarations for deck browser scene
|
|
void tarot_app_scene_on_enter_deck_browser(void* context);
|
|
bool tarot_app_scene_on_event_deck_browser(void* context, SceneManagerEvent event);
|
|
void tarot_app_scene_on_exit_deck_browser(void* context);
|
|
|
|
/* collection of all scene on_enter handlers - in the same order as their enum */
|
|
void (*const tarot_app_scene_on_enter_handlers[])(void*) = {
|
|
tarot_app_scene_on_enter_main_menu,
|
|
tarot_app_scene_on_enter_about,
|
|
tarot_app_scene_on_enter_game,
|
|
tarot_app_scene_on_enter_deck_browser,
|
|
tarot_app_scene_on_enter_settings};
|
|
|
|
/* collection of all scene on event handlers - in the same order as their enum */
|
|
bool (*const tarot_app_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
|
|
tarot_app_scene_on_event_main_menu,
|
|
tarot_app_scene_on_event_about,
|
|
tarot_app_scene_on_event_game,
|
|
tarot_app_scene_on_event_deck_browser,
|
|
tarot_app_scene_on_event_settings};
|
|
|
|
/* collection of all scene on exit handlers - in the same order as their enum */
|
|
void (*const tarot_app_scene_on_exit_handlers[])(void*) = {
|
|
tarot_app_scene_on_exit_main_menu,
|
|
tarot_app_scene_on_exit_about,
|
|
tarot_app_scene_on_exit_game,
|
|
tarot_app_scene_on_exit_deck_browser,
|
|
tarot_app_scene_on_exit_settings};
|
|
|
|
/* collection of all on_enter, on_event, on_exit handlers */
|
|
const SceneManagerHandlers tarot_app_scene_event_handlers = {
|
|
.on_enter_handlers = tarot_app_scene_on_enter_handlers,
|
|
.on_event_handlers = tarot_app_scene_on_event_handlers,
|
|
.on_exit_handlers = tarot_app_scene_on_exit_handlers,
|
|
.scene_num = AppScene_count};
|
|
|
|
/* custom event handler - passes the event to the scene manager */
|
|
bool tarot_app_scene_manager_custom_event_callback(void* context, uint32_t custom_event) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_manager_custom_event_callback");
|
|
furi_assert(context);
|
|
App* app = context;
|
|
return scene_manager_handle_custom_event(app->scene_manager, custom_event);
|
|
}
|
|
|
|
/* navigation event handler - passes the event to the scene manager */
|
|
bool tarot_app_scene_manager_navigation_event_callback(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_manager_navigation_event_callback");
|
|
furi_assert(context);
|
|
App* app = context;
|
|
return scene_manager_handle_back_event(app->scene_manager);
|
|
}
|
|
|
|
/* initialise the scene manager with all handlers */
|
|
void tarot_app_scene_manager_init(App* app) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_manager_init");
|
|
app->scene_manager = scene_manager_alloc(&tarot_app_scene_event_handlers, app);
|
|
}
|
|
|
|
/* initialise the views, and initialise the view dispatcher with all views */
|
|
void tarot_app_view_dispatcher_init(App* app) {
|
|
FURI_LOG_T(TAG, "tarot_app_view_dispatcher_init");
|
|
app->view_dispatcher = view_dispatcher_alloc();
|
|
// allocate each view
|
|
FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init allocating views");
|
|
app->submenu = submenu_alloc();
|
|
app->popup = popup_alloc();
|
|
app->widget = widget_alloc();
|
|
|
|
// assign callback that pass events from views to the scene manager
|
|
FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init setting callbacks");
|
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
|
view_dispatcher_set_custom_event_callback(
|
|
app->view_dispatcher, tarot_app_scene_manager_custom_event_callback);
|
|
view_dispatcher_set_navigation_event_callback(
|
|
app->view_dispatcher, tarot_app_scene_manager_navigation_event_callback);
|
|
|
|
// add views to the dispatcher, indexed by their enum value
|
|
FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init adding view menu");
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher, AppView_Submenu, submenu_get_view(app->submenu));
|
|
|
|
FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init adding view popup");
|
|
view_dispatcher_add_view(app->view_dispatcher, AppView_Popup, popup_get_view(app->popup));
|
|
|
|
FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init adding view widget");
|
|
view_dispatcher_add_view(app->view_dispatcher, AppView_Widget, widget_get_view(app->widget));
|
|
}
|
|
|
|
/* initialise app data, scene manager, and view dispatcher */
|
|
App* tarot_app_init() {
|
|
FURI_LOG_T(TAG, "tarot_app_init");
|
|
App* app = malloc(sizeof(App));
|
|
tarot_app_scene_manager_init(app);
|
|
tarot_app_view_dispatcher_init(app);
|
|
app->allow_reversed = false; // Default to NOT allow reversed cards
|
|
return app;
|
|
}
|
|
|
|
/* free all app data, scene manager, and view dispatcher */
|
|
void tarot_app_free(App* app) {
|
|
FURI_LOG_T(TAG, "tarot_app_free");
|
|
scene_manager_free(app->scene_manager);
|
|
view_dispatcher_remove_view(app->view_dispatcher, AppView_Submenu);
|
|
view_dispatcher_remove_view(app->view_dispatcher, AppView_Popup);
|
|
view_dispatcher_remove_view(app->view_dispatcher, AppView_Widget);
|
|
view_dispatcher_free(app->view_dispatcher);
|
|
submenu_free(app->submenu);
|
|
popup_free(app->popup);
|
|
widget_free(app->widget);
|
|
free(app);
|
|
}
|
|
|
|
/* go to trace log level in the dev environment */
|
|
void tarot_app_set_log_level() {
|
|
#ifdef FURI_DEBUG
|
|
furi_log_set_level(FuriLogLevelTrace);
|
|
#else
|
|
furi_log_set_level(FuriLogLevelInfo);
|
|
#endif
|
|
}
|
|
|
|
/* entrypoint */
|
|
int32_t tarot_app(void* p) {
|
|
UNUSED(p);
|
|
tarot_app_set_log_level();
|
|
|
|
// create the app context struct, scene manager, and view dispatcher
|
|
FURI_LOG_I(TAG, "Tarot app starting...");
|
|
App* app = tarot_app_init();
|
|
|
|
// set the scene and launch the main loop
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
|
scene_manager_next_scene(app->scene_manager, AppScene_MainMenu);
|
|
FURI_LOG_D(TAG, "Starting dispatcher...");
|
|
view_dispatcher_run(app->view_dispatcher);
|
|
|
|
// free all memory
|
|
FURI_LOG_I(TAG, "Tarot app finishing...");
|
|
furi_record_close(RECORD_GUI);
|
|
tarot_app_free(app);
|
|
return 0;
|
|
}
|
|
|
|
/* Deck browser scene */
|
|
static int deck_browser_index = 0;
|
|
|
|
static void draw_deck_browser(void* context) {
|
|
App* app = context;
|
|
widget_reset(app->widget);
|
|
// 22 majors (upright+reversed shown together) + 56 minors = 78 cards
|
|
int total = 78;
|
|
if(deck_browser_index < 0) deck_browser_index = 0;
|
|
if(deck_browser_index >= total) deck_browser_index = total - 1;
|
|
if(deck_browser_index < 22) {
|
|
// Major arcana: show upright and reversed side by side
|
|
int major_idx = deck_browser_index;
|
|
const Card* upright = &card[major_idx];
|
|
const Card* reversed = &card[major_idx + 22];
|
|
// Upright image
|
|
if(upright->icon) {
|
|
widget_add_icon_element(app->widget, 64 - card_x - 8, 10, upright->icon);
|
|
} else {
|
|
widget_add_string_element(
|
|
app->widget, 32, 24, AlignCenter, AlignTop, FontPrimary, "(no image)");
|
|
}
|
|
// Reversed image
|
|
if(reversed->icon) {
|
|
widget_add_icon_element(app->widget, 64 + 8, 10, reversed->icon);
|
|
} else {
|
|
widget_add_string_element(
|
|
app->widget, 88, 24, AlignCenter, AlignTop, FontPrimary, "(no image)");
|
|
}
|
|
// Name (centered below both images)
|
|
widget_add_string_element(
|
|
app->widget, 64, 60, AlignCenter, AlignBottom, FontPrimary, upright->name);
|
|
} else {
|
|
// Minor arcana: upright only
|
|
const Card* c = &card[44 + (deck_browser_index - 22)];
|
|
if(c->icon) {
|
|
widget_add_icon_element(app->widget, (128 - card_x) / 2, 10, c->icon);
|
|
} else {
|
|
widget_add_string_element(
|
|
app->widget, 64, 24, AlignCenter, AlignTop, FontPrimary, "(no image)");
|
|
}
|
|
widget_add_string_element(
|
|
app->widget, 64, 60, AlignCenter, AlignBottom, FontPrimary, c->name);
|
|
}
|
|
// Card number starting from 0 to align the index with the card number
|
|
char idxbuf[16];
|
|
snprintf(idxbuf, sizeof(idxbuf), "%d/%d", deck_browser_index, total - 1);
|
|
widget_add_string_element(app->widget, 124, 2, AlignRight, AlignTop, FontSecondary, idxbuf);
|
|
}
|
|
|
|
static bool deck_browser_input_callback(InputEvent* input_event, void* context) {
|
|
App* app = context;
|
|
bool consumed = false;
|
|
int total = 78;
|
|
if(input_event->type == InputTypeShort) {
|
|
switch(input_event->key) {
|
|
case InputKeyRight:
|
|
deck_browser_index++;
|
|
if(deck_browser_index >= total) deck_browser_index = 0;
|
|
consumed = true;
|
|
break;
|
|
case InputKeyLeft:
|
|
deck_browser_index--;
|
|
if(deck_browser_index < 0) deck_browser_index = total - 1;
|
|
consumed = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if(consumed) draw_deck_browser(app);
|
|
return consumed;
|
|
}
|
|
|
|
void tarot_app_scene_on_enter_deck_browser(void* context) {
|
|
App* app = context;
|
|
deck_browser_index = 0;
|
|
draw_deck_browser(app);
|
|
view_set_context(widget_get_view(app->widget), app);
|
|
view_set_input_callback(widget_get_view(app->widget), deck_browser_input_callback);
|
|
view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Widget);
|
|
}
|
|
|
|
bool tarot_app_scene_on_event_deck_browser(void* context, SceneManagerEvent event) {
|
|
UNUSED(context);
|
|
UNUSED(event);
|
|
return false;
|
|
}
|
|
|
|
void tarot_app_scene_on_exit_deck_browser(void* context) {
|
|
App* app = context;
|
|
widget_reset(app->widget);
|
|
}
|
|
|
|
/* Settings scene */
|
|
|
|
// New: Settings scene state
|
|
static void draw_settings_menu(App* app);
|
|
|
|
static void settings_menu_callback(void* context, uint32_t index) {
|
|
UNUSED(index);
|
|
App* app = context;
|
|
app->allow_reversed = !app->allow_reversed;
|
|
draw_settings_menu(app);
|
|
}
|
|
|
|
static void draw_settings_menu(App* app) {
|
|
submenu_reset(app->submenu);
|
|
submenu_add_item(
|
|
app->submenu,
|
|
app->allow_reversed ? "Reversed cards: On" : "Reversed cards: Off",
|
|
0,
|
|
settings_menu_callback,
|
|
app);
|
|
view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Submenu);
|
|
}
|
|
|
|
void tarot_app_scene_on_enter_settings(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_enter_settings");
|
|
App* app = context;
|
|
draw_settings_menu(app);
|
|
// Do NOT set a custom input callback here!
|
|
// view_set_context and view_set_input_callback are not needed for submenu
|
|
}
|
|
|
|
bool tarot_app_scene_on_event_settings(void* context, SceneManagerEvent event) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_event_settings");
|
|
UNUSED(context);
|
|
UNUSED(event);
|
|
return false;
|
|
}
|
|
|
|
void tarot_app_scene_on_exit_settings(void* context) {
|
|
FURI_LOG_T(TAG, "tarot_app_scene_on_exit_settings");
|
|
App* app = context;
|
|
submenu_reset(app->submenu);
|
|
}
|