[FL-867] GUI: ViewPort arrangement API, better input and draw dispatching (#333)
* Input: refactoring, platform agnostic key configuration, update usage across project. Minor queue usage fixes and tick timings. * Gui: lighter and more efficient input and draw call dispatching, ViewPort rearranging API. View: conditional model updates, API usage update. * BT: smaller update delay * GUI: ViewPort visibility check
This commit is contained in:
		| @@ -46,6 +46,6 @@ void bt_task() { | ||||
|  | ||||
|     while(1) { | ||||
|         view_port_enabled_set(bt->statusbar_view_port, api_hal_bt_is_alive()); | ||||
|         osDelay(1000); | ||||
|         osDelay(1024); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -9,12 +9,15 @@ bool dolphin_view_first_start_input(InputEvent* event, void* context) { | ||||
|             with_view_model( | ||||
|                 dolphin->idle_view_first_start, (DolphinViewFirstStartModel * model) { | ||||
|                     if(model->page > 0) model->page--; | ||||
|                     return true; | ||||
|                 }); | ||||
|         } else if(event->key == InputKeyRight) { | ||||
|             uint32_t page; | ||||
|             with_view_model( | ||||
|                 dolphin->idle_view_first_start, | ||||
|                 (DolphinViewFirstStartModel * model) { page = ++model->page; }); | ||||
|                 dolphin->idle_view_first_start, (DolphinViewFirstStartModel * model) { | ||||
|                     page = ++model->page; | ||||
|                     return true; | ||||
|                 }); | ||||
|             if(page > 8) { | ||||
|                 dolphin_save(dolphin); | ||||
|                 view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); | ||||
| @@ -140,6 +143,7 @@ void dolphin_task() { | ||||
|         dolphin->idle_view_stats, (DolphinViewIdleStatsModel * model) { | ||||
|             model->icounter = dolphin_state_get_icounter(dolphin->state); | ||||
|             model->butthurt = dolphin_state_get_butthurt(dolphin->state); | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
|     furi_record_create("dolphin", dolphin); | ||||
| @@ -153,6 +157,7 @@ void dolphin_task() { | ||||
|                 dolphin->idle_view_stats, (DolphinViewIdleStatsModel * model) { | ||||
|                     model->icounter = dolphin_state_get_icounter(dolphin->state); | ||||
|                     model->butthurt = dolphin_state_get_butthurt(dolphin->state); | ||||
|                     return true; | ||||
|                 }); | ||||
|         } else if(event.type == DolphinEventTypeSave) { | ||||
|             dolphin_state_save(dolphin->state); | ||||
|   | ||||
| @@ -1,41 +1,44 @@ | ||||
| #include "gui.h" | ||||
| #include "gui_i.h" | ||||
|  | ||||
| #include <furi.h> | ||||
| #include <m-array.h> | ||||
| #include <stdio.h> | ||||
|  | ||||
| #include "gui_event.h" | ||||
| #include "canvas.h" | ||||
| #include "canvas_i.h" | ||||
| #include "view_port.h" | ||||
| #include "view_port_i.h" | ||||
|  | ||||
| ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); | ||||
|  | ||||
| struct Gui { | ||||
|     GuiEvent* event; | ||||
|     Canvas* canvas; | ||||
|     ViewPortArray_t layers[GuiLayerMAX]; | ||||
|     osMutexId_t mutex; | ||||
| }; | ||||
|  | ||||
| ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { | ||||
|     size_t view_ports_count = ViewPortArray_size(array); | ||||
|     for(size_t i = 0; i < view_ports_count; i++) { | ||||
|         ViewPort* view_port = *ViewPortArray_get(array, view_ports_count - i - 1); | ||||
|     // Iterating backward | ||||
|     ViewPortArray_it_t it; | ||||
|     ViewPortArray_it_last(it, array); | ||||
|     while(!ViewPortArray_end_p(it)) { | ||||
|         ViewPort* view_port = *ViewPortArray_ref(it); | ||||
|         if(view_port_is_enabled(view_port)) { | ||||
|             return view_port; | ||||
|         } | ||||
|         ViewPortArray_previous(it); | ||||
|     } | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| void gui_update(Gui* gui) { | ||||
| void gui_update(Gui* gui, ViewPort* view_port) { | ||||
|     furi_assert(gui); | ||||
|     GuiMessage message; | ||||
|     message.type = GuiMessageTypeRedraw; | ||||
|     gui_event_messsage_send(gui->event, &message); | ||||
|     if(view_port) { | ||||
|         // Visibility check | ||||
|         gui_lock(gui); | ||||
|         for(size_t i = 0; i < GuiLayerMAX; i++) { | ||||
|             if(gui_view_port_find_enabled(gui->layers[i]) == view_port) { | ||||
|                 osThreadFlagsSet(gui->thread, GUI_THREAD_FLAG_DRAW); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         gui_unlock(gui); | ||||
|     } else { | ||||
|         osThreadFlagsSet(gui->thread, GUI_THREAD_FLAG_DRAW); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void gui_input_events_callback(const void* value, void* ctx) { | ||||
|     furi_assert(value); | ||||
|     furi_assert(ctx); | ||||
|  | ||||
|     Gui* gui = ctx; | ||||
|  | ||||
|     osMessageQueuePut(gui->input_queue, value, 0, osWaitForever); | ||||
|     osThreadFlagsSet(gui->thread, GUI_THREAD_FLAG_INPUT); | ||||
| } | ||||
|  | ||||
| bool gui_redraw_fs(Gui* gui) { | ||||
| @@ -133,7 +136,9 @@ void gui_input(Gui* gui, InputEvent* input_event) { | ||||
|  | ||||
|     gui_lock(gui); | ||||
|  | ||||
|     ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); | ||||
|     ViewPort* view_port; | ||||
|  | ||||
|     view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); | ||||
|     if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerMain]); | ||||
|     if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerNone]); | ||||
|  | ||||
| @@ -160,10 +165,21 @@ void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) { | ||||
|     furi_check(layer < GuiLayerMAX); | ||||
|  | ||||
|     gui_lock(gui); | ||||
|     // Verify that view port is not yet added | ||||
|     ViewPortArray_it_t it; | ||||
|     for(size_t i = 0; i < GuiLayerMAX; i++) { | ||||
|         ViewPortArray_it(it, gui->layers[i]); | ||||
|         while(!ViewPortArray_end_p(it)) { | ||||
|             furi_assert(*ViewPortArray_ref(it) != view_port); | ||||
|             ViewPortArray_next(it); | ||||
|         } | ||||
|     } | ||||
|     // Add view port and link with gui | ||||
|     ViewPortArray_push_back(gui->layers[layer], view_port); | ||||
|     view_port_gui_set(view_port, gui); | ||||
|     gui_unlock(gui); | ||||
|     gui_update(gui); | ||||
|  | ||||
|     gui_update(gui, NULL); | ||||
| } | ||||
|  | ||||
| void gui_remove_view_port(Gui* gui, ViewPort* view_port) { | ||||
| @@ -179,27 +195,87 @@ void gui_remove_view_port(Gui* gui, ViewPort* view_port) { | ||||
|         while(!ViewPortArray_end_p(it)) { | ||||
|             if(*ViewPortArray_ref(it) == view_port) { | ||||
|                 ViewPortArray_remove(gui->layers[i], it); | ||||
|             } else { | ||||
|                 ViewPortArray_next(it); | ||||
|             } | ||||
|             ViewPortArray_next(it); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     gui_unlock(gui); | ||||
| } | ||||
|  | ||||
| void gui_send_view_port_front(Gui* gui, ViewPort* view_port) { | ||||
|     furi_assert(gui); | ||||
|     furi_assert(view_port); | ||||
|  | ||||
|     gui_lock(gui); | ||||
|     // Remove | ||||
|     GuiLayer layer = GuiLayerMAX; | ||||
|     ViewPortArray_it_t it; | ||||
|     for(size_t i = 0; i < GuiLayerMAX; i++) { | ||||
|         ViewPortArray_it(it, gui->layers[i]); | ||||
|         while(!ViewPortArray_end_p(it)) { | ||||
|             if(*ViewPortArray_ref(it) == view_port) { | ||||
|                 ViewPortArray_remove(gui->layers[i], it); | ||||
|                 furi_assert(layer == GuiLayerMAX); | ||||
|                 layer = i; | ||||
|             } else { | ||||
|                 ViewPortArray_next(it); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     furi_assert(layer != GuiLayerMAX); | ||||
|     // Return to the top | ||||
|     ViewPortArray_push_back(gui->layers[layer], view_port); | ||||
|     gui_unlock(gui); | ||||
| } | ||||
|  | ||||
| void gui_send_view_port_back(Gui* gui, ViewPort* view_port) { | ||||
|     furi_assert(gui); | ||||
|     furi_assert(view_port); | ||||
|  | ||||
|     gui_lock(gui); | ||||
|     // Remove | ||||
|     GuiLayer layer = GuiLayerMAX; | ||||
|     ViewPortArray_it_t it; | ||||
|     for(size_t i = 0; i < GuiLayerMAX; i++) { | ||||
|         ViewPortArray_it(it, gui->layers[i]); | ||||
|         while(!ViewPortArray_end_p(it)) { | ||||
|             if(*ViewPortArray_ref(it) == view_port) { | ||||
|                 ViewPortArray_remove(gui->layers[i], it); | ||||
|                 furi_assert(layer == GuiLayerMAX); | ||||
|                 layer = i; | ||||
|             } else { | ||||
|                 ViewPortArray_next(it); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     furi_assert(layer != GuiLayerMAX); | ||||
|     // Return to the top | ||||
|     ViewPortArray_push_at(gui->layers[layer], 0, view_port); | ||||
|     gui_unlock(gui); | ||||
| } | ||||
|  | ||||
| Gui* gui_alloc() { | ||||
|     Gui* gui = furi_alloc(sizeof(Gui)); | ||||
|     // Thread ID | ||||
|     gui->thread = osThreadGetId(); | ||||
|     gui->mutex_attr.name = "mtx_gui"; | ||||
|     gui->mutex_attr.attr_bits |= osMutexRecursive; | ||||
|     // Allocate mutex | ||||
|     gui->mutex = osMutexNew(NULL); | ||||
|     gui->mutex = osMutexNew(&gui->mutex_attr); | ||||
|     furi_check(gui->mutex); | ||||
|     // Event dispatcher | ||||
|     gui->event = gui_event_alloc(); | ||||
|     // Drawing canvas | ||||
|     gui->canvas = canvas_init(); | ||||
|     // Compose Layers | ||||
|     // Layers | ||||
|     for(size_t i = 0; i < GuiLayerMAX; i++) { | ||||
|         ViewPortArray_init(gui->layers[i]); | ||||
|     } | ||||
|     // Drawing canvas | ||||
|     gui->canvas = canvas_init(); | ||||
|     // Input | ||||
|     gui->input_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL); | ||||
|     gui->input_events = furi_record_open("input_events"); | ||||
|     furi_check(gui->input_events); | ||||
|     subscribe_pubsub(gui->input_events, gui_input_events_callback, gui); | ||||
|  | ||||
|     return gui; | ||||
| } | ||||
| @@ -207,16 +283,23 @@ Gui* gui_alloc() { | ||||
| void gui_task(void* p) { | ||||
|     Gui* gui = gui_alloc(); | ||||
|  | ||||
|     // Create FURI record | ||||
|     furi_record_create("gui", gui); | ||||
|  | ||||
|     // Forever dispatch | ||||
|     while(1) { | ||||
|         GuiMessage message = gui_event_message_next(gui->event); | ||||
|         if(message.type == GuiMessageTypeRedraw) { | ||||
|         uint32_t flags = osThreadFlagsWait(GUI_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); | ||||
|         // Process and dispatch input | ||||
|         if(flags & GUI_THREAD_FLAG_INPUT) { | ||||
|             // Process till queue become empty | ||||
|             InputEvent input_event; | ||||
|             while(osMessageQueueGet(gui->input_queue, &input_event, NULL, 0) == osOK) { | ||||
|                 gui_input(gui, &input_event); | ||||
|             } | ||||
|         } | ||||
|         // Process and dispatch draw call | ||||
|         if(flags & GUI_THREAD_FLAG_DRAW) { | ||||
|             // Clear flags that arrived on input step | ||||
|             osThreadFlagsClear(GUI_THREAD_FLAG_DRAW); | ||||
|             gui_redraw(gui); | ||||
|         } else if(message.type == GuiMessageTypeInput) { | ||||
|             gui_input(gui, &message.input); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,26 +7,13 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| #define GUI_DISPLAY_WIDTH 128 | ||||
| #define GUI_DISPLAY_HEIGHT 64 | ||||
|  | ||||
| #define GUI_STATUS_BAR_X 0 | ||||
| #define GUI_STATUS_BAR_Y 0 | ||||
| #define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH | ||||
| #define GUI_STATUS_BAR_HEIGHT 8 | ||||
|  | ||||
| #define GUI_MAIN_X 0 | ||||
| #define GUI_MAIN_Y 9 | ||||
| #define GUI_MAIN_WIDTH GUI_DISPLAY_WIDTH | ||||
| #define GUI_MAIN_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_MAIN_Y) | ||||
|  | ||||
| typedef enum { | ||||
|     GuiLayerNone, /* Special layer for internal use only */ | ||||
|  | ||||
|     GuiLayerStatusBarLeft, /* Status bar left-side view_port layer, auto-layout */ | ||||
|     GuiLayerStatusBarRight, /* Status bar right-side view_port layer, auto-layout */ | ||||
|     GuiLayerMain, /* Main view_port layer, status bar is shown */ | ||||
|     GuiLayerFullscreen, /* Fullscreen view_port layer */ | ||||
|     GuiLayerStatusBarLeft, /* Status bar left-side layer, auto-layout */ | ||||
|     GuiLayerStatusBarRight, /* Status bar right-side layer, auto-layout */ | ||||
|     GuiLayerMain, /* Main layer, status bar is shown */ | ||||
|     GuiLayerFullscreen, /* Fullscreen layer */ | ||||
|  | ||||
|     GuiLayerMAX /* Don't use or move, special value */ | ||||
| } GuiLayer; | ||||
| @@ -45,6 +32,20 @@ void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer); | ||||
|  */ | ||||
| void gui_remove_view_port(Gui* gui, ViewPort* view_port); | ||||
|  | ||||
| /* Send ViewPort to the front | ||||
|  * Places selected ViewPort to the top of the drawing stack | ||||
|  * @param gui, Gui instance | ||||
|  * @param view_port, ViewPort instance | ||||
|  */ | ||||
| void gui_send_view_port_front(Gui* gui, ViewPort* view_port); | ||||
|  | ||||
| /* Send ViewPort to the back | ||||
|  * Places selected ViewPort to the bottom of the drawing stack | ||||
|  * @param gui, Gui instance | ||||
|  * @param view_port, ViewPort instance | ||||
|  */ | ||||
| void gui_send_view_port_back(Gui* gui, ViewPort* view_port); | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|   | ||||
| @@ -1,74 +0,0 @@ | ||||
| #include "gui_event.h" | ||||
|  | ||||
| #include <furi.h> | ||||
|  | ||||
| #define GUI_EVENT_MQUEUE_SIZE 8 | ||||
|  | ||||
| struct GuiEvent { | ||||
|     PubSub* input_event_record; | ||||
|     osTimerId_t timer; | ||||
|     osMessageQueueId_t mqueue; | ||||
| }; | ||||
|  | ||||
| void gui_event_timer_callback(void* arg) { | ||||
|     assert(arg); | ||||
|     GuiEvent* gui_event = arg; | ||||
|  | ||||
|     GuiMessage message; | ||||
|     message.type = GuiMessageTypeRedraw; | ||||
|  | ||||
|     osMessageQueuePut(gui_event->mqueue, &message, 0, osWaitForever); | ||||
| } | ||||
|  | ||||
| void gui_event_input_events_callback(const void* value, void* ctx) { | ||||
|     furi_assert(value); | ||||
|     furi_assert(ctx); | ||||
|  | ||||
|     GuiEvent* gui_event = ctx; | ||||
|  | ||||
|     GuiMessage message; | ||||
|     message.type = GuiMessageTypeInput; | ||||
|     message.input = *(InputEvent*)value; | ||||
|  | ||||
|     osMessageQueuePut(gui_event->mqueue, &message, 0, osWaitForever); | ||||
| } | ||||
|  | ||||
| GuiEvent* gui_event_alloc() { | ||||
|     GuiEvent* gui_event = furi_alloc(sizeof(GuiEvent)); | ||||
|  | ||||
|     // Allocate message queue | ||||
|     gui_event->mqueue = osMessageQueueNew(GUI_EVENT_MQUEUE_SIZE, sizeof(GuiMessage), NULL); | ||||
|     furi_check(gui_event->mqueue); | ||||
|  | ||||
|     gui_event->timer = osTimerNew(gui_event_timer_callback, osTimerPeriodic, gui_event, NULL); | ||||
|     assert(gui_event->timer); | ||||
|     // osTimerStart(gui_event->timer, 1024 / 4); | ||||
|  | ||||
|     // Input | ||||
|     gui_event->input_event_record = furi_record_open("input_events"); | ||||
|     furi_check(gui_event->input_event_record != NULL); | ||||
|     subscribe_pubsub(gui_event->input_event_record, gui_event_input_events_callback, gui_event); | ||||
|  | ||||
|     return gui_event; | ||||
| } | ||||
|  | ||||
| void gui_event_free(GuiEvent* gui_event) { | ||||
|     furi_assert(gui_event); | ||||
|     furi_check(osMessageQueueDelete(gui_event->mqueue) == osOK); | ||||
|     free(gui_event); | ||||
| } | ||||
|  | ||||
| void gui_event_messsage_send(GuiEvent* gui_event, GuiMessage* message) { | ||||
|     furi_assert(gui_event); | ||||
|     furi_assert(message); | ||||
|     osMessageQueuePut(gui_event->mqueue, message, 0, 0); | ||||
| } | ||||
|  | ||||
| GuiMessage gui_event_message_next(GuiEvent* gui_event) { | ||||
|     furi_assert(gui_event); | ||||
|     GuiMessage message; | ||||
|  | ||||
|     furi_check(osMessageQueueGet(gui_event->mqueue, &message, NULL, osWaitForever) == osOK); | ||||
|  | ||||
|     return message; | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <stdint.h> | ||||
| #include <input/input.h> | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| typedef enum { | ||||
|     GuiMessageTypeRedraw = 0x00, | ||||
|     GuiMessageTypeInput = 0x01, | ||||
| } GuiMessageType; | ||||
|  | ||||
| typedef struct { | ||||
|     GuiMessageType type; | ||||
|     InputEvent input; | ||||
|     void* data; | ||||
| } GuiMessage; | ||||
|  | ||||
| typedef struct GuiEvent GuiEvent; | ||||
|  | ||||
| GuiEvent* gui_event_alloc(); | ||||
|  | ||||
| void gui_event_free(GuiEvent* gui_event); | ||||
|  | ||||
| void gui_event_messsage_send(GuiEvent* gui_event, GuiMessage* message); | ||||
|  | ||||
| GuiMessage gui_event_message_next(GuiEvent* gui_event); | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @@ -1,7 +1,60 @@ | ||||
| #pragma once | ||||
|  | ||||
| typedef struct Gui Gui; | ||||
| #include "gui.h" | ||||
|  | ||||
| #include <furi.h> | ||||
| #include <m-array.h> | ||||
| #include <stdio.h> | ||||
|  | ||||
| #include "canvas.h" | ||||
| #include "canvas_i.h" | ||||
| #include "view_port.h" | ||||
| #include "view_port_i.h" | ||||
|  | ||||
| #define GUI_DISPLAY_WIDTH 128 | ||||
| #define GUI_DISPLAY_HEIGHT 64 | ||||
|  | ||||
| #define GUI_STATUS_BAR_X 0 | ||||
| #define GUI_STATUS_BAR_Y 0 | ||||
| #define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH | ||||
| #define GUI_STATUS_BAR_HEIGHT 8 | ||||
|  | ||||
| #define GUI_MAIN_X 0 | ||||
| #define GUI_MAIN_Y 9 | ||||
| #define GUI_MAIN_WIDTH GUI_DISPLAY_WIDTH | ||||
| #define GUI_MAIN_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_MAIN_Y) | ||||
|  | ||||
| #define GUI_THREAD_FLAG_DRAW (1 << 0) | ||||
| #define GUI_THREAD_FLAG_INPUT (1 << 1) | ||||
| #define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT) | ||||
|  | ||||
| ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); | ||||
|  | ||||
| struct Gui { | ||||
|     // Thread and lock | ||||
|     osThreadId_t thread; | ||||
|     osMutexAttr_t mutex_attr; | ||||
|     osMutexId_t mutex; | ||||
|     // Layers and Canvas | ||||
|     ViewPortArray_t layers[GuiLayerMAX]; | ||||
|     Canvas* canvas; | ||||
|     // Input | ||||
|     osMessageQueueId_t input_queue; | ||||
|     PubSub* input_events; | ||||
| }; | ||||
|  | ||||
| ViewPort* gui_view_port_find_enabled(ViewPortArray_t array); | ||||
|  | ||||
| /* Update GUI, request redraw | ||||
|  * Real redraw event will be issued only if view_port is currently visible | ||||
|  * Setting view_port to NULL forces redraw, but must be avoided | ||||
|  * @param gui, Gui instance | ||||
|  * @param view_port, ViewPort instance or NULL | ||||
|  */ | ||||
| void gui_update(Gui* gui, ViewPort* view_port); | ||||
|  | ||||
| void gui_input_events_callback(const void* value, void* ctx); | ||||
|  | ||||
| void gui_update(Gui* gui); | ||||
| void gui_lock(Gui* gui); | ||||
|  | ||||
| void gui_unlock(Gui* gui); | ||||
| @@ -91,26 +91,38 @@ void dialog_set_header_text(Dialog* dialog, const char* text) { | ||||
|     furi_assert(dialog); | ||||
|     furi_assert(text); | ||||
|     with_view_model( | ||||
|         dialog->view, (DialogModel * model) { model->header_text = text; }); | ||||
|         dialog->view, (DialogModel * model) { | ||||
|             model->header_text = text; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| void dialog_set_text(Dialog* dialog, const char* text) { | ||||
|     furi_assert(dialog); | ||||
|     furi_assert(text); | ||||
|     with_view_model( | ||||
|         dialog->view, (DialogModel * model) { model->text = text; }); | ||||
|         dialog->view, (DialogModel * model) { | ||||
|             model->text = text; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| void dialog_set_left_button_text(Dialog* dialog, const char* text) { | ||||
|     furi_assert(dialog); | ||||
|     furi_assert(text); | ||||
|     with_view_model( | ||||
|         dialog->view, (DialogModel * model) { model->left_text = text; }); | ||||
|         dialog->view, (DialogModel * model) { | ||||
|             model->left_text = text; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| void dialog_set_right_button_text(Dialog* dialog, const char* text) { | ||||
|     furi_assert(dialog); | ||||
|     furi_assert(text); | ||||
|     with_view_model( | ||||
|         dialog->view, (DialogModel * model) { model->right_text = text; }); | ||||
|         dialog->view, (DialogModel * model) { | ||||
|             model->right_text = text; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|   | ||||
| @@ -94,6 +94,7 @@ static bool dialog_ex_view_input_callback(InputEvent* event, void* context) { | ||||
|             left_text = model->left_text; | ||||
|             center_text = model->center_text; | ||||
|             right_text = model->right_text; | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
|     // Process key presses only | ||||
| @@ -142,6 +143,8 @@ DialogEx* dialog_ex_alloc() { | ||||
|             model->left_text = NULL; | ||||
|             model->center_text = NULL; | ||||
|             model->right_text = NULL; | ||||
|  | ||||
|             return true; | ||||
|         }); | ||||
|     return dialog_ex; | ||||
| } | ||||
| @@ -182,6 +185,7 @@ void dialog_ex_set_header( | ||||
|             model->header.y = y; | ||||
|             model->header.horizontal = horizontal; | ||||
|             model->header.vertical = vertical; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -200,6 +204,7 @@ void dialog_ex_set_text( | ||||
|             model->text.y = y; | ||||
|             model->text.horizontal = horizontal; | ||||
|             model->text.vertical = vertical; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -210,23 +215,33 @@ void dialog_ex_set_icon(DialogEx* dialog_ex, int8_t x, int8_t y, IconName name) | ||||
|             model->icon.x = x; | ||||
|             model->icon.y = y; | ||||
|             model->icon.name = name; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text) { | ||||
|     furi_assert(dialog_ex); | ||||
|     with_view_model( | ||||
|         dialog_ex->view, (DialogExModel * model) { model->left_text = text; }); | ||||
|         dialog_ex->view, (DialogExModel * model) { | ||||
|             model->left_text = text; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text) { | ||||
|     furi_assert(dialog_ex); | ||||
|     with_view_model( | ||||
|         dialog_ex->view, (DialogExModel * model) { model->center_text = text; }); | ||||
|         dialog_ex->view, (DialogExModel * model) { | ||||
|             model->center_text = text; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) { | ||||
|     furi_assert(dialog_ex); | ||||
|     with_view_model( | ||||
|         dialog_ex->view, (DialogExModel * model) { model->right_text = text; }); | ||||
|         dialog_ex->view, (DialogExModel * model) { | ||||
|             model->right_text = text; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|   | ||||
| @@ -141,6 +141,7 @@ Popup* popup_alloc() { | ||||
|             model->icon.x = -1; | ||||
|             model->icon.y = -1; | ||||
|             model->icon.name = I_ButtonCenter_7x7; | ||||
|             return true; | ||||
|         }); | ||||
|     return popup; | ||||
| } | ||||
| @@ -182,6 +183,7 @@ void popup_set_header( | ||||
|             model->header.y = y; | ||||
|             model->header.horizontal = horizontal; | ||||
|             model->header.vertical = vertical; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -200,6 +202,7 @@ void popup_set_text( | ||||
|             model->text.y = y; | ||||
|             model->text.horizontal = horizontal; | ||||
|             model->text.vertical = vertical; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -210,6 +213,7 @@ void popup_set_icon(Popup* popup, int8_t x, int8_t y, IconName name) { | ||||
|             model->icon.x = x; | ||||
|             model->icon.y = y; | ||||
|             model->icon.name = name; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -108,6 +108,7 @@ Submenu* submenu_alloc() { | ||||
|         submenu->view, (SubmenuModel * model) { | ||||
|             SubmenuItemArray_init(model->items); | ||||
|             model->position = 0; | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
|     return submenu; | ||||
| @@ -117,7 +118,10 @@ void submenu_free(Submenu* submenu) { | ||||
|     furi_assert(submenu); | ||||
|  | ||||
|     with_view_model( | ||||
|         submenu->view, (SubmenuModel * model) { SubmenuItemArray_clear(model->items); }); | ||||
|         submenu->view, (SubmenuModel * model) { | ||||
|             SubmenuItemArray_clear(model->items); | ||||
|             return true; | ||||
|         }); | ||||
|     view_free(submenu->view); | ||||
|     free(submenu); | ||||
| } | ||||
| @@ -142,6 +146,7 @@ SubmenuItem* submenu_add_item( | ||||
|             item->label = label; | ||||
|             item->callback = callback; | ||||
|             item->callback_context = callback_context; | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
|     return item; | ||||
| @@ -159,6 +164,7 @@ void submenu_process_up(Submenu* submenu) { | ||||
|                 model->position = SubmenuItemArray_size(model->items) - 1; | ||||
|                 model->window_position = model->position - 3; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -175,6 +181,7 @@ void submenu_process_down(Submenu* submenu) { | ||||
|                 model->position = 0; | ||||
|                 model->window_position = 0; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -186,6 +193,7 @@ void submenu_process_ok(Submenu* submenu) { | ||||
|             if(model->position < (SubmenuItemArray_size(model->items))) { | ||||
|                 item = SubmenuItemArray_get(model->items, model->position); | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
|     if(item && item->callback) { | ||||
|   | ||||
| @@ -223,6 +223,7 @@ static void text_input_handle_up(TextInput* text_input) { | ||||
|             if(model->selected_row > 0) { | ||||
|                 model->selected_row--; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -235,6 +236,7 @@ static void text_input_handle_down(TextInput* text_input) { | ||||
|                     model->selected_column = get_row_size(model->selected_row) - 1; | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -246,6 +248,7 @@ static void text_input_handle_left(TextInput* text_input) { | ||||
|             } else { | ||||
|                 model->selected_column = get_row_size(model->selected_row) - 1; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -257,6 +260,7 @@ static void text_input_handle_right(TextInput* text_input) { | ||||
|             } else { | ||||
|                 model->selected_column = 0; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -281,6 +285,7 @@ static void text_input_handle_ok(TextInput* text_input) { | ||||
|                 model->text[text_length] = selected; | ||||
|                 model->text[text_length + 1] = 0; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -333,6 +338,7 @@ TextInput* text_input_alloc() { | ||||
|             model->header = ""; | ||||
|             model->selected_row = 0; | ||||
|             model->selected_column = 0; | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
|     return text_input; | ||||
| @@ -361,10 +367,14 @@ void text_input_set_result_callback( | ||||
|             model->callback_context = callback_context; | ||||
|             model->text = text; | ||||
|             model->max_text_length = max_text_length; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| void text_input_set_header_text(TextInput* text_input, const char* text) { | ||||
|     with_view_model( | ||||
|         text_input->view, (TextInputModel * model) { model->header = text; }); | ||||
|         text_input->view, (TextInputModel * model) { | ||||
|             model->header = text; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| @@ -102,10 +102,10 @@ void* view_get_model(View* view) { | ||||
|     return view->model; | ||||
| } | ||||
|  | ||||
| void view_commit_model(View* view) { | ||||
| void view_commit_model(View* view, bool update) { | ||||
|     furi_assert(view); | ||||
|     view_unlock_model(view); | ||||
|     if(view->dispatcher) { | ||||
|     if(update && view->dispatcher) { | ||||
|         view_dispatcher_update(view->dispatcher, view); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -137,20 +137,21 @@ void* view_get_model(View* view); | ||||
|  | ||||
| /* Commit view model | ||||
|  * @param view, pointer to View | ||||
|  * @param update, true if you want to emit view update, false otherwise | ||||
|  */ | ||||
| void view_commit_model(View* view); | ||||
| void view_commit_model(View* view, bool update); | ||||
|  | ||||
| /*  | ||||
|  * With clause for view model | ||||
|  * @param view, View instance pointer | ||||
|  * @param function_body a (){} lambda declaration, | ||||
|  * executed within you parent function context. | ||||
|  * @param function_body a (){} lambda declaration, executed within you parent function context | ||||
|  * @return true if you want to emit view update, false otherwise | ||||
|  */ | ||||
| #define with_view_model(view, function_body)        \ | ||||
|     {                                               \ | ||||
|         void* p = view_get_model(view);             \ | ||||
|         ({ void __fn__ function_body __fn__; })(p); \ | ||||
|         view_commit_model(view);                    \ | ||||
| #define with_view_model(view, function_body)                      \ | ||||
|     {                                                             \ | ||||
|         void* p = view_get_model(view);                           \ | ||||
|         bool update = ({ bool __fn__ function_body __fn__; })(p); \ | ||||
|         view_commit_model(view, update);                          \ | ||||
|     } | ||||
|  | ||||
| #ifdef __cplusplus | ||||
|   | ||||
| @@ -43,7 +43,7 @@ void view_port_enabled_set(ViewPort* view_port, bool enabled) { | ||||
|     furi_assert(view_port); | ||||
|     if(view_port->is_enabled != enabled) { | ||||
|         view_port->is_enabled = enabled; | ||||
|         view_port_update(view_port); | ||||
|         if(view_port->gui) gui_update(view_port->gui, NULL); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -69,7 +69,7 @@ void view_port_input_callback_set( | ||||
|  | ||||
| void view_port_update(ViewPort* view_port) { | ||||
|     furi_assert(view_port); | ||||
|     if(view_port->gui) gui_update(view_port->gui); | ||||
|     if(view_port->gui && view_port->is_enabled) gui_update(view_port->gui, view_port); | ||||
| } | ||||
|  | ||||
| void view_port_gui_set(ViewPort* view_port, Gui* gui) { | ||||
|   | ||||
| @@ -14,6 +14,7 @@ struct Menu { | ||||
|     MenuEvent* event; | ||||
|  | ||||
|     // GUI | ||||
|     Gui* gui; | ||||
|     ViewPort* view_port; | ||||
|     Icon* icon; | ||||
|  | ||||
| @@ -37,12 +38,14 @@ ValueMutex* menu_init() { | ||||
|         furiac_exit(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* gui = furi_record_open("gui"); | ||||
|     gui_add_view_port(gui, menu->view_port, GuiLayerFullscreen); | ||||
|     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); | ||||
| @@ -198,6 +201,7 @@ void menu_ok(Menu* menu) { | ||||
|         menu_update(menu); | ||||
|     } else if(type == MenuItemTypeFunction) { | ||||
|         menu_item_function_call(item); | ||||
|         gui_send_view_port_back(menu->gui, menu->view_port); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -89,8 +89,10 @@ void nfc_start(Nfc* nfc, NfcView view_id, NfcWorkerState worker_state) { | ||||
|     NfcWorkerState state = nfc_worker_get_state(nfc->worker); | ||||
|     if(state == NfcWorkerStateBroken) { | ||||
|         with_view_model( | ||||
|             nfc->view_error, | ||||
|             (NfcViewErrorModel * model) { model->error = nfc_worker_get_error(nfc->worker); }); | ||||
|             nfc->view_error, (NfcViewErrorModel * model) { | ||||
|                 model->error = nfc_worker_get_error(nfc->worker); | ||||
|                 return true; | ||||
|             }); | ||||
|         view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewError); | ||||
|     } else if(state == NfcWorkerStateReady) { | ||||
|         view_dispatcher_switch_to_view(nfc->view_dispatcher, view_id); | ||||
| @@ -114,7 +116,10 @@ void nfc_task(void* p) { | ||||
|         furi_check(osMessageQueueGet(nfc->message_queue, &message, NULL, osWaitForever) == osOK); | ||||
|         if(message.type == NfcMessageTypeDetect) { | ||||
|             with_view_model( | ||||
|                 nfc->view_detect, (NfcViewReadModel * model) { model->found = false; }); | ||||
|                 nfc->view_detect, (NfcViewReadModel * model) { | ||||
|                     model->found = false; | ||||
|                     return true; | ||||
|                 }); | ||||
|             nfc_start(nfc, NfcViewRead, NfcWorkerStatePoll); | ||||
|         } else if(message.type == NfcMessageTypeEmulate) { | ||||
|             nfc_start(nfc, NfcViewEmulate, NfcWorkerStateEmulate); | ||||
| @@ -127,10 +132,14 @@ void nfc_task(void* p) { | ||||
|                 nfc->view_detect, (NfcViewReadModel * model) { | ||||
|                     model->found = true; | ||||
|                     model->device = message.device; | ||||
|                     return true; | ||||
|                 }); | ||||
|         } else if(message.type == NfcMessageTypeDeviceNotFound) { | ||||
|             with_view_model( | ||||
|                 nfc->view_detect, (NfcViewReadModel * model) { model->found = false; }); | ||||
|                 nfc->view_detect, (NfcViewReadModel * model) { | ||||
|                     model->found = false; | ||||
|                     return true; | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -46,6 +46,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { | ||||
|     with_view_model( | ||||
|         power->info_view, (PowerInfoModel * model) { | ||||
|             canvas_draw_box(canvas, 2, 2, (float)model->charge / 100 * 14, 4); | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @@ -215,10 +216,11 @@ void power_task(void* p) { | ||||
|                     api_hal_power_get_battery_temperature(ApiHalPowerICCharger); | ||||
|                 model->temperature_gauge = | ||||
|                     api_hal_power_get_battery_temperature(ApiHalPowerICFuelGauge); | ||||
|                 return true; | ||||
|             }); | ||||
|  | ||||
|         view_port_update(power->battery_view_port); | ||||
|         view_port_enabled_set(power->usb_view_port, api_hal_power_is_charging()); | ||||
|         osDelay(1000); | ||||
|         osDelay(1024); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user