[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:
		
							parent
							
								
									1c4e6ec74d
								
							
						
					
					
						commit
						61c8f3325a
					
				| @ -9,7 +9,6 @@ extern int32_t dolphin_srv(void* p); | ||||
| extern int32_t gui_srv(void* p); | ||||
| extern int32_t input_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 power_observer_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}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_MENU | ||||
|     {.app = menu_srv, .name = "Menu", .stack_size = 1024, .icon = NULL}, | ||||
| #ifdef SRV_LOADER | ||||
|     {.app = loader_srv, .name = "Loader", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| @ -107,43 +105,6 @@ const FlipperApplication FLIPPER_SERVICES[] = { | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_srv, .name = "Storage", .stack_size = 4096, .icon = NULL}, | ||||
| #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); | ||||
| @ -184,25 +145,35 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | ||||
| #ifdef SRV_CLI | ||||
|     crypto_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IRDA | ||||
|     irda_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
|     nfc_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SUBGHZ | ||||
|     subghz_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     lfrfid_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     ibutton_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     bt_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_POWER | ||||
|     power_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     storage_cli_init, | ||||
| #endif | ||||
|  | ||||
| @ -1,278 +1,248 @@ | ||||
| APP_DIR		= $(PROJECT_ROOT)/applications | ||||
| LIB_DIR 	= $(PROJECT_ROOT)/lib | ||||
| LIB_DIR		= $(PROJECT_ROOT)/lib | ||||
| 
 | ||||
| CFLAGS		+= -I$(APP_DIR) | ||||
| C_SOURCES   += $(shell find $(APP_DIR) -name *.c) | ||||
| CPP_SOURCES += $(shell find $(APP_DIR) -name *.cpp) | ||||
| C_SOURCES	+= $(shell find $(APP_DIR) -name *.c) | ||||
| CPP_SOURCES	+= $(shell find $(APP_DIR) -name *.cpp) | ||||
| 
 | ||||
| 
 | ||||
| # Use SRV_* for autostart app
 | ||||
| # Use APP_* for add app to build
 | ||||
| 
 | ||||
| APP_RELEASE ?= 1 | ||||
| ifeq ($(APP_RELEASE), 1) | ||||
| # Services
 | ||||
| SRV_BT = 1 | ||||
| SRV_CLI = 1 | ||||
| SRV_DIALOGS = 1 | ||||
| SRV_DOLPHIN = 1 | ||||
| SRV_GUI = 1 | ||||
| SRV_INPUT = 1 | ||||
| SRV_MENU = 1 | ||||
| SRV_BT		= 1 | ||||
| SRV_CLI		= 1 | ||||
| SRV_DIALOGS	= 1 | ||||
| SRV_DOLPHIN	= 1 | ||||
| SRV_GUI		= 1 | ||||
| SRV_INPUT	= 1 | ||||
| SRV_LOADER	= 1 | ||||
| SRV_NOTIFICATION = 1 | ||||
| SRV_POWER = 1 | ||||
| SRV_POWER	= 1 | ||||
| SRV_POWER_OBSERVER = 1 | ||||
| SRV_STORAGE = 1 | ||||
| SRV_STORAGE	= 1 | ||||
| 
 | ||||
| # Apps
 | ||||
| SRV_DESKTOP = 1 | ||||
| APP_ARCHIVE = 1 | ||||
| SRV_DESKTOP	= 1 | ||||
| APP_ARCHIVE	= 1 | ||||
| APP_GPIO_TEST = 1 | ||||
| APP_IBUTTON = 1 | ||||
| APP_IRDA  = 1 | ||||
| APP_LF_RFID = 1 | ||||
| APP_NFC = 1 | ||||
| APP_SUBGHZ = 1 | ||||
| APP_ABOUT  = 1 | ||||
| APP_IBUTTON	= 1 | ||||
| APP_IRDA	= 1 | ||||
| APP_LF_RFID	= 1 | ||||
| APP_NFC		= 1 | ||||
| APP_SUBGHZ	= 1 | ||||
| APP_ABOUT	= 1 | ||||
| 
 | ||||
| # Plugins
 | ||||
| APP_MUSIC_PLAYER = 1 | ||||
| 
 | ||||
| # Debug
 | ||||
| APP_ACCESSOR = 1 | ||||
| APP_BLINK = 1 | ||||
| APP_BLINK	= 1 | ||||
| APP_IRDA_MONITOR = 1 | ||||
| APP_KEYPAD_TEST = 1 | ||||
| APP_SD_TEST = 1 | ||||
| APP_SD_TEST	= 1 | ||||
| APP_UNIT_TESTS = 0 | ||||
| APP_VIBRO_DEMO = 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_BT ?= 0 | ||||
| ifeq ($(SRV_BT), 1) | ||||
| SRV_CLI		= 1 | ||||
| CFLAGS		+= -DSRV_BT | ||||
| endif | ||||
| # Applications
 | ||||
| # that will be shown in menu
 | ||||
| # Prefix with APP_*
 | ||||
| 
 | ||||
| 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 | ||||
| ifeq ($(APP_IRDA_MONITOR), 1) | ||||
| CFLAGS		+= -DAPP_IRDA_MONITOR | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_UNIT_TESTS	?= 0 | ||||
| ifeq ($(APP_UNIT_TESTS), 1) | ||||
| CFLAGS		+= -DAPP_UNIT_TESTS | ||||
| endif | ||||
| 
 | ||||
| SRV_DESKTOP ?= 0 | ||||
| ifeq ($(SRV_DESKTOP), 1) | ||||
| CFLAGS		+= -DSRV_DESKTOP | ||||
| SRV_DESKTOP = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_ARCHIVE ?= 0 | ||||
| ifeq ($(APP_NFC), 1) | ||||
| ifeq ($(APP_ARCHIVE), 1) | ||||
| CFLAGS		+= -DAPP_ARCHIVE | ||||
| APP_ARCHIVE = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_BLINK ?= 0 | ||||
| ifeq ($(SRV_BLINK), 1) | ||||
| CFLAGS		+= -DSRV_BLINK | ||||
| APP_BLINK = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_BLINK ?= 0 | ||||
| ifeq ($(APP_BLINK), 1) | ||||
| CFLAGS		+= -DAPP_BLINK | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI		= 1 | ||||
| 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) | ||||
| CFLAGS		+= -DAPP_SUBGHZ | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| SRV_CLI = 1 | ||||
| SRV_GUI		= 1 | ||||
| SRV_CLI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_ABOUT ?= 0 | ||||
| ifeq ($(APP_ABOUT), 1) | ||||
| CFLAGS		+= -DAPP_ABOUT | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_LF_RFID ?= 0 | ||||
| ifeq ($(SRV_LF_RFID), 1) | ||||
| CFLAGS		+= -DSRV_LF_RFID | ||||
| APP_LF_RFID = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_LF_RFID ?= 0 | ||||
| ifeq ($(APP_LF_RFID), 1) | ||||
| CFLAGS		+= -DAPP_LF_RFID | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_NFC ?= 0 | ||||
| ifeq ($(APP_NFC), 1) | ||||
| CFLAGS		+= -DAPP_NFC | ||||
| SRV_MENU = 1 | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_IRDA ?= 0 | ||||
| ifeq ($(SRV_IRDA), 1) | ||||
| CFLAGS		+= -DSRV_IRDA | ||||
| APP_IRDA = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_IRDA ?= 0 | ||||
| ifeq ($(APP_IRDA), 1) | ||||
| CFLAGS		+= -DAPP_IRDA | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_VIBRO_DEMO ?= 0 | ||||
| ifeq ($(APP_VIBRO_DEMO), 1) | ||||
| CFLAGS		+= -DAPP_VIBRO_DEMO | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_KEYPAD_TEST ?= 0 | ||||
| ifeq ($(SRV_KEYPAD_TEST), 1) | ||||
| CFLAGS		+= -DSRV_KEYPAD_TEST | ||||
| APP_KEYPAD_TEST = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_KEYPAD_TEST ?= 0 | ||||
| ifeq ($(APP_KEYPAD_TEST), 1) | ||||
| CFLAGS		+= -DAPP_KEYPAD_TEST | ||||
| APP_KEYPAD_TEST = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_ACCESSOR ?= 0 | ||||
| ifeq ($(SRV_ACCESSOR), 1) | ||||
| CFLAGS		+= -DSRV_ACCESSOR | ||||
| APP_ACCESSOR = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_ACCESSOR ?= 0 | ||||
| ifeq ($(APP_ACCESSOR), 1) | ||||
| CFLAGS		+= -DAPP_ACCESSOR | ||||
| APP_ACCESSOR = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_GPIO_TEST ?= 0 | ||||
| ifeq ($(SRV_GPIO_TEST), 1) | ||||
| CFLAGS		+= -DSRV_GPIO_TEST | ||||
| APP_GPIO_TEST = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_GPIO_TEST ?= 0 | ||||
| ifeq ($(APP_GPIO_TEST), 1) | ||||
| CFLAGS		+= -DAPP_GPIO_TEST | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_MUSIC_PLAYER ?= 0 | ||||
| ifeq ($(SRV_MUSIC_PLAYER), 1) | ||||
| CFLAGS		+= -DSRV_MUSIC_PLAYER | ||||
| APP_MUSIC_PLAYER = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_MUSIC_PLAYER ?= 0 | ||||
| ifeq ($(APP_MUSIC_PLAYER), 1) | ||||
| CFLAGS		+= -DAPP_MUSIC_PLAYER | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_IBUTTON ?= 0 | ||||
| ifeq ($(SRV_IBUTTON), 1) | ||||
| CFLAGS		+= -DSRV_IBUTTON | ||||
| APP_IBUTTON = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_IBUTTON ?= 0 | ||||
| ifeq ($(APP_IBUTTON), 1) | ||||
| 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 | ||||
| 
 | ||||
