From 4341da90dc7d063be6bdf832e681dc73d46532fe Mon Sep 17 00:00:00 2001 From: DrZlo13 Date: Fri, 5 Feb 2021 09:35:06 +1000 Subject: [PATCH] Implementation of some widgets based on real use cases and designs [FL-392][FL-809] (#315) * gui test app * aligned string draw functions * add canvas_invert_color, canvas_draw_button_left, canvas_draw_button_right * use new str and button fns in dialog * real dialog mockup * add new gui test app recipe * submenu module init * delete unused variable * move buttons to element, add canvas_string_width fn, new center button element * button icons * submenu module * use submenu module, switch views * keyboard buttons img * new font for keyboard * text input (keyboard) module * add text input to gui test app * add gui tesst app to release build, fix flags * handle transition from start and end position, fix input switch * add long text support to text input * canvas_string_width and the underlying u8g2_GetStrWidth now return uint16_t * remove deprecated libs and apps * canvas_font_max_height fn * new element, aligned multiline text * use multiline text instead of plain string * fix second keyboard row, rename uppercase fn * qwerty-like keyboard layout * new icons for iButton app * better dialog text position and events handling * remove confusing comment * new extended dialog module * extended dialog module usage * update docs * new gui module, popup with timeout * popup usage * canvas, remove outdated canvas_font_max_height, use canvas_current_font_height * use furi check * use new view_enter and view_exit callback for timers * add DrZlo to gui tester codeowner Co-authored-by: aanper --- .github/CODEOWNERS | 1 + applications/applications.c | 15 +- applications/applications.mk | 28 +- applications/gui-test/gui-test.c | 157 ++++++++ applications/gui/canvas.c | 57 ++- applications/gui/canvas.h | 38 +- applications/gui/elements.c | 143 ++++++- applications/gui/elements.h | 32 ++ applications/gui/modules/dialog.c | 26 +- applications/gui/modules/dialog.h | 6 +- applications/gui/modules/dialog_ex.c | 232 +++++++++++ applications/gui/modules/dialog_ex.h | 105 +++++ applications/gui/modules/popup.c | 227 +++++++++++ applications/gui/modules/popup.h | 92 +++++ applications/gui/modules/submenu.c | 194 +++++++++ applications/gui/modules/submenu.h | 36 ++ applications/gui/modules/text_input.c | 370 ++++++++++++++++++ applications/gui/modules/text_input.h | 42 ++ assets/icons/Common/ButtonCenter_7x7.png | Bin 0 -> 1440 bytes assets/icons/Common/ButtonLeft_4x7.png | Bin 0 -> 1415 bytes assets/icons/Common/ButtonRight_4x7.png | Bin 0 -> 1839 bytes .../Keyboard/KeyBackspaceSelected_16x9.png | Bin 0 -> 1812 bytes assets/icons/Keyboard/KeyBackspace_16x9.png | Bin 0 -> 1829 bytes .../icons/Keyboard/KeySaveSelected_24x11.png | Bin 0 -> 1853 bytes assets/icons/Keyboard/KeySave_24x11.png | Bin 0 -> 1863 bytes assets/icons/iButton/DolphinExcited_64x63.png | Bin 0 -> 2304 bytes assets/icons/iButton/DolphinMafia_115x62.png | Bin 0 -> 2504 bytes assets/icons/iButton/DolphinWait_61x59.png | Bin 0 -> 2023 bytes .../iButton/iButtonDolphinSuccess_109x60.png | Bin 0 -> 2178 bytes .../iButtonDolphinVerySuccess_108x52.png | Bin 0 -> 2157 bytes assets/icons/iButton/iButtonKey_49x44.png | Bin 0 -> 1970 bytes lib/u8g2/u8g2.h | 2 +- lib/u8g2/u8g2_font.c | 11 +- lib/u8g2_vendor/u8g2_vendor.c | 106 ----- lib/u8g2_vendor/u8g2_vendor.h | 4 - 35 files changed, 1768 insertions(+), 156 deletions(-) create mode 100644 applications/gui-test/gui-test.c create mode 100644 applications/gui/modules/dialog_ex.c create mode 100644 applications/gui/modules/dialog_ex.h create mode 100644 applications/gui/modules/popup.c create mode 100644 applications/gui/modules/popup.h create mode 100644 applications/gui/modules/submenu.c create mode 100644 applications/gui/modules/submenu.h create mode 100644 applications/gui/modules/text_input.c create mode 100644 applications/gui/modules/text_input.h create mode 100644 assets/icons/Common/ButtonCenter_7x7.png create mode 100644 assets/icons/Common/ButtonLeft_4x7.png create mode 100644 assets/icons/Common/ButtonRight_4x7.png create mode 100644 assets/icons/Keyboard/KeyBackspaceSelected_16x9.png create mode 100644 assets/icons/Keyboard/KeyBackspace_16x9.png create mode 100644 assets/icons/Keyboard/KeySaveSelected_24x11.png create mode 100644 assets/icons/Keyboard/KeySave_24x11.png create mode 100644 assets/icons/iButton/DolphinExcited_64x63.png create mode 100644 assets/icons/iButton/DolphinMafia_115x62.png create mode 100644 assets/icons/iButton/DolphinWait_61x59.png create mode 100644 assets/icons/iButton/iButtonDolphinSuccess_109x60.png create mode 100644 assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png create mode 100644 assets/icons/iButton/iButtonKey_49x44.png delete mode 100644 lib/u8g2_vendor/u8g2_vendor.c delete mode 100644 lib/u8g2_vendor/u8g2_vendor.h diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b0bac30b..91bd385a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -110,6 +110,7 @@ firmware/targets/f4/api-hal/api-hal-power.c @skotopes applications/music-player/** @DrZlo13 applications/floopper-bloopper/** @glitchcore applications/gpio-tester/** @glitchcore +applications/gui-test/** @DrZlo13 lib/app-template/** @DrZlo13 lib/qrcode/** @DrZlo13 diff --git a/applications/applications.c b/applications/applications.c index 70bf28d8..3f9ea08c 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -7,7 +7,6 @@ void flipper_test_app(void* p); void application_blink(void* p); void application_uart_write(void* p); void application_input_dump(void* p); -void display_u8g2(void* p); void u8g2_example(void* p); void input_task(void* p); void menu_task(void* p); @@ -34,11 +33,9 @@ void sdnfc(void* p); void floopper_bloopper(void* p); void sd_filesystem(void* p); -const FuriApplication FLIPPER_SERVICES[] = { -#ifdef APP_DISPLAY - {.app = display_u8g2, .name = "display_u8g2", .stack_size = 1024, .icon = A_Plugins_14}, -#endif +void gui_test(void* p); +const FuriApplication FLIPPER_SERVICES[] = { #ifdef APP_CLI {.app = cli_task, .name = "cli_task", .stack_size = 1024, .icon = A_Plugins_14}, #endif @@ -152,6 +149,10 @@ const FuriApplication FLIPPER_SERVICES[] = { #ifdef APP_SDNFC {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, #endif + +#ifdef APP_GUI_TEST + {.app = gui_test, .name = "gui_test", .icon = A_Plugins_14}, +#endif }; size_t FLIPPER_SERVICES_size() { @@ -224,6 +225,10 @@ const FuriApplication FLIPPER_PLUGINS[] = { #ifdef BUILD_SDNFC {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, #endif + +#ifdef BUILD_GUI_TEST + {.app = gui_test, .name = "gui_test", .icon = A_Plugins_14}, +#endif }; size_t FLIPPER_PLUGINS_size() { diff --git a/applications/applications.mk b/applications/applications.mk index 63487ef8..9214f94a 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -29,6 +29,7 @@ BUILD_GPIO_DEMO = 1 BUILD_MUSIC_PLAYER = 1 BUILD_FLOOPPER_BLOOPPER = 1 BUILD_IBUTTON = 1 +BUILD_GUI_TEST = 1 endif APP_NFC ?= 0 @@ -144,15 +145,6 @@ ifeq ($(BUILD_EXAMPLE_QRCODE), 1) CFLAGS += -DBUILD_EXAMPLE_QRCODE C_SOURCES += $(APP_DIR)/examples/u8g2_qrcode.c C_SOURCES += $(LIB_DIR)/qrcode/qrcode.c -APP_DISPLAY = 1 -endif - -# deprecated -APP_EXAMPLE_DISPLAY ?= 0 -ifeq ($(APP_EXAMPLE_DISPLAY), 1) -CFLAGS += -DAPP_EXAMPLE_DISPLAY -C_SOURCES += $(APP_DIR)/examples/u8g2_example.c -APP_DISPLAY = 1 endif APP_EXAMPLE_FATFS ?= 0 @@ -165,7 +157,6 @@ ifeq ($(BUILD_EXAMPLE_FATFS), 1) CFLAGS += -DBUILD_EXAMPLE_FATFS C_SOURCES += $(APP_DIR)/examples/fatfs_list.c APP_INPUT = 1 -APP_DISPLAY = 1 endif APP_CC1101 ?= 0 @@ -289,6 +280,17 @@ CFLAGS += -DBUILD_IBUTTON CPP_SOURCES += $(wildcard $(APP_DIR)/ibutton/*.cpp) endif +APP_GUI_TEST ?= 0 +ifeq ($(APP_GUI_TEST), 1) +CFLAGS += -DAPP_GUI_TEST +BUILD_GUI_TEST = 1 +endif +BUILD_GUI_TEST ?= 0 +ifeq ($(BUILD_GUI_TEST), 1) +CFLAGS += -DBUILD_GUI_TEST +C_SOURCES += $(wildcard $(APP_DIR)/gui-test/*.c) +endif + APP_SDNFC ?= 0 ifeq ($(APP_SDNFC), 1) CFLAGS += -DAPP_SDNFC @@ -315,12 +317,6 @@ CFLAGS += -DAPP_SD_FILESYSTEM C_SOURCES += $(wildcard $(APP_DIR)/sd-filesystem/*.c) endif -# deprecated -ifeq ($(APP_DISPLAY), 1) -CFLAGS += -DAPP_DISPLAY -C_SOURCES += $(APP_DIR)/display-u8g2/display-u8g2.c -endif - APP_INPUT ?= 0 ifeq ($(APP_INPUT), 1) CFLAGS += -DAPP_INPUT diff --git a/applications/gui-test/gui-test.c b/applications/gui-test/gui-test.c new file mode 100644 index 00000000..731454b8 --- /dev/null +++ b/applications/gui-test/gui-test.c @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + GuiTesterViewTextInput = 0, + GuiTesterViewSubmenu, + GuiTesterViewDialog, + GuiTesterViewDialogEx, + GuiTesterViewPopup, + GuiTesterViewLast +} GuiTesterView; + +typedef struct { + ViewDispatcher* view_dispatcher; + Dialog* dialog; + DialogEx* dialog_ex; + Submenu* submenu; + TextInput* text_input; + Popup* popup; + GuiTesterView view_index; +} GuiTester; + +GuiTester* gui_test_alloc(void) { + GuiTester* gui_tester = furi_alloc(sizeof(GuiTester)); + gui_tester->view_dispatcher = view_dispatcher_alloc(); + gui_tester->view_index = GuiTesterViewDialogEx; + + gui_tester->dialog = dialog_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, GuiTesterViewDialog, dialog_get_view(gui_tester->dialog)); + + gui_tester->dialog_ex = dialog_ex_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, + GuiTesterViewDialogEx, + dialog_ex_get_view(gui_tester->dialog_ex)); + + gui_tester->submenu = submenu_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, GuiTesterViewSubmenu, submenu_get_view(gui_tester->submenu)); + + gui_tester->text_input = text_input_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, + GuiTesterViewTextInput, + text_input_get_view(gui_tester->text_input)); + + gui_tester->popup = popup_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, GuiTesterViewPopup, popup_get_view(gui_tester->popup)); + + return gui_tester; +} + +void next_view(void* context) { + furi_assert(context); + GuiTester* gui_tester = context; + + gui_tester->view_index++; + if(gui_tester->view_index >= GuiTesterViewLast) { + gui_tester->view_index = 0; + } + + view_dispatcher_switch_to_view(gui_tester->view_dispatcher, gui_tester->view_index); +} + +void popup_callback(void* context) { + next_view(context); +} + +void submenu_callback(void* context) { + next_view(context); +} + +void dialog_callback(DialogResult result, void* context) { + next_view(context); +} + +void dialog_ex_callback(DialogExResult result, void* context) { + next_view(context); +} + +void text_input_callback(void* context, char* text) { + next_view(context); +} + +void gui_test(void* param) { + (void)param; + GuiTester* gui_tester = gui_test_alloc(); + + Gui* gui = furi_record_open("gui"); + view_dispatcher_attach_to_gui(gui_tester->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + + // Submenu + submenu_add_item(gui_tester->submenu, "Read", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Saved", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Emulate", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Enter manually", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Blah blah", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Set time", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Gender-bender", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Hack American Elections", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Hack the White House", submenu_callback, gui_tester); + + // Dialog + dialog_set_result_callback(gui_tester->dialog, dialog_callback); + dialog_set_context(gui_tester->dialog, gui_tester); + dialog_set_header_text(gui_tester->dialog, "Delete Abc123?"); + dialog_set_text(gui_tester->dialog, "ID: F0 00 01 02 03 04\nAre you shure?"); + dialog_set_left_button_text(gui_tester->dialog, "< Yes"); + dialog_set_right_button_text(gui_tester->dialog, "No >"); + + // Dialog extended + dialog_ex_set_result_callback(gui_tester->dialog_ex, dialog_ex_callback); + dialog_ex_set_context(gui_tester->dialog_ex, gui_tester); + dialog_ex_set_header(gui_tester->dialog_ex, "Dallas", 95, 12, AlignCenter, AlignCenter); + dialog_ex_set_text( + gui_tester->dialog_ex, "F6 E5 D4\nC3 B2 A1", 95, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(gui_tester->dialog_ex, 0, 1, I_DolphinExcited_64x63); + dialog_ex_set_left_button_text(gui_tester->dialog_ex, "< More"); + dialog_ex_set_right_button_text(gui_tester->dialog_ex, "Save >"); + + // Popup + popup_set_callback(gui_tester->popup, popup_callback); + popup_set_context(gui_tester->popup, gui_tester); + popup_set_icon(gui_tester->popup, 0, 2, I_DolphinMafia_115x62); + popup_set_text(gui_tester->popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_timeout(gui_tester->popup, 5000); + popup_enable_timeout(gui_tester->popup); + + // Text input + const uint8_t text_input_text_len = 64; + char* text_input_text = calloc(text_input_text_len + 1, 1); + memcpy(text_input_text, "New_ke", strlen("New_ke")); + + text_input_set_result_callback( + gui_tester->text_input, + text_input_callback, + gui_tester, + text_input_text, + text_input_text_len); + text_input_set_header_text(gui_tester->text_input, "Name the key"); + + view_dispatcher_switch_to_view(gui_tester->view_dispatcher, gui_tester->view_index); + + while(1) { + osDelay(1000); + } +} \ No newline at end of file diff --git a/applications/gui/canvas.c b/applications/gui/canvas.c index a2a5092f..1c1e06e3 100644 --- a/applications/gui/canvas.c +++ b/applications/gui/canvas.c @@ -77,6 +77,10 @@ void canvas_set_color(Canvas* canvas, Color color) { u8g2_SetDrawColor(&canvas->fb, color); } +void canvas_invert_color(Canvas* canvas) { + canvas->fb.draw_color = !canvas->fb.draw_color; +} + void canvas_set_font(Canvas* canvas, Font font) { furi_assert(canvas); u8g2_SetFontMode(&canvas->fb, 1); @@ -86,6 +90,8 @@ void canvas_set_font(Canvas* canvas, Font font) { u8g2_SetFont(&canvas->fb, u8g2_font_haxrcorp4089_tr); } else if(font == FontGlyph) { u8g2_SetFont(&canvas->fb, u8g2_font_unifont_t_symbols); + } else if(font == FontKeyboard) { + u8g2_SetFont(&canvas->fb, u8g2_font_profont11_mf); } else { furi_check(0); } @@ -99,6 +105,55 @@ void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str) { u8g2_DrawStr(&canvas->fb, x, y, str); } +void canvas_draw_str_aligned( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* str) { + furi_assert(canvas); + if(!str) return; + x += canvas->offset_x; + y += canvas->offset_y; + + switch(horizontal) { + case AlignLeft: + break; + case AlignRight: + x -= u8g2_GetStrWidth(&canvas->fb, str); + break; + case AlignCenter: + x -= (u8g2_GetStrWidth(&canvas->fb, str) / 2); + break; + default: + furi_check(0); + break; + } + + switch(vertical) { + case AlignTop: + y += u8g2_GetAscent(&canvas->fb); + break; + case AlignBottom: + break; + case AlignCenter: + y += (u8g2_GetAscent(&canvas->fb) / 2); + break; + default: + furi_check(0); + break; + } + + u8g2_DrawStr(&canvas->fb, x, y, str); +} + +uint16_t canvas_string_width(Canvas* canvas, const char* str) { + furi_assert(canvas); + if(!str) return 0; + return u8g2_GetStrWidth(&canvas->fb, str); +} + void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon) { furi_assert(canvas); if(!icon) return; @@ -164,4 +219,4 @@ void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) { x += canvas->offset_x; y += canvas->offset_y; u8g2_DrawGlyph(&canvas->fb, x, y, ch); -} +} \ No newline at end of file diff --git a/applications/gui/canvas.h b/applications/gui/canvas.h index 62258ed6..9cfeb2b0 100644 --- a/applications/gui/canvas.h +++ b/applications/gui/canvas.h @@ -13,7 +13,20 @@ typedef enum { ColorBlack = 0x01, } Color; -typedef enum { FontPrimary = 0x00, FontSecondary = 0x01, FontGlyph = 0x02 } Font; +typedef enum { + FontPrimary = 0x00, + FontSecondary = 0x01, + FontGlyph = 0x02, + FontKeyboard = 0x03 +} Font; + +typedef enum { + AlignLeft, + AlignRight, + AlignTop, + AlignBottom, + AlignCenter, +} Align; typedef struct Canvas Canvas; @@ -45,6 +58,11 @@ void canvas_clear(Canvas* canvas); */ void canvas_set_color(Canvas* canvas, Color color); +/* + * Invert drawing color + */ +void canvas_invert_color(Canvas* canvas); + /* * Set drawing font */ @@ -55,6 +73,24 @@ void canvas_set_font(Canvas* canvas, Font font); */ void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str); +/* + * Draw aligned string defined by x, y. + * Align calculated from position of baseline, string width and ascent (height of the glyphs above the baseline) + */ +void canvas_draw_str_aligned( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* str); + +/* + * Get string width + * @return width in pixels. + */ +uint16_t canvas_string_width(Canvas* canvas, const char* str); + /* * Draw stateful icon at position defined by x,y. */ diff --git a/applications/gui/elements.c b/applications/gui/elements.c index 7c58a311..8bf6b2dd 100644 --- a/applications/gui/elements.c +++ b/applications/gui/elements.c @@ -1,10 +1,10 @@ #include "elements.h" -#include "canvas_i.h" - -#include - -#include +#include +#include #include +#include +#include "canvas_i.h" +#include void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total) { furi_assert(canvas); @@ -40,6 +40,137 @@ void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t canvas_draw_dot(canvas, x + 1, y + 1); } +void elements_button_left(Canvas* canvas, const char* str) { + const uint8_t button_height = 13; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const IconData* icon = assets_icons_get_data(I_ButtonLeft_4x7); + const uint8_t icon_offset = 6; + const uint8_t icon_width_with_offset = icon->width + icon_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = 0; + const uint8_t y = canvas_height(canvas); + + canvas_draw_box(canvas, x, y - button_height, button_width, button_height); + canvas_draw_line(canvas, x + button_width + 0, y, x + button_width + 0, y - button_height + 0); + canvas_draw_line(canvas, x + button_width + 1, y, x + button_width + 1, y - button_height + 1); + canvas_draw_line(canvas, x + button_width + 2, y, x + button_width + 2, y - button_height + 2); + + canvas_invert_color(canvas); + canvas_draw_icon_name( + canvas, x + horizontal_offset, y - button_height + vertical_offset, I_ButtonLeft_4x7); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_button_right(Canvas* canvas, const char* str) { + const uint8_t button_height = 13; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const IconData* icon = assets_icons_get_data(I_ButtonRight_4x7); + const uint8_t icon_offset = 6; + const uint8_t icon_width_with_offset = icon->width + icon_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = canvas_width(canvas); + const uint8_t y = canvas_height(canvas); + + canvas_draw_box(canvas, x - button_width, y - button_height, button_width, button_height); + canvas_draw_line(canvas, x - button_width - 1, y, x - button_width - 1, y - button_height + 0); + canvas_draw_line(canvas, x - button_width - 2, y, x - button_width - 2, y - button_height + 1); + canvas_draw_line(canvas, x - button_width - 3, y, x - button_width - 3, y - button_height + 2); + + canvas_invert_color(canvas); + canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str); + canvas_draw_icon_name( + canvas, + x - horizontal_offset - icon->width, + y - button_height + vertical_offset, + I_ButtonRight_4x7); + canvas_invert_color(canvas); +} + +void elements_button_center(Canvas* canvas, const char* str) { + const uint8_t button_height = 13; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const IconData* icon = assets_icons_get_data(I_ButtonCenter_7x7); + const uint8_t icon_offset = 6; + const uint8_t icon_width_with_offset = icon->width + icon_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = (canvas_width(canvas) - button_width) / 2; + const uint8_t y = canvas_height(canvas); + + canvas_draw_box(canvas, x, y - button_height, button_width, button_height); + + canvas_draw_line(canvas, x - 1, y, x - 1, y - button_height + 0); + canvas_draw_line(canvas, x - 2, y, x - 2, y - button_height + 1); + canvas_draw_line(canvas, x - 3, y, x - 3, y - button_height + 2); + + canvas_draw_line(canvas, x + button_width + 0, y, x + button_width + 0, y - button_height + 0); + canvas_draw_line(canvas, x + button_width + 1, y, x + button_width + 1, y - button_height + 1); + canvas_draw_line(canvas, x + button_width + 2, y, x + button_width + 2, y - button_height + 2); + + canvas_invert_color(canvas); + canvas_draw_icon_name( + canvas, x + horizontal_offset, y - button_height + vertical_offset, I_ButtonCenter_7x7); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_multiline_text_aligned( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* text) { + furi_assert(canvas); + furi_assert(text); + + uint8_t font_height = canvas_current_font_height(canvas); + string_t str; + string_init(str); + const char* start = text; + char* end; + + // get lines count + uint8_t i, lines_count; + for(i = 0, lines_count = 0; text[i]; i++) lines_count += (text[i] == '\n'); + + switch(vertical) { + case AlignBottom: + y -= font_height * lines_count; + break; + case AlignCenter: + y -= (font_height * lines_count) / 2; + break; + case AlignTop: + default: + break; + } + + do { + end = strchr(start, '\n'); + if(end) { + string_set_strn(str, start, end - start); + } else { + string_set_str(str, start); + } + canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, string_get_cstr(str)); + start = end + 1; + y += font_height; + } while(end); + string_clear(str); +} + void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, char* text) { furi_assert(canvas); furi_assert(text); @@ -61,4 +192,4 @@ void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, char* text) { y += font_height; } while(end); string_clear(str); -} +} \ No newline at end of file diff --git a/applications/gui/elements.h b/applications/gui/elements.h index 68f7ec85..8252f81b 100644 --- a/applications/gui/elements.h +++ b/applications/gui/elements.h @@ -22,6 +22,38 @@ void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total); */ void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); +/* + * Draw button in left corner + * @param str - button text + */ +void elements_button_left(Canvas* canvas, const char* str); + +/* + * Draw button in right corner + * @param str - button text + */ +void elements_button_right(Canvas* canvas, const char* str); + +/* + * Draw button in center + * @param str - button text + */ +void elements_button_center(Canvas* canvas, const char* str); + +/* + * Draw aligned multiline text + * @param x, y - coordinates based on align param + * @param horizontal, vertical - aligment of multiline text + * @param text - string (possible multiline) + */ +void elements_multiline_text_aligned( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* text); + /* * Draw multiline text * @param x, y - top left corner coordinates diff --git a/applications/gui/modules/dialog.c b/applications/gui/modules/dialog.c index ae0f85a0..74298e95 100644 --- a/applications/gui/modules/dialog.c +++ b/applications/gui/modules/dialog.c @@ -1,4 +1,5 @@ #include "dialog.h" +#include #include struct Dialog { @@ -16,34 +17,43 @@ typedef struct { static void dialog_view_draw_callback(Canvas* canvas, void* _model) { DialogModel* model = _model; + uint8_t canvas_center = canvas_width(canvas) / 2; + // Prepare canvas canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); + // Draw header canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 2, 10, model->header_text); + canvas_draw_str_aligned( + canvas, canvas_center, 17, AlignCenter, AlignBottom, model->header_text); + // Draw text canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 5, 22, model->text); + elements_multiline_text_aligned( + canvas, canvas_center, 32, AlignCenter, AlignCenter, model->text); + // Draw buttons - uint8_t bottom_base_line = canvas_height(canvas) - 2; - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 5, bottom_base_line, model->left_text); - canvas_draw_str(canvas, 69, bottom_base_line, model->right_text); + elements_button_left(canvas, model->left_text); + elements_button_right(canvas, model->right_text); } static bool dialog_view_input_callback(InputEvent* event, void* context) { Dialog* dialog = context; + bool consumed = false; + // Process key presses only if(event->state && dialog->callback) { if(event->input == InputLeft) { dialog->callback(DialogResultLeft, dialog->context); + consumed = true; } else if(event->input == InputRight) { dialog->callback(DialogResultRight, dialog->context); + consumed = true; } } - // All input events consumed - return true; + + return consumed; } Dialog* dialog_alloc() { diff --git a/applications/gui/modules/dialog.h b/applications/gui/modules/dialog.h index 36cd3164..a34159f1 100644 --- a/applications/gui/modules/dialog.h +++ b/applications/gui/modules/dialog.h @@ -32,13 +32,13 @@ void dialog_free(Dialog* dialog); */ View* dialog_get_view(Dialog* dialog); -/* Set dialog header text +/* Set dialog result callback * @param dialog - Dialog instance - * @param text - text to be shown + * @param callback - result callback function */ void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback); -/* Set dialog header text +/* Set dialog context * @param dialog - Dialog instance * @param context - context pointer, will be passed to result callback */ diff --git a/applications/gui/modules/dialog_ex.c b/applications/gui/modules/dialog_ex.c new file mode 100644 index 00000000..23534b4f --- /dev/null +++ b/applications/gui/modules/dialog_ex.c @@ -0,0 +1,232 @@ +#include "dialog_ex.h" +#include +#include + +struct DialogEx { + View* view; + void* context; + DialogExResultCallback callback; +}; + +typedef struct { + const char* text; + uint8_t x; + uint8_t y; + Align horizontal; + Align vertical; +} TextElement; + +typedef struct { + int8_t x; + int8_t y; + IconName name; +} IconElement; + +typedef struct { + TextElement header; + TextElement text; + IconElement icon; + + const char* left_text; + const char* center_text; + const char* right_text; +} DialogExModel; + +static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { + DialogExModel* model = _model; + + // Prepare canvas + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // TODO other criteria for the draw + if(model->icon.x >= 0 && model->icon.y >= 0) { + canvas_draw_icon_name(canvas, model->icon.x, model->icon.y, model->icon.name); + } + + // Draw header + if(model->header.text != NULL) { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, + model->header.x, + model->header.y, + model->header.horizontal, + model->header.vertical, + model->header.text); + } + + // Draw text + if(model->text.text != NULL) { + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, + model->text.x, + model->text.y, + model->text.horizontal, + model->text.vertical, + model->text.text); + } + + // Draw buttons + if(model->left_text != NULL) { + elements_button_left(canvas, model->left_text); + } + + if(model->center_text != NULL) { + elements_button_center(canvas, model->center_text); + } + + if(model->right_text != NULL) { + elements_button_right(canvas, model->right_text); + } +} + +static bool dialog_ex_view_input_callback(InputEvent* event, void* context) { + DialogEx* dialog_ex = context; + bool consumed = false; + const char* left_text = NULL; + const char* center_text = NULL; + const char* right_text = NULL; + + with_view_model( + dialog_ex->view, (DialogExModel * model) { + left_text = model->left_text; + center_text = model->center_text; + right_text = model->right_text; + }); + + // Process key presses only + if(event->state && dialog_ex->callback) { + if(event->input == InputLeft && left_text != NULL) { + dialog_ex->callback(DialogExResultLeft, dialog_ex->context); + consumed = true; + } else if(event->input == InputOk && center_text != NULL) { + dialog_ex->callback(DialogExResultCenter, dialog_ex->context); + consumed = true; + } else if(event->input == InputRight && right_text != NULL) { + dialog_ex->callback(DialogExResultRight, dialog_ex->context); + consumed = true; + } + } + + return consumed; +} + +DialogEx* dialog_ex_alloc() { + DialogEx* dialog_ex = furi_alloc(sizeof(DialogEx)); + dialog_ex->view = view_alloc(); + view_set_context(dialog_ex->view, dialog_ex); + view_allocate_model(dialog_ex->view, ViewModelTypeLockFree, sizeof(DialogExModel)); + view_set_draw_callback(dialog_ex->view, dialog_ex_view_draw_callback); + view_set_input_callback(dialog_ex->view, dialog_ex_view_input_callback); + with_view_model( + dialog_ex->view, (DialogExModel * model) { + model->header.text = NULL; + model->header.x = 0; + model->header.y = 0; + model->header.horizontal = AlignLeft; + model->header.vertical = AlignBottom; + + model->text.text = NULL; + model->text.x = 0; + model->text.y = 0; + model->text.horizontal = AlignLeft; + model->text.vertical = AlignBottom; + + // TODO other criteria for the draw + model->icon.x = -1; + model->icon.y = -1; + model->icon.name = I_ButtonCenter_7x7; + + model->left_text = NULL; + model->center_text = NULL; + model->right_text = NULL; + }); + return dialog_ex; +} + +void dialog_ex_free(DialogEx* dialog_ex) { + furi_assert(dialog_ex); + view_free(dialog_ex->view); + free(dialog_ex); +} + +View* dialog_ex_get_view(DialogEx* dialog_ex) { + furi_assert(dialog_ex); + return dialog_ex->view; +} + +void dialog_ex_set_result_callback(DialogEx* dialog_ex, DialogExResultCallback callback) { + furi_assert(dialog_ex); + dialog_ex->callback = callback; +} + +void dialog_ex_set_context(DialogEx* dialog_ex, void* context) { + furi_assert(dialog_ex); + dialog_ex->context = context; +} + +void dialog_ex_set_header( + DialogEx* dialog_ex, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { + model->header.text = text; + model->header.x = x; + model->header.y = y; + model->header.horizontal = horizontal; + model->header.vertical = vertical; + }); +} + +void dialog_ex_set_text( + DialogEx* dialog_ex, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { + model->text.text = text; + model->text.x = x; + model->text.y = y; + model->text.horizontal = horizontal; + model->text.vertical = vertical; + }); +} + +void dialog_ex_set_icon(DialogEx* dialog_ex, int8_t x, int8_t y, IconName name) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { + model->icon.x = x; + model->icon.y = y; + model->icon.name = name; + }); +} + +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; }); +} + +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; }); +} + +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; }); +} diff --git a/applications/gui/modules/dialog_ex.h b/applications/gui/modules/dialog_ex.h new file mode 100644 index 00000000..9c9dce91 --- /dev/null +++ b/applications/gui/modules/dialog_ex.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +/* Dialog anonymous structure */ +typedef struct DialogEx DialogEx; + +/* DialogEx result */ +typedef enum { + DialogExResultLeft, + DialogExResultCenter, + DialogExResultRight, +} DialogExResult; + +/* DialogEx result callback type + * @warning comes from GUI thread + */ +typedef void (*DialogExResultCallback)(DialogExResult result, void* context); + +/* Allocate and initialize dialog + * This dialog used to ask simple questions like Yes/ + */ +DialogEx* dialog_ex_alloc(); + +/* Deinitialize and free dialog + * @param dialog - DialogEx instance + */ +void dialog_ex_free(DialogEx* dialog_ex); + +/* Get dialog view + * @param dialog - DialogEx instance + * @return View instance that can be used for embedding + */ +View* dialog_ex_get_view(DialogEx* dialog_ex); + +/* Set dialog result callback + * @param dialog_ex - DialogEx instance + * @param callback - result callback function + */ +void dialog_ex_set_result_callback(DialogEx* dialog_ex, DialogExResultCallback callback); + +/* Set dialog context + * @param dialog_ex - DialogEx instance + * @param context - context pointer, will be passed to result callback + */ +void dialog_ex_set_context(DialogEx* dialog_ex, void* context); + +/* Set dialog header text + * If text is null, dialog header will not be rendered + * @param dialog - DialogEx instance + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ +void dialog_ex_set_header( + DialogEx* dialog_ex, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical); + +/* Set dialog text + * If text is null, dialog text will not be rendered + * @param dialog - DialogEx instance + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ +void dialog_ex_set_text( + DialogEx* dialog_ex, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical); + +/* Set dialog icon + * If x or y is negative, dialog icon will not be rendered + * @param dialog - DialogEx instance + * @param x, y - icon position + * @param name - icon to be shown + */ +void dialog_ex_set_icon(DialogEx* dialog_ex, int8_t x, int8_t y, IconName name); + +/* Set left button text + * If text is null, left button will not be rendered and processed + * @param dialog - DialogEx instance + * @param text - text to be shown + */ +void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text); + +/* Set center button text + * If text is null, center button will not be rendered and processed + * @param dialog - DialogEx instance + * @param text - text to be shown + */ +void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text); + +/* Set right button text + * If text is null, right button will not be rendered and processed + * @param dialog - DialogEx instance + * @param text - text to be shown + */ +void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text); diff --git a/applications/gui/modules/popup.c b/applications/gui/modules/popup.c new file mode 100644 index 00000000..73a855d2 --- /dev/null +++ b/applications/gui/modules/popup.c @@ -0,0 +1,227 @@ +#include "popup.h" +#include +#include + +struct Popup { + View* view; + void* context; + PopupCallback callback; + + osTimerId_t timer; + uint32_t timer_period_in_ms; + bool timer_enabled; +}; + +typedef struct { + const char* text; + uint8_t x; + uint8_t y; + Align horizontal; + Align vertical; +} TextElement; + +typedef struct { + int8_t x; + int8_t y; + IconName name; +} IconElement; + +typedef struct { + TextElement header; + TextElement text; + IconElement icon; +} PopupModel; + +static void popup_view_draw_callback(Canvas* canvas, void* _model) { + PopupModel* model = _model; + + // Prepare canvas + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // TODO other criteria for the draw + if(model->icon.x >= 0 && model->icon.y >= 0) { + canvas_draw_icon_name(canvas, model->icon.x, model->icon.y, model->icon.name); + } + + // Draw header + if(model->header.text != NULL) { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, + model->header.x, + model->header.y, + model->header.horizontal, + model->header.vertical, + model->header.text); + } + + // Draw text + if(model->text.text != NULL) { + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, + model->text.x, + model->text.y, + model->text.horizontal, + model->text.vertical, + model->text.text); + } +} + +static void popup_timer_callback(void* context) { + furi_assert(context); + Popup* popup = context; + + if(popup->callback) { + popup->callback(popup->context); + } +} + +static bool popup_view_input_callback(InputEvent* event, void* context) { + Popup* popup = context; + bool consumed = false; + + // Process key presses only + if(event->state && popup->callback) { + popup->callback(popup->context); + consumed = true; + } + + return consumed; +} + +void popup_start_timer(void* context) { + Popup* popup = context; + if(popup->timer_enabled) { + uint32_t timer_period = popup->timer_period_in_ms / (1000.0f / osKernelGetTickFreq()); + if(timer_period == 0) timer_period = 1; + + if(osTimerStart(popup->timer, timer_period) != osOK) { + furi_assert(0); + }; + } +} + +void popup_stop_timer(void* context) { + Popup* popup = context; + osTimerStop(popup->timer); +} + +Popup* popup_alloc() { + Popup* popup = furi_alloc(sizeof(Popup)); + popup->view = view_alloc(); + popup->timer = osTimerNew(popup_timer_callback, osTimerOnce, popup, NULL); + furi_assert(popup->timer); + popup->timer_period_in_ms = 1000; + popup->timer_enabled = false; + + view_set_context(popup->view, popup); + view_allocate_model(popup->view, ViewModelTypeLockFree, sizeof(PopupModel)); + view_set_draw_callback(popup->view, popup_view_draw_callback); + view_set_input_callback(popup->view, popup_view_input_callback); + view_set_enter_callback(popup->view, popup_start_timer); + view_set_exit_callback(popup->view, popup_stop_timer); + + with_view_model( + popup->view, (PopupModel * model) { + model->header.text = NULL; + model->header.x = 0; + model->header.y = 0; + model->header.horizontal = AlignLeft; + model->header.vertical = AlignBottom; + + model->text.text = NULL; + model->text.x = 0; + model->text.y = 0; + model->text.horizontal = AlignLeft; + model->text.vertical = AlignBottom; + + // TODO other criteria for the draw + model->icon.x = -1; + model->icon.y = -1; + model->icon.name = I_ButtonCenter_7x7; + }); + return popup; +} + +void popup_free(Popup* popup) { + furi_assert(popup); + osTimerDelete(popup->timer); + view_free(popup->view); + free(popup); +} + +View* popup_get_view(Popup* popup) { + furi_assert(popup); + return popup->view; +} + +void popup_set_callback(Popup* popup, PopupCallback callback) { + furi_assert(popup); + popup->callback = callback; +} + +void popup_set_context(Popup* popup, void* context) { + furi_assert(popup); + popup->context = context; +} + +void popup_set_header( + Popup* popup, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + furi_assert(popup); + with_view_model( + popup->view, (PopupModel * model) { + model->header.text = text; + model->header.x = x; + model->header.y = y; + model->header.horizontal = horizontal; + model->header.vertical = vertical; + }); +} + +void popup_set_text( + Popup* popup, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + furi_assert(popup); + with_view_model( + popup->view, (PopupModel * model) { + model->text.text = text; + model->text.x = x; + model->text.y = y; + model->text.horizontal = horizontal; + model->text.vertical = vertical; + }); +} + +void popup_set_icon(Popup* popup, int8_t x, int8_t y, IconName name) { + furi_assert(popup); + with_view_model( + popup->view, (PopupModel * model) { + model->icon.x = x; + model->icon.y = y; + model->icon.name = name; + }); +} + +void popup_set_timeout(Popup* popup, uint32_t timeout_in_ms) { + furi_assert(popup); + popup->timer_period_in_ms = timeout_in_ms; +} + +void popup_enable_timeout(Popup* popup) { + popup->timer_enabled = true; +} + +void popup_disable_timeout(Popup* popup) { + popup->timer_enabled = false; +} \ No newline at end of file diff --git a/applications/gui/modules/popup.h b/applications/gui/modules/popup.h new file mode 100644 index 00000000..b7e3ac16 --- /dev/null +++ b/applications/gui/modules/popup.h @@ -0,0 +1,92 @@ +#pragma once + +#include + +/* Popup anonymous structure */ +typedef struct Popup Popup; + +/* Popup result callback type + * @warning comes from GUI thread + */ +typedef void (*PopupCallback)(void* context); + +/* Allocate and initialize popup + * This popup used to ask simple questions like Yes/ + */ +Popup* popup_alloc(); + +/* Deinitialize and free popup + * @param popup - Popup instance + */ +void popup_free(Popup* popup); + +/* Get popup view + * @param popup - Popup instance + * @return View instance that can be used for embedding + */ +View* popup_get_view(Popup* popup); + +/* Set popup header text + * @param popup - Popup instance + * @param text - text to be shown + */ +void popup_set_callback(Popup* popup, PopupCallback callback); + +/* Set popup context + * @param popup - Popup instance + * @param context - context pointer, will be passed to result callback + */ +void popup_set_context(Popup* popup, void* context); + +/* Set popup header text + * If text is null, popup header will not be rendered + * @param popup - Popup instance + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ +void popup_set_header( + Popup* popup, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical); + +/* Set popup text + * If text is null, popup text will not be rendered + * @param popup - Popup instance + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ +void popup_set_text( + Popup* popup, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical); + +/* Set popup icon + * @param popup - Popup instance + * @param x, y - icon position + * @param name - icon to be shown + */ +void popup_set_icon(Popup* popup, int8_t x, int8_t y, IconName name); + +/* Set popup timeout + * @param popup - Popup instance + * @param timeout_in_ms - popup timeout value in milliseconds + */ +void popup_set_timeout(Popup* popup, uint32_t timeout_in_ms); + +/* Enable popup timeout + * @param popup - Popup instance + */ +void popup_enable_timeout(Popup* popup); + +/* Disable popup timeout + * @param popup - Popup instance + */ +void popup_disable_timeout(Popup* popup); \ No newline at end of file diff --git a/applications/gui/modules/submenu.c b/applications/gui/modules/submenu.c new file mode 100644 index 00000000..02887fa1 --- /dev/null +++ b/applications/gui/modules/submenu.c @@ -0,0 +1,194 @@ +#include "submenu.h" +#include +#include +#include + +struct SubmenuItem { + const char* label; + SubmenuItemCallback callback; + void* callback_context; +}; + +ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); + +struct Submenu { + View* view; +}; + +typedef struct { + SubmenuItemArray_t items; + uint8_t position; + uint8_t window_position; +} SubmenuModel; + +static void submenu_process_up(Submenu* submenu); +static void submenu_process_down(Submenu* submenu); +static void submenu_process_ok(Submenu* submenu); + +static void submenu_view_draw_callback(Canvas* canvas, void* _model) { + SubmenuModel* model = _model; + + const uint8_t item_height = 16; + const uint8_t item_width = 123; + + canvas_clear(canvas); + canvas_set_font(canvas, FontSecondary); + + uint8_t position = 0; + SubmenuItemArray_it_t it; + + for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); + SubmenuItemArray_next(it)) { + uint8_t item_position = position - model->window_position; + + if(item_position < 4) { + if(position == model->position) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, 0, (item_position * item_height) + 1, item_width, item_height - 2); + canvas_set_color(canvas, ColorWhite); + + canvas_draw_dot(canvas, 0, (item_position * item_height) + 1); + canvas_draw_dot(canvas, 0, (item_position * item_height) + item_height - 2); + canvas_draw_dot(canvas, item_width - 1, (item_position * item_height) + 1); + canvas_draw_dot( + canvas, item_width - 1, (item_position * item_height) + item_height - 2); + } else { + canvas_set_color(canvas, ColorBlack); + } + canvas_draw_str( + canvas, + 6, + (item_position * item_height) + item_height - 4, + SubmenuItemArray_cref(it)->label); + } + + position++; + } + + elements_scrollbar(canvas, model->position, SubmenuItemArray_size(model->items)); +} + +static bool submenu_view_input_callback(InputEvent* event, void* context) { + Submenu* submenu = context; + furi_assert(submenu); + bool consumed = false; + + if(event->state) { + switch(event->input) { + case InputUp: + consumed = true; + submenu_process_up(submenu); + break; + case InputDown: + consumed = true; + submenu_process_down(submenu); + break; + case InputOk: + consumed = true; + submenu_process_ok(submenu); + break; + default: + break; + } + } + + return consumed; +} + +Submenu* submenu_alloc() { + Submenu* submenu = furi_alloc(sizeof(Submenu)); + submenu->view = view_alloc(); + view_set_context(submenu->view, submenu); + view_allocate_model(submenu->view, ViewModelTypeLocking, sizeof(SubmenuModel)); + view_set_draw_callback(submenu->view, submenu_view_draw_callback); + view_set_input_callback(submenu->view, submenu_view_input_callback); + + with_view_model( + submenu->view, (SubmenuModel * model) { + SubmenuItemArray_init(model->items); + model->position = 0; + }); + + return submenu; +} + +void submenu_free(Submenu* submenu) { + furi_assert(submenu); + + with_view_model( + submenu->view, (SubmenuModel * model) { SubmenuItemArray_clear(model->items); }); + view_free(submenu->view); + free(submenu); +} + +View* submenu_get_view(Submenu* submenu) { + furi_assert(submenu); + return submenu->view; +} + +SubmenuItem* submenu_add_item( + Submenu* submenu, + const char* label, + SubmenuItemCallback callback, + void* callback_context) { + SubmenuItem* item = NULL; + furi_assert(label); + furi_assert(submenu); + + with_view_model( + submenu->view, (SubmenuModel * model) { + item = SubmenuItemArray_push_new(model->items); + item->label = label; + item->callback = callback; + item->callback_context = callback_context; + }); + + return item; +} + +void submenu_process_up(Submenu* submenu) { + with_view_model( + submenu->view, (SubmenuModel * model) { + if(model->position > 0) { + model->position--; + if((model->position - model->window_position) < 1 && model->window_position > 0) { + model->window_position--; + } + } else { + model->position = SubmenuItemArray_size(model->items) - 1; + model->window_position = model->position - 3; + } + }); +} + +void submenu_process_down(Submenu* submenu) { + with_view_model( + submenu->view, (SubmenuModel * model) { + if(model->position < (SubmenuItemArray_size(model->items) - 1)) { + model->position++; + if((model->position - model->window_position) > 2 && + model->window_position < (SubmenuItemArray_size(model->items) - 4)) { + model->window_position++; + } + } else { + model->position = 0; + model->window_position = 0; + } + }); +} + +void submenu_process_ok(Submenu* submenu) { + SubmenuItem* item = NULL; + + with_view_model( + submenu->view, (SubmenuModel * model) { + if(model->position < (SubmenuItemArray_size(model->items))) { + item = SubmenuItemArray_get(model->items, model->position); + } + }); + + if(item && item->callback) { + item->callback(item->callback_context); + } +} \ No newline at end of file diff --git a/applications/gui/modules/submenu.h b/applications/gui/modules/submenu.h new file mode 100644 index 00000000..401b1b69 --- /dev/null +++ b/applications/gui/modules/submenu.h @@ -0,0 +1,36 @@ +#pragma once +#include + +/* Submenu anonymous structure */ +typedef struct Submenu Submenu; +typedef struct SubmenuItem SubmenuItem; +typedef void (*SubmenuItemCallback)(void* context); + +/* Allocate and initialize submenu + * This submenu is used to select one option + */ +Submenu* submenu_alloc(); + +/* Deinitialize and free submenu + * @param submenu - Submenu instance + */ +void submenu_free(Submenu* submenu); + +/* Get submenu view + * @param submenu - Submenu instance + * @return View instance that can be used for embedding + */ +View* submenu_get_view(Submenu* submenu); + +/* Add item to submenu + * @param submenu - Submenu instance + * @param label - menu item label + * @param callback - menu item callback + * @param callback_context - menu item callback context + * @return SubmenuItem instance that can be used to modify or delete that item + */ +SubmenuItem* submenu_add_item( + Submenu* submenu, + const char* label, + SubmenuItemCallback callback, + void* callback_context); \ No newline at end of file diff --git a/applications/gui/modules/text_input.c b/applications/gui/modules/text_input.c new file mode 100644 index 00000000..80a7fe6b --- /dev/null +++ b/applications/gui/modules/text_input.c @@ -0,0 +1,370 @@ +#include "text_input.h" +#include + +struct TextInput { + View* view; +}; + +typedef struct { + const char text; + const uint8_t x; + const uint8_t y; +} TextInputKey; + +typedef struct { + const char* header; + char* text; + uint8_t max_text_length; + + TextInputCallback callback; + void* callback_context; + + uint8_t selected_row; + uint8_t selected_column; +} TextInputModel; + +static const uint8_t keyboard_origin_x = 1; +static const uint8_t keyboard_origin_y = 29; +static const uint8_t keyboard_row_count = 3; + +#define ENTER_KEY '\r' +#define BACKSPACE_KEY '\b' + +static const TextInputKey keyboard_keys_row_1[] = { + {'q', 1, 8}, + {'w', 10, 8}, + {'e', 19, 8}, + {'r', 28, 8}, + {'t', 37, 8}, + {'y', 46, 8}, + {'u', 55, 8}, + {'i', 64, 8}, + {'o', 73, 8}, + {'p', 82, 8}, + {'7', 91, 8}, + {'8', 100, 8}, + {'9', 109, 8}, + {'_', 118, 8}, +}; + +static const TextInputKey keyboard_keys_row_2[] = { + {'a', 1, 20}, + {'s', 10, 20}, + {'d', 19, 20}, + {'f', 28, 20}, + {'g', 37, 20}, + {'h', 46, 20}, + {'j', 55, 20}, + {'k', 64, 20}, + {'l', 73, 20}, + {'4', 82, 20}, + {'5', 91, 20}, + {'6', 100, 20}, + {BACKSPACE_KEY, 110, 12}, +}; + +static const TextInputKey keyboard_keys_row_3[] = { + {'z', 1, 32}, + {'x', 10, 32}, + {'c', 19, 32}, + {'v', 28, 32}, + {'b', 37, 32}, + {'n', 46, 32}, + {'m', 55, 32}, + {'0', 64, 32}, + {'1', 73, 32}, + {'2', 82, 32}, + {'3', 91, 32}, + {ENTER_KEY, 102, 23}, +}; + +static uint8_t get_row_size(uint8_t row_index) { + uint8_t row_size = 0; + + switch(row_index + 1) { + case 1: + row_size = sizeof(keyboard_keys_row_1) / sizeof(TextInputKey); + break; + case 2: + row_size = sizeof(keyboard_keys_row_2) / sizeof(TextInputKey); + break; + case 3: + row_size = sizeof(keyboard_keys_row_3) / sizeof(TextInputKey); + break; + } + + return row_size; +} + +static const TextInputKey* get_row(uint8_t row_index) { + const TextInputKey* row = NULL; + + switch(row_index + 1) { + case 1: + row = keyboard_keys_row_1; + break; + case 2: + row = keyboard_keys_row_2; + break; + case 3: + row = keyboard_keys_row_3; + break; + } + + return row; +} + +static const char get_selected_char(TextInputModel* model) { + return get_row(model->selected_row)[model->selected_column].text; +} + +static const bool char_is_lowercase(char letter) { + return (letter >= 0x61 && letter <= 0x7A); +} + +static const char char_to_uppercase(const char letter) { + return (letter - 0x20); +} + +static void text_input_view_draw_callback(Canvas* canvas, void* _model) { + TextInputModel* model = _model; + uint8_t text_length = strlen(model->text); + uint8_t needed_string_width = canvas_width(canvas) - 4 - 7 - 4; + char* text = model->text; + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_str(canvas, 2, 8, model->header); + canvas_draw_line(canvas, 2, 12, canvas_width(canvas) - 7, 12); + canvas_draw_line(canvas, 1, 13, 1, 25); + canvas_draw_line(canvas, canvas_width(canvas) - 6, 13, canvas_width(canvas) - 6, 25); + canvas_draw_line(canvas, 2, 26, canvas_width(canvas) - 7, 26); + + while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) { + text++; + } + + canvas_draw_str(canvas, 4, 22, text); + canvas_draw_str(canvas, 4 + canvas_string_width(canvas, text) + 1, 22, "|"); + + canvas_set_font(canvas, FontKeyboard); + + for(uint8_t row = 0; row <= keyboard_row_count; row++) { + uint8_t volatile column_count = get_row_size(row); + const TextInputKey* keys = get_row(row); + + for(size_t column = 0; column < column_count; column++) { + if(keys[column].text == ENTER_KEY) { + canvas_set_color(canvas, ColorBlack); + if(model->selected_row == row && model->selected_column == column) { + canvas_draw_icon_name( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + I_KeySaveSelected_24x11); + } else { + canvas_draw_icon_name( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + I_KeySave_24x11); + } + } else if(keys[column].text == BACKSPACE_KEY) { + canvas_set_color(canvas, ColorBlack); + if(model->selected_row == row && model->selected_column == column) { + canvas_draw_icon_name( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + I_KeyBackspaceSelected_16x9); + } else { + canvas_draw_icon_name( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + I_KeyBackspace_16x9); + } + } else { + if(model->selected_row == row && model->selected_column == column) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, + keyboard_origin_x + keys[column].x - 1, + keyboard_origin_y + keys[column].y - 8, + 7, + 10); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_color(canvas, ColorBlack); + } + + if(text_length == 0 && char_is_lowercase(keys[column].text)) { + canvas_draw_glyph( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + char_to_uppercase(keys[column].text)); + } else { + canvas_draw_glyph( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + keys[column].text); + } + } + } + } +} + +static void text_input_handle_up(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + if(model->selected_row > 0) { + model->selected_row--; + } + }); +} + +static void text_input_handle_down(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + if(model->selected_row < keyboard_row_count - 1) { + model->selected_row++; + if(model->selected_column > get_row_size(model->selected_row) - 1) { + model->selected_column = get_row_size(model->selected_row) - 1; + } + } + }); +} + +static void text_input_handle_left(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + if(model->selected_column > 0) { + model->selected_column--; + } else { + model->selected_column = get_row_size(model->selected_row) - 1; + } + }); +} + +static void text_input_handle_right(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + if(model->selected_column < get_row_size(model->selected_row) - 1) { + model->selected_column++; + } else { + model->selected_column = 0; + } + }); +} + +static void text_input_handle_ok(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + char selected = get_selected_char(model); + uint8_t text_length = strlen(model->text); + + if(selected == ENTER_KEY) { + if(model->callback != 0) { + model->callback(model->callback_context, model->text); + } + } else if(selected == BACKSPACE_KEY) { + if(text_length > 0) { + model->text[text_length - 1] = 0; + } + } else if(text_length < model->max_text_length) { + if(text_length == 0 && char_is_lowercase(selected)) { + selected = char_to_uppercase(selected); + } + model->text[text_length] = selected; + model->text[text_length + 1] = 0; + } + }); +} + +static bool text_input_view_input_callback(InputEvent* event, void* context) { + TextInput* text_input = context; + furi_assert(text_input); + bool consumed = false; + + if(event->state) { + switch(event->input) { + case InputUp: + text_input_handle_up(text_input); + consumed = true; + break; + case InputDown: + text_input_handle_down(text_input); + consumed = true; + break; + case InputLeft: + text_input_handle_left(text_input); + consumed = true; + break; + case InputRight: + text_input_handle_right(text_input); + consumed = true; + break; + case InputOk: + text_input_handle_ok(text_input); + consumed = true; + break; + default: + break; + } + } + + return consumed; +} + +TextInput* text_input_alloc() { + TextInput* text_input = furi_alloc(sizeof(TextInput)); + text_input->view = view_alloc(); + view_set_context(text_input->view, text_input); + view_allocate_model(text_input->view, ViewModelTypeLocking, sizeof(TextInputModel)); + view_set_draw_callback(text_input->view, text_input_view_draw_callback); + view_set_input_callback(text_input->view, text_input_view_input_callback); + + with_view_model( + text_input->view, (TextInputModel * model) { + model->max_text_length = 0; + model->header = ""; + model->selected_row = 0; + model->selected_column = 0; + }); + + return text_input; +} + +void text_input_free(TextInput* text_input) { + furi_assert(text_input); + view_free(text_input->view); + free(text_input); +} + +View* text_input_get_view(TextInput* text_input) { + furi_assert(text_input); + return text_input->view; +} + +void text_input_set_result_callback( + TextInput* text_input, + TextInputCallback callback, + void* callback_context, + char* text, + uint8_t max_text_length) { + with_view_model( + text_input->view, (TextInputModel * model) { + model->callback = callback; + model->callback_context = callback_context; + model->text = text; + model->max_text_length = max_text_length; + }); +} + +void text_input_set_header_text(TextInput* text_input, const char* text) { + with_view_model( + text_input->view, (TextInputModel * model) { model->header = text; }); +} \ No newline at end of file diff --git a/applications/gui/modules/text_input.h b/applications/gui/modules/text_input.h new file mode 100644 index 00000000..b829ca31 --- /dev/null +++ b/applications/gui/modules/text_input.h @@ -0,0 +1,42 @@ +#pragma once +#include + +/* Text input anonymous structure */ +typedef struct TextInput TextInput; +typedef void (*TextInputCallback)(void* context, char* text); + +/* Allocate and initialize text input + * This text input is used to enter string + */ +TextInput* text_input_alloc(); + +/* Deinitialize and free text input + * @param text_input - Text input instance + */ +void text_input_free(TextInput* text_input); + +/* Get text input view + * @param text_input - Text input instance + * @return View instance that can be used for embedding + */ +View* text_input_get_view(TextInput* text_input); + +/* Deinitialize and free text input + * @param text_input - Text input instance + * @param callback - callback fn + * @param callback_context - callback context + * @param text - text buffer to use + * @param max_text_length - text buffer length + */ +void text_input_set_result_callback( + TextInput* text_input, + TextInputCallback callback, + void* callback_context, + char* text, + uint8_t max_text_length); + +/* Set text input header text + * @param text input - Text input instance + * @param text - text to be shown + */ +void text_input_set_header_text(TextInput* text_input, const char* text); \ No newline at end of file diff --git a/assets/icons/Common/ButtonCenter_7x7.png b/assets/icons/Common/ButtonCenter_7x7.png new file mode 100644 index 0000000000000000000000000000000000000000..a66461b227b321f11bc85a38c63afda0d982d78d GIT binary patch literal 1440 zcmbVMO>5LZ7>3B0UTd{6LeL$!>SMgR$1#+OC>f>WbBi&Q4}`hi)=4nY!B^ zRS++F@uGMUJb3aT=s^*^`ByxtKS0pQengRK)xc!Fo_XHy_wM4t+~~;u5yLP>tMip5 zJ#SCPj;;EC7QMf%r=8LK<-{;{ji+Oa@#yhB!`QZ0)RxtQGk2+A_%pkO15A>@E!$;O3zzJQJa2RR6$Nr)W_ z7YND|Tm*y9)Y4+VL6$0~2eS0eHCswW1j0_IV|Q}4jGGXbN+pOK=s1=}Sjk$bXx9pp z14D)iPgpD>C1eOvMp~Dv$~Cp7eG`HxqYaaRo3z7VmqrlVC^e}E^jU_BR^xV0xX&PO z^MHpc(O8^ewU<0EgKtP11Q|L}vp2Lx1KT!4C$+VR!zG@`)tK?w8(QLlp<+=>Dw(w8 zZ|umfGixGJjyY8_u1VP*25NE00vv2R;P538`m&Q8Nu>-P@CsGjHFeamg|L;wH8e#W zO!E1o7!ic}P*3N9PZj+;u=GV^ZBwYvNJGW})m{<-ZE_fe&7L&RBh@fbG-SM5aZRnN zErd@BC0xw0I=6BS)Uc*#9$8bxdfj3UYD_xCJf@Ru_`V|9Y8t`ed;cHbpO1yEAlm+a zo-9J7EZZ{nq_4f4Gr^DZYPq}^%Z6y{i)3l;6sXRY3%FD$SdNX;MDHaWnHPzU>e`@m zF7WGvRa<~wjuFbzGH^|n#-ID<;8^^(3;VR47T2VI$csKKkqyJgp_fv5X;ksj_%dA!DP1fvqs5@M&TiMq)XFS~oQMEEt+wm~CH#1^@v8^9I ze!KeW*2}SvVq)w;aB`3F^wYBk_vc38mDg{_-`&1AJ@Mku!7q1qAAXYd(CX|$<@u>= GH@^cCN4$jq literal 0 HcmV?d00001 diff --git a/assets/icons/Common/ButtonLeft_4x7.png b/assets/icons/Common/ButtonLeft_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4655d43247083aa705620e9836ac415b42ca46 GIT binary patch literal 1415 zcmbVM+iKK67*5rq)>aU2M7$VM1Vxif;vTv~W2u`S7ED{V3s&&L*<`XiG|9wd+THd> z5CnY!sdyuJtrvQyAo>KpiLcV|{Tkc)riAbluXfwSZCApL`ztB&p zx6LGKvks4K_4~)qD&oGa-YdJlW)hAKMNJd7<=t?6c^RI1>c$ifyjaM>^|&8!ey zB4!nh9u>5uen6Ve@<H5rru6h<2Ef#GQdQ*CmZOlQi~N!?9H`Rp;C% zU}CB21#?;r`&0|6C0}b-=jODa5|nEJ#ntxQ&{~jpgtwDta4hftr~G=#p@V36e4Zjh zq%J~{y26Jjn=1Nw-l*3%QW5YFE*v4z3gt0$&(*xf2en34c?JpH8+FYldo+Alvg8af-pG4(=!fyUi-Wsg z`g#n9VUcf(DFr{poMSNzw-lz>w+HV+n1ELr&SLA#LHUb0p(xWQ(1*vJ-i+1!`swxZ Z!O7;c$;lT_->m1Ovaz)0yuI`A$q$F8u*d)a literal 0 HcmV?d00001 diff --git a/assets/icons/Common/ButtonRight_4x7.png b/assets/icons/Common/ButtonRight_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1c74c1c0038ea55172f19ac875003fc80c2d06 GIT binary patch literal 1839 zcmcIlO>f*p7#Yw)M6zw!O+@VZ{?d|D~WYi~8rHRY?X-&T}Yen`g$^+EJ;z+|RV zE@PoDvZ9%#+_}3bC_5Cj8jDGq541mi{7F+&KF}W65sr$Xn5H|YrMQ2(J7%Yc%;(zO z57ax000=TsQ+1Ke@+w#iw3au3cGGQWY740k2ijH>P(6tD)S)be>gX6Tj7`<`b>di- zgWp$8Y+?i31~CzF0&E4uRlA=C(Mp~K`{74jEchB|)4DDK!ZVhSwdFyw0YIZ1cDh0S{OvfO-U_~ zvmRF*m9sWDXNH)GOyqS1Skhxbr6}s*7t&@~kFM(NW5}qh?Lu@lJ}HE;FDiLdGO>LO z5pS*%E2grR)l^;|?O5b_?u0me&c1U}%jrk8*%=Wk%i)8yp2P|kuxmKg<=(u_`oQRI_0 zS`-DNysBx=#3&qSkgA@hJP>~D+ZM(s5jI6Owp`?yE=3e`YGUqkVOp#Cp=3wR3O4hX zX6BLsN3UBzV(vI5;|SZHgOb=HD0VFjpTyfFW}GnQuh>2*Q`k>*cAmA#iUT7EXSpo# zkPm5~#I-o^cpgfe#P$=4-Pi*SpT!-@nJgp8L347xe>5EKl`=_ZFc8XGy+_j=_R_7! z@vZZMowS1GJ?Zw)eetks%~G{BTR>T}9|jt0j3Btyb*C3-`C?fwY3EY`q*oYZ39DpM z&uJ;PCZPLs4QO1Jd_|A1PF)azZJ)RZ`^-VMWr6e#XUOA%3eLG_Ch@BDOHzMk*MF0G zCo7xMd?Mg*HMIXw%nNz?%60fZiZPlqb?GqUpXO`F&Yi!okZl(n>P@r1P2i)yk3DgRwbHeNn6e|;J^SK4TM LH~i+q&mR8;k>NTA literal 0 HcmV?d00001 diff --git a/assets/icons/Keyboard/KeyBackspaceSelected_16x9.png b/assets/icons/Keyboard/KeyBackspaceSelected_16x9.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc0759a8ca6acdb9b9c2e3dc00edde2f0e93a67 GIT binary patch literal 1812 zcmcIl&x_()N{W z-JN@vFI~T+Y1-v}FWiIo6?k5J;aT|qw)dv2CwcE-scA1=t)FMK&%d~)Y0rO}4EC%2 z=aK4R$F?2(hE6fX7H(UFBH{$t4v4EaKLer_Vi@d&Z#A)C)-lFal?RqJo6XEw%T&e4 zBEIiim|Bz~ut4QeR$2KDgeVQ)Gl9#&Q7)}LS*mHl<@TY>s++4|`B+t|9IGdATYvr+ zL&4Vp^Jy_zlt*w&PGkz$CD@V$zdYy`l2xi0C^cC%YIhY;r^KZCtp`aa)U15HX4E*y zkX5o{K-UPu6k#$T&@w-;?b`$g7%xpD(1BnTyO^;O$?)hRrco61v$A3tm;JC~04Xy` zM9{K5&YYn{cI-;z+O~({*abcDHnS;pNXu(4c!7VY__VG>?Z1?*P#iGU)eM+zGa?B_ zvgI?>CU%T`*JH?wZ6SP@IW~7zXzvsW>>M^Zjasu3fJoi8ARJ`vf+uoa+eOUrBobcB zw>cJ!4w<1pj@wleRYXcabz7&```zwtp@zu>K9qa+w)FmX*CD>+AZijr7d#lMB4r@7 zBxNIM<=Lo~JFR)Z8qvz(k!=8Gk?gq@8g zfS#k0rCF(l)r=K#a|A8Qr4h!%R?w1c= z{pq*skHW8}pZxgsP>68$^3NZ9>0PQ& Cw>kg- literal 0 HcmV?d00001 diff --git a/assets/icons/Keyboard/KeyBackspace_16x9.png b/assets/icons/Keyboard/KeyBackspace_16x9.png new file mode 100644 index 0000000000000000000000000000000000000000..9946232d953ef1cbfbf0e6754be6645e5ea2747b GIT binary patch literal 1829 zcmcIl&u`pB6gEEvMG+BPN`-{wNT>+Lo*8@XwOcpZ?1t{5I;81J4Y!WR<6SFjkFlNX zCgRjnK|!3jpo$Y0E}Xb=;LeTzpnm{T)f-4i;dy_hpfu#tmAsxAzxTcGz4y(`m)l!6 zS1w(-q$tWtuiM#y_bNQEzxE>h|J=PM>Pg=HtW=aY-mae)lh*~S0I8^$I!Q-a=}mlXitE9+UN$s!YEtd_TB{DI?graxTNXmKb&NR1RCQdP z*p_AEk5q~&HgLlr6cO9QmPZ_Q{?i~@5yjq4=i_-SnEBeUs&daT#^bR*Hg#DH4C1=3 zfvG_$0t-|gW)+*DtXx|lbVSLEB(D;gsWl=C<$mRBz;u>EnlE9qa$Y7Vm@#3wL3CWF zv@i^U^G(xqX_e|ijf0zqnN0f5E;9~PYWYyXtSU!}MEQj(L+?JpJ#W3Q_ zfcbtgnwBTxh8T$yuuHHdQ+~PEE(EJ&(U)?xXw>#1qDqNQ)vI@tERy5$gPPIYL3CIp zd=0ur5T*!|K7p3G9x*>8*u!{c8h{QWRntB?yEl08lWCYa({L}SbyS-h=I2pl*a_8oT+S_c~#IXiq#zs}!z^4spCcOR_0+*o_q`N6;X{rjK1`QsBs`Gcx|z4v#k QTVG_o&8^N)8~5)21Ktij=l}o! literal 0 HcmV?d00001 diff --git a/assets/icons/Keyboard/KeySaveSelected_24x11.png b/assets/icons/Keyboard/KeySaveSelected_24x11.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb3569d3accc5a5c56829b12c85079172b56729 GIT binary patch literal 1853 zcmcIlPiW*+7#}^zw%YZuf+B)3d$6*;fxiXAXK-X$#&ks`?Z1FR>QX z26aVbT~%`&N5w=X1OWo&yGcQZD9KMx7+O3JvM4Pgkw_&Y^~HA4kU?pcLYz)%lYCqz zD405=sj4ZsOlbo2yrZFUJVocl(hfu!>phe>@9d^rUFW&j&H}!)!;|9lBv{%Lg~)s2 z4%()#|Dlit(}3xA)*qFJ1uF0J7`Su5Y9oEA+srsEMAi|aKWWt3B%(w#g-G)oQNqL^ zf1*@0Ucg(l;0+nNrXfra);gN*63r#X84bG_S5Oapz-U2_2No;}caH=0Jhz?X1x*6p zZZ%{Or9=^P?a(oNj2+}NPLO5lb>xS(hK#yzQ(Fto(z;~|u)ZaN?XnW(`pULU1i&$^ zrW-ON3~kFtm|7MJL)}ES1tUU3N-T@l>$)*vdoGLM%c1>)tfeXjj8tQ`U&jXGx~+pC zJw&zve}0HDBg6^Jz?Y@lahswqGEXq5ZvEhVyV+dJL>TqqMZUhgD7BZGrskL?B8nzU zEO0}S#T1Md#k9-SH0hSMuhLzKa_I5y_(QtDUmB14ku-9rOM~*GXvjh71`cJarlUj3 ze7uCJ^@AP<(j#0_!EzB61Df%LF0|x7U8vqkd`@?cmVP{k{EyPdWes{X>2la%Rk=(? zE%&0TDeAxbb=w#db1i`F%Wmf5GAz>Wv>@jW_p)ho-#0CeC9cGbyZ*s9Cn^o)Rq=_$h#NIZix%+wt GFa8a7)lmol literal 0 HcmV?d00001 diff --git a/assets/icons/Keyboard/KeySave_24x11.png b/assets/icons/Keyboard/KeySave_24x11.png new file mode 100644 index 0000000000000000000000000000000000000000..e7dba987a04dad7dd96001913c55566dbde96c8e GIT binary patch literal 1863 zcmcIlO^6&t6dpxnjjSF75f9pQJZy|LUDdzSZ6$n-*6-2D5s-9_fx~uK( zotfQBVDX?Q(UXLzXF6aX)%U*l z-d9y`w^q+Do_PF3p-@5qOL5h2N9RU z^i-~BIziNECdw*wjUcQeOxncsbnKa>(*%1MPoPck0jC)~9$50g-#!ks+4LGwn$d`f zMy;%ZsA3Rsk2!`#ELuW>2#g3fF>;CFBHMCo-El0(@X1&g%&$qdl~*F4Kd~*B3^?Z1 z^bEmDf}0)Wn??sYC6l9$X;6esLO7#_ZCmDy?ZqU3l|%anS#wn!7%f39-Qp(l9fyJ- z(?=x}n~2%2PcX9#VmYdECvH{tWzv)!s%sn^Z&a(TMEXG=KBQ~smzBm!)h4cOBfSV| zapw6l2`LyY2x(Vnan#Li4>BO#dXPeox2Fr~f_P*4)DM)gJ3Y$sMNw8+?gqit>2PpJ znU9yygm%~yKzf8rCa_fc*^nlp(uJ1%rwg^aiBIX^Xz9mu$p0vPT2|JhQCGkYtEqW1 zTD})enxg%?Uw4c#Ggk#{pLa8zmSLH8=LI=?xR>pc=yYsHAgcQUxz^arx`9fR>e$sw zj@}Uy75!kQXF{tT9e=F+z^*!*3|n>nI6oucWq!(t2og`=40-O!tAE1z^ID@;X)nEd zVK7xqj`wg%Ed2Op{k=a*YZpKHX787$ zzhKuN9(?M5ojmn%xcU1}OP>$^`ZD?cW@lIaTn=x3xBtP#z16o~{qVMZ>hecsD?jQQ ME3387mS5lf8`39Il>h($ literal 0 HcmV?d00001 diff --git a/assets/icons/iButton/DolphinExcited_64x63.png b/assets/icons/iButton/DolphinExcited_64x63.png new file mode 100644 index 0000000000000000000000000000000000000000..e695c85f08109523700392e311416ab7413a624e GIT binary patch literal 2304 zcmbVO32+lt7~ZzhR?1;1Xu&Bg3(6szJ#uxIMre~_YnxIa*eVKac3+Z(WH;_^o20dZ z6b38E;0%H=a+IMS2w0V4T2!iKP)3wPF0YoB0jxL_R7OtqB@MA62;2>PM{0={1A;GS>u9!j-L%Q)Ct_8WWhRj?!r(}uwW%dJ8Ab@fk(`% zP{5RmA_reFn`Z=U+@ok#kc9+3pmAu>=ap18Xu%@9ENq8|2@H)uwAmIcJ8TdwwHKpS zSpld?N8&t5(x{oyQ6_`IM2c$7m%rDiaip2y%Xc(vjhiB0zzU;rwNaH)34i3E%}jAZaSZQZcOGBWtqik#9pC zoBoFZD6QQdvvEsbe7=|oRm&}hVMGPFC0cca{D7DYRJlyyL2fy8b67YUKWkM0r^$*# zmc2JNs`!S>C{5{TbYzJr335Ok)olT=aT>5-aMviDq~YzLX;yDwN#i3F$&zFj)GiC6 zD|8c7?;tZ-(!}ab|A4}j5jc&zBUs>Bm#p|W7_jK$+<@>)ZVZjd$XaEutU$xiJAL=@ ze5Ij#)_D)_GYf2TC{h{hY$VS_Bl;g1HpghjwL*)8|Ju zIob)Q&utl1uKd5nxVz-⁢8g{(TPqj~G#*c@HPK0j$|XBp4!G-MY4!xH$tNbKNQ- zR5LOJ%WJq9tLv~6`(*$Uq*h?vUtGK5Qv`|U^KID<&kIMMDtNyc?NwdaIR~`NTYkP* z@;A@rZ2fWTsMP!Sn8-`_%^RE0QdvzM>p7*oy?@;Hw!oR>rt>X3{$O*E!OvgoOpQkx zr<@JNHQ62}*F10_e&EqP#{*=&a{SWe4Zj`y<&91usbj$On$dNNbm+l@SO@J%$=S87 zVRxbXohAH2Y0JfijEw^a>~hR_uViVqws&u6?6T@4s%}w5#_^dSsk-^&Pdn$nR9m@e z#Ll#yUo3BA>w9lTMyAbm?7!b%mD}vCnZI{ZsB&Au&pj-MB%%| z`=$-OXF-y5F!!eyCwd1ZW&~46t@ThdpLO!Rt8wHtkhOGv;@RtcJN71shfVI?2Zmo5 za-nkmGVi#$VU>f7e0%z-tn2A{{r6pZVNOYDXJdSOy>EI(!;Jdn`wR5CW95=mb!G9G z^;OrH0^yOZ8?TK_eEh66`B>T12kKqwz|o{3Z~T#jh9MiP_FLZUc>iq+(mr%*V?#~9 z=JvRKnWeQ`d(GbwswU2%!cI9TI7`rEyLHX+SED}IlR)^Lz{Rcttho=+L-#Mk(Hgkf=N5t zHfDd^nzZ)xsVhr%_6u67kF9ye+xT<#ERvM_jjw1rvG9YenXlzuJ(q&7%3Z-!^eSyz z_=9WmzW~;NICua6 literal 0 HcmV?d00001 diff --git a/assets/icons/iButton/DolphinMafia_115x62.png b/assets/icons/iButton/DolphinMafia_115x62.png new file mode 100644 index 0000000000000000000000000000000000000000..66fdb40ff2651916faed4a2ae1d564cafdbf7bcb GIT binary patch literal 2504 zcmbVO3se(V8ji5Kg5ZmV3I#ewZHbsZB8b0=g#+k z_y7La$vcsnwa$(njyxXES*27&ad(Ehf@a%szx(??vFC0MMrAy=Img9%&ES885R5X2P@K{dB9p<$p?SQ()g~i~r4cNkC3JdH&i}sg6d%yza(--p8dMv@ zh!njtmnNcfH8EIj8V2M1)j>d@3E>C~1d9SDLpsSICOLnC7va{{Z80C1fUs$Deu(uz zAWj_#gi$mBNJXF!13?Io!6J#&-(L!@03Z+o#bAI~0tqEj1oTHFGGOY%=T4*XWF$)Q z`>C_ICpkZbWsQhfoSmI5%Jvgcv`#F6VOR`8Vh9p)2qBY0vZzT&GE1fz6a<6OdLyf+ zNWjX7YNwhuAF&nut zlTM%T7{|m!I$L;81?0JCCML&7h@%LG%A_%3 zO%`|J5~~^`5=Ij!OVKeDl|G%Q$Z2^1BoRS?PpqEAscc5@GXp|_vV@$^WlbUkA?_Ok zK?n#V5acTX5fGe&s<}GAQ5OB*z!a`e&iO^CEx1S+l}^!W3g`Ur;{!N`BvZ5jW@%VH?>OF2Tk@O zPGOv@&rGEfX|lv0Cxk2gKu)ie6Af#Vr9x}>!CI+Aiv@szVry$~6u{(al2-hTBEgTzn_D^}jklllIvu1V{Q`ig6OgP|0jI zN)sVEE|=@hm?j7H6PqgYzU5==|fB0<6@J90B?N8); z?B48M`Q6&q<>QYftD|a*tJ$!0YduA;TS}(23t@i9jJ}9E&d>+O-{j}lDtd6mP7wiU?pLh0* zla-TQ!!6f>9b(>jct-Z*@vzVmEjaUp9adYyRH)W#u&{1)0G7#K8z}OOe9Z4J`?k~5 z;u#n4^?R%GdBZDjly!H8xtVMF9ud_Q|CsUp%X4BI?jMd19&&9{QqgG_a)Rz9J*BH| z$zM9cbZYA6R(n(=QYD(cO(#Aoy6CQh;hG<}_gRz&>ZIovmNuT&Z9VwM8m5pu&$kG$ zvTJ!+pA|E6E-UBtJJrv;*XaRo7|Z#x4L(qON`UQa?6`jZqnkg3XliTEuJKo%PCa~M z@WlnE3u1ZRT?c;b@m&$07PGImr1km-TQZ8*DS|rZudw{x4R!5F9=$VOt{XWj(Y>BT zd-yG`a(KJ-o0Dfs8h&U=J*C(_ z=8hNq6aC?^r7wqGy5!v`zvX@KNEDDEpXqBVXiB`Z=eNZRgGG2tG`F;x~xDn9)G1Y@4Fl28Px*E!|ivy@~-8Lx%@`DyQ}?V z4f!BGF*jl}N~1D%!=YeZY6W)9lyDw_Uq#NDJx^=CJZDD2|CF# zA7Ixt{Z7BT8@4fZgFkI{D9fJxang<$JS``+d(*81cbB@prG*c!rZ)8U4y-<__Pt)Z zZ3lJfK;Y5eZHd?A3O-!mWX3$UChhmy)r@4iKkvyz(mdTtF7?TWn4`7t4=} zZ`OLe!fHzEo3eUH7jwVD-n?Xnx$AC<-H6`;RB2iYH9UO}ROfZkPOl32mRZ%`xW#FL zD@GqK${E&#=gzidc(qkxLZ^tk7u}u0Uu|;00}}A@rq4$9xE75>Hwj!4$Nk!`)YmDg{{4HeKCy?7Z85xPzg%Peucca}QJ6#D*z!+`G0ZOj literal 0 HcmV?d00001 diff --git a/assets/icons/iButton/DolphinWait_61x59.png b/assets/icons/iButton/DolphinWait_61x59.png new file mode 100644 index 0000000000000000000000000000000000000000..423e079199b00df0d910981caf8944cbaf8ee67e GIT binary patch literal 2023 zcmbVN2~ZPP7!Hc#RVu|&R6N!I%3-nxkdW1AfgnT)&=3`rikr>mC`oqNT?mIZ73u(L zrQQXsTAVP$bgCRqVL%3}R4ZPzma(V>JPRlyt(Mwx+HOKfI~`kFcXs!^eeZkUfB##O zlo0DNW!4lPkLMwelPS4T$~}uGjpN?2soqDpVKNn$%J6tor`sPlUipC;Jl=#i45}11 zMG=qUq)CWrNHrnMF;N_v$6K;2hr;j-f(6us&R~}EhnidYfI%bWuL)N`3M!h=8{+b4 zA~`QXh39495)FUZQea6A$`P0d76WojMl*xvNcj$4l$+a^K|bJsuo+T*q+KA8qDTUw zNtyseLP&r^5CVuLLRb_QCW00L2!uc&6b{0O02ZN87z&F4=f&rw(HbqPlr4A4;=ZJO zJE3n9Bn4xk2i;ixRy=n$^KLBdFw2s6uYSlET-yrfXL z;LoKsnOtawjmhRTa@zJ>G^5I;2vA8dWEPDRG1;6%zcIxqJ;{=cp8N+pT-z>dC^VWT zFqWiMBxxKARMHp=fWSfo2wY<@Ye)+dWS8PRK*%tbkn*{x!2$^3ZWV%{P&f+1AuxnO z&?r>F<$(rcvHu1pH3n_&3!xeu)snOc(Gwi$zvRUzj3KqG1*3^b z9p~;B<{ii>584ZM)DH0PCOY>1Qru&3u4CAzu2#i;xSAbd<~khBwX$#Sj?d@u##!XD zNR@tb=h}6&`}|35K||KN=gwBmj&-Xj(w;uMx-L?`(_*_ZqL8ard_B`EtMjXyZhc;> zZF)8CG-7Mu_=mE#!jz);z6%T5S~mVv@At*=X|?Ol?+v-67}*3~ z(I*yd*!+Hl{6bH0ad%YF(4l>;^=h9m2|jf9+!^TVd3?C0_k*j|7SExD#fc+65!1f++)=)#z9VKCyt!)o1FiS^CqLF68$Nlu@R-*F`KK+GldJrn z-6KW!*!*?i?t>eHLo#YCZFefNm-}Sy+HzW#(kJAQ% zu3Sm`#Ai<6U{7oT)is=0nUQ{99C7-Y$5MCXl>F$xVZhye@tt~FtDW0WISrk6aF6fm zc|x z#r8!%cqXJshqsp~m3WISwlJh}Z|BwFsa3Puno2spY&@ROyQ9E+zRBvfE9A|~$kXqv z6B}Ob_YXK*dq(uIvE9}XXM5*fnIhFsF*9M3Y+v$?f)8s}ZTskiwP8;Zmba>-dq;Q2 swOjp-{igh?qWDbj&x1Zp}@CP3WTf1pBX89+MzD8nvO|4+g31aR2}S literal 0 HcmV?d00001 diff --git a/assets/icons/iButton/iButtonDolphinSuccess_109x60.png b/assets/icons/iButton/iButtonDolphinSuccess_109x60.png new file mode 100644 index 0000000000000000000000000000000000000000..f234aba9043fc3ab21e63d5ae71fc1634ee50dd6 GIT binary patch literal 2178 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.glyph_width = 0; u8x8_utf8_init(u8g2_GetU8x8(u8g2)); @@ -1251,7 +1252,7 @@ static u8g2_uint_t u8g2_calculate_exact_string_width(u8g2_t *u8g2, const char *s -u8g2_uint_t u8g2_GetStrWidth(u8g2_t *u8g2, const char *s) +u8g2_long_t u8g2_GetStrWidth(u8g2_t *u8g2, const char *s) { u8g2->u8x8.next_cb = u8x8_ascii_next; return u8g2_string_width(u8g2, s); diff --git a/lib/u8g2_vendor/u8g2_vendor.c b/lib/u8g2_vendor/u8g2_vendor.c deleted file mode 100644 index 739a37e3..00000000 --- a/lib/u8g2_vendor/u8g2_vendor.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "u8g2_support.h" -#include "main.h" -#include "cmsis_os.h" -#include "gpio.h" - -#include - -extern SPI_HandleTypeDef hspi1; - -// #define DEBUG 1 - -uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - switch(msg){ - //Initialize SPI peripheral - case U8X8_MSG_GPIO_AND_DELAY_INIT: - /* HAL initialization contains all what we need so we can skip this part. */ - break; - - //Function which implements a delay, arg_int contains the amount of ms - case U8X8_MSG_DELAY_MILLI: - osDelay(arg_int); - break; - - //Function which delays 10us - case U8X8_MSG_DELAY_10MICRO: - delay_us(10); - break; - - //Function which delays 100ns - case U8X8_MSG_DELAY_100NANO: - asm("nop"); - break; - - //Function to define the logic level of the RESET line - case U8X8_MSG_GPIO_RESET: - #ifdef DEBUG - printf("[u8g2] rst %d\n", arg_int); - #endif - - HAL_GPIO_WritePin(DISPLAY_RST_GPIO_Port, DISPLAY_RST_Pin, arg_int ? GPIO_PIN_SET : GPIO_PIN_RESET); - break; - - default: - #ifdef DEBUG - printf("[u8g2] unknown io %d\n", msg); - #endif - - return 0; //A message was received which is not implemented, return 0 to indicate an error - } - - return 1; // command processed successfully. -} - -uint8_t u8x8_hw_spi_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr){ - switch (msg) { - case U8X8_MSG_BYTE_SEND: - #ifdef DEBUG - printf("[u8g2] send %d bytes %02X\n", arg_int, ((uint8_t*)arg_ptr)[0]); - #endif - - HAL_SPI_Transmit(&hspi1, (uint8_t *)arg_ptr, arg_int, 10000); - break; - - case U8X8_MSG_BYTE_SET_DC: - #ifdef DEBUG - printf("[u8g2] dc %d\n", arg_int); - #endif - - HAL_GPIO_WritePin(DISPLAY_DI_GPIO_Port, DISPLAY_DI_Pin, arg_int ? GPIO_PIN_SET : GPIO_PIN_RESET); - break; - - case U8X8_MSG_BYTE_INIT: - #ifdef DEBUG - printf("[u8g2] init\n"); - #endif - HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_RESET); - break; - - case U8X8_MSG_BYTE_START_TRANSFER: - #ifdef DEBUG - printf("[u8g2] start\n"); - #endif - - HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_RESET); - asm("nop"); - break; - - case U8X8_MSG_BYTE_END_TRANSFER: - #ifdef DEBUG - printf("[u8g2] end\n"); - #endif - - asm("nop"); - HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_SET); - break; - - default: - #ifdef DEBUG - printf("[u8g2] unknown xfer %d\n", msg); - #endif - - return 0; - } - - return 1; -} diff --git a/lib/u8g2_vendor/u8g2_vendor.h b/lib/u8g2_vendor/u8g2_vendor.h deleted file mode 100644 index c2581ddb..00000000 --- a/lib/u8g2_vendor/u8g2_vendor.h +++ /dev/null @@ -1,4 +0,0 @@ -#include "u8g2/u8g2.h" - -uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr); -uint8_t u8x8_hw_spi_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);