[FL-1217] Menu refactoring (#726)

* menu: remove dead code
* loader: change views from modules instead of menu service
* dolphin: start main menu with loader API
* applications: don't start menu service
* loader: add debug tools menu
* gui modules: introduce menu model
* loader: remove calls to menu service API
* gui modules: implement menu module
* loader: add menu view
* gui menu: add animation
* applications: remove menu service
* gui modules: rename icon_menu -> menu
* loader: clean up code
* menu module: add documentation, format code
* menu: remove unused parameter
* desktop: use loader to launch primary menu
* Applications: cleaner makefile app declaration. Loader: application autostart
* Gui: cleanup menu and submenu API.

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
gornekich 2021-09-28 16:10:13 +03:00 committed by GitHub
parent 1c4e6ec74d
commit 61c8f3325a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 587 additions and 972 deletions

View file

@ -9,7 +9,6 @@ extern int32_t dolphin_srv(void* p);
extern int32_t gui_srv(void* p); extern int32_t gui_srv(void* p);
extern int32_t input_srv(void* p); extern int32_t input_srv(void* p);
extern int32_t loader_srv(void* p); extern int32_t loader_srv(void* p);
extern int32_t menu_srv(void* p);
extern int32_t notification_srv(void* p); extern int32_t notification_srv(void* p);
extern int32_t power_observer_srv(void* p); extern int32_t power_observer_srv(void* p);
extern int32_t power_srv(void* p); extern int32_t power_srv(void* p);
@ -87,8 +86,7 @@ const FlipperApplication FLIPPER_SERVICES[] = {
{.app = input_srv, .name = "Input", .stack_size = 1024, .icon = NULL}, {.app = input_srv, .name = "Input", .stack_size = 1024, .icon = NULL},
#endif #endif
#ifdef SRV_MENU #ifdef SRV_LOADER
{.app = menu_srv, .name = "Menu", .stack_size = 1024, .icon = NULL},
{.app = loader_srv, .name = "Loader", .stack_size = 1024, .icon = NULL}, {.app = loader_srv, .name = "Loader", .stack_size = 1024, .icon = NULL},
#endif #endif
@ -107,43 +105,6 @@ const FlipperApplication FLIPPER_SERVICES[] = {
#ifdef SRV_STORAGE #ifdef SRV_STORAGE
{.app = storage_srv, .name = "Storage", .stack_size = 4096, .icon = NULL}, {.app = storage_srv, .name = "Storage", .stack_size = 4096, .icon = NULL},
#endif #endif
/* Fake services (autorun) */
#ifdef SRV_BLINK
{.app = blink_test_app, .name = "Blink", .stack_size = 1024, .icon = &A_Plugins_14},
#endif
#ifdef SRV_LF_RFID
{.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_Plugins_14},
#endif
#ifdef SRV_IRDA
{.app = irda_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Plugins_14},
#endif
#ifdef SRV_MUSIC_PLAYER
{.app = music_player_app, .name = "Music Player", .stack_size = 1024, .icon = &A_Plugins_14},
#endif
#ifdef SRV_IBUTTON
{.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_Plugins_14},
#endif
#ifdef SRV_GPIO_TEST
{.app = gpio_test_app, .name = "GPIO Test", .stack_size = 1024, .icon = &A_Plugins_14},
#endif
#ifdef SRV_KEYPAD_TEST
{.app = keypad_test_app, .name = "Keypad Test", .stack_size = 1024, .icon = &A_Plugins_14},
#endif
#ifdef SRV_ACCESSOR
{.app = accessor_app, .name = "Accessor", .stack_size = 4096, .icon = &A_Plugins_14},
#endif
#ifdef SRV_STORAGE_TEST
{.app = storage_test_app, .name = "Storage Test", .stack_size = 1024, .icon = &A_Plugins_14},
#endif
}; };
const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperApplication); const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperApplication);
@ -184,25 +145,35 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = {
#ifdef SRV_CLI #ifdef SRV_CLI
crypto_cli_init, crypto_cli_init,
#endif #endif
#ifdef APP_IRDA
irda_cli_init, irda_cli_init,
#endif
#ifdef APP_NFC #ifdef APP_NFC
nfc_cli_init, nfc_cli_init,
#endif #endif
#ifdef APP_SUBGHZ #ifdef APP_SUBGHZ
subghz_cli_init, subghz_cli_init,
#endif #endif
#ifdef APP_LF_RFID #ifdef APP_LF_RFID
lfrfid_cli_init, lfrfid_cli_init,
#endif #endif
#ifdef APP_IBUTTON #ifdef APP_IBUTTON
ibutton_cli_init, ibutton_cli_init,
#endif #endif
#ifdef SRV_BT #ifdef SRV_BT
bt_cli_init, bt_cli_init,
#endif #endif
#ifdef SRV_POWER #ifdef SRV_POWER
power_cli_init, power_cli_init,
#endif #endif
#ifdef SRV_STORAGE #ifdef SRV_STORAGE
storage_cli_init, storage_cli_init,
#endif #endif

View file

@ -6,9 +6,6 @@ C_SOURCES += $(shell find $(APP_DIR) -name *.c)
CPP_SOURCES += $(shell find $(APP_DIR) -name *.cpp) CPP_SOURCES += $(shell find $(APP_DIR) -name *.cpp)
# Use SRV_* for autostart app
# Use APP_* for add app to build
APP_RELEASE ?= 1 APP_RELEASE ?= 1
ifeq ($(APP_RELEASE), 1) ifeq ($(APP_RELEASE), 1)
# Services # Services
@ -18,7 +15,7 @@ SRV_DIALOGS = 1
SRV_DOLPHIN = 1 SRV_DOLPHIN = 1
SRV_GUI = 1 SRV_GUI = 1
SRV_INPUT = 1 SRV_INPUT = 1
SRV_MENU = 1 SRV_LOADER = 1
SRV_NOTIFICATION = 1 SRV_NOTIFICATION = 1
SRV_POWER = 1 SRV_POWER = 1
SRV_POWER_OBSERVER = 1 SRV_POWER_OBSERVER = 1
@ -49,230 +46,203 @@ APP_VIBRO_DEMO = 1
endif endif
SRV_BT ?= 0 # Applications
ifeq ($(SRV_BT), 1) # that will be shown in menu
SRV_CLI = 1 # Prefix with APP_*
CFLAGS += -DSRV_BT
endif
SRV_DOLPHIN ?= 0
ifeq ($(SRV_DOLPHIN), 1)
SRV_MENU = 1
CFLAGS += -DSRV_DOLPHIN
endif
SRV_POWER ?= 0
ifeq ($(SRV_POWER), 1)
SRV_GUI = 1
SRV_CLI = 1
CFLAGS += -DSRV_POWER
endif
SRV_POWER_OBSERVER ?= 0
ifeq ($(SRV_POWER_OBSERVER), 1)
SRV_POWER = 1
CFLAGS += -DSRV_POWER_OBSERVER
endif
SRV_MENU ?= 0
ifeq ($(SRV_MENU), 1)
CFLAGS += -DSRV_MENU
APP_MENU = 1
endif
APP_MENU ?= 0
ifeq ($(APP_MENU), 1)
SRV_INPUT = 1
SRV_GUI = 1
CFLAGS += -DAPP_MENU
endif
APP_IRDA_MONITOR ?= 0 APP_IRDA_MONITOR ?= 0
ifeq ($(APP_IRDA_MONITOR), 1) ifeq ($(APP_IRDA_MONITOR), 1)
CFLAGS += -DAPP_IRDA_MONITOR CFLAGS += -DAPP_IRDA_MONITOR
SRV_GUI = 1
endif endif
APP_UNIT_TESTS ?= 0 APP_UNIT_TESTS ?= 0
ifeq ($(APP_UNIT_TESTS), 1) ifeq ($(APP_UNIT_TESTS), 1)
CFLAGS += -DAPP_UNIT_TESTS CFLAGS += -DAPP_UNIT_TESTS
endif endif
SRV_DESKTOP ?= 0
ifeq ($(SRV_DESKTOP), 1)
CFLAGS += -DSRV_DESKTOP
SRV_DESKTOP = 1
endif
APP_ARCHIVE ?= 0 APP_ARCHIVE ?= 0
ifeq ($(APP_NFC), 1) ifeq ($(APP_ARCHIVE), 1)
CFLAGS += -DAPP_ARCHIVE CFLAGS += -DAPP_ARCHIVE
APP_ARCHIVE = 1 SRV_GUI = 1
endif endif
SRV_BLINK ?= 0
ifeq ($(SRV_BLINK), 1)
CFLAGS += -DSRV_BLINK
APP_BLINK = 1
endif
APP_BLINK ?= 0 APP_BLINK ?= 0
ifeq ($(APP_BLINK), 1) ifeq ($(APP_BLINK), 1)
CFLAGS += -DAPP_BLINK CFLAGS += -DAPP_BLINK
SRV_INPUT = 1 SRV_GUI = 1
endif endif
SRV_UART_WRITE ?= 0
ifeq ($(SRV_UART_WRITE), 1)
CFLAGS += -DSRV_UART_WRITE
APP_UART_WRITE = 1
endif
APP_UART_WRITE ?= 0
ifeq ($(APP_UART_WRITE), 1)
CFLAGS += -DAPP_UART_WRITE
endif
SRV_IPC ?= 0
ifeq ($(SRV_IPC), 1)
CFLAGS += -DSRV_IPC
APP_IPC = 1
endif
APP_IPC ?= 0
ifeq ($(APP_IPC), 1)
CFLAGS += -DAPP_IPC
endif
APP_SUBGHZ ?= 0
ifeq ($(APP_SUBGHZ), 1) ifeq ($(APP_SUBGHZ), 1)
CFLAGS += -DAPP_SUBGHZ CFLAGS += -DAPP_SUBGHZ
SRV_INPUT = 1
SRV_GUI = 1 SRV_GUI = 1
SRV_CLI = 1 SRV_CLI = 1
endif endif
APP_ABOUT ?= 0
ifeq ($(APP_ABOUT), 1) ifeq ($(APP_ABOUT), 1)
CFLAGS += -DAPP_ABOUT CFLAGS += -DAPP_ABOUT
SRV_INPUT = 1
SRV_GUI = 1 SRV_GUI = 1
endif endif
SRV_LF_RFID ?= 0
ifeq ($(SRV_LF_RFID), 1)
CFLAGS += -DSRV_LF_RFID
APP_LF_RFID = 1
endif
APP_LF_RFID ?= 0 APP_LF_RFID ?= 0
ifeq ($(APP_LF_RFID), 1) ifeq ($(APP_LF_RFID), 1)
CFLAGS += -DAPP_LF_RFID CFLAGS += -DAPP_LF_RFID
SRV_INPUT = 1
SRV_GUI = 1 SRV_GUI = 1
endif endif
APP_NFC ?= 0 APP_NFC ?= 0
ifeq ($(APP_NFC), 1) ifeq ($(APP_NFC), 1)
CFLAGS += -DAPP_NFC CFLAGS += -DAPP_NFC
SRV_MENU = 1
SRV_INPUT = 1
SRV_GUI = 1 SRV_GUI = 1
endif endif
SRV_IRDA ?= 0
ifeq ($(SRV_IRDA), 1)
CFLAGS += -DSRV_IRDA
APP_IRDA = 1
endif
APP_IRDA ?= 0 APP_IRDA ?= 0
ifeq ($(APP_IRDA), 1) ifeq ($(APP_IRDA), 1)
CFLAGS += -DAPP_IRDA CFLAGS += -DAPP_IRDA
SRV_INPUT = 1
SRV_GUI = 1 SRV_GUI = 1
endif endif
APP_VIBRO_DEMO ?= 0 APP_VIBRO_DEMO ?= 0
ifeq ($(APP_VIBRO_DEMO), 1) ifeq ($(APP_VIBRO_DEMO), 1)
CFLAGS += -DAPP_VIBRO_DEMO CFLAGS += -DAPP_VIBRO_DEMO
SRV_INPUT = 1 SRV_GUI = 1
endif endif
SRV_KEYPAD_TEST ?= 0
ifeq ($(SRV_KEYPAD_TEST), 1)
CFLAGS += -DSRV_KEYPAD_TEST
APP_KEYPAD_TEST = 1
endif
APP_KEYPAD_TEST ?= 0 APP_KEYPAD_TEST ?= 0
ifeq ($(APP_KEYPAD_TEST), 1) ifeq ($(APP_KEYPAD_TEST), 1)
CFLAGS += -DAPP_KEYPAD_TEST CFLAGS += -DAPP_KEYPAD_TEST
APP_KEYPAD_TEST = 1 SRV_GUI = 1
endif endif
SRV_ACCESSOR ?= 0
ifeq ($(SRV_ACCESSOR), 1)
CFLAGS += -DSRV_ACCESSOR
APP_ACCESSOR = 1
endif
APP_ACCESSOR ?= 0 APP_ACCESSOR ?= 0
ifeq ($(APP_ACCESSOR), 1) ifeq ($(APP_ACCESSOR), 1)
CFLAGS += -DAPP_ACCESSOR CFLAGS += -DAPP_ACCESSOR
APP_ACCESSOR = 1 SRV_GUI = 1
endif endif
SRV_GPIO_TEST ?= 0
ifeq ($(SRV_GPIO_TEST), 1)
CFLAGS += -DSRV_GPIO_TEST
APP_GPIO_TEST = 1
endif
APP_GPIO_TEST ?= 0 APP_GPIO_TEST ?= 0
ifeq ($(APP_GPIO_TEST), 1) ifeq ($(APP_GPIO_TEST), 1)
CFLAGS += -DAPP_GPIO_TEST CFLAGS += -DAPP_GPIO_TEST
SRV_GUI = 1
endif endif
SRV_MUSIC_PLAYER ?= 0
ifeq ($(SRV_MUSIC_PLAYER), 1)
CFLAGS += -DSRV_MUSIC_PLAYER
APP_MUSIC_PLAYER = 1
endif
APP_MUSIC_PLAYER ?= 0 APP_MUSIC_PLAYER ?= 0
ifeq ($(APP_MUSIC_PLAYER), 1) ifeq ($(APP_MUSIC_PLAYER), 1)
CFLAGS += -DAPP_MUSIC_PLAYER CFLAGS += -DAPP_MUSIC_PLAYER
SRV_GUI = 1
endif endif
SRV_IBUTTON ?= 0
ifeq ($(SRV_IBUTTON), 1)
CFLAGS += -DSRV_IBUTTON
APP_IBUTTON = 1
endif
APP_IBUTTON ?= 0 APP_IBUTTON ?= 0
ifeq ($(APP_IBUTTON), 1) ifeq ($(APP_IBUTTON), 1)
CFLAGS += -DAPP_IBUTTON CFLAGS += -DAPP_IBUTTON
SRV_GUI = 1
endif
# Services
# that will start with OS
# Prefix with SRV_*
SRV_BT ?= 0
ifeq ($(SRV_BT), 1)
CFLAGS += -DSRV_BT
SRV_CLI = 1
endif
SRV_DESKTOP ?= 0
ifeq ($(SRV_DESKTOP), 1)
CFLAGS += -DSRV_DESKTOP
SRV_LOADER = 1
SRV_GUI = 1
endif
SRV_DOLPHIN ?= 0
ifeq ($(SRV_DOLPHIN), 1)
CFLAGS += -DSRV_DOLPHIN
endif
SRV_POWER_OBSERVER ?= 0
ifeq ($(SRV_POWER_OBSERVER), 1)
CFLAGS += -DSRV_POWER_OBSERVER
SRV_POWER = 1
endif
SRV_POWER ?= 0
ifeq ($(SRV_POWER), 1)
CFLAGS += -DSRV_POWER
SRV_GUI = 1
SRV_CLI = 1
endif
SRV_LOADER ?= 0
ifeq ($(SRV_LOADER), 1)
CFLAGS += -DSRV_LOADER
SRV_GUI = 1
# Loader autostart hook
LOADER_AUTOSTART ?= ""
ifneq ($(strip $(LOADER_AUTOSTART)),)
CFLAGS += -DLOADER_AUTOSTART="\"$(LOADER_AUTOSTART)\""
endif
# Loader autostart hook END
endif
SRV_DIALOGS ?= 0
ifeq ($(SRV_DIALOGS), 1)
CFLAGS += -DSRV_DIALOGS
SRV_GUI = 1
endif endif
#
# Essential services
#
SRV_GUI ?= 0 SRV_GUI ?= 0
ifeq ($(SRV_GUI), 1) ifeq ($(SRV_GUI), 1)
CFLAGS += -DSRV_GUI CFLAGS += -DSRV_GUI
SRV_INPUT = 1
endif endif
SRV_INPUT ?= 0 SRV_INPUT ?= 0
ifeq ($(SRV_INPUT), 1) ifeq ($(SRV_INPUT), 1)
CFLAGS += -DSRV_INPUT CFLAGS += -DSRV_INPUT
endif endif
SRV_CLI ?= 0 SRV_CLI ?= 0
ifeq ($(SRV_CLI), 1) ifeq ($(SRV_CLI), 1)
SRV_GUI = 1
CFLAGS += -DSRV_CLI CFLAGS += -DSRV_CLI
endif endif
SRV_NOTIFICATION ?= 0 SRV_NOTIFICATION ?= 0
ifeq ($(SRV_NOTIFICATION), 1) ifeq ($(SRV_NOTIFICATION), 1)
CFLAGS += -DSRV_NOTIFICATION CFLAGS += -DSRV_NOTIFICATION
endif endif
SRV_STORAGE ?= 0 SRV_STORAGE ?= 0
ifeq ($(SRV_STORAGE), 1) ifeq ($(SRV_STORAGE), 1)
CFLAGS += -DSRV_STORAGE CFLAGS += -DSRV_STORAGE
endif endif
SRV_DIALOGS ?= 0
ifeq ($(SRV_DIALOGS), 1)
CFLAGS += -DSRV_DIALOGS
endif

View file

@ -21,7 +21,6 @@ bool desktop_back_event_callback(void* context) {
Desktop* desktop_alloc() { Desktop* desktop_alloc() {
Desktop* desktop = furi_alloc(sizeof(Desktop)); Desktop* desktop = furi_alloc(sizeof(Desktop));
desktop->menu_vm = furi_record_open("menu");
desktop->gui = furi_record_open("gui"); desktop->gui = furi_record_open("gui");
desktop->scene_thread = furi_thread_alloc(); desktop->scene_thread = furi_thread_alloc();
desktop->view_dispatcher = view_dispatcher_alloc(); desktop->view_dispatcher = view_dispatcher_alloc();
@ -101,7 +100,6 @@ void desktop_free(Desktop* desktop) {
furi_thread_free(desktop->scene_thread); furi_thread_free(desktop->scene_thread);
furi_record_close("menu"); furi_record_close("menu");
desktop->menu_vm = NULL;
free(desktop); free(desktop);
} }

View file

@ -2,7 +2,6 @@
#include <furi.h> #include <furi.h>
#include <furi-hal.h> #include <furi-hal.h>
#include <menu/menu.h>
#include <gui/gui.h> #include <gui/gui.h>
#include <gui/view_dispatcher.h> #include <gui/view_dispatcher.h>
#include <gui/scene_manager.h> #include <gui/scene_manager.h>
@ -34,8 +33,6 @@ typedef enum {
} DesktopViewEnum; } DesktopViewEnum;
struct Desktop { struct Desktop {
// Menu
ValueMutex* menu_vm;
// Scene // Scene
FuriThread* scene_thread; FuriThread* scene_thread;
// GUI // GUI

View file

@ -1,6 +1,7 @@
#include "../desktop_i.h" #include "../desktop_i.h"
#include "../views/desktop_main.h" #include "../views/desktop_main.h"
#include "applications.h" #include "applications.h"
#include <loader/loader.h>
#define MAIN_VIEW_DEFAULT (0UL) #define MAIN_VIEW_DEFAULT (0UL)
static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) {
@ -48,8 +49,7 @@ const bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) { switch(event.event) {
case DesktopMainEventOpenMenu: case DesktopMainEventOpenMenu:
with_value_mutex( loader_show_menu();
desktop->menu_vm, (Menu * menu) { menu_ok(menu); });
consumed = true; consumed = true;
break; break;

199
applications/gui/modules/menu.c Executable file
View file

@ -0,0 +1,199 @@
#include "menu.h"
#include <m-array.h>
#include <gui/elements.h>
#include <furi.h>
struct Menu {
View* view;
};
typedef struct {
const char* label;
IconAnimation* icon;
uint32_t index;
MenuItemCallback callback;
void* callback_context;
} MenuItem;
ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST);
typedef struct {
MenuItemArray_t items;
uint8_t position;
} MenuModel;
static void menu_process_up(Menu* menu);
static void menu_process_down(Menu* menu);
static void menu_process_ok(Menu* menu);
static void menu_draw_callback(Canvas* canvas, void* _model) {
MenuModel* model = _model;
canvas_clear(canvas);
uint8_t position = model->position;
size_t items_count = MenuItemArray_size(model->items);
if(items_count) {
MenuItem* item;
size_t shift_position;
// First line
canvas_set_font(canvas, FontSecondary);
shift_position = (0 + position + items_count - 1) % items_count;
item = MenuItemArray_get(model->items, shift_position);
if(item->icon) {
canvas_draw_icon_animation(canvas, 4, 3, item->icon);
icon_animation_stop(item->icon);
}
canvas_draw_str(canvas, 22, 14, item->label);
// Second line main
canvas_set_font(canvas, FontPrimary);
shift_position = (1 + position + items_count - 1) % items_count;
item = MenuItemArray_get(model->items, shift_position);
if(item->icon) {
canvas_draw_icon_animation(canvas, 4, 25, item->icon);
icon_animation_start(item->icon);
}
canvas_draw_str(canvas, 22, 36, item->label);
// Third line
canvas_set_font(canvas, FontSecondary);
shift_position = (2 + position + items_count - 1) % items_count;
item = MenuItemArray_get(model->items, shift_position);
if(item->icon) {
canvas_draw_icon_animation(canvas, 4, 47, item->icon);
icon_animation_stop(item->icon);
}
canvas_draw_str(canvas, 22, 58, item->label);
// Frame and scrollbar
elements_frame(canvas, 0, 21, 128 - 5, 21);
elements_scrollbar(canvas, position, items_count);
} else {
canvas_draw_str(canvas, 2, 32, "Empty");
elements_scrollbar(canvas, 0, 0);
}
}
static bool menu_input_callback(InputEvent* event, void* context) {
Menu* menu = context;
bool consumed = false;
if(event->type == InputTypeShort) {
if(event->key == InputKeyUp) {
consumed = true;
menu_process_up(menu);
} else if(event->key == InputKeyDown) {
consumed = true;
menu_process_down(menu);
} else if(event->key == InputKeyOk) {
consumed = true;
menu_process_ok(menu);
}
}
return consumed;
}
Menu* menu_alloc() {
Menu* menu = furi_alloc(sizeof(Menu));
menu->view = view_alloc(menu->view);
view_set_context(menu->view, menu);
view_allocate_model(menu->view, ViewModelTypeLocking, sizeof(MenuModel));
view_set_draw_callback(menu->view, menu_draw_callback);
view_set_input_callback(menu->view, menu_input_callback);
with_view_model(
menu->view, (MenuModel * model) {
MenuItemArray_init(model->items);
model->position = 0;
return true;
});
return menu;
}
void menu_free(Menu* menu) {
furi_assert(menu);
with_view_model(
menu->view, (MenuModel * model) {
MenuItemArray_clear(model->items);
return true;
});
view_free(menu->view);
free(menu);
}
View* menu_get_view(Menu* menu) {
furi_assert(menu);
return (menu->view);
}
void menu_add_item(
Menu* menu,
const char* label,
IconAnimation* icon,
uint32_t index,
MenuItemCallback callback,
void* context) {
furi_assert(menu);
furi_assert(label);
MenuItem* item = NULL;
with_view_model(
menu->view, (MenuModel * model) {
item = MenuItemArray_push_new(model->items);
item->label = label;
item->icon = icon;
item->index = index;
item->callback = callback;
item->callback_context = context;
return true;
});
}
void menu_clean(Menu* menu) {
furi_assert(menu);
with_view_model(
menu->view, (MenuModel * model) {
MenuItemArray_clean(model->items);
model->position = 0;
return true;
});
}
static void menu_process_up(Menu* menu) {
with_view_model(
menu->view, (MenuModel * model) {
if(model->position > 0) {
model->position--;
} else {
model->position = MenuItemArray_size(model->items) - 1;
}
return true;
});
}
static void menu_process_down(Menu* menu) {
with_view_model(
menu->view, (MenuModel * model) {
if(model->position < MenuItemArray_size(model->items) - 1) {
model->position++;
} else {
model->position = 0;
}
return true;
});
}
static void menu_process_ok(Menu* menu) {
MenuItem* item = NULL;
with_view_model(
menu->view, (MenuModel * model) {
if(model->position < MenuItemArray_size(model->items)) {
item = MenuItemArray_get(model->items, model->position);
}
return true;
});
if(item && item->callback) {
item->callback(item->callback_context, item->index);
}
}

52
applications/gui/modules/menu.h Executable file
View file

@ -0,0 +1,52 @@
#pragma once
#include <gui/view.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Menu anonymous structure */
typedef struct Menu Menu;
typedef void (*MenuItemCallback)(void* context, uint32_t index);
/** Menu allocation and initialization
* @return Menu instance
*/
Menu* menu_alloc();
/** Free menu
* @param menu - Menu instance
*/
void menu_free(Menu* menu);
/** Get Menu view
* @param menu - Menu instance
* @return View instance
*/
View* menu_get_view(Menu* menu);
/** Add item to menu
* @param menu - Menu instance
* @param label - menu item string label
* @param icon - IconAnimation instance
* @param index - menu item index
* @param callback - MenuItemCallback instance
* @param context - pointer to context
*/
void menu_add_item(
Menu* menu,
const char* label,
IconAnimation* icon,
uint32_t index,
MenuItemCallback callback,
void* context);
/** Clean menu
* Note: this function does not free menu instance
* @param menu - Menu instance
*/
void menu_clean(Menu* menu);
#ifdef __cplusplus
}
#endif

View file

@ -1,23 +1,22 @@
#include "submenu.h" #include "submenu.h"
#include "gui/canvas.h"
#include <m-array.h> #include <m-array.h>
#include <furi.h>
#include <gui/elements.h> #include <gui/elements.h>
#include <stdint.h> #include <furi.h>
struct SubmenuItem {
const char* label;
uint32_t index;
SubmenuItemCallback callback;
void* callback_context;
};
ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST);
struct Submenu { struct Submenu {
View* view; View* view;
}; };
typedef struct {
const char* label;
uint32_t index;
SubmenuItemCallback callback;
void* callback_context;
} SubmenuItem;
ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST);
typedef struct { typedef struct {
SubmenuItemArray_t items; SubmenuItemArray_t items;
const char* header; const char* header;
@ -149,7 +148,7 @@ View* submenu_get_view(Submenu* submenu) {
return submenu->view; return submenu->view;
} }
SubmenuItem* submenu_add_item( void submenu_add_item(
Submenu* submenu, Submenu* submenu,
const char* label, const char* label,
uint32_t index, uint32_t index,
@ -168,8 +167,6 @@ SubmenuItem* submenu_add_item(
item->callback_context = callback_context; item->callback_context = callback_context;
return true; return true;
}); });
return item;
} }
void submenu_clean(Submenu* submenu) { void submenu_clean(Submenu* submenu) {

View file

@ -7,7 +7,6 @@ extern "C" {
/* Submenu anonymous structure */ /* Submenu anonymous structure */
typedef struct Submenu Submenu; typedef struct Submenu Submenu;
typedef struct SubmenuItem SubmenuItem;
typedef void (*SubmenuItemCallback)(void* context, uint32_t index); typedef void (*SubmenuItemCallback)(void* context, uint32_t index);
/** /**
@ -36,9 +35,8 @@ View* submenu_get_view(Submenu* submenu);
* @param index - menu item index, used for callback, may be the same with other items * @param index - menu item index, used for callback, may be the same with other items
* @param callback - menu item callback * @param callback - menu item callback
* @param callback_context - menu item callback context * @param callback_context - menu item callback context
* @return SubmenuItem instance that can be used to modify or delete that item
*/ */
SubmenuItem* submenu_add_item( void submenu_add_item(
Submenu* submenu, Submenu* submenu,
const char* label, const char* label,
uint32_t index, uint32_t index,

243
applications/loader/loader.c Normal file → Executable file
View file

@ -1,8 +1,11 @@
#include "loader_i.h" #include "loader_i.h"
#define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0)
#define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU)
static Loader* loader_instance = NULL; static Loader* loader_instance = NULL;
static void loader_menu_callback(void* _ctx) { static void loader_menu_callback(void* _ctx, uint32_t index) {
const FlipperApplication* flipper_app = _ctx; const FlipperApplication* flipper_app = _ctx;
furi_assert(flipper_app->app); furi_assert(flipper_app->app);
@ -27,6 +30,11 @@ static void loader_menu_callback(void* _ctx) {
furi_thread_start(loader_instance->thread); furi_thread_start(loader_instance->thread);
} }
static void loader_submenu_callback(void* context, uint32_t index) {
uint32_t view_id = (uint32_t)context;
view_dispatcher_switch_to_view(loader_instance->view_dispatcher, view_id);
}
static void loader_cli_callback(Cli* cli, string_t args, void* _ctx) { static void loader_cli_callback(Cli* cli, string_t args, void* _ctx) {
furi_assert(_ctx); furi_assert(_ctx);
const FlipperApplication* flipper_app = (FlipperApplication*)_ctx; const FlipperApplication* flipper_app = (FlipperApplication*)_ctx;
@ -60,6 +68,15 @@ bool loader_start(Loader* instance, const char* name, const char* args) {
} }
} }
if(!flipper_app) {
for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
if(strcmp(FLIPPER_DEBUG_APPS[i].name, name) == 0) {
flipper_app = &FLIPPER_DEBUG_APPS[i];
break;
}
}
}
if(!flipper_app) { if(!flipper_app) {
FURI_LOG_E(LOADER_LOG_TAG, "Can't find application with name %s", name); FURI_LOG_E(LOADER_LOG_TAG, "Can't find application with name %s", name);
return false; return false;
@ -138,6 +155,14 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con
} }
} }
static uint32_t loader_hide_menu(void* context) {
return VIEW_NONE;
}
static uint32_t loader_back_to_primary_menu(void* context) {
return LoaderMenuViewPrimary;
}
static Loader* loader_alloc() { static Loader* loader_alloc() {
Loader* instance = furi_alloc(sizeof(Loader)); Loader* instance = furi_alloc(sizeof(Loader));
@ -150,10 +175,45 @@ static Loader* loader_alloc() {
instance->mutex = osMutexNew(NULL); instance->mutex = osMutexNew(NULL);
instance->menu_vm = furi_record_open("menu");
instance->cli = furi_record_open("cli"); instance->cli = furi_record_open("cli");
instance->loader_thread = osThreadGetId();
// Gui
instance->gui = furi_record_open("gui");
instance->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_attach_to_gui(
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
// Primary menu
instance->primary_menu = menu_alloc();
view_set_previous_callback(menu_get_view(instance->primary_menu), loader_hide_menu);
view_dispatcher_add_view(
instance->view_dispatcher, LoaderMenuViewPrimary, menu_get_view(instance->primary_menu));
// Plugins menu
instance->plugins_menu = submenu_alloc();
view_set_previous_callback(
submenu_get_view(instance->plugins_menu), loader_back_to_primary_menu);
view_dispatcher_add_view(
instance->view_dispatcher,
LoaderMenuViewPlugins,
submenu_get_view(instance->plugins_menu));
// Debug menu
instance->debug_menu = submenu_alloc();
view_set_previous_callback(
submenu_get_view(instance->debug_menu), loader_back_to_primary_menu);
view_dispatcher_add_view(
instance->view_dispatcher, LoaderMenuViewDebug, submenu_get_view(instance->debug_menu));
// Settings menu
instance->settings_menu = submenu_alloc();
view_set_previous_callback(
submenu_get_view(instance->settings_menu), loader_back_to_primary_menu);
view_dispatcher_add_view(
instance->view_dispatcher,
LoaderMenuViewSettings,
submenu_get_view(instance->settings_menu));
view_dispatcher_enable_queue(instance->view_dispatcher);
return instance; return instance;
} }
@ -162,133 +222,111 @@ static void loader_free(Loader* instance) {
furi_record_close("cli"); furi_record_close("cli");
furi_record_close("menu");
osMutexDelete(instance->mutex); osMutexDelete(instance->mutex);
string_clear(instance->args); string_clear(instance->args);
furi_thread_free(instance->thread); furi_thread_free(instance->thread);
menu_free(loader_instance->primary_menu);
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary);
submenu_free(loader_instance->plugins_menu);
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPlugins);
submenu_free(loader_instance->debug_menu);
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewDebug);
submenu_free(loader_instance->settings_menu);
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewSettings);
view_dispatcher_free(loader_instance->view_dispatcher);
furi_record_close("gui");
free(instance); free(instance);
instance = NULL;
}
static void loader_add_cli_command(FlipperApplication* app) {
string_t cli_name;
string_init_printf(cli_name, "app_%s", app->name);
cli_add_command(
loader_instance->cli,
string_get_cstr(cli_name),
CliCommandFlagDefault,
loader_cli_callback,
app);
string_clear(cli_name);
} }
static void loader_build_menu() { static void loader_build_menu() {
FURI_LOG_I(LOADER_LOG_TAG, "Building main menu"); FURI_LOG_I(LOADER_LOG_TAG, "Building main menu");
with_value_mutex( size_t i;
loader_instance->menu_vm, (Menu * menu) { for(i = 0; i < FLIPPER_APPS_COUNT; i++) {
for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { loader_add_cli_command((FlipperApplication*)&FLIPPER_APPS[i]);
// Add menu item menu_add_item(
menu_item_add( loader_instance->primary_menu,
menu,
menu_item_alloc_function(
FLIPPER_APPS[i].name, FLIPPER_APPS[i].name,
FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL, FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL,
i,
loader_menu_callback, loader_menu_callback,
(void*)&FLIPPER_APPS[i]));
// Add cli command
string_t cli_name;
string_init_set_str(cli_name, "app_");
string_cat_str(cli_name, FLIPPER_APPS[i].name);
cli_add_command(
loader_instance->cli,
string_get_cstr(cli_name),
CliCommandFlagDefault,
loader_cli_callback,
(void*)&FLIPPER_APPS[i]); (void*)&FLIPPER_APPS[i]);
string_clear(cli_name);
} }
}); menu_add_item(
loader_instance->primary_menu,
"Plugins",
icon_animation_alloc(&A_Plugins_14),
i++,
loader_submenu_callback,
(void*)LoaderMenuViewPlugins);
menu_add_item(
loader_instance->primary_menu,
"Debug tools",
icon_animation_alloc(&A_Debug_14),
i++,
loader_submenu_callback,
(void*)LoaderMenuViewDebug);
menu_add_item(
loader_instance->primary_menu,
"Settings",
icon_animation_alloc(&A_Settings_14),
i++,
loader_submenu_callback,
(void*)LoaderMenuViewSettings);
FURI_LOG_I(LOADER_LOG_TAG, "Building plugins menu"); FURI_LOG_I(LOADER_LOG_TAG, "Building plugins menu");
with_value_mutex( for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) {
loader_instance->menu_vm, (Menu * menu) { loader_add_cli_command((FlipperApplication*)&FLIPPER_PLUGINS[i]);
MenuItem* menu_plugins = submenu_add_item(
menu_item_alloc_menu("Plugins", icon_animation_alloc(&A_Plugins_14)); loader_instance->plugins_menu,
for(size_t i = 0; i < FLIPPER_PLUGINS_COUNT; i++) {
// Add menu item
menu_item_subitem_add(
menu_plugins,
menu_item_alloc_function(
FLIPPER_PLUGINS[i].name, FLIPPER_PLUGINS[i].name,
FLIPPER_PLUGINS[i].icon ? icon_animation_alloc(FLIPPER_PLUGINS[i].icon) : i,
NULL,
loader_menu_callback, loader_menu_callback,
(void*)&FLIPPER_PLUGINS[i]));
// Add cli command
string_t cli_name;
string_init_set_str(cli_name, "app_");
string_cat_str(cli_name, FLIPPER_PLUGINS[i].name);
cli_add_command(
loader_instance->cli,
string_get_cstr(cli_name),
CliCommandFlagDefault,
loader_cli_callback,
(void*)&FLIPPER_PLUGINS[i]); (void*)&FLIPPER_PLUGINS[i]);
string_clear(cli_name);
} }
menu_item_add(menu, menu_plugins);
});
FURI_LOG_I(LOADER_LOG_TAG, "Building debug menu"); FURI_LOG_I(LOADER_LOG_TAG, "Building debug menu");
with_value_mutex( for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
loader_instance->menu_vm, (Menu * menu) { loader_add_cli_command((FlipperApplication*)&FLIPPER_DEBUG_APPS[i]);
MenuItem* menu_debug = submenu_add_item(
menu_item_alloc_menu("Debug tools", icon_animation_alloc(&A_Debug_14)); loader_instance->debug_menu,
for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
// Add menu item
menu_item_subitem_add(
menu_debug,
menu_item_alloc_function(
FLIPPER_DEBUG_APPS[i].name, FLIPPER_DEBUG_APPS[i].name,
FLIPPER_DEBUG_APPS[i].icon ? i,
icon_animation_alloc(FLIPPER_DEBUG_APPS[i].icon) :
NULL,
loader_menu_callback, loader_menu_callback,
(void*)&FLIPPER_DEBUG_APPS[i]));
// Add cli command
string_t cli_name;
string_init_set_str(cli_name, "app_");
string_cat_str(cli_name, FLIPPER_DEBUG_APPS[i].name);
cli_add_command(
loader_instance->cli,
string_get_cstr(cli_name),
CliCommandFlagDefault,
loader_cli_callback,
(void*)&FLIPPER_DEBUG_APPS[i]); (void*)&FLIPPER_DEBUG_APPS[i]);
string_clear(cli_name);
} }
menu_item_add(menu, menu_debug);
});
FURI_LOG_I(LOADER_LOG_TAG, "Building settings menu"); FURI_LOG_I(LOADER_LOG_TAG, "Building settings menu");
with_value_mutex( for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
loader_instance->menu_vm, (Menu * menu) { submenu_add_item(
MenuItem* menu_debug = loader_instance->settings_menu,
menu_item_alloc_menu("Settings", icon_animation_alloc(&A_Settings_14));
for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
// Add menu item
menu_item_subitem_add(
menu_debug,
menu_item_alloc_function(
FLIPPER_SETTINGS_APPS[i].name, FLIPPER_SETTINGS_APPS[i].name,
FLIPPER_SETTINGS_APPS[i].icon ? i,
icon_animation_alloc(FLIPPER_SETTINGS_APPS[i].icon) :
NULL,
loader_menu_callback, loader_menu_callback,
(void*)&FLIPPER_SETTINGS_APPS[i])); (void*)&FLIPPER_SETTINGS_APPS[i]);
} }
}
menu_item_add(menu, menu_debug); void loader_show_menu() {
}); furi_assert(loader_instance);
osThreadFlagsSet(loader_instance->loader_thread, LOADER_THREAD_FLAG_SHOW_MENU);
} }
int32_t loader_srv(void* p) { int32_t loader_srv(void* p) {
@ -300,15 +338,24 @@ int32_t loader_srv(void* p) {
// Call on start hooks // Call on start hooks
for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) { for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) {
(*FLIPPER_ON_SYSTEM_START[i])(); FLIPPER_ON_SYSTEM_START[i]();
} }
FURI_LOG_I(LOADER_LOG_TAG, "Started"); FURI_LOG_I(LOADER_LOG_TAG, "Started");
furi_record_create("loader", loader_instance); furi_record_create("loader", loader_instance);
#ifdef LOADER_AUTOSTART
loader_start(loader_instance, LOADER_AUTOSTART, NULL);
#endif
while(1) { while(1) {
osThreadSuspend(osThreadGetId()); uint32_t flags = osThreadFlagsWait(LOADER_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever);
if(flags & LOADER_THREAD_FLAG_SHOW_MENU) {
view_dispatcher_switch_to_view(
loader_instance->view_dispatcher, LoaderMenuViewPrimary);
view_dispatcher_run(loader_instance->view_dispatcher);
}
} }
loader_free(loader_instance); loader_free(loader_instance);

View file

@ -18,3 +18,6 @@ bool loader_lock(Loader* instance);
/** Unlock application start */ /** Unlock application start */
void loader_unlock(Loader* instance); void loader_unlock(Loader* instance);
/** Show primary loader */
void loader_show_menu();

View file

@ -3,20 +3,39 @@
#include <furi.h> #include <furi.h>
#include <furi-hal.h> #include <furi-hal.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <menu/menu.h>
#include <menu/menu_item.h> #include <gui/view_dispatcher.h>
#include <gui/modules/menu.h>
#include <gui/modules/submenu.h>
#include <applications.h> #include <applications.h>
#include <assets_icons.h> #include <assets_icons.h>
#define LOADER_LOG_TAG "loader" #define LOADER_LOG_TAG "loader"
struct Loader { struct Loader {
osThreadId_t loader_thread;
FuriThread* thread; FuriThread* thread;
const FlipperApplication* current_app; const FlipperApplication* current_app;
string_t args; string_t args;
Cli* cli; Cli* cli;
ValueMutex* menu_vm; Gui* gui;
ViewDispatcher* view_dispatcher;
Menu* primary_menu;
Submenu* plugins_menu;
Submenu* debug_menu;
Submenu* settings_menu;
size_t free_heap_size; size_t free_heap_size;
osMutexId_t mutex; osMutexId_t mutex;
volatile uint8_t lock_semaphore; volatile uint8_t lock_semaphore;
}; };
typedef enum {
LoaderMenuViewPrimary,
LoaderMenuViewPlugins,
LoaderMenuViewDebug,
LoaderMenuViewSettings,
} LoaderMenuView;

View file

@ -1,337 +0,0 @@
#include "menu.h"
#include <stdio.h>
#include <stdbool.h>
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include "menu_event.h"
#include "menu_item.h"
#include <assets_icons.h>
struct Menu {
MenuEvent* event;
// GUI
Gui* gui;
ViewPort* view_port;
IconAnimation* icon;
// State
MenuItem* root;
MenuItem* settings;
MenuItem* current;
};
void menu_view_port_callback(Canvas* canvas, void* context);
ValueMutex* menu_init() {
Menu* menu = furi_alloc(sizeof(Menu));
// Event dispatcher
menu->event = menu_event_alloc();
ValueMutex* menu_mutex = furi_alloc(sizeof(ValueMutex));
if(menu_mutex == NULL || !init_mutex(menu_mutex, menu, sizeof(Menu))) {
printf("[menu_task] cannot create menu mutex\r\n");
furi_crash(NULL);
}
// OpenGui record
menu->gui = furi_record_open("gui");
// Allocate and configure view_port
menu->view_port = view_port_alloc();
// Open GUI and register fullscreen view_port
gui_add_view_port(menu->gui, menu->view_port, GuiLayerFullscreen);
view_port_enabled_set(menu->view_port, false);
view_port_draw_callback_set(menu->view_port, menu_view_port_callback, menu_mutex);
view_port_input_callback_set(menu->view_port, menu_event_input_callback, menu->event);
return menu_mutex;
}
void menu_build_main(Menu* menu) {
furi_assert(menu);
// Root point
menu->root = menu_item_alloc_menu(NULL, NULL);
}
void menu_item_add(Menu* menu, MenuItem* item) {
menu_item_subitem_add(menu->root, item);
}
void menu_settings_item_add(Menu* menu, MenuItem* item) {
menu_item_subitem_add(menu->settings, item);
}
void menu_draw_primary(Menu* menu, Canvas* canvas) {
size_t position = menu_item_get_position(menu->current);
MenuItemArray_t* items = menu_item_get_subitems(menu->current);
size_t items_count = MenuItemArray_size(*items);
if(items_count) {
MenuItem* item;
size_t shift_position;
// First line
canvas_set_font(canvas, FontSecondary);
shift_position = (0 + position + items_count - 1) % (MenuItemArray_size(*items));
item = *MenuItemArray_get(*items, shift_position);
canvas_draw_icon_animation(canvas, 4, 3, menu_item_get_icon(item));
canvas_draw_str(canvas, 22, 14, menu_item_get_label(item));
// Second line main
canvas_set_font(canvas, FontPrimary);
shift_position = (1 + position + items_count - 1) % (MenuItemArray_size(*items));
item = *MenuItemArray_get(*items, shift_position);
canvas_draw_icon_animation(canvas, 4, 25, menu_item_get_icon(item));
canvas_draw_str(canvas, 22, 36, menu_item_get_label(item));
// Third line
canvas_set_font(canvas, FontSecondary);
shift_position = (2 + position + items_count - 1) % (MenuItemArray_size(*items));
item = *MenuItemArray_get(*items, shift_position);
canvas_draw_icon_animation(canvas, 4, 47, menu_item_get_icon(item));
canvas_draw_str(canvas, 22, 58, menu_item_get_label(item));
// Frame and scrollbar
// elements_frame(canvas, 0, 0, 128 - 5, 21);
elements_frame(canvas, 0, 21, 128 - 5, 21);
// elements_frame(canvas, 0, 42, 128 - 5, 21);
elements_scrollbar(canvas, position, items_count);
} else {
canvas_draw_str(canvas, 2, 32, "Empty");
elements_scrollbar(canvas, 0, 0);
}
}
void menu_draw_secondary(Menu* menu, Canvas* canvas) {
size_t position = 0;
size_t selected_position = menu_item_get_position(menu->current);
size_t window_position = menu_item_get_window_position(menu->current);
MenuItemArray_t* items = menu_item_get_subitems(menu->current);
const uint8_t items_on_screen = 4;
const uint8_t item_height = 16;
const uint8_t item_width = 123;
size_t items_count = MenuItemArray_size(*items);
MenuItemArray_it_t it;
canvas_set_font(canvas, FontSecondary);
for(MenuItemArray_it(it, *items); !MenuItemArray_end_p(it); MenuItemArray_next(it)) {
size_t item_position = position - window_position;
if(item_position < items_on_screen) {
if(position == selected_position) {
canvas_set_color(canvas, ColorBlack);
elements_slightly_rounded_box(
canvas, 0, (item_position * item_height) + 1, item_width, item_height - 2);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_color(canvas, ColorBlack);
}
canvas_draw_str(
canvas,
6,
(item_position * item_height) + item_height - 4,
menu_item_get_label(*MenuItemArray_ref(it)));
}
position++;
}
elements_scrollbar(canvas, selected_position, items_count);
}
void menu_view_port_callback(Canvas* canvas, void* context) {
furi_assert(canvas);
furi_assert(context);
Menu* menu = acquire_mutex((ValueMutex*)context, 100); // wait 10 ms to get mutex
if(menu == NULL) return; // redraw fail
furi_assert(menu->current);
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
// if top level
if(menu_item_get_parent(menu->current) == NULL) {
menu_draw_primary(menu, canvas);
} else {
menu_draw_secondary(menu, canvas);
}
release_mutex((ValueMutex*)context, menu);
}
void menu_set_icon(Menu* menu, IconAnimation* icon) {
furi_assert(menu);
if(menu->icon) {
icon_animation_stop(menu->icon);
}
menu->icon = icon;
if(menu->icon) {
icon_animation_start(menu->icon);
}
}
void menu_update(Menu* menu) {
furi_assert(menu);
if(menu->current) {
size_t position = menu_item_get_position(menu->current);
MenuItemArray_t* items = menu_item_get_subitems(menu->current);
size_t items_count = MenuItemArray_size(*items);
if(items_count) {
MenuItem* item = *MenuItemArray_get(*items, position);
menu_set_icon(menu, menu_item_get_icon(item));
}
}
menu_event_activity_notify(menu->event);
view_port_update(menu->view_port);
}
void menu_up(Menu* menu) {
furi_assert(menu);
size_t position = menu_item_get_position(menu->current);
size_t window_position = menu_item_get_window_position(menu->current);
MenuItemArray_t* items = menu_item_get_subitems(menu->current);
const uint8_t items_on_screen = 4;
if(position > 0) {
position--;
if(((position - window_position) < 1) && window_position > 0) {
window_position--;
}
} else {
position = MenuItemArray_size(*items) - 1;
if(position > (items_on_screen - 1)) {
window_position = position - (items_on_screen - 1);
}
}
menu_item_set_position(menu->current, position);
menu_item_set_window_position(menu->current, window_position);
menu_update(menu);
}
void menu_down(Menu* menu) {
furi_assert(menu);
size_t position = menu_item_get_position(menu->current);
size_t window_position = menu_item_get_window_position(menu->current);
MenuItemArray_t* items = menu_item_get_subitems(menu->current);
const uint8_t items_on_screen = 4;
if(position < (MenuItemArray_size(*items) - 1)) {
position++;
if((position - window_position) > (items_on_screen - 2) &&
window_position < (MenuItemArray_size(*items) - items_on_screen)) {
window_position++;
}
} else {
position = 0;
window_position = 0;
}
menu_item_set_position(menu->current, position);
menu_item_set_window_position(menu->current, window_position);
menu_update(menu);
}
void menu_ok(Menu* menu) {
furi_assert(menu);
if(!menu->current) {
view_port_enabled_set(menu->view_port, true);
menu->current = menu->root;
menu_item_set_position(menu->current, 0);
menu_item_set_window_position(menu->current, 0);
menu_update(menu);
return;
}
MenuItemArray_t* items = menu_item_get_subitems(menu->current);
if(!items || MenuItemArray_size(*items) == 0) {
return;
}
size_t position = menu_item_get_position(menu->current);
MenuItem* item = *MenuItemArray_get(*items, position);
MenuItemType type = menu_item_get_type(item);
if(type == MenuItemTypeMenu) {
menu->current = item;
menu_item_set_position(menu->current, 0);
menu_item_set_window_position(menu->current, 0);
menu_update(menu);
} else if(type == MenuItemTypeFunction) {
menu_item_function_call(item);
gui_send_view_port_back(menu->gui, menu->view_port);
}
}
void menu_back(Menu* menu) {
furi_assert(menu);
MenuItem* parent = menu_item_get_parent(menu->current);
if(parent) {
menu->current = parent;
menu_update(menu);
} else {
menu_exit(menu);
}
}
void menu_exit(Menu* menu) {
furi_assert(menu);
view_port_enabled_set(menu->view_port, false);
menu->current = NULL;
menu_update(menu);
}
int32_t menu_srv(void* p) {
ValueMutex* menu_mutex = menu_init();
MenuEvent* menu_event = NULL;
{
Menu* menu = acquire_mutex_block(menu_mutex);
furi_check(menu);
menu_build_main(menu);
// immutable thread-safe object
menu_event = menu->event;
release_mutex(menu_mutex, menu);
}
furi_record_create("menu", menu_mutex);
while(1) {
MenuMessage m = menu_event_next(menu_event);
Menu* menu = acquire_mutex_block(menu_mutex);
if(!menu->current && m.type != MenuMessageTypeOk) {
} else if(m.type == MenuMessageTypeUp) {
menu_up(menu);
} else if(m.type == MenuMessageTypeDown) {
menu_down(menu);
} else if(m.type == MenuMessageTypeOk) {
menu_ok(menu);
} else if(m.type == MenuMessageTypeBack) {
menu_back(menu);
} else if(m.type == MenuMessageTypeIdle) {
menu_exit(menu);
} else {
// TODO: fail somehow?
}
release_mutex(menu_mutex, menu);
}
return 0;
}

View file

@ -1,19 +0,0 @@
#pragma once
#include "menu/menu_item.h"
typedef struct Menu Menu;
typedef struct MenuItem MenuItem;
// Add menu item to root menu
void menu_item_add(Menu* menu, MenuItem* item);
// Add menu item to settings menu
void menu_settings_item_add(Menu* menu, MenuItem* item);
// Menu controls
void menu_up(Menu* menu);
void menu_down(Menu* menu);
void menu_ok(Menu* menu);
void menu_back(Menu* menu);
void menu_exit(Menu* menu);

View file

@ -1,65 +0,0 @@
#include "menu_event.h"
#include <string.h>
#include <stdlib.h>
#include <furi.h>
#define MENU_MESSAGE_MQUEUE_SIZE 8
struct MenuEvent {
osMessageQueueId_t mqueue;
};
void MenuEventimeout_callback(void* arg) {
MenuEvent* menu_event = arg;
MenuMessage message;
message.type = MenuMessageTypeIdle;
osMessageQueuePut(menu_event->mqueue, &message, 0, osWaitForever);
}
MenuEvent* menu_event_alloc() {
MenuEvent* menu_event = furi_alloc(sizeof(MenuEvent));
menu_event->mqueue = osMessageQueueNew(MENU_MESSAGE_MQUEUE_SIZE, sizeof(MenuMessage), NULL);
furi_check(menu_event->mqueue);
return menu_event;
}
void menu_event_free(MenuEvent* menu_event) {
furi_assert(menu_event);
furi_check(osMessageQueueDelete(menu_event->mqueue) == osOK);
free(menu_event);
}
void menu_event_activity_notify(MenuEvent* menu_event) {
furi_assert(menu_event);
}
MenuMessage menu_event_next(MenuEvent* menu_event) {
furi_assert(menu_event);
MenuMessage message;
while(osMessageQueueGet(menu_event->mqueue, &message, NULL, osWaitForever) != osOK) {
};
return message;
}
void menu_event_input_callback(InputEvent* input_event, void* context) {
MenuEvent* menu_event = context;
MenuMessage message;
if(input_event->type != InputTypeShort) return;
if(input_event->key == InputKeyUp) {
message.type = MenuMessageTypeUp;
} else if(input_event->key == InputKeyDown) {
message.type = MenuMessageTypeDown;
} else if(input_event->key == InputKeyOk) {
message.type = MenuMessageTypeOk;
} else if(input_event->key == InputKeyBack) {
message.type = MenuMessageTypeBack;
} else {
message.type = MenuMessageTypeUnknown;
}
osMessageQueuePut(menu_event->mqueue, &message, 0, osWaitForever);
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <stdint.h>
#include <input/input.h>
typedef enum {
MenuMessageTypeUp = 0x00,
MenuMessageTypeDown = 0x01,
MenuMessageTypeLeft = 0x02,
MenuMessageTypeRight = 0x03,
MenuMessageTypeOk = 0x04,
MenuMessageTypeBack = 0x05,
MenuMessageTypeIdle = 0x06,
MenuMessageTypeUnknown = 0xFF,
} MenuMessageType;
typedef struct {
MenuMessageType type;
void* data;
} MenuMessage;
typedef struct MenuEvent MenuEvent;
MenuEvent* menu_event_alloc();
void menu_event_free(MenuEvent* menu_event);
void menu_event_activity_notify(MenuEvent* menu_event);
MenuMessage menu_event_next(MenuEvent* menu_event);
void menu_event_input_callback(InputEvent* input_event, void* context);

View file

@ -1,135 +0,0 @@
#include "menu_item.h"
#include <stdlib.h>
#include <string.h>
#include <furi.h>
struct MenuItem {
MenuItemType type;
const char* label;
IconAnimation* icon;
size_t position;
size_t window_position;
MenuItem* parent;
void* data;
// callback related
MenuItemCallback callback;
void* callback_context;
};
MenuItem* menu_item_alloc() {
MenuItem* menu_item = furi_alloc(sizeof(MenuItem));
return menu_item;
}
MenuItem* menu_item_alloc_menu(const char* label, IconAnimation* icon) {
MenuItem* menu_item = menu_item_alloc();
menu_item->type = MenuItemTypeMenu;
menu_item->label = label;
menu_item->icon = icon;
MenuItemArray_t* items = furi_alloc(sizeof(MenuItemArray_t));
MenuItemArray_init(*items);
menu_item->data = items;
return menu_item;
}
MenuItem* menu_item_alloc_function(
const char* label,
IconAnimation* icon,
MenuItemCallback callback,
void* context) {
MenuItem* menu_item = menu_item_alloc();
menu_item->type = MenuItemTypeFunction;
menu_item->label = label;
menu_item->icon = icon;
menu_item->callback = callback;
menu_item->callback_context = context;
menu_item->parent = NULL;
return menu_item;
}
void menu_item_release(MenuItem* menu_item) {
furi_assert(menu_item);
if(menu_item->type == MenuItemTypeMenu) {
//TODO: iterate and release
free(menu_item->data);
}
free(menu_item);
}
MenuItem* menu_item_get_parent(MenuItem* menu_item) {
furi_assert(menu_item);
return menu_item->parent;
}
void menu_item_subitem_add(MenuItem* menu_item, MenuItem* sub_item) {
furi_assert(menu_item);
furi_check(menu_item->type == MenuItemTypeMenu);
MenuItemArray_t* items = menu_item->data;
sub_item->parent = menu_item;
MenuItemArray_push_back(*items, sub_item);
}
MenuItemType menu_item_get_type(MenuItem* menu_item) {
furi_assert(menu_item);
return menu_item->type;
}
void menu_item_set_position(MenuItem* menu_item, size_t position) {
furi_assert(menu_item);
menu_item->position = position;
}
size_t menu_item_get_position(MenuItem* menu_item) {
furi_assert(menu_item);
return menu_item->position;
}
void menu_item_set_window_position(MenuItem* menu_item, size_t window_position) {
furi_assert(menu_item);
menu_item->window_position = window_position;
}
size_t menu_item_get_window_position(MenuItem* menu_item) {
furi_assert(menu_item);
return menu_item->window_position;
}
void menu_item_set_label(MenuItem* menu_item, const char* label) {
furi_assert(menu_item);
menu_item->label = label;
}
const char* menu_item_get_label(MenuItem* menu_item) {
furi_assert(menu_item);
return menu_item->label;
}
void menu_item_set_icon(MenuItem* menu_item, IconAnimation* icon) {
furi_assert(menu_item);
menu_item->icon = icon;
}
IconAnimation* menu_item_get_icon(MenuItem* menu_item) {
furi_assert(menu_item);
return menu_item->icon;
}
MenuItemArray_t* menu_item_get_subitems(MenuItem* menu_item) {
furi_assert(menu_item);
furi_check(menu_item->type == MenuItemTypeMenu);
return menu_item->data;
}
void menu_item_function_call(MenuItem* menu_item) {
furi_assert(menu_item);
furi_check(menu_item->type == MenuItemTypeFunction);
if(menu_item->callback) menu_item->callback(menu_item->callback_context);
}

View file

@ -1,47 +0,0 @@
#pragma once
#include <stdint.h>
#include <m-array.h>
#include <gui/icon_animation.h>
typedef enum {
MenuItemTypeMenu = 0x00,
MenuItemTypeFunction = 0x01,
} MenuItemType;
typedef struct MenuItem MenuItem;
typedef void (*MenuItemCallback)(void* context);
ARRAY_DEF(MenuItemArray, MenuItem*, M_PTR_OPLIST);
MenuItem* menu_item_alloc_menu(const char* label, IconAnimation* icon);
MenuItem* menu_item_alloc_function(
const char* label,
IconAnimation* icon,
MenuItemCallback callback,
void* context);
void menu_item_release(MenuItem* menu_item);
MenuItem* menu_item_get_parent(MenuItem* menu_item);
void menu_item_subitem_add(MenuItem* menu_item, MenuItem* sub_item);
MenuItemType menu_item_get_type(MenuItem* menu_item);
void menu_item_set_position(MenuItem* menu_item, size_t position);
size_t menu_item_get_position(MenuItem* menu_item);
void menu_item_set_window_position(MenuItem* menu_item, size_t window_position);
size_t menu_item_get_window_position(MenuItem* menu_item);
void menu_item_set_label(MenuItem* menu_item, const char* label);
const char* menu_item_get_label(MenuItem* menu_item);
void menu_item_set_icon(MenuItem* menu_item, IconAnimation* icon);
IconAnimation* menu_item_get_icon(MenuItem* menu_item);
MenuItemArray_t* menu_item_get_subitems(MenuItem* menu_item);
void menu_item_function_call(MenuItem* menu_item);

View file

@ -16,12 +16,12 @@ void SubmenuVM::clean() {
submenu_clean(submenu); submenu_clean(submenu);
} }
SubmenuItem* SubmenuVM::add_item( void SubmenuVM::add_item(
const char* label, const char* label,
uint32_t index, uint32_t index,
SubmenuItemCallback callback, SubmenuItemCallback callback,
void* callback_context) { void* callback_context) {
return submenu_add_item(submenu, label, index, callback, callback_context); submenu_add_item(submenu, label, index, callback, callback_context);
} }
void SubmenuVM::set_selected_item(uint32_t index) { void SubmenuVM::set_selected_item(uint32_t index) {

View file

@ -16,9 +16,8 @@ public:
* @param index - menu item index, used for callback, may be the same with other items * @param index - menu item index, used for callback, may be the same with other items
* @param callback - menu item callback * @param callback - menu item callback
* @param callback_context - menu item callback context * @param callback_context - menu item callback context
* @return SubmenuItem instance that can be used to modify or delete that item
*/ */
SubmenuItem* add_item( void add_item(
const char* label, const char* label,
uint32_t index, uint32_t index,
SubmenuItemCallback callback, SubmenuItemCallback callback,