| #
 | ||||
| # Essential services
 | ||||
| #
 | ||||
| 
 | ||||
| SRV_GUI	?= 0 | ||||
| ifeq ($(SRV_GUI), 1) | ||||
| CFLAGS		+= -DSRV_GUI | ||||
| SRV_INPUT	= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_INPUT	?= 0 | ||||
| ifeq ($(SRV_INPUT), 1) | ||||
| CFLAGS		+= -DSRV_INPUT | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_CLI ?= 0 | ||||
| ifeq ($(SRV_CLI), 1) | ||||
| SRV_GUI		= 1 | ||||
| CFLAGS		+= -DSRV_CLI | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_NOTIFICATION ?= 0 | ||||
| ifeq ($(SRV_NOTIFICATION), 1) | ||||
| CFLAGS		+= -DSRV_NOTIFICATION | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_STORAGE ?= 0 | ||||
| ifeq ($(SRV_STORAGE), 1) | ||||
| CFLAGS		+= -DSRV_STORAGE | ||||
| endif | ||||
| 
 | ||||
| SRV_DIALOGS ?= 0 | ||||
| ifeq ($(SRV_DIALOGS), 1) | ||||
| CFLAGS		+= -DSRV_DIALOGS | ||||
| endif | ||||
|  | ||||
| @ -21,7 +21,6 @@ bool desktop_back_event_callback(void* context) { | ||||
| Desktop* desktop_alloc() { | ||||
|     Desktop* desktop = furi_alloc(sizeof(Desktop)); | ||||
| 
 | ||||
|     desktop->menu_vm = furi_record_open("menu"); | ||||
|     desktop->gui = furi_record_open("gui"); | ||||
|     desktop->scene_thread = furi_thread_alloc(); | ||||
|     desktop->view_dispatcher = view_dispatcher_alloc(); | ||||
| @ -101,7 +100,6 @@ void desktop_free(Desktop* desktop) { | ||||
|     furi_thread_free(desktop->scene_thread); | ||||
| 
 | ||||
|     furi_record_close("menu"); | ||||
|     desktop->menu_vm = NULL; | ||||
| 
 | ||||
|     free(desktop); | ||||
| } | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| #include <menu/menu.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/scene_manager.h> | ||||
| @ -34,8 +33,6 @@ typedef enum { | ||||
| } DesktopViewEnum; | ||||
| 
 | ||||
| struct Desktop { | ||||
|     // Menu
 | ||||
|     ValueMutex* menu_vm; | ||||
|     // Scene
 | ||||
|     FuriThread* scene_thread; | ||||
|     // GUI
 | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| #include "../desktop_i.h" | ||||
| #include "../views/desktop_main.h" | ||||
| #include "applications.h" | ||||
| #include <loader/loader.h> | ||||
| #define MAIN_VIEW_DEFAULT (0UL) | ||||
| 
 | ||||
| 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) { | ||||
|         switch(event.event) { | ||||
|         case DesktopMainEventOpenMenu: | ||||
|             with_value_mutex( | ||||
|                 desktop->menu_vm, (Menu * menu) { menu_ok(menu); }); | ||||
|             loader_show_menu(); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										199
									
								
								applications/gui/modules/menu.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										199
									
								
								applications/gui/modules/menu.c
									
									
									
									
									
										Executable 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
									
								
							
							
						
						
									
										52
									
								
								applications/gui/modules/menu.h
									
									
									
									
									
										Executable 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 | ||||
| @ -1,23 +1,22 @@ | ||||
| #include "submenu.h" | ||||
| #include "gui/canvas.h" | ||||
| 
 | ||||
| #include <m-array.h> | ||||
| #include <furi.h> | ||||
| #include <gui/elements.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| struct SubmenuItem { | ||||
|     const char* label; | ||||
|     uint32_t index; | ||||
|     SubmenuItemCallback callback; | ||||
|     void* callback_context; | ||||
| }; | ||||
| 
 | ||||
| ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); | ||||
| #include <furi.h> | ||||
| 
 | ||||
| struct Submenu { | ||||
|     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 { | ||||
|     SubmenuItemArray_t items; | ||||
|     const char* header; | ||||
| @ -149,7 +148,7 @@ View* submenu_get_view(Submenu* submenu) { | ||||
|     return submenu->view; | ||||
| } | ||||
| 
 | ||||
| SubmenuItem* submenu_add_item( | ||||
| void submenu_add_item( | ||||
|     Submenu* submenu, | ||||
|     const char* label, | ||||
|     uint32_t index, | ||||
| @ -168,8 +167,6 @@ SubmenuItem* submenu_add_item( | ||||
|             item->callback_context = callback_context; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     return item; | ||||
| } | ||||
| 
 | ||||
| void submenu_clean(Submenu* submenu) { | ||||
|  | ||||
| @ -7,7 +7,6 @@ extern "C" { | ||||
| 
 | ||||
| /* Submenu anonymous structure */ | ||||
| typedef struct Submenu Submenu; | ||||
| typedef struct SubmenuItem SubmenuItem; | ||||
| 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 callback - menu item callback | ||||
|  * @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, | ||||
|     const char* label, | ||||
|     uint32_t index, | ||||
|  | ||||
							
								
								
									
										275
									
								
								applications/loader/loader.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										275
									
								
								applications/loader/loader.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -1,8 +1,11 @@ | ||||
| #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 void loader_menu_callback(void* _ctx) { | ||||
| static void loader_menu_callback(void* _ctx, uint32_t index) { | ||||
|     const FlipperApplication* flipper_app = _ctx; | ||||
| 
 | ||||
|     furi_assert(flipper_app->app); | ||||
| @ -27,6 +30,11 @@ static void loader_menu_callback(void* _ctx) { | ||||
|     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) { | ||||
|     furi_assert(_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) { | ||||
|         FURI_LOG_E(LOADER_LOG_TAG, "Can't find application with name %s", name); | ||||
|         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() { | ||||
|     Loader* instance = furi_alloc(sizeof(Loader)); | ||||
| 
 | ||||
| @ -150,10 +175,45 @@ static Loader* loader_alloc() { | ||||
| 
 | ||||
|     instance->mutex = osMutexNew(NULL); | ||||
| 
 | ||||
|     instance->menu_vm = furi_record_open("menu"); | ||||
| 
 | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| @ -162,133 +222,111 @@ static void loader_free(Loader* instance) { | ||||
| 
 | ||||
|     furi_record_close("cli"); | ||||
| 
 | ||||
|     furi_record_close("menu"); | ||||
| 
 | ||||
|     osMutexDelete(instance->mutex); | ||||
| 
 | ||||
|     string_clear(instance->args); | ||||
| 
 | ||||
|     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); | ||||
|     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() { | ||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building main menu"); | ||||
|     with_value_mutex( | ||||
|         loader_instance->menu_vm, (Menu * menu) { | ||||
|             for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||
|                 // Add menu item
 | ||||
|                 menu_item_add( | ||||
|                     menu, | ||||
|                     menu_item_alloc_function( | ||||
|                         FLIPPER_APPS[i].name, | ||||
|                         FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL, | ||||
|                         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]); | ||||
|                 string_clear(cli_name); | ||||
|             } | ||||
|         }); | ||||
|     size_t i; | ||||
|     for(i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_APPS[i]); | ||||
|         menu_add_item( | ||||
|             loader_instance->primary_menu, | ||||
|             FLIPPER_APPS[i].name, | ||||
|             FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL, | ||||
|             i, | ||||
|             loader_menu_callback, | ||||
|             (void*)&FLIPPER_APPS[i]); | ||||
|     } | ||||
|     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"); | ||||
|     with_value_mutex( | ||||
|         loader_instance->menu_vm, (Menu * menu) { | ||||
|             MenuItem* menu_plugins = | ||||
|                 menu_item_alloc_menu("Plugins", icon_animation_alloc(&A_Plugins_14)); | ||||
| 
 | ||||
|             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].icon ? icon_animation_alloc(FLIPPER_PLUGINS[i].icon) : | ||||
|                                                   NULL, | ||||
|                         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]); | ||||
|                 string_clear(cli_name); | ||||
|             } | ||||
| 
 | ||||
|             menu_item_add(menu, menu_plugins); | ||||
|         }); | ||||
|     for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { | ||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_PLUGINS[i]); | ||||
|         submenu_add_item( | ||||
|             loader_instance->plugins_menu, | ||||
|             FLIPPER_PLUGINS[i].name, | ||||
|             i, | ||||
|             loader_menu_callback, | ||||
|             (void*)&FLIPPER_PLUGINS[i]); | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building debug menu"); | ||||
|     with_value_mutex( | ||||
|         loader_instance->menu_vm, (Menu * menu) { | ||||
|             MenuItem* menu_debug = | ||||
|                 menu_item_alloc_menu("Debug tools", icon_animation_alloc(&A_Debug_14)); | ||||
| 
 | ||||
|             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].icon ? | ||||
|                             icon_animation_alloc(FLIPPER_DEBUG_APPS[i].icon) : | ||||
|                             NULL, | ||||
|                         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]); | ||||
|                 string_clear(cli_name); | ||||
|             } | ||||
| 
 | ||||
|             menu_item_add(menu, menu_debug); | ||||
|         }); | ||||
|     for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { | ||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_DEBUG_APPS[i]); | ||||
|         submenu_add_item( | ||||
|             loader_instance->debug_menu, | ||||
|             FLIPPER_DEBUG_APPS[i].name, | ||||
|             i, | ||||
|             loader_menu_callback, | ||||
|             (void*)&FLIPPER_DEBUG_APPS[i]); | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building settings menu"); | ||||
|     with_value_mutex( | ||||
|         loader_instance->menu_vm, (Menu * menu) { | ||||
|             MenuItem* menu_debug = | ||||
|                 menu_item_alloc_menu("Settings", icon_animation_alloc(&A_Settings_14)); | ||||
|     for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { | ||||
|         submenu_add_item( | ||||
|             loader_instance->settings_menu, | ||||
|             FLIPPER_SETTINGS_APPS[i].name, | ||||
|             i, | ||||
|             loader_menu_callback, | ||||
|             (void*)&FLIPPER_SETTINGS_APPS[i]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|             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].icon ? | ||||
|                             icon_animation_alloc(FLIPPER_SETTINGS_APPS[i].icon) : | ||||
|                             NULL, | ||||
|                         loader_menu_callback, | ||||
|                         (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) { | ||||
| @ -300,15 +338,24 @@ int32_t loader_srv(void* p) { | ||||
| 
 | ||||
|     // Call on start hooks
 | ||||
|     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_record_create("loader", loader_instance); | ||||
| 
 | ||||
| #ifdef LOADER_AUTOSTART | ||||
|     loader_start(loader_instance, LOADER_AUTOSTART, NULL); | ||||
| #endif | ||||
| 
 | ||||
|     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); | ||||
|  | ||||
| @ -18,3 +18,6 @@ bool loader_lock(Loader* instance); | ||||
| 
 | ||||
| /** Unlock application start */ | ||||
| void loader_unlock(Loader* instance); | ||||
| 
 | ||||
| /** Show primary loader */ | ||||
| void loader_show_menu(); | ||||
|  | ||||
| @ -3,20 +3,39 @@ | ||||
| #include <furi.h> | ||||
| #include <furi-hal.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 <assets_icons.h> | ||||
| 
 | ||||
| #define LOADER_LOG_TAG "loader" | ||||
| 
 | ||||
| struct Loader { | ||||
|     osThreadId_t loader_thread; | ||||
|     FuriThread* thread; | ||||
|     const FlipperApplication* current_app; | ||||
|     string_t args; | ||||
|     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; | ||||
|     osMutexId_t mutex; | ||||
|     volatile uint8_t lock_semaphore; | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|     LoaderMenuViewPrimary, | ||||
|     LoaderMenuViewPlugins, | ||||
|     LoaderMenuViewDebug, | ||||
|     LoaderMenuViewSettings, | ||||
| } LoaderMenuView; | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
| @ -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); | ||||
| @ -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); | ||||
| } | ||||
| @ -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); | ||||
| @ -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); | ||||
| } | ||||
| @ -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); | ||||
| @ -16,12 +16,12 @@ void SubmenuVM::clean() { | ||||
|     submenu_clean(submenu); | ||||
| } | ||||
| 
 | ||||
| SubmenuItem* SubmenuVM::add_item( | ||||
| void SubmenuVM::add_item( | ||||
|     const char* label, | ||||
|     uint32_t index, | ||||
|     SubmenuItemCallback callback, | ||||
|     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) { | ||||
|  | ||||
| @ -16,9 +16,8 @@ public: | ||||
|      * @param index - menu item index, used for callback, may be the same with other items | ||||
|      * @param callback - menu item callback | ||||
|      * @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, | ||||
|         uint32_t index, | ||||
|         SubmenuItemCallback callback, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 gornekich
						gornekich