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/floopper-bloopper/** @glitchcore | ||||
| applications/gpio-tester/** @glitchcore | ||||
| applications/gui-test/** @DrZlo13 | ||||
| 
 | ||||
| lib/app-template/** @DrZlo13 | ||||
| lib/qrcode/** @DrZlo13 | ||||
|  | ||||
| @ -7,7 +7,6 @@ void flipper_test_app(void* p); | ||||
| void application_blink(void* p); | ||||
| void application_uart_write(void* p); | ||||
| void application_input_dump(void* p); | ||||
| void display_u8g2(void* p); | ||||
| void u8g2_example(void* p); | ||||
| void input_task(void* p); | ||||
| void menu_task(void* p); | ||||
| @ -34,11 +33,9 @@ void sdnfc(void* p); | ||||
| void floopper_bloopper(void* p); | ||||
| void sd_filesystem(void* p); | ||||
| 
 | ||||
| const FuriApplication FLIPPER_SERVICES[] = { | ||||
| #ifdef APP_DISPLAY | ||||
|     {.app = display_u8g2, .name = "display_u8g2", .stack_size = 1024, .icon = A_Plugins_14}, | ||||
| #endif | ||||
| void gui_test(void* p); | ||||
| 
 | ||||
| const FuriApplication FLIPPER_SERVICES[] = { | ||||
| #ifdef APP_CLI | ||||
|     {.app = cli_task, .name = "cli_task", .stack_size = 1024, .icon = A_Plugins_14}, | ||||
| #endif | ||||
| @ -152,6 +149,10 @@ const FuriApplication FLIPPER_SERVICES[] = { | ||||
| #ifdef APP_SDNFC | ||||
|     {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_GUI_TEST | ||||
|     {.app = gui_test, .name = "gui_test", .icon = A_Plugins_14}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| size_t FLIPPER_SERVICES_size() { | ||||
| @ -224,6 +225,10 @@ const FuriApplication FLIPPER_PLUGINS[] = { | ||||
| #ifdef BUILD_SDNFC | ||||
|     {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef BUILD_GUI_TEST | ||||
|     {.app = gui_test, .name = "gui_test", .icon = A_Plugins_14}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| size_t FLIPPER_PLUGINS_size() { | ||||
|  | ||||
| @ -29,6 +29,7 @@ BUILD_GPIO_DEMO = 1 | ||||
| BUILD_MUSIC_PLAYER = 1 | ||||
| BUILD_FLOOPPER_BLOOPPER = 1 | ||||
| BUILD_IBUTTON = 1 | ||||
| BUILD_GUI_TEST = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_NFC ?= 0 | ||||
| @ -144,15 +145,6 @@ ifeq ($(BUILD_EXAMPLE_QRCODE), 1) | ||||
| CFLAGS		+= -DBUILD_EXAMPLE_QRCODE | ||||
| C_SOURCES	+= $(APP_DIR)/examples/u8g2_qrcode.c | ||||
| C_SOURCES	+= $(LIB_DIR)/qrcode/qrcode.c | ||||
| APP_DISPLAY = 1 | ||||
| endif | ||||
| 
 | ||||
| # deprecated
 | ||||
| APP_EXAMPLE_DISPLAY ?= 0 | ||||
| ifeq ($(APP_EXAMPLE_DISPLAY), 1) | ||||
| CFLAGS		+= -DAPP_EXAMPLE_DISPLAY | ||||
| C_SOURCES	+= $(APP_DIR)/examples/u8g2_example.c | ||||
| APP_DISPLAY = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_EXAMPLE_FATFS ?= 0 | ||||
| @ -165,7 +157,6 @@ ifeq ($(BUILD_EXAMPLE_FATFS), 1) | ||||
| CFLAGS		+= -DBUILD_EXAMPLE_FATFS | ||||
| C_SOURCES	+= $(APP_DIR)/examples/fatfs_list.c | ||||
| APP_INPUT = 1 | ||||
| APP_DISPLAY = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_CC1101 ?= 0 | ||||
| @ -289,6 +280,17 @@ CFLAGS		+= -DBUILD_IBUTTON | ||||
| CPP_SOURCES	+= $(wildcard $(APP_DIR)/ibutton/*.cpp) | ||||
| endif | ||||
| 
 | ||||
| APP_GUI_TEST ?= 0 | ||||
| ifeq ($(APP_GUI_TEST), 1) | ||||
| CFLAGS		+= -DAPP_GUI_TEST | ||||
| BUILD_GUI_TEST = 1 | ||||
| endif | ||||
| BUILD_GUI_TEST ?= 0 | ||||
| ifeq ($(BUILD_GUI_TEST), 1) | ||||
| CFLAGS		+= -DBUILD_GUI_TEST | ||||
| C_SOURCES	+= $(wildcard $(APP_DIR)/gui-test/*.c) | ||||
| endif | ||||
| 
 | ||||
| APP_SDNFC ?= 0 | ||||
| ifeq ($(APP_SDNFC), 1) | ||||
| CFLAGS		+= -DAPP_SDNFC | ||||
| @ -315,12 +317,6 @@ CFLAGS		+= -DAPP_SD_FILESYSTEM | ||||
| C_SOURCES	+= $(wildcard $(APP_DIR)/sd-filesystem/*.c) | ||||
| endif | ||||
| 
 | ||||
| # deprecated
 | ||||
| ifeq ($(APP_DISPLAY), 1) | ||||
| CFLAGS		+= -DAPP_DISPLAY | ||||
| C_SOURCES	+= $(APP_DIR)/display-u8g2/display-u8g2.c | ||||
| endif | ||||
| 
 | ||||
| APP_INPUT	?= 0 | ||||
| ifeq ($(APP_INPUT), 1) | ||||
| CFLAGS		+= -DAPP_INPUT | ||||
|  | ||||
							
								
								
									
										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); | ||||
| } | ||||
| 
 | ||||
| void canvas_invert_color(Canvas* canvas) { | ||||
|     canvas->fb.draw_color = !canvas->fb.draw_color; | ||||
| } | ||||
| 
 | ||||
| void canvas_set_font(Canvas* canvas, Font font) { | ||||
|     furi_assert(canvas); | ||||
|     u8g2_SetFontMode(&canvas->fb, 1); | ||||
| @ -86,6 +90,8 @@ void canvas_set_font(Canvas* canvas, Font font) { | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_haxrcorp4089_tr); | ||||
|     } else if(font == FontGlyph) { | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_unifont_t_symbols); | ||||
|     } else if(font == FontKeyboard) { | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_profont11_mf); | ||||
|     } else { | ||||
|         furi_check(0); | ||||
|     } | ||||
| @ -99,6 +105,55 @@ void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str) { | ||||
|     u8g2_DrawStr(&canvas->fb, x, y, str); | ||||
| } | ||||
| 
 | ||||
| void canvas_draw_str_aligned( | ||||
|     Canvas* canvas, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical, | ||||
|     const char* str) { | ||||
|     furi_assert(canvas); | ||||
|     if(!str) return; | ||||
|     x += canvas->offset_x; | ||||
|     y += canvas->offset_y; | ||||
| 
 | ||||
|     switch(horizontal) { | ||||
|     case AlignLeft: | ||||
|         break; | ||||
|     case AlignRight: | ||||
|         x -= u8g2_GetStrWidth(&canvas->fb, str); | ||||
|         break; | ||||
|     case AlignCenter: | ||||
|         x -= (u8g2_GetStrWidth(&canvas->fb, str) / 2); | ||||
|         break; | ||||
|     default: | ||||
|         furi_check(0); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     switch(vertical) { | ||||
|     case AlignTop: | ||||
|         y += u8g2_GetAscent(&canvas->fb); | ||||
|         break; | ||||
|     case AlignBottom: | ||||
|         break; | ||||
|     case AlignCenter: | ||||
|         y += (u8g2_GetAscent(&canvas->fb) / 2); | ||||
|         break; | ||||
|     default: | ||||
|         furi_check(0); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     u8g2_DrawStr(&canvas->fb, x, y, str); | ||||
| } | ||||
| 
 | ||||
| uint16_t canvas_string_width(Canvas* canvas, const char* str) { | ||||
|     furi_assert(canvas); | ||||
|     if(!str) return 0; | ||||
|     return u8g2_GetStrWidth(&canvas->fb, str); | ||||
| } | ||||
| 
 | ||||
| void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon) { | ||||
|     furi_assert(canvas); | ||||
|     if(!icon) return; | ||||
|  | ||||
| @ -13,7 +13,20 @@ typedef enum { | ||||
|     ColorBlack = 0x01, | ||||
| } Color; | ||||
| 
 | ||||
| typedef enum { FontPrimary = 0x00, FontSecondary = 0x01, FontGlyph = 0x02 } Font; | ||||
| typedef enum { | ||||
|     FontPrimary = 0x00, | ||||
|     FontSecondary = 0x01, | ||||
|     FontGlyph = 0x02, | ||||
|     FontKeyboard = 0x03 | ||||
| } Font; | ||||
| 
 | ||||
| typedef enum { | ||||
|     AlignLeft, | ||||
|     AlignRight, | ||||
|     AlignTop, | ||||
|     AlignBottom, | ||||
|     AlignCenter, | ||||
| } Align; | ||||
| 
 | ||||
| typedef struct Canvas Canvas; | ||||
| 
 | ||||
| @ -45,6 +58,11 @@ void canvas_clear(Canvas* canvas); | ||||
|  */ | ||||
| void canvas_set_color(Canvas* canvas, Color color); | ||||
| 
 | ||||
