FL-528 GUI: View, ViewDispather. Dolphin: first start. (#276)
* GUI: view. Flooper-blooper fix compilation error. * GUI: view and viewdispatcher bones * GUI: view implementation, view models, view dispatcher * GUI: view navigation, model refinement. Power: use view, view dispatcher. * HAL Flash: proper page write. Dolphin: views. Power: views * Dolphin: transition idle scree to Views * Dolphin: input events on stats view. Format sources. * HAL: flash erase. Dolphin: permanent state storage. * Dolphin: first start welcome. HAL: flash operation status, errata 2.2.9 crutch.
| @ -1,100 +1,126 @@ | |||||||
| #include "dolphin_i.h" | #include "dolphin_i.h" | ||||||
| 
 | 
 | ||||||
| void dolphin_draw_callback(Canvas* canvas, void* context) { | bool dolphin_view_first_start_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     furi_assert(context); | ||||||
|     Dolphin* dolphin = context; |     Dolphin* dolphin = context; | ||||||
| 
 |     if(event->state) { | ||||||
|     canvas_clear(canvas); |         if(event->input == InputRight) { | ||||||
|     canvas_set_color(canvas, ColorBlack); |             uint32_t page; | ||||||
|     if(dolphin->screen == DolphinScreenIdle) { |             with_view_model( | ||||||
|         dolphin_draw_idle(canvas, dolphin); |                 dolphin->idle_view_first_start, | ||||||
|     } else if(dolphin->screen == DolphinScreenDebug) { |                 (DolphinViewFirstStartModel * model) { page = ++model->page; }); | ||||||
|         dolphin_draw_debug(canvas, dolphin); |             if(page > 8) { | ||||||
|     } else if(dolphin->screen == DolphinScreenStats) { |                 dolphin_save(dolphin); | ||||||
|         dolphin_draw_stats(canvas, dolphin); |                 view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     // All events consumed
 | ||||||
|  |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_draw_idle(Canvas* canvas, Dolphin* dolphin) { | bool dolphin_view_idle_main_input(InputEvent* event, void* context) { | ||||||
|     canvas_draw_icon(canvas, 128 - 80, 0, dolphin->icon); |     furi_assert(event); | ||||||
|     canvas_set_font(canvas, FontSecondary); |     furi_assert(context); | ||||||
|     canvas_draw_str(canvas, 2, 10, "/\\: Stats"); |  | ||||||
|     canvas_draw_str(canvas, 5, 32, "OK: Menu"); |  | ||||||
|     canvas_draw_str(canvas, 2, 52, "\\/: Version"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_draw_debug(Canvas* canvas, Dolphin* dolphin) { |  | ||||||
|     canvas_set_font(canvas, FontPrimary); |  | ||||||
|     canvas_draw_str(canvas, 2, 10, "Version info:"); |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     canvas_draw_str(canvas, 5, 22, TARGET " " BUILD_DATE); |  | ||||||
|     canvas_draw_str(canvas, 5, 32, GIT_BRANCH); |  | ||||||
|     canvas_draw_str(canvas, 5, 42, GIT_BRANCH_NUM); |  | ||||||
|     canvas_draw_str(canvas, 5, 52, GIT_COMMIT); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_draw_stats(Canvas* canvas, Dolphin* dolphin) { |  | ||||||
|     canvas_set_font(canvas, FontPrimary); |  | ||||||
|     canvas_draw_str(canvas, 2, 10, "Dolphin stats:"); |  | ||||||
| 
 |  | ||||||
|     char buffer[64]; |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     snprintf(buffer, 64, "Icounter: %ld", dolphin_state_get_icounter(dolphin->state)); |  | ||||||
|     canvas_draw_str(canvas, 5, 22, buffer); |  | ||||||
|     snprintf(buffer, 64, "Butthurt: %ld", dolphin_state_get_butthurt(dolphin->state)); |  | ||||||
|     canvas_draw_str(canvas, 5, 32, buffer); |  | ||||||
|     canvas_draw_str(canvas, 5, 40, "< > change icounter"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_input_callback(InputEvent* event, void* context) { |  | ||||||
|     Dolphin* dolphin = context; |     Dolphin* dolphin = context; | ||||||
| 
 | 
 | ||||||
|     if(!event->state) return; |     if(event->state) { | ||||||
| 
 |  | ||||||
|         if(event->input == InputOk) { |         if(event->input == InputOk) { | ||||||
|             with_value_mutex( |             with_value_mutex( | ||||||
|                 dolphin->menu_vm, (Menu * menu) { menu_ok(menu); }); |                 dolphin->menu_vm, (Menu * menu) { menu_ok(menu); }); | ||||||
|         } else if(event->input == InputUp) { |         } else if(event->input == InputUp) { | ||||||
|         if(dolphin->screen != DolphinScreenStats) { |             view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleStats); | ||||||
|             dolphin->screen++; |  | ||||||
|         } |  | ||||||
|         } else if(event->input == InputDown) { |         } else if(event->input == InputDown) { | ||||||
|         if(dolphin->screen != DolphinScreenDebug) { |             view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleDebug); | ||||||
|             dolphin->screen--; |  | ||||||
|         } |         } | ||||||
|     } else if(event->input == InputBack) { |     } | ||||||
|         dolphin->screen = DolphinScreenIdle; |     // All events consumed
 | ||||||
|     } else if(event->input == InputLeft) { |     return true; | ||||||
|         dolphin_deed(dolphin, DolphinDeedIButtonEmulate); | } | ||||||
|     } else if(event->input == InputRight) { | 
 | ||||||
|  | bool dolphin_view_idle_stats_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     furi_assert(context); | ||||||
|  |     Dolphin* dolphin = context; | ||||||
|  | 
 | ||||||
|  |     if(!event->state) return false; | ||||||
|  | 
 | ||||||
|  |     if(event->input == InputLeft) { | ||||||
|         dolphin_deed(dolphin, DolphinDeedWrong); |         dolphin_deed(dolphin, DolphinDeedWrong); | ||||||
|  |     } else if(event->input == InputRight) { | ||||||
|  |         dolphin_deed(dolphin, DolphinDeedIButtonRead); | ||||||
|  |     } else if(event->input == InputOk) { | ||||||
|  |         dolphin_save(dolphin); | ||||||
|  |     } else { | ||||||
|  |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     widget_update(dolphin->widget); |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Dolphin* dolphin_alloc() { | Dolphin* dolphin_alloc() { | ||||||
|     Dolphin* dolphin = furi_alloc(sizeof(Dolphin)); |     Dolphin* dolphin = furi_alloc(sizeof(Dolphin)); | ||||||
| 
 |     // Message queue
 | ||||||
|     dolphin->icon = assets_icons_get(I_Flipper_young_80x60); |  | ||||||
|     icon_start_animation(dolphin->icon); |  | ||||||
| 
 |  | ||||||
|     dolphin->widget = widget_alloc(); |  | ||||||
|     widget_draw_callback_set(dolphin->widget, dolphin_draw_callback, dolphin); |  | ||||||
|     widget_input_callback_set(dolphin->widget, dolphin_input_callback, dolphin); |  | ||||||
| 
 |  | ||||||
|     dolphin->menu_vm = furi_open("menu"); |  | ||||||
|     furi_check(dolphin->menu_vm); |  | ||||||
| 
 |  | ||||||
|     dolphin->state = dolphin_state_alloc(); |  | ||||||
| 
 |  | ||||||
|     dolphin->screen = DolphinScreenIdle; |  | ||||||
| 
 |  | ||||||
|     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); |     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); | ||||||
|     furi_check(dolphin->event_queue); |     furi_check(dolphin->event_queue); | ||||||
|  |     // State
 | ||||||
|  |     dolphin->state = dolphin_state_alloc(); | ||||||
|  |     // Menu
 | ||||||
|  |     dolphin->menu_vm = furi_open("menu"); | ||||||
|  |     furi_check(dolphin->menu_vm); | ||||||
|  |     // GUI
 | ||||||
|  |     dolphin->idle_view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     // First start View
 | ||||||
|  |     dolphin->idle_view_first_start = view_alloc(); | ||||||
|  |     view_allocate_model( | ||||||
|  |         dolphin->idle_view_first_start, ViewModelTypeLockFree, sizeof(DolphinViewFirstStartModel)); | ||||||
|  |     view_set_context(dolphin->idle_view_first_start, dolphin); | ||||||
|  |     view_set_draw_callback(dolphin->idle_view_first_start, dolphin_view_first_start_draw); | ||||||
|  |     view_set_input_callback(dolphin->idle_view_first_start, dolphin_view_first_start_input); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         dolphin->idle_view_dispatcher, DolphinViewFirstStart, dolphin->idle_view_first_start); | ||||||
|  |     // Main Idle View
 | ||||||
|  |     dolphin->idle_view_main = view_alloc(); | ||||||
|  |     view_set_context(dolphin->idle_view_main, dolphin); | ||||||
|  |     view_set_draw_callback(dolphin->idle_view_main, dolphin_view_idle_main_draw); | ||||||
|  |     view_set_input_callback(dolphin->idle_view_main, dolphin_view_idle_main_input); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         dolphin->idle_view_dispatcher, DolphinViewIdleMain, dolphin->idle_view_main); | ||||||
|  |     // Stats Idle View
 | ||||||
|  |     dolphin->idle_view_stats = view_alloc(); | ||||||
|  |     view_set_context(dolphin->idle_view_stats, dolphin); | ||||||
|  |     view_allocate_model( | ||||||
|  |         dolphin->idle_view_stats, ViewModelTypeLockFree, sizeof(DolphinViewIdleStatsModel)); | ||||||
|  |     with_view_model( | ||||||
|  |         dolphin->idle_view_stats, (DolphinViewIdleStatsModel * model) { | ||||||
|  |             model->icounter = dolphin_state_get_icounter(dolphin->state); | ||||||
|  |             model->butthurt = dolphin_state_get_butthurt(dolphin->state); | ||||||
|  |         }); | ||||||
|  |     view_set_draw_callback(dolphin->idle_view_stats, dolphin_view_idle_stats_draw); | ||||||
|  |     view_set_input_callback(dolphin->idle_view_stats, dolphin_view_idle_stats_input); | ||||||
|  |     view_set_previous_callback(dolphin->idle_view_stats, dolphin_view_idle_back); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         dolphin->idle_view_dispatcher, DolphinViewIdleStats, dolphin->idle_view_stats); | ||||||
|  |     // Debug Idle View
 | ||||||
|  |     dolphin->idle_view_debug = view_alloc(); | ||||||
|  |     view_set_draw_callback(dolphin->idle_view_debug, dolphin_view_idle_debug_draw); | ||||||
|  |     view_set_previous_callback(dolphin->idle_view_debug, dolphin_view_idle_back); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         dolphin->idle_view_dispatcher, DolphinViewIdleDebug, dolphin->idle_view_debug); | ||||||
|  | 
 | ||||||
|     return dolphin; |     return dolphin; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void dolphin_save(Dolphin* dolphin) { | ||||||
|  |     furi_assert(dolphin); | ||||||
|  |     DolphinEvent event; | ||||||
|  |     event.type = DolphinEventTypeSave; | ||||||
|  |     furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) { | void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) { | ||||||
|  |     furi_assert(dolphin); | ||||||
|     DolphinEvent event; |     DolphinEvent event; | ||||||
|     event.type = DolphinEventTypeDeed; |     event.type = DolphinEventTypeDeed; | ||||||
|     event.deed = deed; |     event.deed = deed; | ||||||
| @ -105,7 +131,12 @@ void dolphin_task() { | |||||||
|     Dolphin* dolphin = dolphin_alloc(); |     Dolphin* dolphin = dolphin_alloc(); | ||||||
| 
 | 
 | ||||||
|     Gui* gui = furi_open("gui"); |     Gui* gui = furi_open("gui"); | ||||||
|     gui_add_widget(gui, dolphin->widget, GuiLayerNone); |     view_dispatcher_attach_to_gui(dolphin->idle_view_dispatcher, gui, ViewDispatcherTypeWindow); | ||||||
|  |     if(dolphin_state_load(dolphin->state)) { | ||||||
|  |         view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); | ||||||
|  |     } else { | ||||||
|  |         view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewFirstStart); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if(!furi_create("dolphin", dolphin)) { |     if(!furi_create("dolphin", dolphin)) { | ||||||
|         printf("[dolphin_task] cannot create the dolphin record\n"); |         printf("[dolphin_task] cannot create the dolphin record\n"); | ||||||
| @ -119,6 +150,13 @@ void dolphin_task() { | |||||||
|         furi_check(osMessageQueueGet(dolphin->event_queue, &event, NULL, osWaitForever) == osOK); |         furi_check(osMessageQueueGet(dolphin->event_queue, &event, NULL, osWaitForever) == osOK); | ||||||
|         if(event.type == DolphinEventTypeDeed) { |         if(event.type == DolphinEventTypeDeed) { | ||||||
|             dolphin_state_on_deed(dolphin->state, event.deed); |             dolphin_state_on_deed(dolphin->state, event.deed); | ||||||
|  |             with_view_model( | ||||||
|  |                 dolphin->idle_view_stats, (DolphinViewIdleStatsModel * model) { | ||||||
|  |                     model->icounter = dolphin_state_get_icounter(dolphin->state); | ||||||
|  |                     model->butthurt = dolphin_state_get_butthurt(dolphin->state); | ||||||
|  |                 }); | ||||||
|  |         } else if(event.type == DolphinEventTypeSave) { | ||||||
|  |             dolphin_state_save(dolphin->state); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,8 +4,8 @@ | |||||||
| 
 | 
 | ||||||
| typedef struct Dolphin Dolphin; | typedef struct Dolphin Dolphin; | ||||||
| 
 | 
 | ||||||
| /*
 | /* Deed complete notification. Call it on deed completion.
 | ||||||
|  * Deed complete notification. Call it on deed completion. |  | ||||||
|  * See dolphin_deed.h for available deeds. In futures it will become part of assets. |  * See dolphin_deed.h for available deeds. In futures it will become part of assets. | ||||||
|  |  * Thread safe | ||||||
|  */ |  */ | ||||||
| void dolphin_deed(Dolphin* dolphin, DolphinDeed deed); | void dolphin_deed(Dolphin* dolphin, DolphinDeed deed); | ||||||
|  | |||||||
| @ -2,20 +2,21 @@ | |||||||
| 
 | 
 | ||||||
| #include "dolphin.h" | #include "dolphin.h" | ||||||
| #include "dolphin_state.h" | #include "dolphin_state.h" | ||||||
|  | #include "dolphin_views.h" | ||||||
| 
 | 
 | ||||||
| #include <flipper_v2.h> | #include <flipper_v2.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/widget.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <menu/menu.h> | #include <menu/menu.h> | ||||||
| 
 | 
 | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| 
 |  | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     DolphinEventTypeDeed, |     DolphinEventTypeDeed, | ||||||
|  |     DolphinEventTypeSave, | ||||||
| } DolphinEventType; | } DolphinEventType; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -25,27 +26,24 @@ typedef struct { | |||||||
|     }; |     }; | ||||||
| } DolphinEvent; | } DolphinEvent; | ||||||
| 
 | 
 | ||||||
| typedef enum { |  | ||||||
|     DolphinScreenDebug, |  | ||||||
|     DolphinScreenIdle, |  | ||||||
|     DolphinScreenStats, |  | ||||||
| } DolphinScreen; |  | ||||||
| 
 |  | ||||||
| struct Dolphin { | struct Dolphin { | ||||||
|     Icon* icon; |  | ||||||
|     Widget* widget; |  | ||||||
|     ValueMutex* menu_vm; |  | ||||||
|     // State
 |  | ||||||
|     DolphinState* state; |  | ||||||
|     DolphinScreen screen; |  | ||||||
|     // Internal message queue
 |     // Internal message queue
 | ||||||
|     osMessageQueueId_t event_queue; |     osMessageQueueId_t event_queue; | ||||||
|  |     // State
 | ||||||
|  |     DolphinState* state; | ||||||
|  |     // Menu
 | ||||||
|  |     ValueMutex* menu_vm; | ||||||
|  |     // GUI
 | ||||||
|  |     ViewDispatcher* idle_view_dispatcher; | ||||||
|  |     View* idle_view_first_start; | ||||||
|  |     View* idle_view_main; | ||||||
|  |     View* idle_view_stats; | ||||||
|  |     View* idle_view_debug; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void dolphin_draw_callback(Canvas* canvas, void* context); |  | ||||||
| void dolphin_draw_idle(Canvas* canvas, Dolphin* dolphin); |  | ||||||
| void dolphin_draw_debug(Canvas* canvas, Dolphin* dolphin); |  | ||||||
| void dolphin_draw_stats(Canvas* canvas, Dolphin* dolphin); |  | ||||||
| void dolphin_input_callback(InputEvent* event, void* context); |  | ||||||
| 
 |  | ||||||
| Dolphin* dolphin_alloc(); | Dolphin* dolphin_alloc(); | ||||||
|  | 
 | ||||||
|  | /* Save Dolphin state (write to permanent memory)
 | ||||||
|  |  * Thread safe | ||||||
|  |  */ | ||||||
|  | void dolphin_save(Dolphin* dolphin); | ||||||
|  | |||||||
| @ -1,18 +1,35 @@ | |||||||
| #include "dolphin_state.h" | #include "dolphin_state.h" | ||||||
|  | #include <api-hal-flash.h> | ||||||
| #include <flipper_v2.h> | #include <flipper_v2.h> | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint32_t ibutton; |     uint8_t magic; | ||||||
|     uint32_t nfc; |     uint8_t version; | ||||||
|     uint32_t ir; |     uint8_t checksum; | ||||||
|     uint32_t rfid; |     uint8_t flags; | ||||||
| } DolphinLimit; |     uint32_t timestamp; | ||||||
|  | } DolphinDataHeader; | ||||||
| 
 | 
 | ||||||
| struct DolphinState { | #define DOLPHIN_DATA_PAGE 0xC0 | ||||||
|  | #define DOLPHIN_DATA_HEADER_ADDRESS 0x080C0000U | ||||||
|  | #define DOLPHIN_DATA_DATA_ADDRESS (DOLPHIN_DATA_HEADER_ADDRESS + sizeof(DolphinDataHeader)) | ||||||
|  | 
 | ||||||
|  | #define DOLPHIN_DATA_HEADER_MAGIC 0xD0 | ||||||
|  | #define DOLPHIN_DATA_HEADER_VERSION 0x00 | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint32_t limit_ibutton; | ||||||
|  |     uint32_t limit_nfc; | ||||||
|  |     uint32_t limit_ir; | ||||||
|  |     uint32_t limit_rfid; | ||||||
|  | 
 | ||||||
|  |     uint32_t flags; | ||||||
|     uint32_t icounter; |     uint32_t icounter; | ||||||
|     uint32_t butthurt; |     uint32_t butthurt; | ||||||
|  | } DolphinData; | ||||||
| 
 | 
 | ||||||
|     DolphinLimit limit; | struct DolphinState { | ||||||
|  |     DolphinData data; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| DolphinState* dolphin_state_alloc() { | DolphinState* dolphin_state_alloc() { | ||||||
| @ -24,29 +41,81 @@ void dolphin_state_release(DolphinState* dolphin_state) { | |||||||
|     free(dolphin_state); |     free(dolphin_state); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_state_save(DolphinState* dolphin_state) { | bool dolphin_state_save(DolphinState* dolphin_state) { | ||||||
|  |     if(!api_hal_flash_erase(DOLPHIN_DATA_PAGE, 1)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     uint8_t* source = (uint8_t*)&dolphin_state->data; | ||||||
|  |     uint8_t checksum = 0; | ||||||
|  |     for(size_t i = 0; i < sizeof(DolphinData); i++) { | ||||||
|  |         checksum += source[i]; | ||||||
|  |     } | ||||||
|  |     DolphinDataHeader header; | ||||||
|  |     header.magic = DOLPHIN_DATA_HEADER_MAGIC; | ||||||
|  |     header.version = DOLPHIN_DATA_HEADER_VERSION; | ||||||
|  |     header.checksum = checksum; | ||||||
|  |     header.flags = 0; | ||||||
|  |     header.timestamp = 0; | ||||||
|  |     if(!api_hal_flash_write_dword(DOLPHIN_DATA_HEADER_ADDRESS, *(uint64_t*)&header)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     uint8_t destination[sizeof(uint64_t)]; | ||||||
|  |     size_t block_count = sizeof(DolphinData) / sizeof(uint64_t) + 1; | ||||||
|  |     size_t offset = 0; | ||||||
|  |     for(size_t i = 0; i < block_count; i++) { | ||||||
|  |         for(size_t n = 0; n < sizeof(uint64_t); n++) { | ||||||
|  |             if(offset < sizeof(DolphinData)) { | ||||||
|  |                 destination[n] = source[offset]; | ||||||
|  |             } else { | ||||||
|  |                 destination[n] = 0; | ||||||
|  |             } | ||||||
|  |             offset++; | ||||||
|  |         } | ||||||
|  |         if(!api_hal_flash_write_dword( | ||||||
|  |                DOLPHIN_DATA_DATA_ADDRESS + i * sizeof(uint64_t), *(uint64_t*)destination)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_state_load(DolphinState* dolphin_state) { | bool dolphin_state_load(DolphinState* dolphin_state) { | ||||||
|  |     const DolphinDataHeader* header = (const DolphinDataHeader*)DOLPHIN_DATA_HEADER_ADDRESS; | ||||||
|  |     if(header->magic == DOLPHIN_DATA_HEADER_MAGIC && | ||||||
|  |        header->version == DOLPHIN_DATA_HEADER_VERSION) { | ||||||
|  |         uint8_t checksum = 0; | ||||||
|  |         const uint8_t* source = (const uint8_t*)DOLPHIN_DATA_DATA_ADDRESS; | ||||||
|  |         for(size_t i = 0; i < sizeof(DolphinData); i++) { | ||||||
|  |             checksum += source[i]; | ||||||
|  |         } | ||||||
|  |         if(header->checksum == checksum) { | ||||||
|  |             memcpy( | ||||||
|  |                 &dolphin_state->data, (const void*)DOLPHIN_DATA_DATA_ADDRESS, sizeof(DolphinData)); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_state_clear(DolphinState* dolphin_state) { | void dolphin_state_clear(DolphinState* dolphin_state) { | ||||||
|     memset(dolphin_state, 0, sizeof(DolphinState)); |     memset(&dolphin_state->data, 0, sizeof(DolphinData)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { | void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { | ||||||
|     const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed); |     const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed); | ||||||
|     int32_t icounter = dolphin_state->icounter + deed_weight->icounter; |     int32_t icounter = dolphin_state->data.icounter + deed_weight->icounter; | ||||||
| 
 | 
 | ||||||
|     if(icounter >= 0) { |     if(icounter >= 0) { | ||||||
|         dolphin_state->icounter = icounter; |         dolphin_state->data.icounter = icounter; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint32_t dolphin_state_get_icounter(DolphinState* dolphin_state) { | uint32_t dolphin_state_get_icounter(DolphinState* dolphin_state) { | ||||||
|     return dolphin_state->icounter; |     return dolphin_state->data.icounter; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint32_t dolphin_state_get_butthurt(DolphinState* dolphin_state) { | uint32_t dolphin_state_get_butthurt(DolphinState* dolphin_state) { | ||||||
|     return dolphin_state->butthurt; |     return dolphin_state->data.butthurt; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "dolphin_deed.h" | #include "dolphin_deed.h" | ||||||
|  | #include <stdbool.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| 
 | 
 | ||||||
| typedef struct DolphinState DolphinState; | typedef struct DolphinState DolphinState; | ||||||
| @ -9,9 +10,9 @@ DolphinState* dolphin_state_alloc(); | |||||||
| 
 | 
 | ||||||
| void dolphin_state_release(DolphinState* dolphin_state); | void dolphin_state_release(DolphinState* dolphin_state); | ||||||
| 
 | 
 | ||||||
| void dolphin_state_save(DolphinState* dolphin_state); | bool dolphin_state_save(DolphinState* dolphin_state); | ||||||
| 
 | 
 | ||||||
| void dolphin_state_load(DolphinState* dolphin_state); | bool dolphin_state_load(DolphinState* dolphin_state); | ||||||
| 
 | 
 | ||||||
| void dolphin_state_clear(DolphinState* dolphin_state); | void dolphin_state_clear(DolphinState* dolphin_state); | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										69
									
								
								applications/dolphin/dolphin_views.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,69 @@ | |||||||
|  | #include "dolphin_views.h" | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | void dolphin_view_first_start_draw(Canvas* canvas, void* model) { | ||||||
|  |     DolphinViewFirstStartModel* m = model; | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     if(m->page == 0) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart0_128x54); | ||||||
|  |     } else if(m->page == 1) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart1_128x54); | ||||||
|  |     } else if(m->page == 2) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart2_128x54); | ||||||
|  |     } else if(m->page == 3) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart3_128x54); | ||||||
|  |     } else if(m->page == 4) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart4_128x54); | ||||||
|  |     } else if(m->page == 5) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart5_128x54); | ||||||
|  |     } else if(m->page == 6) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart6_128x54); | ||||||
|  |     } else if(m->page == 7) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart7_128x54); | ||||||
|  |     } else if(m->page == 8) { | ||||||
|  |         canvas_draw_icon_name(canvas, 0, 1, I_DolphinFirstStart8_128x54); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dolphin_view_idle_main_draw(Canvas* canvas, void* model) { | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_draw_icon_name(canvas, 128 - 80, 0, I_Flipper_young_80x60); | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     canvas_draw_str(canvas, 2, 10, "/\\: Stats"); | ||||||
|  |     canvas_draw_str(canvas, 5, 32, "OK: Menu"); | ||||||
|  |     canvas_draw_str(canvas, 2, 52, "\\/: Version"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dolphin_view_idle_stats_draw(Canvas* canvas, void* model) { | ||||||
|  |     DolphinViewIdleStatsModel* m = model; | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_set_font(canvas, FontPrimary); | ||||||
|  |     canvas_draw_str(canvas, 2, 10, "Dolphin stats:"); | ||||||
|  | 
 | ||||||
|  |     char buffer[64]; | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     snprintf(buffer, 64, "Icounter: %ld", m->icounter); | ||||||
|  |     canvas_draw_str(canvas, 5, 22, buffer); | ||||||
|  |     snprintf(buffer, 64, "Butthurt: %ld", m->butthurt); | ||||||
|  |     canvas_draw_str(canvas, 5, 32, buffer); | ||||||
|  |     canvas_draw_str(canvas, 5, 40, "< > change icounter"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dolphin_view_idle_debug_draw(Canvas* canvas, void* model) { | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_set_font(canvas, FontPrimary); | ||||||
|  |     canvas_draw_str(canvas, 2, 10, "Version info:"); | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     canvas_draw_str(canvas, 5, 22, TARGET " " BUILD_DATE); | ||||||
|  |     canvas_draw_str(canvas, 5, 32, GIT_BRANCH); | ||||||
|  |     canvas_draw_str(canvas, 5, 42, GIT_BRANCH_NUM); | ||||||
|  |     canvas_draw_str(canvas, 5, 52, GIT_COMMIT); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint32_t dolphin_view_idle_back(void* context) { | ||||||
|  |     return DolphinViewIdleMain; | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								applications/dolphin/dolphin_views.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,33 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <flipper_v2.h> | ||||||
|  | 
 | ||||||
|  | // Idle scree
 | ||||||
|  | typedef enum { | ||||||
|  |     DolphinViewFirstStart, | ||||||
|  |     DolphinViewIdleMain, | ||||||
|  |     DolphinViewIdleStats, | ||||||
|  |     DolphinViewIdleDebug, | ||||||
|  | } DolphinViewIdle; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint32_t page; | ||||||
|  | } DolphinViewFirstStartModel; | ||||||
|  | 
 | ||||||
|  | void dolphin_view_first_start_draw(Canvas* canvas, void* model); | ||||||
|  | bool dolphin_view_first_start_input(InputEvent* event, void* context); | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint32_t icounter; | ||||||
|  |     uint32_t butthurt; | ||||||
|  | } DolphinViewIdleStatsModel; | ||||||
|  | 
 | ||||||
|  | void dolphin_view_idle_main_draw(Canvas* canvas, void* model); | ||||||
|  | bool dolphin_view_idle_main_input(InputEvent* event, void* context); | ||||||
|  | void dolphin_view_idle_stats_draw(Canvas* canvas, void* model); | ||||||
|  | bool dolphin_view_idle_stats_input(InputEvent* event, void* context); | ||||||
|  | void dolphin_view_idle_debug_draw(Canvas* canvas, void* model); | ||||||
|  | uint32_t dolphin_view_idle_back(void* context); | ||||||
| @ -1,5 +1,4 @@ | |||||||
| #include "canvas_i.h" | #include "canvas_i.h" | ||||||
| #include "icon.h" |  | ||||||
| #include "icon_i.h" | #include "icon_i.h" | ||||||
| 
 | 
 | ||||||
| #include <flipper.h> | #include <flipper.h> | ||||||
| @ -113,6 +112,14 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon) { | |||||||
|         &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_get_data(icon)); |         &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_get_data(icon)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void canvas_draw_icon_name(Canvas* canvas, uint8_t x, uint8_t y, IconName name) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     const IconData* data = assets_icons_get_data(name); | ||||||
|  |     x += canvas->offset_x; | ||||||
|  |     y += canvas->offset_y; | ||||||
|  |     u8g2_DrawXBM(&canvas->fb, x, y, data->width, data->height, data->frames[0]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) { | void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|     x += canvas->offset_x; |     x += canvas->offset_x; | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <u8g2.h> | #include <u8g2.h> | ||||||
| #include <gui/icon.h> | #include <gui/icon.h> | ||||||
|  | #include <assets_icons_i.h> | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     ColorWhite = 0x00, |     ColorWhite = 0x00, | ||||||
| @ -46,10 +47,15 @@ void canvas_set_font(Canvas* canvas, Font font); | |||||||
| void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str); | void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str); | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Draw icon at position defined by x,y. |  * Draw stateful icon at position defined by x,y. | ||||||
|  */ |  */ | ||||||
| void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon); | void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon); | ||||||
| 
 | 
 | ||||||
|  | /*
 | ||||||
|  |  * Draw stateless icon at position defined by x,y. | ||||||
|  |  */ | ||||||
|  | void canvas_draw_icon_name(Canvas* canvas, uint8_t x, uint8_t y, IconName name); | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Draw xbm icon of width, height at position defined by x,y. |  * Draw xbm icon of width, height at position defined by x,y. | ||||||
|  */ |  */ | ||||||
|  | |||||||
							
								
								
									
										140
									
								
								applications/gui/view.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,140 @@ | |||||||
|  | #include "view_i.h" | ||||||
|  | 
 | ||||||
|  | View* view_alloc() { | ||||||
|  |     View* view = furi_alloc(sizeof(View)); | ||||||
|  |     return view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_free(View* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     view_free_model(view); | ||||||
|  |     free(view); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_set_dispatcher(View* view, ViewDispatcher* view_dispatcher) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     furi_assert(view_dispatcher); | ||||||
|  |     furi_assert(view->dispatcher == NULL); | ||||||
|  |     view->dispatcher = view_dispatcher; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_set_draw_callback(View* view, ViewDrawCallback callback) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     furi_assert(view->draw_callback == NULL); | ||||||
|  |     view->draw_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_set_input_callback(View* view, ViewInputCallback callback) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     furi_assert(view->input_callback == NULL); | ||||||
|  |     view->input_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_set_previous_callback(View* view, ViewNavigationCallback callback) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     view->previous_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_set_next_callback(View* view, ViewNavigationCallback callback) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     view->next_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_set_context(View* view, void* context) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     furi_assert(context); | ||||||
|  |     view->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_allocate_model(View* view, ViewModelType type, size_t size) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     furi_assert(size > 0); | ||||||
|  |     furi_assert(view->model_type == ViewModelTypeNone); | ||||||
|  |     furi_assert(view->model == NULL); | ||||||
|  |     view->model_type = type; | ||||||
|  |     if(view->model_type == ViewModelTypeLockFree) { | ||||||
|  |         view->model = furi_alloc(size); | ||||||
|  |     } else if(view->model_type == ViewModelTypeLocking) { | ||||||
|  |         ViewModelLocking* model = furi_alloc(sizeof(ViewModelLocking)); | ||||||
|  |         model->mutex = osMutexNew(NULL); | ||||||
|  |         furi_check(model->mutex); | ||||||
|  |         model->data = furi_alloc(size); | ||||||
|  |         view->model = model; | ||||||
|  |     } else { | ||||||
|  |         furi_assert(false); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_free_model(View* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     if(view->model_type == ViewModelTypeNone) { | ||||||
|  |         return; | ||||||
|  |     } else if(view->model_type == ViewModelTypeLockFree) { | ||||||
|  |         free(view->model); | ||||||
|  |     } else if(view->model_type == ViewModelTypeLocking) { | ||||||
|  |         ViewModelLocking* model = view->model; | ||||||
|  |         furi_check(osMutexDelete(model->mutex) == osOK); | ||||||
|  |         free(model->data); | ||||||
|  |         free(model); | ||||||
|  |         view->model = NULL; | ||||||
|  |     } else { | ||||||
|  |         furi_assert(false); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void* view_get_model(View* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     if(view->model_type == ViewModelTypeLocking) { | ||||||
|  |         ViewModelLocking* model = (ViewModelLocking*)(view->model); | ||||||
|  |         furi_check(osMutexAcquire(model->mutex, osWaitForever) == osOK); | ||||||
|  |         return model->data; | ||||||
|  |     } | ||||||
|  |     return view->model; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_commit_model(View* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     if(view->model_type == ViewModelTypeLocking) { | ||||||
|  |         ViewModelLocking* model = (ViewModelLocking*)(view->model); | ||||||
|  |         furi_check(osMutexRelease(model->mutex) == osOK); | ||||||
|  |     } | ||||||
|  |     if(view->dispatcher) { | ||||||
|  |         view_dispatcher_update(view->dispatcher, view); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_draw(View* view, Canvas* canvas) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     if(view->draw_callback) { | ||||||
|  |         void* data = view_get_model(view); | ||||||
|  |         view->draw_callback(canvas, data); | ||||||
|  |         view_commit_model(view); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool view_input(View* view, InputEvent* event) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     if(view->input_callback) { | ||||||
|  |         return view->input_callback(event, view->context); | ||||||
|  |     } else { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint32_t view_previous(View* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     if(view->previous_callback) { | ||||||
|  |         return view->previous_callback(view->context); | ||||||
|  |     } else { | ||||||
|  |         return VIEW_IGNORE; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint32_t view_next(View* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     if(view->next_callback) { | ||||||
|  |         return view->next_callback(view->context); | ||||||
|  |     } else { | ||||||
|  |         return VIEW_IGNORE; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										129
									
								
								applications/gui/view.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,129 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <input/input.h> | ||||||
|  | #include "canvas.h" | ||||||
|  | 
 | ||||||
|  | /* Hides drawing widget */ | ||||||
|  | #define VIEW_NONE 0xFFFFFFFF | ||||||
|  | /* Ignore navigation event */ | ||||||
|  | #define VIEW_IGNORE 0xFFFFFFFE | ||||||
|  | /* Deatch from gui, deallocate Views and ViewDispatcher
 | ||||||
|  |  * BE SUPER CAREFUL, deallocation happens automatically on GUI thread | ||||||
|  |  * You ARE NOT owning ViewDispatcher and Views instances | ||||||
|  |  */ | ||||||
|  | #define VIEW_DESTROY 0xFFFFFFFA | ||||||
|  | 
 | ||||||
|  | /* View Draw callback
 | ||||||
|  |  * @param canvas, pointer to canvas | ||||||
|  |  * @param view_model, pointer to context | ||||||
|  |  * @warning called from GUI thread | ||||||
|  |  */ | ||||||
|  | typedef void (*ViewDrawCallback)(Canvas* canvas, void* model); | ||||||
|  | 
 | ||||||
|  | /* View Input callback
 | ||||||
|  |  * @param event, pointer to input event data | ||||||
|  |  * @param context, pointer to context | ||||||
|  |  * @return true if event handled, false if event ignored | ||||||
|  |  * @warning called from GUI thread | ||||||
|  |  */ | ||||||
|  | typedef bool (*ViewInputCallback)(InputEvent* event, void* context); | ||||||
|  | 
 | ||||||
|  | /* View navigation callback
 | ||||||
|  |  * @param context, pointer to context | ||||||
|  |  * @return next view id | ||||||
|  |  * @warning called from GUI thread | ||||||
|  |  */ | ||||||
|  | typedef uint32_t (*ViewNavigationCallback)(void* context); | ||||||
|  | 
 | ||||||
|  | /* View model types */ | ||||||
|  | typedef enum { | ||||||
|  |     /* Model is not allocated */ | ||||||
|  |     ViewModelTypeNone, | ||||||
|  |     /* Model consist of atomic types and/or partial update is not critical for rendering.
 | ||||||
|  |      * Lock free. | ||||||
|  |      */ | ||||||
|  |     ViewModelTypeLockFree, | ||||||
|  |     /* Model access is guarded with mutex.
 | ||||||
|  |      * Locking gui thread. | ||||||
|  |      */ | ||||||
|  |     ViewModelTypeLocking, | ||||||
|  | } ViewModelType; | ||||||
|  | 
 | ||||||
|  | typedef struct View View; | ||||||
|  | 
 | ||||||
|  | /* Allocate and init View
 | ||||||
|  |  * @return pointer to View | ||||||
|  |  */ | ||||||
|  | View* view_alloc(); | ||||||
|  | 
 | ||||||
|  | /* Free View
 | ||||||
|  |  * @param pointer to View | ||||||
|  |  */ | ||||||
|  | void view_free(View* view); | ||||||
|  | 
 | ||||||
|  | /* Set View Draw callback
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  * @param callback, draw callback | ||||||
|  |  */ | ||||||
|  | void view_set_draw_callback(View* view, ViewDrawCallback callback); | ||||||
|  | 
 | ||||||
|  | /* Set View Draw callback
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  * @param callback, input callback | ||||||
|  |  */ | ||||||
|  | void view_set_input_callback(View* view, ViewInputCallback callback); | ||||||
|  | 
 | ||||||
|  | /* Set Navigation Previous callback
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  * @param callback, input callback | ||||||
|  |  */ | ||||||
|  | void view_set_previous_callback(View* view, ViewNavigationCallback callback); | ||||||
|  | 
 | ||||||
|  | /* Set Navigation Next callback
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  * @param callback, input callback | ||||||
|  |  */ | ||||||
|  | void view_set_next_callback(View* view, ViewNavigationCallback callback); | ||||||
|  | 
 | ||||||
|  | /* Set View Draw callback
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  * @param context, context for callbacks | ||||||
|  |  */ | ||||||
|  | void view_set_context(View* view, void* context); | ||||||
|  | 
 | ||||||
|  | /* Allocate view model.
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  * @param type, View Model Type | ||||||
|  |  * @param size, size | ||||||
|  |  */ | ||||||
|  | void view_allocate_model(View* view, ViewModelType type, size_t size); | ||||||
|  | 
 | ||||||
|  | /* Free view model data memory.
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  */ | ||||||
|  | void view_free_model(View* view); | ||||||
|  | 
 | ||||||
|  | /* Get view model data
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  * @return pointer to model data | ||||||
|  |  * @warning Don't forget to commit model changes | ||||||
|  |  */ | ||||||
|  | void* view_get_model(View* view); | ||||||
|  | 
 | ||||||
|  | /* Commit view model
 | ||||||
|  |  * @param view, pointer to View | ||||||
|  |  */ | ||||||
|  | void view_commit_model(View* view); | ||||||
|  | 
 | ||||||
|  | /* 
 | ||||||
|  |  * With clause for view model | ||||||
|  |  * @param view, View instance pointer | ||||||
|  |  * @param function_body a (){} lambda declaration, | ||||||
|  |  * executed within you parent function context. | ||||||
|  |  */ | ||||||
|  | #define with_view_model(view, function_body)        \ | ||||||
|  |     {                                               \ | ||||||
|  |         void* p = view_get_model(view);             \ | ||||||
|  |         ({ void __fn__ function_body __fn__; })(p); \ | ||||||
|  |         view_commit_model(view);                    \ | ||||||
|  |     } | ||||||
							
								
								
									
										113
									
								
								applications/gui/view_dispatcher.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,113 @@ | |||||||
|  | #include "view_dispatcher_i.h" | ||||||
|  | 
 | ||||||
|  | ViewDispatcher* view_dispatcher_alloc() { | ||||||
|  |     ViewDispatcher* view_dispatcher = furi_alloc(sizeof(ViewDispatcher)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher->widget = widget_alloc(); | ||||||
|  |     widget_draw_callback_set( | ||||||
|  |         view_dispatcher->widget, view_dispatcher_draw_callback, view_dispatcher); | ||||||
|  |     widget_input_callback_set( | ||||||
|  |         view_dispatcher->widget, view_dispatcher_input_callback, view_dispatcher); | ||||||
|  |     widget_enabled_set(view_dispatcher->widget, false); | ||||||
|  | 
 | ||||||
|  |     ViewDict_init(view_dispatcher->views); | ||||||
|  | 
 | ||||||
|  |     return view_dispatcher; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_dispatcher_free(ViewDispatcher* view_dispatcher) { | ||||||
|  |     // Detach from gui
 | ||||||
|  |     if(view_dispatcher->gui) { | ||||||
|  |         gui_remove_widget(view_dispatcher->gui, view_dispatcher->widget); | ||||||
|  |     } | ||||||
|  |     // Free views
 | ||||||
|  |     ViewDict_it_t it; | ||||||
|  |     ViewDict_it(it, view_dispatcher->views); | ||||||
|  |     while(!ViewDict_end_p(it)) { | ||||||
|  |         ViewDict_itref_t* ref = ViewDict_ref(it); | ||||||
|  |         view_free(ref->value); | ||||||
|  |         ViewDict_next(it); | ||||||
|  |     } | ||||||
|  |     ViewDict_clear(view_dispatcher->views); | ||||||
|  |     // Free dispatcher
 | ||||||
|  |     free(view_dispatcher); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_dispatcher_add_view(ViewDispatcher* view_dispatcher, uint32_t view_id, View* view) { | ||||||
|  |     furi_assert(view_dispatcher); | ||||||
|  |     furi_assert(view); | ||||||
|  |     // Check if view id is not used and resgister view
 | ||||||
|  |     furi_check(ViewDict_get(view_dispatcher->views, view_id) == NULL); | ||||||
|  |     ViewDict_set_at(view_dispatcher->views, view_id, view); | ||||||
|  |     view_set_dispatcher(view, view_dispatcher); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t view_id) { | ||||||
|  |     furi_assert(view_dispatcher); | ||||||
|  |     if(view_id == VIEW_NONE) { | ||||||
|  |         view_dispatcher->current_view = NULL; | ||||||
|  |         widget_enabled_set(view_dispatcher->widget, false); | ||||||
|  |     } else if(view_id == VIEW_IGNORE) { | ||||||
|  |     } else if(view_id == VIEW_DESTROY) { | ||||||
|  |         view_dispatcher_free(view_dispatcher); | ||||||
|  |     } else { | ||||||
|  |         View** view_pp = ViewDict_get(view_dispatcher->views, view_id); | ||||||
|  |         furi_check(view_pp != NULL); | ||||||
|  |         view_dispatcher->current_view = *view_pp; | ||||||
|  |         widget_enabled_set(view_dispatcher->widget, true); | ||||||
|  |         widget_update(view_dispatcher->widget); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_dispatcher_attach_to_gui( | ||||||
|  |     ViewDispatcher* view_dispatcher, | ||||||
|  |     Gui* gui, | ||||||
|  |     ViewDispatcherType type) { | ||||||
|  |     furi_assert(view_dispatcher); | ||||||
|  |     furi_assert(view_dispatcher->gui == NULL); | ||||||
|  |     furi_assert(gui); | ||||||
|  | 
 | ||||||
|  |     if(type == ViewDispatcherTypeNone) { | ||||||
|  |         gui_add_widget(gui, view_dispatcher->widget, GuiLayerNone); | ||||||
|  |     } else if(type == ViewDispatcherTypeFullscreen) { | ||||||
|  |         gui_add_widget(gui, view_dispatcher->widget, GuiLayerFullscreen); | ||||||
|  |     } else if(type == ViewDispatcherTypeWindow) { | ||||||
|  |         gui_add_widget(gui, view_dispatcher->widget, GuiLayerMain); | ||||||
|  |     } else { | ||||||
|  |         furi_check(NULL); | ||||||
|  |     } | ||||||
|  |     view_dispatcher->gui = gui; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_dispatcher_draw_callback(Canvas* canvas, void* context) { | ||||||
|  |     ViewDispatcher* view_dispatcher = context; | ||||||
|  |     if(view_dispatcher->current_view) { | ||||||
|  |         view_draw(view_dispatcher->current_view, canvas); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_dispatcher_input_callback(InputEvent* event, void* context) { | ||||||
|  |     ViewDispatcher* view_dispatcher = context; | ||||||
|  |     bool is_consumed = false; | ||||||
|  |     if(view_dispatcher->current_view) { | ||||||
|  |         is_consumed = view_input(view_dispatcher->current_view, event); | ||||||
|  |     } | ||||||
|  |     if(!is_consumed && event->state) { | ||||||
|  |         uint32_t view_id = VIEW_IGNORE; | ||||||
|  |         if(event->input == InputBack) { | ||||||
|  |             view_id = view_previous(view_dispatcher->current_view); | ||||||
|  |         } else if(event->input == InputOk) { | ||||||
|  |             view_id = view_next(view_dispatcher->current_view); | ||||||
|  |         } | ||||||
|  |         view_dispatcher_switch_to_view(view_dispatcher, view_id); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void view_dispatcher_update(ViewDispatcher* view_dispatcher, View* view) { | ||||||
|  |     furi_assert(view_dispatcher); | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     if(view_dispatcher->current_view == view) { | ||||||
|  |         widget_update(view_dispatcher->widget); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								applications/gui/view_dispatcher.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "view.h" | ||||||
|  | #include "gui.h" | ||||||
|  | 
 | ||||||
|  | /* ViewDispatcher widget placement */ | ||||||
|  | typedef enum { | ||||||
|  |     ViewDispatcherTypeNone, /* Special layer for internal use only */ | ||||||
|  |     ViewDispatcherTypeWindow, /* Main widget layer, status bar is shown */ | ||||||
|  |     ViewDispatcherTypeFullscreen /* Fullscreen widget layer */ | ||||||
|  | } ViewDispatcherType; | ||||||
|  | 
 | ||||||
|  | typedef struct ViewDispatcher ViewDispatcher; | ||||||
|  | 
 | ||||||
|  | /* Allocate ViewDispatcher
 | ||||||
|  |  * @return pointer to ViewDispatcher instance | ||||||
|  |  */ | ||||||
|  | ViewDispatcher* view_dispatcher_alloc(); | ||||||
|  | 
 | ||||||
|  | /* Free ViewDispatcher
 | ||||||
|  |  * @param pointer to View | ||||||
|  |  */ | ||||||
|  | void view_dispatcher_free(ViewDispatcher* view_dispatcher); | ||||||
|  | 
 | ||||||
|  | /* Add view to ViewDispatcher
 | ||||||
|  |  * @param view_dispatcher, ViewDispatcher instance | ||||||
|  |  * @param view_id, View id to register | ||||||
|  |  * @param view, View instance | ||||||
|  |  */ | ||||||
|  | void view_dispatcher_add_view(ViewDispatcher* view_dispatcher, uint32_t view_id, View* view); | ||||||
|  | 
 | ||||||
|  | /* Switch to View
 | ||||||
|  |  * @param view_dispatcher, ViewDispatcher instance | ||||||
|  |  * @param view_id, View id to register | ||||||
|  |  */ | ||||||
|  | void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t view_id); | ||||||
|  | 
 | ||||||
|  | /* Attach ViewDispatcher to GUI
 | ||||||
|  |  * @param view_dispatcher, ViewDispatcher instance | ||||||
|  |  * @param gui, GUI instance to attach to | ||||||
|  |  */ | ||||||
|  | void view_dispatcher_attach_to_gui( | ||||||
|  |     ViewDispatcher* view_dispatcher, | ||||||
|  |     Gui* gui, | ||||||
|  |     ViewDispatcherType type); | ||||||
							
								
								
									
										24
									
								
								applications/gui/view_dispatcher_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "view_dispatcher.h" | ||||||
|  | #include "view_i.h" | ||||||
|  | #include <flipper_v2.h> | ||||||
|  | #include <m-dict.h> | ||||||
|  | 
 | ||||||
|  | DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) | ||||||
|  | 
 | ||||||
|  | struct ViewDispatcher { | ||||||
|  |     Gui* gui; | ||||||
|  |     Widget* widget; | ||||||
|  |     ViewDict_t views; | ||||||
|  |     View* current_view; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* Widget Draw Callback */ | ||||||
|  | void view_dispatcher_draw_callback(Canvas* canvas, void* context); | ||||||
|  | 
 | ||||||
|  | /* Widget Input Callback */ | ||||||
|  | void view_dispatcher_input_callback(InputEvent* event, void* context); | ||||||
|  | 
 | ||||||
|  | /* View to ViewDispatcher update event */ | ||||||
|  | void view_dispatcher_update(ViewDispatcher* view_dispatcher, View* view); | ||||||
							
								
								
									
										36
									
								
								applications/gui/view_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,36 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "view.h" | ||||||
|  | #include "view_dispatcher_i.h" | ||||||
|  | #include <flipper_v2.h> | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     void* data; | ||||||
|  |     osMutexId_t mutex; | ||||||
|  | } ViewModelLocking; | ||||||
|  | 
 | ||||||
|  | struct View { | ||||||
|  |     ViewDispatcher* dispatcher; | ||||||
|  |     ViewDrawCallback draw_callback; | ||||||
|  |     ViewInputCallback input_callback; | ||||||
|  |     ViewModelType model_type; | ||||||
|  |     ViewNavigationCallback previous_callback; | ||||||
|  |     ViewNavigationCallback next_callback; | ||||||
|  |     void* model; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* Set View dispatcher */ | ||||||
|  | void view_set_dispatcher(View* view, ViewDispatcher* view_dispatcher); | ||||||
|  | 
 | ||||||
|  | /* Draw Callback for View dispatcher */ | ||||||
|  | void view_draw(View* view, Canvas* canvas); | ||||||
|  | 
 | ||||||
|  | /* Input Callback for View dispatcher */ | ||||||
|  | bool view_input(View* view, InputEvent* event); | ||||||
|  | 
 | ||||||
|  | /* Previous Callback for View dispatcher */ | ||||||
|  | uint32_t view_previous(View* view); | ||||||
|  | 
 | ||||||
|  | /* Next Callback for View dispatcher */ | ||||||
|  | uint32_t view_next(View* view); | ||||||
| @ -9,7 +9,7 @@ typedef struct Widget Widget; | |||||||
|  * Widget Draw callback |  * Widget Draw callback | ||||||
|  * @warning called from GUI thread |  * @warning called from GUI thread | ||||||
|  */ |  */ | ||||||
| typedef void (*WidgetDrawCallback)(Canvas* api, void* context); | typedef void (*WidgetDrawCallback)(Canvas* canvas, void* context); | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Widget Input callback |  * Widget Input callback | ||||||
|  | |||||||
| @ -1,38 +1,33 @@ | |||||||
| #include "power.h" | #include "power.h" | ||||||
|  | #include "power_views.h" | ||||||
| 
 | 
 | ||||||
| #include <flipper_v2.h> | #include <flipper_v2.h> | ||||||
| 
 | 
 | ||||||
| #include <menu/menu.h> | #include <menu/menu.h> | ||||||
| #include <menu/menu_item.h> | #include <menu/menu_item.h> | ||||||
|  | 
 | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/widget.h> | #include <gui/widget.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | 
 | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| #include <api-hal-power.h> | #include <api-hal-power.h> | ||||||
| #include <cli/cli.h> | #include <cli/cli.h> | ||||||
| 
 | 
 | ||||||
| struct Power { | struct Power { | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     View* info_view; | ||||||
|  | 
 | ||||||
|     Icon* usb_icon; |     Icon* usb_icon; | ||||||
|     Widget* usb_widget; |     Widget* usb_widget; | ||||||
| 
 | 
 | ||||||
|     Icon* battery_icon; |     Icon* battery_icon; | ||||||
|     Widget* battery_widget; |     Widget* battery_widget; | ||||||
| 
 | 
 | ||||||
|     Widget* widget; |  | ||||||
| 
 |  | ||||||
|     ValueMutex* menu_vm; |     ValueMutex* menu_vm; | ||||||
|     Cli* cli; |     Cli* cli; | ||||||
|     MenuItem* menu; |     MenuItem* menu; | ||||||
| 
 |  | ||||||
|     float current_charger; |  | ||||||
|     float current_gauge; |  | ||||||
|     float voltage_charger; |  | ||||||
|     float voltage_gauge; |  | ||||||
|     uint32_t capacity_remaining; |  | ||||||
|     uint32_t capacity_full; |  | ||||||
|     float temperature_charger; |  | ||||||
|     float temperature_gauge; |  | ||||||
| 
 |  | ||||||
|     uint8_t charge; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void power_draw_usb_callback(Canvas* canvas, void* context) { | void power_draw_usb_callback(Canvas* canvas, void* context) { | ||||||
| @ -46,70 +41,31 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { | |||||||
|     Power* power = context; |     Power* power = context; | ||||||
| 
 | 
 | ||||||
|     canvas_draw_icon(canvas, 0, 0, power->battery_icon); |     canvas_draw_icon(canvas, 0, 0, power->battery_icon); | ||||||
|     canvas_draw_box(canvas, 2, 2, (float)power->charge / 100 * 14, 4); |     with_view_model( | ||||||
|  |         power->info_view, (PowerInfoModel * model) { | ||||||
|  |             canvas_draw_box(canvas, 2, 2, (float)model->charge / 100 * 14, 4); | ||||||
|  |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void power_off_callback(void* context) { | void power_menu_off_callback(void* context) { | ||||||
|     api_hal_power_off(); |     api_hal_power_off(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void power_enable_otg_callback(void* context) { | void power_menu_reset_callback(void* context) { | ||||||
|  |     NVIC_SystemReset(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void power_menu_enable_otg_callback(void* context) { | ||||||
|     api_hal_power_enable_otg(); |     api_hal_power_enable_otg(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void power_disable_otg_callback(void* context) { | void power_menu_disable_otg_callback(void* context) { | ||||||
|     api_hal_power_disable_otg(); |     api_hal_power_disable_otg(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void power_info_callback(void* context) { | void power_menu_info_callback(void* context) { | ||||||
|     Power* power = context; |     Power* power = context; | ||||||
|     widget_enabled_set(power->widget, true); |     view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewInfo); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void power_draw_callback(Canvas* canvas, void* context) { |  | ||||||
|     Power* power = context; |  | ||||||
| 
 |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_set_font(canvas, FontPrimary); |  | ||||||
|     canvas_draw_str(canvas, 2, 10, "Power state:"); |  | ||||||
| 
 |  | ||||||
|     char buffer[64]; |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     snprintf( |  | ||||||
|         buffer, |  | ||||||
|         64, |  | ||||||
|         "Current: %ld/%ldmA", |  | ||||||
|         (int32_t)(power->current_gauge * 1000), |  | ||||||
|         (int32_t)(power->current_charger * 1000)); |  | ||||||
|     canvas_draw_str(canvas, 5, 22, buffer); |  | ||||||
|     snprintf( |  | ||||||
|         buffer, |  | ||||||
|         64, |  | ||||||
|         "Voltage: %ld/%ldmV", |  | ||||||
|         (uint32_t)(power->voltage_gauge * 1000), |  | ||||||
|         (uint32_t)(power->voltage_charger * 1000)); |  | ||||||
|     canvas_draw_str(canvas, 5, 32, buffer); |  | ||||||
|     snprintf(buffer, 64, "Charge: %ld%%", (uint32_t)(power->charge)); |  | ||||||
|     canvas_draw_str(canvas, 5, 42, buffer); |  | ||||||
|     snprintf( |  | ||||||
|         buffer, 64, "Capacity: %ld of %ldmAh", power->capacity_remaining, power->capacity_full); |  | ||||||
|     canvas_draw_str(canvas, 5, 52, buffer); |  | ||||||
|     snprintf( |  | ||||||
|         buffer, |  | ||||||
|         64, |  | ||||||
|         "Temperature: %ld/%ldC", |  | ||||||
|         (uint32_t)(power->temperature_gauge), |  | ||||||
|         (uint32_t)(power->temperature_charger)); |  | ||||||
|     canvas_draw_str(canvas, 5, 62, buffer); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void power_input_callback(InputEvent* event, void* context) { |  | ||||||
|     Power* power = context; |  | ||||||
| 
 |  | ||||||
|     if(!event->state || event->input != InputBack) return; |  | ||||||
| 
 |  | ||||||
|     widget_enabled_set(power->widget, false); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Power* power_alloc() { | Power* power_alloc() { | ||||||
| @ -122,26 +78,30 @@ Power* power_alloc() { | |||||||
| 
 | 
 | ||||||
|     power->menu = menu_item_alloc_menu("Power", NULL); |     power->menu = menu_item_alloc_menu("Power", NULL); | ||||||
|     menu_item_subitem_add( |     menu_item_subitem_add( | ||||||
|         power->menu, menu_item_alloc_function("Poweroff", NULL, power_off_callback, power)); |         power->menu, menu_item_alloc_function("Off", NULL, power_menu_off_callback, power)); | ||||||
|  |     menu_item_subitem_add( | ||||||
|  |         power->menu, menu_item_alloc_function("Reset", NULL, power_menu_reset_callback, power)); | ||||||
|     menu_item_subitem_add( |     menu_item_subitem_add( | ||||||
|         power->menu, |         power->menu, | ||||||
|         menu_item_alloc_function("Enable OTG", NULL, power_enable_otg_callback, power)); |         menu_item_alloc_function("Enable OTG", NULL, power_menu_enable_otg_callback, power)); | ||||||
|     menu_item_subitem_add( |     menu_item_subitem_add( | ||||||
|         power->menu, |         power->menu, | ||||||
|         menu_item_alloc_function("Disable OTG", NULL, power_disable_otg_callback, power)); |         menu_item_alloc_function("Disable OTG", NULL, power_menu_disable_otg_callback, power)); | ||||||
|     menu_item_subitem_add( |     menu_item_subitem_add( | ||||||
|         power->menu, menu_item_alloc_function("Info", NULL, power_info_callback, power)); |         power->menu, menu_item_alloc_function("Info", NULL, power_menu_info_callback, power)); | ||||||
|  | 
 | ||||||
|  |     power->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     power->info_view = view_alloc(); | ||||||
|  |     view_allocate_model(power->info_view, ViewModelTypeLockFree, sizeof(PowerInfoModel)); | ||||||
|  |     view_set_draw_callback(power->info_view, power_info_draw_callback); | ||||||
|  |     view_set_previous_callback(power->info_view, power_info_back_callback); | ||||||
|  |     view_dispatcher_add_view(power->view_dispatcher, PowerViewInfo, power->info_view); | ||||||
| 
 | 
 | ||||||
|     power->usb_icon = assets_icons_get(I_USBConnected_15x8); |     power->usb_icon = assets_icons_get(I_USBConnected_15x8); | ||||||
|     power->usb_widget = widget_alloc(); |     power->usb_widget = widget_alloc(); | ||||||
|     widget_set_width(power->usb_widget, icon_get_width(power->usb_icon)); |     widget_set_width(power->usb_widget, icon_get_width(power->usb_icon)); | ||||||
|     widget_draw_callback_set(power->usb_widget, power_draw_usb_callback, power); |     widget_draw_callback_set(power->usb_widget, power_draw_usb_callback, power); | ||||||
| 
 | 
 | ||||||
|     power->widget = widget_alloc(); |  | ||||||
|     widget_draw_callback_set(power->widget, power_draw_callback, power); |  | ||||||
|     widget_input_callback_set(power->widget, power_input_callback, power); |  | ||||||
|     widget_enabled_set(power->widget, false); |  | ||||||
| 
 |  | ||||||
|     power->battery_icon = assets_icons_get(I_Battery_19x8); |     power->battery_icon = assets_icons_get(I_Battery_19x8); | ||||||
|     power->battery_widget = widget_alloc(); |     power->battery_widget = widget_alloc(); | ||||||
|     widget_set_width(power->battery_widget, icon_get_width(power->battery_icon)); |     widget_set_width(power->battery_widget, icon_get_width(power->battery_icon)); | ||||||
| @ -204,9 +164,9 @@ void power_task(void* p) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Gui* gui = furi_open("gui"); |     Gui* gui = furi_open("gui"); | ||||||
|     gui_add_widget(gui, power->widget, GuiLayerFullscreen); |  | ||||||
|     gui_add_widget(gui, power->usb_widget, GuiLayerStatusBarLeft); |     gui_add_widget(gui, power->usb_widget, GuiLayerStatusBarLeft); | ||||||
|     gui_add_widget(gui, power->battery_widget, GuiLayerStatusBarRight); |     gui_add_widget(gui, power->battery_widget, GuiLayerStatusBarRight); | ||||||
|  |     view_dispatcher_attach_to_gui(power->view_dispatcher, gui, ViewDispatcherTypeFullscreen); | ||||||
| 
 | 
 | ||||||
|     with_value_mutex( |     with_value_mutex( | ||||||
|         power->menu_vm, (Menu * menu) { menu_item_add(menu, power->menu); }); |         power->menu_vm, (Menu * menu) { menu_item_add(menu, power->menu); }); | ||||||
| @ -221,16 +181,22 @@ void power_task(void* p) { | |||||||
|     furiac_ready(); |     furiac_ready(); | ||||||
| 
 | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         power->charge = api_hal_power_get_pct(); |         with_view_model( | ||||||
|         power->capacity_remaining = api_hal_power_get_battery_remaining_capacity(); |             power->info_view, (PowerInfoModel * model) { | ||||||
|         power->capacity_full = api_hal_power_get_battery_full_capacity(); |                 model->charge = api_hal_power_get_pct(); | ||||||
|         power->current_charger = api_hal_power_get_battery_current(ApiHalPowerICCharger); |                 model->capacity_remaining = api_hal_power_get_battery_remaining_capacity(); | ||||||
|         power->current_gauge = api_hal_power_get_battery_current(ApiHalPowerICFuelGauge); |                 model->capacity_full = api_hal_power_get_battery_full_capacity(); | ||||||
|         power->voltage_charger = api_hal_power_get_battery_voltage(ApiHalPowerICCharger); |                 model->current_charger = api_hal_power_get_battery_current(ApiHalPowerICCharger); | ||||||
|         power->voltage_gauge = api_hal_power_get_battery_voltage(ApiHalPowerICFuelGauge); |                 model->current_gauge = api_hal_power_get_battery_current(ApiHalPowerICFuelGauge); | ||||||
|         power->temperature_charger = api_hal_power_get_battery_temperature(ApiHalPowerICCharger); |                 model->voltage_charger = api_hal_power_get_battery_voltage(ApiHalPowerICCharger); | ||||||
|         power->temperature_gauge = api_hal_power_get_battery_temperature(ApiHalPowerICFuelGauge); |                 model->voltage_gauge = api_hal_power_get_battery_voltage(ApiHalPowerICFuelGauge); | ||||||
|         widget_update(power->widget); |                 model->temperature_charger = | ||||||
|  |                     api_hal_power_get_battery_temperature(ApiHalPowerICCharger); | ||||||
|  |                 model->temperature_gauge = | ||||||
|  |                     api_hal_power_get_battery_temperature(ApiHalPowerICFuelGauge); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |         widget_update(power->battery_widget); | ||||||
|         widget_enabled_set(power->usb_widget, api_hal_power_is_charging()); |         widget_enabled_set(power->usb_widget, api_hal_power_is_charging()); | ||||||
|         osDelay(1000); |         osDelay(1000); | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										38
									
								
								applications/power/power_views.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,38 @@ | |||||||
|  | #include "power_views.h" | ||||||
|  | 
 | ||||||
|  | void power_info_draw_callback(Canvas* canvas, void* context) { | ||||||
|  |     PowerInfoModel* data = context; | ||||||
|  | 
 | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_set_font(canvas, FontPrimary); | ||||||
|  |     canvas_draw_str(canvas, 2, 10, "Power state:"); | ||||||
|  | 
 | ||||||
|  |     char buffer[64]; | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     snprintf( | ||||||
|  |         buffer, | ||||||
|  |         64, | ||||||
|  |         "Current: %ld/%ldmA", | ||||||
|  |         (int32_t)(data->current_gauge * 1000), | ||||||
|  |         (int32_t)(data->current_charger * 1000)); | ||||||
|  |     canvas_draw_str(canvas, 5, 22, buffer); | ||||||
|  |     snprintf( | ||||||
|  |         buffer, | ||||||
|  |         64, | ||||||
|  |         "Voltage: %ld/%ldmV", | ||||||
|  |         (uint32_t)(data->voltage_gauge * 1000), | ||||||
|  |         (uint32_t)(data->voltage_charger * 1000)); | ||||||
|  |     canvas_draw_str(canvas, 5, 32, buffer); | ||||||
|  |     snprintf(buffer, 64, "Charge: %ld%%", (uint32_t)(data->charge)); | ||||||
|  |     canvas_draw_str(canvas, 5, 42, buffer); | ||||||
|  |     snprintf(buffer, 64, "Capacity: %ld of %ldmAh", data->capacity_remaining, data->capacity_full); | ||||||
|  |     canvas_draw_str(canvas, 5, 52, buffer); | ||||||
|  |     snprintf( | ||||||
|  |         buffer, | ||||||
|  |         64, | ||||||
|  |         "Temperature: %ld/%ldC", | ||||||
|  |         (uint32_t)(data->temperature_gauge), | ||||||
|  |         (uint32_t)(data->temperature_charger)); | ||||||
|  |     canvas_draw_str(canvas, 5, 62, buffer); | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								applications/power/power_views.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,31 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <flipper_v2.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { PowerViewInfo } PowerView; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     float current_charger; | ||||||
|  |     float current_gauge; | ||||||
|  | 
 | ||||||
|  |     float voltage_charger; | ||||||
|  |     float voltage_gauge; | ||||||
|  | 
 | ||||||
|  |     uint32_t capacity_remaining; | ||||||
|  |     uint32_t capacity_full; | ||||||
|  | 
 | ||||||
|  |     float temperature_charger; | ||||||
|  |     float temperature_gauge; | ||||||
|  | 
 | ||||||
|  |     uint8_t charge; | ||||||
|  | } PowerInfoModel; | ||||||
|  | 
 | ||||||
|  | static uint32_t power_info_back_callback(void* context) { | ||||||
|  |     return VIEW_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void power_info_draw_callback(Canvas* canvas, void* context); | ||||||
| @ -21,7 +21,14 @@ ICONS_TEMPLATE_H_FOOTER = """} IconName; | |||||||
| Icon * assets_icons_get(IconName name); | Icon * assets_icons_get(IconName name); | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| ICONS_TEMPLATE_C_HEADER = """#include \"assets_icons.h\" | ICONS_TEMPLATE_H_I = """#pragma once | ||||||
|  | 
 | ||||||
|  | #include <assets_icons.h> | ||||||
|  | 
 | ||||||
|  | const IconData * assets_icons_get_data(IconName name); | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | ICONS_TEMPLATE_C_HEADER = """#include \"assets_icons_i.h\" | ||||||
| #include <gui/icon_i.h> | #include <gui/icon_i.h> | ||||||
| 
 | 
 | ||||||
| """ | """ | ||||||
| @ -31,9 +38,12 @@ ICONS_TEMPLATE_C_ICONS_ARRAY_START = "const IconData icons[] = {\n" | |||||||
| ICONS_TEMPLATE_C_ICONS_ITEM = "\t{{ .width={width}, .height={height}, .frame_count={frame_count}, .frame_rate={frame_rate}, .frames=_{name} }},\n" | ICONS_TEMPLATE_C_ICONS_ITEM = "\t{{ .width={width}, .height={height}, .frame_count={frame_count}, .frame_rate={frame_rate}, .frames=_{name} }},\n" | ||||||
| ICONS_TEMPLATE_C_ICONS_ARRAY_END = "};" | ICONS_TEMPLATE_C_ICONS_ARRAY_END = "};" | ||||||
| ICONS_TEMPLATE_C_FOOTER = """ | ICONS_TEMPLATE_C_FOOTER = """ | ||||||
|  | const IconData * assets_icons_get_data(IconName name) { | ||||||
|  |     return &icons[name]; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| Icon * assets_icons_get(IconName name) { | Icon * assets_icons_get(IconName name) { | ||||||
|     return icon_alloc(&icons[name]); |     return icon_alloc(assets_icons_get_data(name)); | ||||||
| } | } | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| @ -157,13 +167,18 @@ class Assets: | |||||||
|         icons_c.write(ICONS_TEMPLATE_C_ICONS_ARRAY_END) |         icons_c.write(ICONS_TEMPLATE_C_ICONS_ARRAY_END) | ||||||
|         icons_c.write(ICONS_TEMPLATE_C_FOOTER) |         icons_c.write(ICONS_TEMPLATE_C_FOOTER) | ||||||
|         icons_c.write("\n") |         icons_c.write("\n") | ||||||
|         # Create Header |         # Create Public Header | ||||||
|         self.logger.debug(f"Creating header") |         self.logger.debug(f"Creating header") | ||||||
|         icons_h = open(os.path.join(self.args.output_directory, "assets_icons.h"), "w") |         icons_h = open(os.path.join(self.args.output_directory, "assets_icons.h"), "w") | ||||||
|         icons_h.write(ICONS_TEMPLATE_H_HEADER) |         icons_h.write(ICONS_TEMPLATE_H_HEADER) | ||||||
|         for name, width, height, frame_rate, frame_count in icons: |         for name, width, height, frame_rate, frame_count in icons: | ||||||
|             icons_h.write(ICONS_TEMPLATE_H_ICON_NAME.format(name=name)) |             icons_h.write(ICONS_TEMPLATE_H_ICON_NAME.format(name=name)) | ||||||
|         icons_h.write(ICONS_TEMPLATE_H_FOOTER) |         icons_h.write(ICONS_TEMPLATE_H_FOOTER) | ||||||
|  |         # Create Private Header | ||||||
|  |         icons_h_i = open( | ||||||
|  |             os.path.join(self.args.output_directory, "assets_icons_i.h"), "w" | ||||||
|  |         ) | ||||||
|  |         icons_h_i.write(ICONS_TEMPLATE_H_I) | ||||||
|         self.logger.debug(f"Done") |         self.logger.debug(f"Done") | ||||||
| 
 | 
 | ||||||
|     def icon2header(self, file): |     def icon2header(self, file): | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart0_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 871 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart1_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 736 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart2_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 838 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart3_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 806 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart4_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 829 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart5_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 860 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart6_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 852 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart7_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 841 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Dolphin/DolphinFirstStart8_128x54.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 853 B | 
| @ -122,6 +122,8 @@ int main(void) | |||||||
|   delay_us_init_DWT(); |   delay_us_init_DWT(); | ||||||
|   api_hal_vcp_init(); |   api_hal_vcp_init(); | ||||||
|   api_hal_spi_init(); |   api_hal_spi_init(); | ||||||
|  |   // Errata 2.2.9, Flash OPTVERR flag is always set after system reset
 | ||||||
|  |   __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); | ||||||
|   /* USER CODE END 2 */ |   /* USER CODE END 2 */ | ||||||
| 
 | 
 | ||||||
|   /* Init scheduler */ |   /* Init scheduler */ | ||||||
|  | |||||||
| @ -2,14 +2,28 @@ | |||||||
| #include <api-hal-bt.h> | #include <api-hal-bt.h> | ||||||
| #include <stm32wbxx.h> | #include <stm32wbxx.h> | ||||||
| 
 | 
 | ||||||
| void api_hal_flash_write_dword(size_t address, uint64_t data) { | bool api_hal_flash_erase(uint8_t page, uint8_t count) { | ||||||
|     api_hal_bt_lock_flash(); |     api_hal_bt_lock_flash(); | ||||||
|     HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data); |     FLASH_EraseInitTypeDef erase; | ||||||
|  |     erase.TypeErase = FLASH_TYPEERASE_PAGES; | ||||||
|  |     erase.Page = page; | ||||||
|  |     erase.NbPages = count; | ||||||
|  |     uint32_t error; | ||||||
|  |     HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&erase, &error); | ||||||
|     api_hal_bt_unlock_flash(); |     api_hal_bt_unlock_flash(); | ||||||
|  |     return status == HAL_OK; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void api_hal_flash_write_row(size_t address, size_t source_address) { | bool api_hal_flash_write_dword(size_t address, uint64_t data) { | ||||||
|     api_hal_bt_lock_flash(); |     api_hal_bt_lock_flash(); | ||||||
|     HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, source_address); |     HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data); | ||||||
|     api_hal_bt_unlock_flash(); |     api_hal_bt_unlock_flash(); | ||||||
|  |     return status == HAL_OK; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool api_hal_flash_write_row(size_t address, size_t source_address) { | ||||||
|  |     api_hal_bt_lock_flash(); | ||||||
|  |     HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FAST, address, source_address); | ||||||
|  |     api_hal_bt_unlock_flash(); | ||||||
|  |     return status == HAL_OK; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,15 +1,24 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <stdbool.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stddef.h> | #include <stddef.h> | ||||||
| 
 | 
 | ||||||
|  | /*
 | ||||||
|  |  * Erase Flash | ||||||
|  |  * Locking operation, uses HSEM to manage shared access. | ||||||
|  |  * @param page, page number | ||||||
|  |  * @param count, page count to erase | ||||||
|  |  */ | ||||||
|  | bool api_hal_flash_erase(uint8_t page, uint8_t count); | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Write double word (64 bits) |  * Write double word (64 bits) | ||||||
|  * Locking operation, uses HSEM to manage shared access. |  * Locking operation, uses HSEM to manage shared access. | ||||||
|  * @param address - destination address, must be double word aligned. |  * @param address - destination address, must be double word aligned. | ||||||
|  * @param data - data to write |  * @param data - data to write | ||||||
|  */ |  */ | ||||||
| void api_hal_flash_write_dword(size_t address, uint64_t data); | bool api_hal_flash_write_dword(size_t address, uint64_t data); | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Write page (4096 bytes or 64 rows of double words). |  * Write page (4096 bytes or 64 rows of double words). | ||||||
| @ -17,4 +26,4 @@ void api_hal_flash_write_dword(size_t address, uint64_t data); | |||||||
|  * @param address - destination address, must be page aligned |  * @param address - destination address, must be page aligned | ||||||
|  * @param source_address - source address |  * @param source_address - source address | ||||||
|  */ |  */ | ||||||
| void api_hal_flash_write_page(size_t address, size_t source_address); | bool api_hal_flash_write_page(size_t address, size_t source_address); | ||||||
|  | |||||||
| @ -62,7 +62,7 @@ $(OBJ_DIR)/upload: $(OBJ_DIR)/$(PROJECT).bin | |||||||
| 	dfu-util -D $(OBJ_DIR)/$(PROJECT).bin -a 0 -s $(FLASH_ADDRESS) -S $(DFU_SERIAL) | 	dfu-util -D $(OBJ_DIR)/$(PROJECT).bin -a 0 -s $(FLASH_ADDRESS) -S $(DFU_SERIAL) | ||||||
| 	touch $@ | 	touch $@ | ||||||
| 
 | 
 | ||||||
| $(ASSETS): $(ASSETS_SOURCES) | $(ASSETS): $(ASSETS_SOURCES) $(ASSETS_COMPILLER) | ||||||
| 	@echo "\tASSETS\t" $@ | 	@echo "\tASSETS\t" $@ | ||||||
| 	@$(ASSETS_COMPILLER) icons -s $(ASSETS_SOURCE_DIR) -o $(ASSETS_OUTPUT_DIR) | 	@$(ASSETS_COMPILLER) icons -s $(ASSETS_SOURCE_DIR) -o $(ASSETS_OUTPUT_DIR) | ||||||
| 
 | 
 | ||||||
|  | |||||||
 あく
						あく