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 <mail@s3f.ru>
							
								
								
									
										1
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -110,6 +110,7 @@ firmware/targets/f4/api-hal/api-hal-power.c @skotopes | |||||||
| applications/music-player/** @DrZlo13 | applications/music-player/** @DrZlo13 | ||||||
| applications/floopper-bloopper/** @glitchcore | applications/floopper-bloopper/** @glitchcore | ||||||
| applications/gpio-tester/** @glitchcore | applications/gpio-tester/** @glitchcore | ||||||
|  | applications/gui-test/** @DrZlo13 | ||||||
| 
 | 
 | ||||||
| lib/app-template/** @DrZlo13 | lib/app-template/** @DrZlo13 | ||||||
| lib/qrcode/** @DrZlo13 | lib/qrcode/** @DrZlo13 | ||||||
|  | |||||||
| @ -7,7 +7,6 @@ void flipper_test_app(void* p); | |||||||
| void application_blink(void* p); | void application_blink(void* p); | ||||||
| void application_uart_write(void* p); | void application_uart_write(void* p); | ||||||
| void application_input_dump(void* p); | void application_input_dump(void* p); | ||||||
| void display_u8g2(void* p); |  | ||||||
| void u8g2_example(void* p); | void u8g2_example(void* p); | ||||||
| void input_task(void* p); | void input_task(void* p); | ||||||
| void menu_task(void* p); | void menu_task(void* p); | ||||||
| @ -34,11 +33,9 @@ void sdnfc(void* p); | |||||||
| void floopper_bloopper(void* p); | void floopper_bloopper(void* p); | ||||||
| void sd_filesystem(void* p); | void sd_filesystem(void* p); | ||||||
| 
 | 
 | ||||||
| const FuriApplication FLIPPER_SERVICES[] = { | void gui_test(void* p); | ||||||
| #ifdef APP_DISPLAY |  | ||||||
|     {.app = display_u8g2, .name = "display_u8g2", .stack_size = 1024, .icon = A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
|  | const FuriApplication FLIPPER_SERVICES[] = { | ||||||
| #ifdef APP_CLI | #ifdef APP_CLI | ||||||
|     {.app = cli_task, .name = "cli_task", .stack_size = 1024, .icon = A_Plugins_14}, |     {.app = cli_task, .name = "cli_task", .stack_size = 1024, .icon = A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
| @ -152,6 +149,10 @@ const FuriApplication FLIPPER_SERVICES[] = { | |||||||
| #ifdef APP_SDNFC | #ifdef APP_SDNFC | ||||||
|     {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, |     {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | #ifdef APP_GUI_TEST | ||||||
|  |     {.app = gui_test, .name = "gui_test", .icon = A_Plugins_14}, | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| size_t FLIPPER_SERVICES_size() { | size_t FLIPPER_SERVICES_size() { | ||||||
| @ -224,6 +225,10 @@ const FuriApplication FLIPPER_PLUGINS[] = { | |||||||
| #ifdef BUILD_SDNFC | #ifdef BUILD_SDNFC | ||||||
|     {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, |     {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | #ifdef BUILD_GUI_TEST | ||||||
|  |     {.app = gui_test, .name = "gui_test", .icon = A_Plugins_14}, | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| size_t FLIPPER_PLUGINS_size() { | size_t FLIPPER_PLUGINS_size() { | ||||||
|  | |||||||
| @ -29,6 +29,7 @@ BUILD_GPIO_DEMO = 1 | |||||||
| BUILD_MUSIC_PLAYER = 1 | BUILD_MUSIC_PLAYER = 1 | ||||||
| BUILD_FLOOPPER_BLOOPPER = 1 | BUILD_FLOOPPER_BLOOPPER = 1 | ||||||
| BUILD_IBUTTON = 1 | BUILD_IBUTTON = 1 | ||||||
|  | BUILD_GUI_TEST = 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| APP_NFC ?= 0 | APP_NFC ?= 0 | ||||||
| @ -144,15 +145,6 @@ ifeq ($(BUILD_EXAMPLE_QRCODE), 1) | |||||||
| CFLAGS		+= -DBUILD_EXAMPLE_QRCODE | CFLAGS		+= -DBUILD_EXAMPLE_QRCODE | ||||||
| C_SOURCES	+= $(APP_DIR)/examples/u8g2_qrcode.c | C_SOURCES	+= $(APP_DIR)/examples/u8g2_qrcode.c | ||||||
| C_SOURCES	+= $(LIB_DIR)/qrcode/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 | endif | ||||||
| 
 | 
 | ||||||
| APP_EXAMPLE_FATFS ?= 0 | APP_EXAMPLE_FATFS ?= 0 | ||||||
| @ -165,7 +157,6 @@ ifeq ($(BUILD_EXAMPLE_FATFS), 1) | |||||||
| CFLAGS		+= -DBUILD_EXAMPLE_FATFS | CFLAGS		+= -DBUILD_EXAMPLE_FATFS | ||||||
| C_SOURCES	+= $(APP_DIR)/examples/fatfs_list.c | C_SOURCES	+= $(APP_DIR)/examples/fatfs_list.c | ||||||
| APP_INPUT = 1 | APP_INPUT = 1 | ||||||
| APP_DISPLAY = 1 |  | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| APP_CC1101 ?= 0 | APP_CC1101 ?= 0 | ||||||
| @ -289,6 +280,17 @@ CFLAGS		+= -DBUILD_IBUTTON | |||||||
| CPP_SOURCES	+= $(wildcard $(APP_DIR)/ibutton/*.cpp) | CPP_SOURCES	+= $(wildcard $(APP_DIR)/ibutton/*.cpp) | ||||||
| endif | 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 | APP_SDNFC ?= 0 | ||||||
| ifeq ($(APP_SDNFC), 1) | ifeq ($(APP_SDNFC), 1) | ||||||
| CFLAGS		+= -DAPP_SDNFC | CFLAGS		+= -DAPP_SDNFC | ||||||
| @ -315,12 +317,6 @@ CFLAGS		+= -DAPP_SD_FILESYSTEM | |||||||
| C_SOURCES	+= $(wildcard $(APP_DIR)/sd-filesystem/*.c) | C_SOURCES	+= $(wildcard $(APP_DIR)/sd-filesystem/*.c) | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| # deprecated
 |  | ||||||
| ifeq ($(APP_DISPLAY), 1) |  | ||||||
| CFLAGS		+= -DAPP_DISPLAY |  | ||||||
| C_SOURCES	+= $(APP_DIR)/display-u8g2/display-u8g2.c |  | ||||||
| endif |  | ||||||
| 
 |  | ||||||
| APP_INPUT	?= 0 | APP_INPUT	?= 0 | ||||||
| ifeq ($(APP_INPUT), 1) | ifeq ($(APP_INPUT), 1) | ||||||
| CFLAGS		+= -DAPP_INPUT | CFLAGS		+= -DAPP_INPUT | ||||||
|  | |||||||
							
								
								
									
										157
									
								
								applications/gui-test/gui-test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,157 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_port.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/modules/dialog.h> | ||||||
|  | #include <gui/modules/dialog_ex.h> | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/text_input.h> | ||||||
|  | #include <gui/modules/popup.h> | ||||||
|  | 
 | ||||||
|  | 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -77,6 +77,10 @@ void canvas_set_color(Canvas* canvas, Color color) { | |||||||
|     u8g2_SetDrawColor(&canvas->fb, 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) { | void canvas_set_font(Canvas* canvas, Font font) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|     u8g2_SetFontMode(&canvas->fb, 1); |     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); |         u8g2_SetFont(&canvas->fb, u8g2_font_haxrcorp4089_tr); | ||||||
|     } else if(font == FontGlyph) { |     } else if(font == FontGlyph) { | ||||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_unifont_t_symbols); |         u8g2_SetFont(&canvas->fb, u8g2_font_unifont_t_symbols); | ||||||
|  |     } else if(font == FontKeyboard) { | ||||||
|  |         u8g2_SetFont(&canvas->fb, u8g2_font_profont11_mf); | ||||||
|     } else { |     } else { | ||||||
|         furi_check(0); |         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); |     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) { | void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|     if(!icon) return; |     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; |     x += canvas->offset_x; | ||||||
|     y += canvas->offset_y; |     y += canvas->offset_y; | ||||||
|     u8g2_DrawGlyph(&canvas->fb, x, y, ch); |     u8g2_DrawGlyph(&canvas->fb, x, y, ch); | ||||||
| } | } | ||||||
| @ -13,7 +13,20 @@ typedef enum { | |||||||
|     ColorBlack = 0x01, |     ColorBlack = 0x01, | ||||||
| } Color; | } 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; | typedef struct Canvas Canvas; | ||||||
| 
 | 
 | ||||||
| @ -45,6 +58,11 @@ void canvas_clear(Canvas* canvas); | |||||||
|  */ |  */ | ||||||
| void canvas_set_color(Canvas* canvas, Color color); | void canvas_set_color(Canvas* canvas, Color color); | ||||||
| 
 | 
 | ||||||
