Просмотр исходного кода

[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>
gornekich 4 лет назад
Родитель
Сommit
61c8f3325a

+ 11 - 40
applications/applications.c

@@ -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

+ 121 - 151
applications/applications.mk

@@ -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
-
-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
+# Applications
+# that will be shown in menu
+# Prefix with APP_*
 
-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
-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
+SRV_GUI		= 1
 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

+ 0 - 2
applications/desktop/desktop.c

@@ -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);
 }

+ 0 - 3
applications/desktop/desktop_i.h

@@ -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

+ 2 - 2
applications/desktop/scenes/desktop_scene_main.c

@@ -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 - 0
applications/gui/modules/menu.c

@@ -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 - 0
applications/gui/modules/menu.h

@@ -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

+ 9 - 12
applications/gui/modules/submenu.c

@@ -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>
+#include <furi.h>
+
+struct Submenu {
+    View* view;
+};
 
-struct SubmenuItem {
+typedef struct {
     const char* label;
     uint32_t index;
     SubmenuItemCallback callback;
     void* callback_context;
-};
+} SubmenuItem;
 
 ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST);
 
-struct Submenu {
-    View* view;
-};
-
 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) {

+ 1 - 3
applications/gui/modules/submenu.h

@@ -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,

+ 161 - 114
applications/loader/loader.c

@@ -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(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]));
-            }
+    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]);
+    }
+}
 
-            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);

+ 3 - 0
applications/loader/loader.h

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

+ 22 - 3
applications/loader/loader_i.h

@@ -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;

+ 0 - 337
applications/menu/menu.c

@@ -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;
-}

+ 0 - 19
applications/menu/menu.h

@@ -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);

+ 0 - 65
applications/menu/menu_event.c

@@ -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);
-}

+ 0 - 32
applications/menu/menu_event.h

@@ -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);

+ 0 - 135
applications/menu/menu_item.c

@@ -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);
-}

+ 0 - 47
applications/menu/menu_item.h

@@ -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);

+ 2 - 2
lib/app-scened-template/view-modules/submenu-vm.cpp

@@ -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) {

+ 1 - 2
lib/app-scened-template/view-modules/submenu-vm.h

@@ -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,