| /*
 | ||||
|  * Invert drawing color | ||||
|  */ | ||||
| void canvas_invert_color(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Set drawing font | ||||
|  */ | ||||
| @ -55,6 +73,24 @@ void canvas_set_font(Canvas* canvas, Font font); | ||||
|  */ | ||||
| void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw aligned string defined by x, y. | ||||
|  * Align calculated from position of baseline, string width and ascent (height of the glyphs above the baseline) | ||||
|  */ | ||||
| void canvas_draw_str_aligned( | ||||
|     Canvas* canvas, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical, | ||||
|     const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get string width | ||||
|  * @return width in pixels. | ||||
|  */ | ||||
| uint16_t canvas_string_width(Canvas* canvas, const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw stateful icon at position defined by x,y. | ||||
|  */ | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| #include "elements.h" | ||||
| #include "canvas_i.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #include <string.h> | ||||
| #include <assets_icons.h> | ||||
| #include <gui/icon_i.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) { | ||||
|     furi_assert(canvas); | ||||
| @ -40,6 +40,137 @@ void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t | ||||
|     canvas_draw_dot(canvas, x + 1, y + 1); | ||||
| } | ||||
| 
 | ||||
| void elements_button_left(Canvas* canvas, const char* str) { | ||||
|     const uint8_t button_height = 13; | ||||
|     const uint8_t vertical_offset = 3; | ||||
|     const uint8_t horizontal_offset = 3; | ||||
|     const uint8_t string_width = canvas_string_width(canvas, str); | ||||
|     const IconData* icon = assets_icons_get_data(I_ButtonLeft_4x7); | ||||
|     const uint8_t icon_offset = 6; | ||||
|     const uint8_t icon_width_with_offset = icon->width + icon_offset; | ||||
|     const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; | ||||
| 
 | ||||
|     const uint8_t x = 0; | ||||
|     const uint8_t y = canvas_height(canvas); | ||||
| 
 | ||||
|     canvas_draw_box(canvas, x, y - button_height, button_width, button_height); | ||||
|     canvas_draw_line(canvas, x + button_width + 0, y, x + button_width + 0, y - button_height + 0); | ||||
|     canvas_draw_line(canvas, x + button_width + 1, y, x + button_width + 1, y - button_height + 1); | ||||
|     canvas_draw_line(canvas, x + button_width + 2, y, x + button_width + 2, y - button_height + 2); | ||||
| 
 | ||||
|     canvas_invert_color(canvas); | ||||
|     canvas_draw_icon_name( | ||||
|         canvas, x + horizontal_offset, y - button_height + vertical_offset, I_ButtonLeft_4x7); | ||||
|     canvas_draw_str( | ||||
|         canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); | ||||
|     canvas_invert_color(canvas); | ||||
| } | ||||
| 
 | ||||
| void elements_button_right(Canvas* canvas, const char* str) { | ||||
|     const uint8_t button_height = 13; | ||||
|     const uint8_t vertical_offset = 3; | ||||
|     const uint8_t horizontal_offset = 3; | ||||
|     const uint8_t string_width = canvas_string_width(canvas, str); | ||||
|     const IconData* icon = assets_icons_get_data(I_ButtonRight_4x7); | ||||
|     const uint8_t icon_offset = 6; | ||||
|     const uint8_t icon_width_with_offset = icon->width + icon_offset; | ||||
|     const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; | ||||
| 
 | ||||
|     const uint8_t x = canvas_width(canvas); | ||||
|     const uint8_t y = canvas_height(canvas); | ||||
| 
 | ||||
|     canvas_draw_box(canvas, x - button_width, y - button_height, button_width, button_height); | ||||
|     canvas_draw_line(canvas, x - button_width - 1, y, x - button_width - 1, y - button_height + 0); | ||||
|     canvas_draw_line(canvas, x - button_width - 2, y, x - button_width - 2, y - button_height + 1); | ||||
|     canvas_draw_line(canvas, x - button_width - 3, y, x - button_width - 3, y - button_height + 2); | ||||
| 
 | ||||
|     canvas_invert_color(canvas); | ||||
|     canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str); | ||||
|     canvas_draw_icon_name( | ||||
|         canvas, | ||||
|         x - horizontal_offset - icon->width, | ||||
|         y - button_height + vertical_offset, | ||||
|         I_ButtonRight_4x7); | ||||
|     canvas_invert_color(canvas); | ||||
| } | ||||
| 
 | ||||
| void elements_button_center(Canvas* canvas, const char* str) { | ||||
|     const uint8_t button_height = 13; | ||||
|     const uint8_t vertical_offset = 3; | ||||
|     const uint8_t horizontal_offset = 3; | ||||
|     const uint8_t string_width = canvas_string_width(canvas, str); | ||||
|     const IconData* icon = assets_icons_get_data(I_ButtonCenter_7x7); | ||||
|     const uint8_t icon_offset = 6; | ||||
|     const uint8_t icon_width_with_offset = icon->width + icon_offset; | ||||
|     const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; | ||||
| 
 | ||||
|     const uint8_t x = (canvas_width(canvas) - button_width) / 2; | ||||
|     const uint8_t y = canvas_height(canvas); | ||||
| 
 | ||||
|     canvas_draw_box(canvas, x, y - button_height, button_width, button_height); | ||||
| 
 | ||||
|     canvas_draw_line(canvas, x - 1, y, x - 1, y - button_height + 0); | ||||
|     canvas_draw_line(canvas, x - 2, y, x - 2, y - button_height + 1); | ||||
|     canvas_draw_line(canvas, x - 3, y, x - 3, y - button_height + 2); | ||||
| 
 | ||||
|     canvas_draw_line(canvas, x + button_width + 0, y, x + button_width + 0, y - button_height + 0); | ||||
|     canvas_draw_line(canvas, x + button_width + 1, y, x + button_width + 1, y - button_height + 1); | ||||
|     canvas_draw_line(canvas, x + button_width + 2, y, x + button_width + 2, y - button_height + 2); | ||||
| 
 | ||||
|     canvas_invert_color(canvas); | ||||
|     canvas_draw_icon_name( | ||||
|         canvas, x + horizontal_offset, y - button_height + vertical_offset, I_ButtonCenter_7x7); | ||||
|     canvas_draw_str( | ||||
|         canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); | ||||
|     canvas_invert_color(canvas); | ||||
| } | ||||
| 
 | ||||
| void elements_multiline_text_aligned( | ||||
|     Canvas* canvas, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical, | ||||
|     const char* text) { | ||||
|     furi_assert(canvas); | ||||
|     furi_assert(text); | ||||
| 
 | ||||
|     uint8_t font_height = canvas_current_font_height(canvas); | ||||
|     string_t str; | ||||
|     string_init(str); | ||||
|     const char* start = text; | ||||
|     char* end; | ||||
| 
 | ||||
|     // get lines count
 | ||||
|     uint8_t i, lines_count; | ||||
|     for(i = 0, lines_count = 0; text[i]; i++) lines_count += (text[i] == '\n'); | ||||
| 
 | ||||
|     switch(vertical) { | ||||
|     case AlignBottom: | ||||
|         y -= font_height * lines_count; | ||||
|         break; | ||||
|     case AlignCenter: | ||||
|         y -= (font_height * lines_count) / 2; | ||||
|         break; | ||||
|     case AlignTop: | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     do { | ||||
|         end = strchr(start, '\n'); | ||||
|         if(end) { | ||||
|             string_set_strn(str, start, end - start); | ||||
|         } else { | ||||
|             string_set_str(str, start); | ||||
|         } | ||||
|         canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, string_get_cstr(str)); | ||||
|         start = end + 1; | ||||
|         y += font_height; | ||||
|     } while(end); | ||||
|     string_clear(str); | ||||
| } | ||||
| 
 | ||||
| void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, char* text) { | ||||
|     furi_assert(canvas); | ||||
|     furi_assert(text); | ||||
|  | ||||
| @ -22,6 +22,38 @@ void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total); | ||||
|  */ | ||||
| void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw button in left corner | ||||
|  * @param str - button text | ||||
|  */ | ||||
| void elements_button_left(Canvas* canvas, const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw button in right corner | ||||
|  * @param str - button text | ||||
|  */ | ||||
| void elements_button_right(Canvas* canvas, const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw button in center | ||||
|  * @param str - button text | ||||
|  */ | ||||
| void elements_button_center(Canvas* canvas, const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw aligned multiline text | ||||
|  * @param x, y - coordinates based on align param | ||||
|  * @param horizontal, vertical - aligment of multiline text | ||||
|  * @param text - string (possible multiline) | ||||
|  */ | ||||
| void elements_multiline_text_aligned( | ||||
|     Canvas* canvas, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical, | ||||
|     const char* text); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw multiline text | ||||
|  * @param x, y - top left corner coordinates | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| #include "dialog.h" | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| struct Dialog { | ||||
| @ -16,34 +17,43 @@ typedef struct { | ||||
| 
 | ||||
| static void dialog_view_draw_callback(Canvas* canvas, void* _model) { | ||||
|     DialogModel* model = _model; | ||||
|     uint8_t canvas_center = canvas_width(canvas) / 2; | ||||
| 
 | ||||
|     // Prepare canvas
 | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| 
 | ||||
|     // Draw header
 | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     canvas_draw_str(canvas, 2, 10, model->header_text); | ||||
|     canvas_draw_str_aligned( | ||||
|         canvas, canvas_center, 17, AlignCenter, AlignBottom, model->header_text); | ||||
| 
 | ||||
|     // Draw text
 | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     canvas_draw_str(canvas, 5, 22, model->text); | ||||
|     elements_multiline_text_aligned( | ||||
|         canvas, canvas_center, 32, AlignCenter, AlignCenter, model->text); | ||||
| 
 | ||||
|     // Draw buttons
 | ||||
|     uint8_t bottom_base_line = canvas_height(canvas) - 2; | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     canvas_draw_str(canvas, 5, bottom_base_line, model->left_text); | ||||
|     canvas_draw_str(canvas, 69, bottom_base_line, model->right_text); | ||||
|     elements_button_left(canvas, model->left_text); | ||||
|     elements_button_right(canvas, model->right_text); | ||||
| } | ||||
| 
 | ||||
| static bool dialog_view_input_callback(InputEvent* event, void* context) { | ||||
|     Dialog* dialog = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     // Process key presses only
 | ||||
|     if(event->state && dialog->callback) { | ||||
|         if(event->input == InputLeft) { | ||||
|             dialog->callback(DialogResultLeft, dialog->context); | ||||
|             consumed = true; | ||||
|         } else if(event->input == InputRight) { | ||||
|             dialog->callback(DialogResultRight, dialog->context); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     // All input events consumed
 | ||||
|     return true; | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| Dialog* dialog_alloc() { | ||||
|  | ||||
| @ -32,13 +32,13 @@ void dialog_free(Dialog* dialog); | ||||
|  */ | ||||
| View* dialog_get_view(Dialog* dialog); | ||||
| 
 | ||||
| /* Set dialog header text
 | ||||
| /* Set dialog result callback
 | ||||
|  * @param dialog - Dialog instance | ||||
|  * @param text - text to be shown | ||||
|  * @param callback - result callback function | ||||
|  */ | ||||
| void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback); | ||||
| 
 | ||||
| /* Set dialog header text
 | ||||
| /* Set dialog context
 | ||||
|  * @param dialog - Dialog instance | ||||
|  * @param context - context pointer, will be passed to result callback | ||||
|  */ | ||||
|  | ||||
							
								
								
									
										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
 | ||||
| 
 | ||||
| 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); | ||||
| 
 | ||||
| void u8g2_SetFontPosBaseline(u8g2_t *u8g2); | ||||
|  | ||||
| @ -1113,11 +1113,12 @@ 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 */ | ||||
| static u8g2_uint_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) U8G2_NOINLINE; | ||||
| static u8g2_long_t u8g2_string_width(u8g2_t *u8g2, const char *str) | ||||
| { | ||||
|   uint16_t e; | ||||
|   u8g2_uint_t  w, dx; | ||||
|   u8g2_uint_t dx; | ||||
|   u8g2_long_t w; | ||||
| 
 | ||||
|   u8g2->font_decode.glyph_width = 0; | ||||
|   u8x8_utf8_init(u8g2_GetU8x8(u8g2)); | ||||
| @ -1251,7 +1252,7 @@ static u8g2_uint_t u8g2_calculate_exact_string_width(u8g2_t *u8g2, const char *s | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| u8g2_uint_t u8g2_GetStrWidth(u8g2_t *u8g2, const char *s) | ||||
| u8g2_long_t u8g2_GetStrWidth(u8g2_t *u8g2, const char *s) | ||||
| { | ||||
|   u8g2->u8x8.next_cb = u8x8_ascii_next; | ||||
|   return u8g2_string_width(u8g2, s); | ||||
|  | ||||
| @ -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