|  | /*
 | ||||||
|  |  * Invert drawing color | ||||||
|  |  */ | ||||||
|  | void canvas_invert_color(Canvas* canvas); | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Set drawing font |  * 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); | 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. |  * Draw stateful icon at position defined by x,y. | ||||||
|  */ |  */ | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| #include "elements.h" | #include "elements.h" | ||||||
| #include "canvas_i.h" | #include <assets_icons.h> | ||||||
| 
 | #include <gui/icon_i.h> | ||||||
| #include <furi.h> |  | ||||||
| 
 |  | ||||||
| #include <string.h> |  | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include "canvas_i.h" | ||||||
|  | #include <string.h> | ||||||
| 
 | 
 | ||||||
| void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total) { | void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total) { | ||||||
|     furi_assert(canvas); |     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); |     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) { | void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, char* text) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|     furi_assert(text); |     furi_assert(text); | ||||||
| @ -61,4 +192,4 @@ void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, char* text) { | |||||||
|         y += font_height; |         y += font_height; | ||||||
|     } while(end); |     } while(end); | ||||||
|     string_clear(str); |     string_clear(str); | ||||||
| } | } | ||||||
| @ -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); | 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 |  * Draw multiline text | ||||||
|  * @param x, y - top left corner coordinates |  * @param x, y - top left corner coordinates | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "dialog.h" | #include "dialog.h" | ||||||
|  | #include <gui/elements.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| struct Dialog { | struct Dialog { | ||||||
| @ -16,34 +17,43 @@ typedef struct { | |||||||
| 
 | 
 | ||||||
| static void dialog_view_draw_callback(Canvas* canvas, void* _model) { | static void dialog_view_draw_callback(Canvas* canvas, void* _model) { | ||||||
|     DialogModel* model = _model; |     DialogModel* model = _model; | ||||||
|  |     uint8_t canvas_center = canvas_width(canvas) / 2; | ||||||
|  | 
 | ||||||
|     // Prepare canvas
 |     // Prepare canvas
 | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
|  | 
 | ||||||
|     // Draw header
 |     // Draw header
 | ||||||
|     canvas_set_font(canvas, FontPrimary); |     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
 |     // Draw text
 | ||||||
|     canvas_set_font(canvas, FontSecondary); |     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
 |     // Draw buttons
 | ||||||
|     uint8_t bottom_base_line = canvas_height(canvas) - 2; |     elements_button_left(canvas, model->left_text); | ||||||
|     canvas_set_font(canvas, FontPrimary); |     elements_button_right(canvas, model->right_text); | ||||||
|     canvas_draw_str(canvas, 5, bottom_base_line, model->left_text); |  | ||||||
|     canvas_draw_str(canvas, 69, bottom_base_line, model->right_text); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool dialog_view_input_callback(InputEvent* event, void* context) { | static bool dialog_view_input_callback(InputEvent* event, void* context) { | ||||||
|     Dialog* dialog = context; |     Dialog* dialog = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|     // Process key presses only
 |     // Process key presses only
 | ||||||
|     if(event->state && dialog->callback) { |     if(event->state && dialog->callback) { | ||||||
|         if(event->input == InputLeft) { |         if(event->input == InputLeft) { | ||||||
|             dialog->callback(DialogResultLeft, dialog->context); |             dialog->callback(DialogResultLeft, dialog->context); | ||||||
|  |             consumed = true; | ||||||
|         } else if(event->input == InputRight) { |         } else if(event->input == InputRight) { | ||||||
|             dialog->callback(DialogResultRight, dialog->context); |             dialog->callback(DialogResultRight, dialog->context); | ||||||
|  |             consumed = true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     // All input events consumed
 | 
 | ||||||
|     return true; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Dialog* dialog_alloc() { | Dialog* dialog_alloc() { | ||||||
|  | |||||||
| @ -32,13 +32,13 @@ void dialog_free(Dialog* dialog); | |||||||
|  */ |  */ | ||||||
| View* dialog_get_view(Dialog* dialog); | View* dialog_get_view(Dialog* dialog); | ||||||
| 
 | 
 | ||||||
| /* Set dialog header text
 | /* Set dialog result callback
 | ||||||
|  * @param dialog - Dialog instance |  * @param dialog - Dialog instance | ||||||
|  * @param text - text to be shown |  * @param callback - result callback function | ||||||
|  */ |  */ | ||||||
| void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback); | void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback); | ||||||
| 
 | 
 | ||||||
| /* Set dialog header text
 | /* Set dialog context
 | ||||||
|  * @param dialog - Dialog instance |  * @param dialog - Dialog instance | ||||||
|  * @param context - context pointer, will be passed to result callback |  * @param context - context pointer, will be passed to result callback | ||||||
|  */ |  */ | ||||||
|  | |||||||
							
								
								
									
										232
									
								
								applications/gui/modules/dialog_ex.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,232 @@ | |||||||
|  | #include "dialog_ex.h" | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | 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; }); | ||||||
|  | } | ||||||
							
								
								
									
										105
									
								
								applications/gui/modules/dialog_ex.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,105 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | /* 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); | ||||||
							
								
								
									
										227
									
								
								applications/gui/modules/popup.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,227 @@ | |||||||
|  | #include "popup.h" | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | } | ||||||
							
								
								
									
										92
									
								
								applications/gui/modules/popup.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,92 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | /* 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); | ||||||
							
								
								
									
										194
									
								
								applications/gui/modules/submenu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,194 @@ | |||||||
|  | #include "submenu.h" | ||||||
|  | #include <m-array.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | 
 | ||||||
|  | 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								applications/gui/modules/submenu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,36 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | /* 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); | ||||||
							
								
								
									
										370
									
								
								applications/gui/modules/text_input.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,370 @@ | |||||||
|  | #include "text_input.h" | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | 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; }); | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								applications/gui/modules/text_input.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,42 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | /* 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); | ||||||
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Common/ButtonCenter_7x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Common/ButtonLeft_4x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Common/ButtonRight_4x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Keyboard/KeyBackspaceSelected_16x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Keyboard/KeyBackspace_16x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Keyboard/KeySaveSelected_24x11.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Keyboard/KeySave_24x11.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/iButton/DolphinExcited_64x63.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/iButton/DolphinMafia_115x62.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/iButton/DolphinWait_61x59.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/iButton/iButtonDolphinSuccess_109x60.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/iButton/iButtonKey_49x44.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
| @ -1325,7 +1325,7 @@ u8g2_uint_t u8g2_DrawExtUTF8(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, uint8_t | |||||||
| 
 | 
 | ||||||
| uint8_t u8g2_IsAllValidUTF8(u8g2_t *u8g2, const char *str);	// checks whether all codes are valid
 | uint8_t u8g2_IsAllValidUTF8(u8g2_t *u8g2, const char *str);	// checks whether all codes are valid
 | ||||||
| 
 | 
 | ||||||
| u8g2_uint_t u8g2_GetStrWidth(u8g2_t *u8g2, const char *s); | u8g2_long_t u8g2_GetStrWidth(u8g2_t *u8g2, const char *s); | ||||||
| u8g2_uint_t u8g2_GetUTF8Width(u8g2_t *u8g2, const char *str); | u8g2_uint_t u8g2_GetUTF8Width(u8g2_t *u8g2, const char *str); | ||||||
| 
 | 
 | ||||||
| void u8g2_SetFontPosBaseline(u8g2_t *u8g2); | void u8g2_SetFontPosBaseline(u8g2_t *u8g2); | ||||||
|  | |||||||
| @ -1113,12 +1113,13 @@ uint8_t u8g2_IsAllValidUTF8(u8g2_t *u8g2, const char *str) | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /* string calculation is stilll not 100% perfect as it addes the initial string offset to the overall size */ | /* string calculation is stilll not 100% perfect as it addes the initial string offset to the overall size */ | ||||||
| static u8g2_uint_t u8g2_string_width(u8g2_t *u8g2, const char *str) U8G2_NOINLINE; | static u8g2_long_t u8g2_string_width(u8g2_t *u8g2, const char *str) U8G2_NOINLINE; | ||||||
| static u8g2_uint_t u8g2_string_width(u8g2_t *u8g2, const char *str) | static u8g2_long_t u8g2_string_width(u8g2_t *u8g2, const char *str) | ||||||
| { | { | ||||||
|   uint16_t e; |   uint16_t e; | ||||||
|   u8g2_uint_t  w, dx; |   u8g2_uint_t dx; | ||||||
|    |   u8g2_long_t w; | ||||||
|  | 
 | ||||||
|   u8g2->font_decode.glyph_width = 0; |   u8g2->font_decode.glyph_width = 0; | ||||||
|   u8x8_utf8_init(u8g2_GetU8x8(u8g2)); |   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; |   u8g2->u8x8.next_cb = u8x8_ascii_next; | ||||||
|   return u8g2_string_width(u8g2, s); |   return u8g2_string_width(u8g2, s); | ||||||
|  | |||||||
| @ -1,106 +0,0 @@ | |||||||
| #include "u8g2_support.h" |  | ||||||
| #include "main.h" |  | ||||||
| #include "cmsis_os.h" |  | ||||||
| #include "gpio.h" |  | ||||||
| 
 |  | ||||||
| #include <stdio.h> |  | ||||||
| 
 |  | ||||||
| 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; |  | ||||||
| } |  | ||||||
| @ -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); |  | ||||||
 DrZlo13
						DrZlo13