diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b0bac30b..91bd385a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -110,6 +110,7 @@ firmware/targets/f4/api-hal/api-hal-power.c @skotopes applications/music-player/** @DrZlo13 applications/floopper-bloopper/** @glitchcore applications/gpio-tester/** @glitchcore +applications/gui-test/** @DrZlo13 lib/app-template/** @DrZlo13 lib/qrcode/** @DrZlo13 diff --git a/applications/applications.c b/applications/applications.c index 70bf28d8..3f9ea08c 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -7,7 +7,6 @@ void flipper_test_app(void* p); void application_blink(void* p); void application_uart_write(void* p); void application_input_dump(void* p); -void display_u8g2(void* p); void u8g2_example(void* p); void input_task(void* p); void menu_task(void* p); @@ -34,11 +33,9 @@ void sdnfc(void* p); void floopper_bloopper(void* p); void sd_filesystem(void* p); -const FuriApplication FLIPPER_SERVICES[] = { -#ifdef APP_DISPLAY - {.app = display_u8g2, .name = "display_u8g2", .stack_size = 1024, .icon = A_Plugins_14}, -#endif +void gui_test(void* p); +const FuriApplication FLIPPER_SERVICES[] = { #ifdef APP_CLI {.app = cli_task, .name = "cli_task", .stack_size = 1024, .icon = A_Plugins_14}, #endif @@ -152,6 +149,10 @@ const FuriApplication FLIPPER_SERVICES[] = { #ifdef APP_SDNFC {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, #endif + +#ifdef APP_GUI_TEST + {.app = gui_test, .name = "gui_test", .icon = A_Plugins_14}, +#endif }; size_t FLIPPER_SERVICES_size() { @@ -224,6 +225,10 @@ const FuriApplication FLIPPER_PLUGINS[] = { #ifdef BUILD_SDNFC {.app = sdnfc, .name = "sdnfc", .stack_size = 1024, .icon = A_Plugins_14}, #endif + +#ifdef BUILD_GUI_TEST + {.app = gui_test, .name = "gui_test", .icon = A_Plugins_14}, +#endif }; size_t FLIPPER_PLUGINS_size() { diff --git a/applications/applications.mk b/applications/applications.mk index 63487ef8..9214f94a 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -29,6 +29,7 @@ BUILD_GPIO_DEMO = 1 BUILD_MUSIC_PLAYER = 1 BUILD_FLOOPPER_BLOOPPER = 1 BUILD_IBUTTON = 1 +BUILD_GUI_TEST = 1 endif APP_NFC ?= 0 @@ -144,15 +145,6 @@ ifeq ($(BUILD_EXAMPLE_QRCODE), 1) CFLAGS += -DBUILD_EXAMPLE_QRCODE C_SOURCES += $(APP_DIR)/examples/u8g2_qrcode.c C_SOURCES += $(LIB_DIR)/qrcode/qrcode.c -APP_DISPLAY = 1 -endif - -# deprecated -APP_EXAMPLE_DISPLAY ?= 0 -ifeq ($(APP_EXAMPLE_DISPLAY), 1) -CFLAGS += -DAPP_EXAMPLE_DISPLAY -C_SOURCES += $(APP_DIR)/examples/u8g2_example.c -APP_DISPLAY = 1 endif APP_EXAMPLE_FATFS ?= 0 @@ -165,7 +157,6 @@ ifeq ($(BUILD_EXAMPLE_FATFS), 1) CFLAGS += -DBUILD_EXAMPLE_FATFS C_SOURCES += $(APP_DIR)/examples/fatfs_list.c APP_INPUT = 1 -APP_DISPLAY = 1 endif APP_CC1101 ?= 0 @@ -289,6 +280,17 @@ CFLAGS += -DBUILD_IBUTTON CPP_SOURCES += $(wildcard $(APP_DIR)/ibutton/*.cpp) endif +APP_GUI_TEST ?= 0 +ifeq ($(APP_GUI_TEST), 1) +CFLAGS += -DAPP_GUI_TEST +BUILD_GUI_TEST = 1 +endif +BUILD_GUI_TEST ?= 0 +ifeq ($(BUILD_GUI_TEST), 1) +CFLAGS += -DBUILD_GUI_TEST +C_SOURCES += $(wildcard $(APP_DIR)/gui-test/*.c) +endif + APP_SDNFC ?= 0 ifeq ($(APP_SDNFC), 1) CFLAGS += -DAPP_SDNFC @@ -315,12 +317,6 @@ CFLAGS += -DAPP_SD_FILESYSTEM C_SOURCES += $(wildcard $(APP_DIR)/sd-filesystem/*.c) endif -# deprecated -ifeq ($(APP_DISPLAY), 1) -CFLAGS += -DAPP_DISPLAY -C_SOURCES += $(APP_DIR)/display-u8g2/display-u8g2.c -endif - APP_INPUT ?= 0 ifeq ($(APP_INPUT), 1) CFLAGS += -DAPP_INPUT diff --git a/applications/gui-test/gui-test.c b/applications/gui-test/gui-test.c new file mode 100644 index 00000000..731454b8 --- /dev/null +++ b/applications/gui-test/gui-test.c @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + GuiTesterViewTextInput = 0, + GuiTesterViewSubmenu, + GuiTesterViewDialog, + GuiTesterViewDialogEx, + GuiTesterViewPopup, + GuiTesterViewLast +} GuiTesterView; + +typedef struct { + ViewDispatcher* view_dispatcher; + Dialog* dialog; + DialogEx* dialog_ex; + Submenu* submenu; + TextInput* text_input; + Popup* popup; + GuiTesterView view_index; +} GuiTester; + +GuiTester* gui_test_alloc(void) { + GuiTester* gui_tester = furi_alloc(sizeof(GuiTester)); + gui_tester->view_dispatcher = view_dispatcher_alloc(); + gui_tester->view_index = GuiTesterViewDialogEx; + + gui_tester->dialog = dialog_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, GuiTesterViewDialog, dialog_get_view(gui_tester->dialog)); + + gui_tester->dialog_ex = dialog_ex_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, + GuiTesterViewDialogEx, + dialog_ex_get_view(gui_tester->dialog_ex)); + + gui_tester->submenu = submenu_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, GuiTesterViewSubmenu, submenu_get_view(gui_tester->submenu)); + + gui_tester->text_input = text_input_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, + GuiTesterViewTextInput, + text_input_get_view(gui_tester->text_input)); + + gui_tester->popup = popup_alloc(); + view_dispatcher_add_view( + gui_tester->view_dispatcher, GuiTesterViewPopup, popup_get_view(gui_tester->popup)); + + return gui_tester; +} + +void next_view(void* context) { + furi_assert(context); + GuiTester* gui_tester = context; + + gui_tester->view_index++; + if(gui_tester->view_index >= GuiTesterViewLast) { + gui_tester->view_index = 0; + } + + view_dispatcher_switch_to_view(gui_tester->view_dispatcher, gui_tester->view_index); +} + +void popup_callback(void* context) { + next_view(context); +} + +void submenu_callback(void* context) { + next_view(context); +} + +void dialog_callback(DialogResult result, void* context) { + next_view(context); +} + +void dialog_ex_callback(DialogExResult result, void* context) { + next_view(context); +} + +void text_input_callback(void* context, char* text) { + next_view(context); +} + +void gui_test(void* param) { + (void)param; + GuiTester* gui_tester = gui_test_alloc(); + + Gui* gui = furi_record_open("gui"); + view_dispatcher_attach_to_gui(gui_tester->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + + // Submenu + submenu_add_item(gui_tester->submenu, "Read", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Saved", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Emulate", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Enter manually", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Blah blah", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Set time", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Gender-bender", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Hack American Elections", submenu_callback, gui_tester); + submenu_add_item(gui_tester->submenu, "Hack the White House", submenu_callback, gui_tester); + + // Dialog + dialog_set_result_callback(gui_tester->dialog, dialog_callback); + dialog_set_context(gui_tester->dialog, gui_tester); + dialog_set_header_text(gui_tester->dialog, "Delete Abc123?"); + dialog_set_text(gui_tester->dialog, "ID: F0 00 01 02 03 04\nAre you shure?"); + dialog_set_left_button_text(gui_tester->dialog, "< Yes"); + dialog_set_right_button_text(gui_tester->dialog, "No >"); + + // Dialog extended + dialog_ex_set_result_callback(gui_tester->dialog_ex, dialog_ex_callback); + dialog_ex_set_context(gui_tester->dialog_ex, gui_tester); + dialog_ex_set_header(gui_tester->dialog_ex, "Dallas", 95, 12, AlignCenter, AlignCenter); + dialog_ex_set_text( + gui_tester->dialog_ex, "F6 E5 D4\nC3 B2 A1", 95, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(gui_tester->dialog_ex, 0, 1, I_DolphinExcited_64x63); + dialog_ex_set_left_button_text(gui_tester->dialog_ex, "< More"); + dialog_ex_set_right_button_text(gui_tester->dialog_ex, "Save >"); + + // Popup + popup_set_callback(gui_tester->popup, popup_callback); + popup_set_context(gui_tester->popup, gui_tester); + popup_set_icon(gui_tester->popup, 0, 2, I_DolphinMafia_115x62); + popup_set_text(gui_tester->popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_timeout(gui_tester->popup, 5000); + popup_enable_timeout(gui_tester->popup); + + // Text input + const uint8_t text_input_text_len = 64; + char* text_input_text = calloc(text_input_text_len + 1, 1); + memcpy(text_input_text, "New_ke", strlen("New_ke")); + + text_input_set_result_callback( + gui_tester->text_input, + text_input_callback, + gui_tester, + text_input_text, + text_input_text_len); + text_input_set_header_text(gui_tester->text_input, "Name the key"); + + view_dispatcher_switch_to_view(gui_tester->view_dispatcher, gui_tester->view_index); + + while(1) { + osDelay(1000); + } +} \ No newline at end of file diff --git a/applications/gui/canvas.c b/applications/gui/canvas.c index a2a5092f..1c1e06e3 100644 --- a/applications/gui/canvas.c +++ b/applications/gui/canvas.c @@ -77,6 +77,10 @@ void canvas_set_color(Canvas* canvas, Color color) { u8g2_SetDrawColor(&canvas->fb, color); } +void canvas_invert_color(Canvas* canvas) { + canvas->fb.draw_color = !canvas->fb.draw_color; +} + void canvas_set_font(Canvas* canvas, Font font) { furi_assert(canvas); u8g2_SetFontMode(&canvas->fb, 1); @@ -86,6 +90,8 @@ void canvas_set_font(Canvas* canvas, Font font) { u8g2_SetFont(&canvas->fb, u8g2_font_haxrcorp4089_tr); } else if(font == FontGlyph) { u8g2_SetFont(&canvas->fb, u8g2_font_unifont_t_symbols); + } else if(font == FontKeyboard) { + u8g2_SetFont(&canvas->fb, u8g2_font_profont11_mf); } else { furi_check(0); } @@ -99,6 +105,55 @@ void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str) { u8g2_DrawStr(&canvas->fb, x, y, str); } +void canvas_draw_str_aligned( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* str) { + furi_assert(canvas); + if(!str) return; + x += canvas->offset_x; + y += canvas->offset_y; + + switch(horizontal) { + case AlignLeft: + break; + case AlignRight: + x -= u8g2_GetStrWidth(&canvas->fb, str); + break; + case AlignCenter: + x -= (u8g2_GetStrWidth(&canvas->fb, str) / 2); + break; + default: + furi_check(0); + break; + } + + switch(vertical) { + case AlignTop: + y += u8g2_GetAscent(&canvas->fb); + break; + case AlignBottom: + break; + case AlignCenter: + y += (u8g2_GetAscent(&canvas->fb) / 2); + break; + default: + furi_check(0); + break; + } + + u8g2_DrawStr(&canvas->fb, x, y, str); +} + +uint16_t canvas_string_width(Canvas* canvas, const char* str) { + furi_assert(canvas); + if(!str) return 0; + return u8g2_GetStrWidth(&canvas->fb, str); +} + void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, Icon* icon) { furi_assert(canvas); if(!icon) return; @@ -164,4 +219,4 @@ void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) { x += canvas->offset_x; y += canvas->offset_y; u8g2_DrawGlyph(&canvas->fb, x, y, ch); -} +} \ No newline at end of file diff --git a/applications/gui/canvas.h b/applications/gui/canvas.h index 62258ed6..9cfeb2b0 100644 --- a/applications/gui/canvas.h +++ b/applications/gui/canvas.h @@ -13,7 +13,20 @@ typedef enum { ColorBlack = 0x01, } Color; -typedef enum { FontPrimary = 0x00, FontSecondary = 0x01, FontGlyph = 0x02 } Font; +typedef enum { + FontPrimary = 0x00, + FontSecondary = 0x01, + FontGlyph = 0x02, + FontKeyboard = 0x03 +} Font; + +typedef enum { + AlignLeft, + AlignRight, + AlignTop, + AlignBottom, + AlignCenter, +} Align; typedef struct Canvas Canvas; @@ -45,6 +58,11 @@ void canvas_clear(Canvas* canvas); */ void canvas_set_color(Canvas* canvas, Color color); +/* + * Invert drawing color + */ +void canvas_invert_color(Canvas* canvas); + /* * Set drawing font */ @@ -55,6 +73,24 @@ void canvas_set_font(Canvas* canvas, Font font); */ void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str); +/* + * Draw aligned string defined by x, y. + * Align calculated from position of baseline, string width and ascent (height of the glyphs above the baseline) + */ +void canvas_draw_str_aligned( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* str); + +/* + * Get string width + * @return width in pixels. + */ +uint16_t canvas_string_width(Canvas* canvas, const char* str); + /* * Draw stateful icon at position defined by x,y. */ diff --git a/applications/gui/elements.c b/applications/gui/elements.c index 7c58a311..8bf6b2dd 100644 --- a/applications/gui/elements.c +++ b/applications/gui/elements.c @@ -1,10 +1,10 @@ #include "elements.h" -#include "canvas_i.h" - -#include - -#include +#include +#include #include +#include +#include "canvas_i.h" +#include void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total) { furi_assert(canvas); @@ -40,6 +40,137 @@ void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t canvas_draw_dot(canvas, x + 1, y + 1); } +void elements_button_left(Canvas* canvas, const char* str) { + const uint8_t button_height = 13; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const IconData* icon = assets_icons_get_data(I_ButtonLeft_4x7); + const uint8_t icon_offset = 6; + const uint8_t icon_width_with_offset = icon->width + icon_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = 0; + const uint8_t y = canvas_height(canvas); + + canvas_draw_box(canvas, x, y - button_height, button_width, button_height); + canvas_draw_line(canvas, x + button_width + 0, y, x + button_width + 0, y - button_height + 0); + canvas_draw_line(canvas, x + button_width + 1, y, x + button_width + 1, y - button_height + 1); + canvas_draw_line(canvas, x + button_width + 2, y, x + button_width + 2, y - button_height + 2); + + canvas_invert_color(canvas); + canvas_draw_icon_name( + canvas, x + horizontal_offset, y - button_height + vertical_offset, I_ButtonLeft_4x7); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_button_right(Canvas* canvas, const char* str) { + const uint8_t button_height = 13; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const IconData* icon = assets_icons_get_data(I_ButtonRight_4x7); + const uint8_t icon_offset = 6; + const uint8_t icon_width_with_offset = icon->width + icon_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = canvas_width(canvas); + const uint8_t y = canvas_height(canvas); + + canvas_draw_box(canvas, x - button_width, y - button_height, button_width, button_height); + canvas_draw_line(canvas, x - button_width - 1, y, x - button_width - 1, y - button_height + 0); + canvas_draw_line(canvas, x - button_width - 2, y, x - button_width - 2, y - button_height + 1); + canvas_draw_line(canvas, x - button_width - 3, y, x - button_width - 3, y - button_height + 2); + + canvas_invert_color(canvas); + canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str); + canvas_draw_icon_name( + canvas, + x - horizontal_offset - icon->width, + y - button_height + vertical_offset, + I_ButtonRight_4x7); + canvas_invert_color(canvas); +} + +void elements_button_center(Canvas* canvas, const char* str) { + const uint8_t button_height = 13; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const IconData* icon = assets_icons_get_data(I_ButtonCenter_7x7); + const uint8_t icon_offset = 6; + const uint8_t icon_width_with_offset = icon->width + icon_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = (canvas_width(canvas) - button_width) / 2; + const uint8_t y = canvas_height(canvas); + + canvas_draw_box(canvas, x, y - button_height, button_width, button_height); + + canvas_draw_line(canvas, x - 1, y, x - 1, y - button_height + 0); + canvas_draw_line(canvas, x - 2, y, x - 2, y - button_height + 1); + canvas_draw_line(canvas, x - 3, y, x - 3, y - button_height + 2); + + canvas_draw_line(canvas, x + button_width + 0, y, x + button_width + 0, y - button_height + 0); + canvas_draw_line(canvas, x + button_width + 1, y, x + button_width + 1, y - button_height + 1); + canvas_draw_line(canvas, x + button_width + 2, y, x + button_width + 2, y - button_height + 2); + + canvas_invert_color(canvas); + canvas_draw_icon_name( + canvas, x + horizontal_offset, y - button_height + vertical_offset, I_ButtonCenter_7x7); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_multiline_text_aligned( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* text) { + furi_assert(canvas); + furi_assert(text); + + uint8_t font_height = canvas_current_font_height(canvas); + string_t str; + string_init(str); + const char* start = text; + char* end; + + // get lines count + uint8_t i, lines_count; + for(i = 0, lines_count = 0; text[i]; i++) lines_count += (text[i] == '\n'); + + switch(vertical) { + case AlignBottom: + y -= font_height * lines_count; + break; + case AlignCenter: + y -= (font_height * lines_count) / 2; + break; + case AlignTop: + default: + break; + } + + do { + end = strchr(start, '\n'); + if(end) { + string_set_strn(str, start, end - start); + } else { + string_set_str(str, start); + } + canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, string_get_cstr(str)); + start = end + 1; + y += font_height; + } while(end); + string_clear(str); +} + void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, char* text) { furi_assert(canvas); furi_assert(text); @@ -61,4 +192,4 @@ void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, char* text) { y += font_height; } while(end); string_clear(str); -} +} \ No newline at end of file diff --git a/applications/gui/elements.h b/applications/gui/elements.h index 68f7ec85..8252f81b 100644 --- a/applications/gui/elements.h +++ b/applications/gui/elements.h @@ -22,6 +22,38 @@ void elements_scrollbar(Canvas* canvas, uint8_t pos, uint8_t total); */ void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); +/* + * Draw button in left corner + * @param str - button text + */ +void elements_button_left(Canvas* canvas, const char* str); + +/* + * Draw button in right corner + * @param str - button text + */ +void elements_button_right(Canvas* canvas, const char* str); + +/* + * Draw button in center + * @param str - button text + */ +void elements_button_center(Canvas* canvas, const char* str); + +/* + * Draw aligned multiline text + * @param x, y - coordinates based on align param + * @param horizontal, vertical - aligment of multiline text + * @param text - string (possible multiline) + */ +void elements_multiline_text_aligned( + Canvas* canvas, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical, + const char* text); + /* * Draw multiline text * @param x, y - top left corner coordinates diff --git a/applications/gui/modules/dialog.c b/applications/gui/modules/dialog.c index ae0f85a0..74298e95 100644 --- a/applications/gui/modules/dialog.c +++ b/applications/gui/modules/dialog.c @@ -1,4 +1,5 @@ #include "dialog.h" +#include #include struct Dialog { @@ -16,34 +17,43 @@ typedef struct { static void dialog_view_draw_callback(Canvas* canvas, void* _model) { DialogModel* model = _model; + uint8_t canvas_center = canvas_width(canvas) / 2; + // Prepare canvas canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); + // Draw header canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 2, 10, model->header_text); + canvas_draw_str_aligned( + canvas, canvas_center, 17, AlignCenter, AlignBottom, model->header_text); + // Draw text canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 5, 22, model->text); + elements_multiline_text_aligned( + canvas, canvas_center, 32, AlignCenter, AlignCenter, model->text); + // Draw buttons - uint8_t bottom_base_line = canvas_height(canvas) - 2; - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 5, bottom_base_line, model->left_text); - canvas_draw_str(canvas, 69, bottom_base_line, model->right_text); + elements_button_left(canvas, model->left_text); + elements_button_right(canvas, model->right_text); } static bool dialog_view_input_callback(InputEvent* event, void* context) { Dialog* dialog = context; + bool consumed = false; + // Process key presses only if(event->state && dialog->callback) { if(event->input == InputLeft) { dialog->callback(DialogResultLeft, dialog->context); + consumed = true; } else if(event->input == InputRight) { dialog->callback(DialogResultRight, dialog->context); + consumed = true; } } - // All input events consumed - return true; + + return consumed; } Dialog* dialog_alloc() { diff --git a/applications/gui/modules/dialog.h b/applications/gui/modules/dialog.h index 36cd3164..a34159f1 100644 --- a/applications/gui/modules/dialog.h +++ b/applications/gui/modules/dialog.h @@ -32,13 +32,13 @@ void dialog_free(Dialog* dialog); */ View* dialog_get_view(Dialog* dialog); -/* Set dialog header text +/* Set dialog result callback * @param dialog - Dialog instance - * @param text - text to be shown + * @param callback - result callback function */ void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback); -/* Set dialog header text +/* Set dialog context * @param dialog - Dialog instance * @param context - context pointer, will be passed to result callback */ diff --git a/applications/gui/modules/dialog_ex.c b/applications/gui/modules/dialog_ex.c new file mode 100644 index 00000000..23534b4f --- /dev/null +++ b/applications/gui/modules/dialog_ex.c @@ -0,0 +1,232 @@ +#include "dialog_ex.h" +#include +#include + +struct DialogEx { + View* view; + void* context; + DialogExResultCallback callback; +}; + +typedef struct { + const char* text; + uint8_t x; + uint8_t y; + Align horizontal; + Align vertical; +} TextElement; + +typedef struct { + int8_t x; + int8_t y; + IconName name; +} IconElement; + +typedef struct { + TextElement header; + TextElement text; + IconElement icon; + + const char* left_text; + const char* center_text; + const char* right_text; +} DialogExModel; + +static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { + DialogExModel* model = _model; + + // Prepare canvas + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // TODO other criteria for the draw + if(model->icon.x >= 0 && model->icon.y >= 0) { + canvas_draw_icon_name(canvas, model->icon.x, model->icon.y, model->icon.name); + } + + // Draw header + if(model->header.text != NULL) { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, + model->header.x, + model->header.y, + model->header.horizontal, + model->header.vertical, + model->header.text); + } + + // Draw text + if(model->text.text != NULL) { + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, + model->text.x, + model->text.y, + model->text.horizontal, + model->text.vertical, + model->text.text); + } + + // Draw buttons + if(model->left_text != NULL) { + elements_button_left(canvas, model->left_text); + } + + if(model->center_text != NULL) { + elements_button_center(canvas, model->center_text); + } + + if(model->right_text != NULL) { + elements_button_right(canvas, model->right_text); + } +} + +static bool dialog_ex_view_input_callback(InputEvent* event, void* context) { + DialogEx* dialog_ex = context; + bool consumed = false; + const char* left_text = NULL; + const char* center_text = NULL; + const char* right_text = NULL; + + with_view_model( + dialog_ex->view, (DialogExModel * model) { + left_text = model->left_text; + center_text = model->center_text; + right_text = model->right_text; + }); + + // Process key presses only + if(event->state && dialog_ex->callback) { + if(event->input == InputLeft && left_text != NULL) { + dialog_ex->callback(DialogExResultLeft, dialog_ex->context); + consumed = true; + } else if(event->input == InputOk && center_text != NULL) { + dialog_ex->callback(DialogExResultCenter, dialog_ex->context); + consumed = true; + } else if(event->input == InputRight && right_text != NULL) { + dialog_ex->callback(DialogExResultRight, dialog_ex->context); + consumed = true; + } + } + + return consumed; +} + +DialogEx* dialog_ex_alloc() { + DialogEx* dialog_ex = furi_alloc(sizeof(DialogEx)); + dialog_ex->view = view_alloc(); + view_set_context(dialog_ex->view, dialog_ex); + view_allocate_model(dialog_ex->view, ViewModelTypeLockFree, sizeof(DialogExModel)); + view_set_draw_callback(dialog_ex->view, dialog_ex_view_draw_callback); + view_set_input_callback(dialog_ex->view, dialog_ex_view_input_callback); + with_view_model( + dialog_ex->view, (DialogExModel * model) { + model->header.text = NULL; + model->header.x = 0; + model->header.y = 0; + model->header.horizontal = AlignLeft; + model->header.vertical = AlignBottom; + + model->text.text = NULL; + model->text.x = 0; + model->text.y = 0; + model->text.horizontal = AlignLeft; + model->text.vertical = AlignBottom; + + // TODO other criteria for the draw + model->icon.x = -1; + model->icon.y = -1; + model->icon.name = I_ButtonCenter_7x7; + + model->left_text = NULL; + model->center_text = NULL; + model->right_text = NULL; + }); + return dialog_ex; +} + +void dialog_ex_free(DialogEx* dialog_ex) { + furi_assert(dialog_ex); + view_free(dialog_ex->view); + free(dialog_ex); +} + +View* dialog_ex_get_view(DialogEx* dialog_ex) { + furi_assert(dialog_ex); + return dialog_ex->view; +} + +void dialog_ex_set_result_callback(DialogEx* dialog_ex, DialogExResultCallback callback) { + furi_assert(dialog_ex); + dialog_ex->callback = callback; +} + +void dialog_ex_set_context(DialogEx* dialog_ex, void* context) { + furi_assert(dialog_ex); + dialog_ex->context = context; +} + +void dialog_ex_set_header( + DialogEx* dialog_ex, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { + model->header.text = text; + model->header.x = x; + model->header.y = y; + model->header.horizontal = horizontal; + model->header.vertical = vertical; + }); +} + +void dialog_ex_set_text( + DialogEx* dialog_ex, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { + model->text.text = text; + model->text.x = x; + model->text.y = y; + model->text.horizontal = horizontal; + model->text.vertical = vertical; + }); +} + +void dialog_ex_set_icon(DialogEx* dialog_ex, int8_t x, int8_t y, IconName name) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { + model->icon.x = x; + model->icon.y = y; + model->icon.name = name; + }); +} + +void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { model->left_text = text; }); +} + +void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { model->center_text = text; }); +} + +void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) { + furi_assert(dialog_ex); + with_view_model( + dialog_ex->view, (DialogExModel * model) { model->right_text = text; }); +} diff --git a/applications/gui/modules/dialog_ex.h b/applications/gui/modules/dialog_ex.h new file mode 100644 index 00000000..9c9dce91 --- /dev/null +++ b/applications/gui/modules/dialog_ex.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +/* Dialog anonymous structure */ +typedef struct DialogEx DialogEx; + +/* DialogEx result */ +typedef enum { + DialogExResultLeft, + DialogExResultCenter, + DialogExResultRight, +} DialogExResult; + +/* DialogEx result callback type + * @warning comes from GUI thread + */ +typedef void (*DialogExResultCallback)(DialogExResult result, void* context); + +/* Allocate and initialize dialog + * This dialog used to ask simple questions like Yes/ + */ +DialogEx* dialog_ex_alloc(); + +/* Deinitialize and free dialog + * @param dialog - DialogEx instance + */ +void dialog_ex_free(DialogEx* dialog_ex); + +/* Get dialog view + * @param dialog - DialogEx instance + * @return View instance that can be used for embedding + */ +View* dialog_ex_get_view(DialogEx* dialog_ex); + +/* Set dialog result callback + * @param dialog_ex - DialogEx instance + * @param callback - result callback function + */ +void dialog_ex_set_result_callback(DialogEx* dialog_ex, DialogExResultCallback callback); + +/* Set dialog context + * @param dialog_ex - DialogEx instance + * @param context - context pointer, will be passed to result callback + */ +void dialog_ex_set_context(DialogEx* dialog_ex, void* context); + +/* Set dialog header text + * If text is null, dialog header will not be rendered + * @param dialog - DialogEx instance + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ +void dialog_ex_set_header( + DialogEx* dialog_ex, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical); + +/* Set dialog text + * If text is null, dialog text will not be rendered + * @param dialog - DialogEx instance + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ +void dialog_ex_set_text( + DialogEx* dialog_ex, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical); + +/* Set dialog icon + * If x or y is negative, dialog icon will not be rendered + * @param dialog - DialogEx instance + * @param x, y - icon position + * @param name - icon to be shown + */ +void dialog_ex_set_icon(DialogEx* dialog_ex, int8_t x, int8_t y, IconName name); + +/* Set left button text + * If text is null, left button will not be rendered and processed + * @param dialog - DialogEx instance + * @param text - text to be shown + */ +void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text); + +/* Set center button text + * If text is null, center button will not be rendered and processed + * @param dialog - DialogEx instance + * @param text - text to be shown + */ +void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text); + +/* Set right button text + * If text is null, right button will not be rendered and processed + * @param dialog - DialogEx instance + * @param text - text to be shown + */ +void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text); diff --git a/applications/gui/modules/popup.c b/applications/gui/modules/popup.c new file mode 100644 index 00000000..73a855d2 --- /dev/null +++ b/applications/gui/modules/popup.c @@ -0,0 +1,227 @@ +#include "popup.h" +#include +#include + +struct Popup { + View* view; + void* context; + PopupCallback callback; + + osTimerId_t timer; + uint32_t timer_period_in_ms; + bool timer_enabled; +}; + +typedef struct { + const char* text; + uint8_t x; + uint8_t y; + Align horizontal; + Align vertical; +} TextElement; + +typedef struct { + int8_t x; + int8_t y; + IconName name; +} IconElement; + +typedef struct { + TextElement header; + TextElement text; + IconElement icon; +} PopupModel; + +static void popup_view_draw_callback(Canvas* canvas, void* _model) { + PopupModel* model = _model; + + // Prepare canvas + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // TODO other criteria for the draw + if(model->icon.x >= 0 && model->icon.y >= 0) { + canvas_draw_icon_name(canvas, model->icon.x, model->icon.y, model->icon.name); + } + + // Draw header + if(model->header.text != NULL) { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, + model->header.x, + model->header.y, + model->header.horizontal, + model->header.vertical, + model->header.text); + } + + // Draw text + if(model->text.text != NULL) { + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, + model->text.x, + model->text.y, + model->text.horizontal, + model->text.vertical, + model->text.text); + } +} + +static void popup_timer_callback(void* context) { + furi_assert(context); + Popup* popup = context; + + if(popup->callback) { + popup->callback(popup->context); + } +} + +static bool popup_view_input_callback(InputEvent* event, void* context) { + Popup* popup = context; + bool consumed = false; + + // Process key presses only + if(event->state && popup->callback) { + popup->callback(popup->context); + consumed = true; + } + + return consumed; +} + +void popup_start_timer(void* context) { + Popup* popup = context; + if(popup->timer_enabled) { + uint32_t timer_period = popup->timer_period_in_ms / (1000.0f / osKernelGetTickFreq()); + if(timer_period == 0) timer_period = 1; + + if(osTimerStart(popup->timer, timer_period) != osOK) { + furi_assert(0); + }; + } +} + +void popup_stop_timer(void* context) { + Popup* popup = context; + osTimerStop(popup->timer); +} + +Popup* popup_alloc() { + Popup* popup = furi_alloc(sizeof(Popup)); + popup->view = view_alloc(); + popup->timer = osTimerNew(popup_timer_callback, osTimerOnce, popup, NULL); + furi_assert(popup->timer); + popup->timer_period_in_ms = 1000; + popup->timer_enabled = false; + + view_set_context(popup->view, popup); + view_allocate_model(popup->view, ViewModelTypeLockFree, sizeof(PopupModel)); + view_set_draw_callback(popup->view, popup_view_draw_callback); + view_set_input_callback(popup->view, popup_view_input_callback); + view_set_enter_callback(popup->view, popup_start_timer); + view_set_exit_callback(popup->view, popup_stop_timer); + + with_view_model( + popup->view, (PopupModel * model) { + model->header.text = NULL; + model->header.x = 0; + model->header.y = 0; + model->header.horizontal = AlignLeft; + model->header.vertical = AlignBottom; + + model->text.text = NULL; + model->text.x = 0; + model->text.y = 0; + model->text.horizontal = AlignLeft; + model->text.vertical = AlignBottom; + + // TODO other criteria for the draw + model->icon.x = -1; + model->icon.y = -1; + model->icon.name = I_ButtonCenter_7x7; + }); + return popup; +} + +void popup_free(Popup* popup) { + furi_assert(popup); + osTimerDelete(popup->timer); + view_free(popup->view); + free(popup); +} + +View* popup_get_view(Popup* popup) { + furi_assert(popup); + return popup->view; +} + +void popup_set_callback(Popup* popup, PopupCallback callback) { + furi_assert(popup); + popup->callback = callback; +} + +void popup_set_context(Popup* popup, void* context) { + furi_assert(popup); + popup->context = context; +} + +void popup_set_header( + Popup* popup, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + furi_assert(popup); + with_view_model( + popup->view, (PopupModel * model) { + model->header.text = text; + model->header.x = x; + model->header.y = y; + model->header.horizontal = horizontal; + model->header.vertical = vertical; + }); +} + +void popup_set_text( + Popup* popup, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + furi_assert(popup); + with_view_model( + popup->view, (PopupModel * model) { + model->text.text = text; + model->text.x = x; + model->text.y = y; + model->text.horizontal = horizontal; + model->text.vertical = vertical; + }); +} + +void popup_set_icon(Popup* popup, int8_t x, int8_t y, IconName name) { + furi_assert(popup); + with_view_model( + popup->view, (PopupModel * model) { + model->icon.x = x; + model->icon.y = y; + model->icon.name = name; + }); +} + +void popup_set_timeout(Popup* popup, uint32_t timeout_in_ms) { + furi_assert(popup); + popup->timer_period_in_ms = timeout_in_ms; +} + +void popup_enable_timeout(Popup* popup) { + popup->timer_enabled = true; +} + +void popup_disable_timeout(Popup* popup) { + popup->timer_enabled = false; +} \ No newline at end of file diff --git a/applications/gui/modules/popup.h b/applications/gui/modules/popup.h new file mode 100644 index 00000000..b7e3ac16 --- /dev/null +++ b/applications/gui/modules/popup.h @@ -0,0 +1,92 @@ +#pragma once + +#include + +/* Popup anonymous structure */ +typedef struct Popup Popup; + +/* Popup result callback type + * @warning comes from GUI thread + */ +typedef void (*PopupCallback)(void* context); + +/* Allocate and initialize popup + * This popup used to ask simple questions like Yes/ + */ +Popup* popup_alloc(); + +/* Deinitialize and free popup + * @param popup - Popup instance + */ +void popup_free(Popup* popup); + +/* Get popup view + * @param popup - Popup instance + * @return View instance that can be used for embedding + */ +View* popup_get_view(Popup* popup); + +/* Set popup header text + * @param popup - Popup instance + * @param text - text to be shown + */ +void popup_set_callback(Popup* popup, PopupCallback callback); + +/* Set popup context + * @param popup - Popup instance + * @param context - context pointer, will be passed to result callback + */ +void popup_set_context(Popup* popup, void* context); + +/* Set popup header text + * If text is null, popup header will not be rendered + * @param popup - Popup instance + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ +void popup_set_header( + Popup* popup, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical); + +/* Set popup text + * If text is null, popup text will not be rendered + * @param popup - Popup instance + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ +void popup_set_text( + Popup* popup, + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical); + +/* Set popup icon + * @param popup - Popup instance + * @param x, y - icon position + * @param name - icon to be shown + */ +void popup_set_icon(Popup* popup, int8_t x, int8_t y, IconName name); + +/* Set popup timeout + * @param popup - Popup instance + * @param timeout_in_ms - popup timeout value in milliseconds + */ +void popup_set_timeout(Popup* popup, uint32_t timeout_in_ms); + +/* Enable popup timeout + * @param popup - Popup instance + */ +void popup_enable_timeout(Popup* popup); + +/* Disable popup timeout + * @param popup - Popup instance + */ +void popup_disable_timeout(Popup* popup); \ No newline at end of file diff --git a/applications/gui/modules/submenu.c b/applications/gui/modules/submenu.c new file mode 100644 index 00000000..02887fa1 --- /dev/null +++ b/applications/gui/modules/submenu.c @@ -0,0 +1,194 @@ +#include "submenu.h" +#include +#include +#include + +struct SubmenuItem { + const char* label; + SubmenuItemCallback callback; + void* callback_context; +}; + +ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); + +struct Submenu { + View* view; +}; + +typedef struct { + SubmenuItemArray_t items; + uint8_t position; + uint8_t window_position; +} SubmenuModel; + +static void submenu_process_up(Submenu* submenu); +static void submenu_process_down(Submenu* submenu); +static void submenu_process_ok(Submenu* submenu); + +static void submenu_view_draw_callback(Canvas* canvas, void* _model) { + SubmenuModel* model = _model; + + const uint8_t item_height = 16; + const uint8_t item_width = 123; + + canvas_clear(canvas); + canvas_set_font(canvas, FontSecondary); + + uint8_t position = 0; + SubmenuItemArray_it_t it; + + for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); + SubmenuItemArray_next(it)) { + uint8_t item_position = position - model->window_position; + + if(item_position < 4) { + if(position == model->position) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, 0, (item_position * item_height) + 1, item_width, item_height - 2); + canvas_set_color(canvas, ColorWhite); + + canvas_draw_dot(canvas, 0, (item_position * item_height) + 1); + canvas_draw_dot(canvas, 0, (item_position * item_height) + item_height - 2); + canvas_draw_dot(canvas, item_width - 1, (item_position * item_height) + 1); + canvas_draw_dot( + canvas, item_width - 1, (item_position * item_height) + item_height - 2); + } else { + canvas_set_color(canvas, ColorBlack); + } + canvas_draw_str( + canvas, + 6, + (item_position * item_height) + item_height - 4, + SubmenuItemArray_cref(it)->label); + } + + position++; + } + + elements_scrollbar(canvas, model->position, SubmenuItemArray_size(model->items)); +} + +static bool submenu_view_input_callback(InputEvent* event, void* context) { + Submenu* submenu = context; + furi_assert(submenu); + bool consumed = false; + + if(event->state) { + switch(event->input) { + case InputUp: + consumed = true; + submenu_process_up(submenu); + break; + case InputDown: + consumed = true; + submenu_process_down(submenu); + break; + case InputOk: + consumed = true; + submenu_process_ok(submenu); + break; + default: + break; + } + } + + return consumed; +} + +Submenu* submenu_alloc() { + Submenu* submenu = furi_alloc(sizeof(Submenu)); + submenu->view = view_alloc(); + view_set_context(submenu->view, submenu); + view_allocate_model(submenu->view, ViewModelTypeLocking, sizeof(SubmenuModel)); + view_set_draw_callback(submenu->view, submenu_view_draw_callback); + view_set_input_callback(submenu->view, submenu_view_input_callback); + + with_view_model( + submenu->view, (SubmenuModel * model) { + SubmenuItemArray_init(model->items); + model->position = 0; + }); + + return submenu; +} + +void submenu_free(Submenu* submenu) { + furi_assert(submenu); + + with_view_model( + submenu->view, (SubmenuModel * model) { SubmenuItemArray_clear(model->items); }); + view_free(submenu->view); + free(submenu); +} + +View* submenu_get_view(Submenu* submenu) { + furi_assert(submenu); + return submenu->view; +} + +SubmenuItem* submenu_add_item( + Submenu* submenu, + const char* label, + SubmenuItemCallback callback, + void* callback_context) { + SubmenuItem* item = NULL; + furi_assert(label); + furi_assert(submenu); + + with_view_model( + submenu->view, (SubmenuModel * model) { + item = SubmenuItemArray_push_new(model->items); + item->label = label; + item->callback = callback; + item->callback_context = callback_context; + }); + + return item; +} + +void submenu_process_up(Submenu* submenu) { + with_view_model( + submenu->view, (SubmenuModel * model) { + if(model->position > 0) { + model->position--; + if((model->position - model->window_position) < 1 && model->window_position > 0) { + model->window_position--; + } + } else { + model->position = SubmenuItemArray_size(model->items) - 1; + model->window_position = model->position - 3; + } + }); +} + +void submenu_process_down(Submenu* submenu) { + with_view_model( + submenu->view, (SubmenuModel * model) { + if(model->position < (SubmenuItemArray_size(model->items) - 1)) { + model->position++; + if((model->position - model->window_position) > 2 && + model->window_position < (SubmenuItemArray_size(model->items) - 4)) { + model->window_position++; + } + } else { + model->position = 0; + model->window_position = 0; + } + }); +} + +void submenu_process_ok(Submenu* submenu) { + SubmenuItem* item = NULL; + + with_view_model( + submenu->view, (SubmenuModel * model) { + if(model->position < (SubmenuItemArray_size(model->items))) { + item = SubmenuItemArray_get(model->items, model->position); + } + }); + + if(item && item->callback) { + item->callback(item->callback_context); + } +} \ No newline at end of file diff --git a/applications/gui/modules/submenu.h b/applications/gui/modules/submenu.h new file mode 100644 index 00000000..401b1b69 --- /dev/null +++ b/applications/gui/modules/submenu.h @@ -0,0 +1,36 @@ +#pragma once +#include + +/* Submenu anonymous structure */ +typedef struct Submenu Submenu; +typedef struct SubmenuItem SubmenuItem; +typedef void (*SubmenuItemCallback)(void* context); + +/* Allocate and initialize submenu + * This submenu is used to select one option + */ +Submenu* submenu_alloc(); + +/* Deinitialize and free submenu + * @param submenu - Submenu instance + */ +void submenu_free(Submenu* submenu); + +/* Get submenu view + * @param submenu - Submenu instance + * @return View instance that can be used for embedding + */ +View* submenu_get_view(Submenu* submenu); + +/* Add item to submenu + * @param submenu - Submenu instance + * @param label - menu item label + * @param callback - menu item callback + * @param callback_context - menu item callback context + * @return SubmenuItem instance that can be used to modify or delete that item + */ +SubmenuItem* submenu_add_item( + Submenu* submenu, + const char* label, + SubmenuItemCallback callback, + void* callback_context); \ No newline at end of file diff --git a/applications/gui/modules/text_input.c b/applications/gui/modules/text_input.c new file mode 100644 index 00000000..80a7fe6b --- /dev/null +++ b/applications/gui/modules/text_input.c @@ -0,0 +1,370 @@ +#include "text_input.h" +#include + +struct TextInput { + View* view; +}; + +typedef struct { + const char text; + const uint8_t x; + const uint8_t y; +} TextInputKey; + +typedef struct { + const char* header; + char* text; + uint8_t max_text_length; + + TextInputCallback callback; + void* callback_context; + + uint8_t selected_row; + uint8_t selected_column; +} TextInputModel; + +static const uint8_t keyboard_origin_x = 1; +static const uint8_t keyboard_origin_y = 29; +static const uint8_t keyboard_row_count = 3; + +#define ENTER_KEY '\r' +#define BACKSPACE_KEY '\b' + +static const TextInputKey keyboard_keys_row_1[] = { + {'q', 1, 8}, + {'w', 10, 8}, + {'e', 19, 8}, + {'r', 28, 8}, + {'t', 37, 8}, + {'y', 46, 8}, + {'u', 55, 8}, + {'i', 64, 8}, + {'o', 73, 8}, + {'p', 82, 8}, + {'7', 91, 8}, + {'8', 100, 8}, + {'9', 109, 8}, + {'_', 118, 8}, +}; + +static const TextInputKey keyboard_keys_row_2[] = { + {'a', 1, 20}, + {'s', 10, 20}, + {'d', 19, 20}, + {'f', 28, 20}, + {'g', 37, 20}, + {'h', 46, 20}, + {'j', 55, 20}, + {'k', 64, 20}, + {'l', 73, 20}, + {'4', 82, 20}, + {'5', 91, 20}, + {'6', 100, 20}, + {BACKSPACE_KEY, 110, 12}, +}; + +static const TextInputKey keyboard_keys_row_3[] = { + {'z', 1, 32}, + {'x', 10, 32}, + {'c', 19, 32}, + {'v', 28, 32}, + {'b', 37, 32}, + {'n', 46, 32}, + {'m', 55, 32}, + {'0', 64, 32}, + {'1', 73, 32}, + {'2', 82, 32}, + {'3', 91, 32}, + {ENTER_KEY, 102, 23}, +}; + +static uint8_t get_row_size(uint8_t row_index) { + uint8_t row_size = 0; + + switch(row_index + 1) { + case 1: + row_size = sizeof(keyboard_keys_row_1) / sizeof(TextInputKey); + break; + case 2: + row_size = sizeof(keyboard_keys_row_2) / sizeof(TextInputKey); + break; + case 3: + row_size = sizeof(keyboard_keys_row_3) / sizeof(TextInputKey); + break; + } + + return row_size; +} + +static const TextInputKey* get_row(uint8_t row_index) { + const TextInputKey* row = NULL; + + switch(row_index + 1) { + case 1: + row = keyboard_keys_row_1; + break; + case 2: + row = keyboard_keys_row_2; + break; + case 3: + row = keyboard_keys_row_3; + break; + } + + return row; +} + +static const char get_selected_char(TextInputModel* model) { + return get_row(model->selected_row)[model->selected_column].text; +} + +static const bool char_is_lowercase(char letter) { + return (letter >= 0x61 && letter <= 0x7A); +} + +static const char char_to_uppercase(const char letter) { + return (letter - 0x20); +} + +static void text_input_view_draw_callback(Canvas* canvas, void* _model) { + TextInputModel* model = _model; + uint8_t text_length = strlen(model->text); + uint8_t needed_string_width = canvas_width(canvas) - 4 - 7 - 4; + char* text = model->text; + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_str(canvas, 2, 8, model->header); + canvas_draw_line(canvas, 2, 12, canvas_width(canvas) - 7, 12); + canvas_draw_line(canvas, 1, 13, 1, 25); + canvas_draw_line(canvas, canvas_width(canvas) - 6, 13, canvas_width(canvas) - 6, 25); + canvas_draw_line(canvas, 2, 26, canvas_width(canvas) - 7, 26); + + while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) { + text++; + } + + canvas_draw_str(canvas, 4, 22, text); + canvas_draw_str(canvas, 4 + canvas_string_width(canvas, text) + 1, 22, "|"); + + canvas_set_font(canvas, FontKeyboard); + + for(uint8_t row = 0; row <= keyboard_row_count; row++) { + uint8_t volatile column_count = get_row_size(row); + const TextInputKey* keys = get_row(row); + + for(size_t column = 0; column < column_count; column++) { + if(keys[column].text == ENTER_KEY) { + canvas_set_color(canvas, ColorBlack); + if(model->selected_row == row && model->selected_column == column) { + canvas_draw_icon_name( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + I_KeySaveSelected_24x11); + } else { + canvas_draw_icon_name( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + I_KeySave_24x11); + } + } else if(keys[column].text == BACKSPACE_KEY) { + canvas_set_color(canvas, ColorBlack); + if(model->selected_row == row && model->selected_column == column) { + canvas_draw_icon_name( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + I_KeyBackspaceSelected_16x9); + } else { + canvas_draw_icon_name( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + I_KeyBackspace_16x9); + } + } else { + if(model->selected_row == row && model->selected_column == column) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, + keyboard_origin_x + keys[column].x - 1, + keyboard_origin_y + keys[column].y - 8, + 7, + 10); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_color(canvas, ColorBlack); + } + + if(text_length == 0 && char_is_lowercase(keys[column].text)) { + canvas_draw_glyph( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + char_to_uppercase(keys[column].text)); + } else { + canvas_draw_glyph( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + keys[column].text); + } + } + } + } +} + +static void text_input_handle_up(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + if(model->selected_row > 0) { + model->selected_row--; + } + }); +} + +static void text_input_handle_down(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + if(model->selected_row < keyboard_row_count - 1) { + model->selected_row++; + if(model->selected_column > get_row_size(model->selected_row) - 1) { + model->selected_column = get_row_size(model->selected_row) - 1; + } + } + }); +} + +static void text_input_handle_left(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + if(model->selected_column > 0) { + model->selected_column--; + } else { + model->selected_column = get_row_size(model->selected_row) - 1; + } + }); +} + +static void text_input_handle_right(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + if(model->selected_column < get_row_size(model->selected_row) - 1) { + model->selected_column++; + } else { + model->selected_column = 0; + } + }); +} + +static void text_input_handle_ok(TextInput* text_input) { + with_view_model( + text_input->view, (TextInputModel * model) { + char selected = get_selected_char(model); + uint8_t text_length = strlen(model->text); + + if(selected == ENTER_KEY) { + if(model->callback != 0) { + model->callback(model->callback_context, model->text); + } + } else if(selected == BACKSPACE_KEY) { + if(text_length > 0) { + model->text[text_length - 1] = 0; + } + } else if(text_length < model->max_text_length) { + if(text_length == 0 && char_is_lowercase(selected)) { + selected = char_to_uppercase(selected); + } + model->text[text_length] = selected; + model->text[text_length + 1] = 0; + } + }); +} + +static bool text_input_view_input_callback(InputEvent* event, void* context) { + TextInput* text_input = context; + furi_assert(text_input); + bool consumed = false; + + if(event->state) { + switch(event->input) { + case InputUp: + text_input_handle_up(text_input); + consumed = true; + break; + case InputDown: + text_input_handle_down(text_input); + consumed = true; + break; + case InputLeft: + text_input_handle_left(text_input); + consumed = true; + break; + case InputRight: + text_input_handle_right(text_input); + consumed = true; + break; + case InputOk: + text_input_handle_ok(text_input); + consumed = true; + break; + default: + break; + } + } + + return consumed; +} + +TextInput* text_input_alloc() { + TextInput* text_input = furi_alloc(sizeof(TextInput)); + text_input->view = view_alloc(); + view_set_context(text_input->view, text_input); + view_allocate_model(text_input->view, ViewModelTypeLocking, sizeof(TextInputModel)); + view_set_draw_callback(text_input->view, text_input_view_draw_callback); + view_set_input_callback(text_input->view, text_input_view_input_callback); + + with_view_model( + text_input->view, (TextInputModel * model) { + model->max_text_length = 0; + model->header = ""; + model->selected_row = 0; + model->selected_column = 0; + }); + + return text_input; +} + +void text_input_free(TextInput* text_input) { + furi_assert(text_input); + view_free(text_input->view); + free(text_input); +} + +View* text_input_get_view(TextInput* text_input) { + furi_assert(text_input); + return text_input->view; +} + +void text_input_set_result_callback( + TextInput* text_input, + TextInputCallback callback, + void* callback_context, + char* text, + uint8_t max_text_length) { + with_view_model( + text_input->view, (TextInputModel * model) { + model->callback = callback; + model->callback_context = callback_context; + model->text = text; + model->max_text_length = max_text_length; + }); +} + +void text_input_set_header_text(TextInput* text_input, const char* text) { + with_view_model( + text_input->view, (TextInputModel * model) { model->header = text; }); +} \ No newline at end of file diff --git a/applications/gui/modules/text_input.h b/applications/gui/modules/text_input.h new file mode 100644 index 00000000..b829ca31 --- /dev/null +++ b/applications/gui/modules/text_input.h @@ -0,0 +1,42 @@ +#pragma once +#include + +/* Text input anonymous structure */ +typedef struct TextInput TextInput; +typedef void (*TextInputCallback)(void* context, char* text); + +/* Allocate and initialize text input + * This text input is used to enter string + */ +TextInput* text_input_alloc(); + +/* Deinitialize and free text input + * @param text_input - Text input instance + */ +void text_input_free(TextInput* text_input); + +/* Get text input view + * @param text_input - Text input instance + * @return View instance that can be used for embedding + */ +View* text_input_get_view(TextInput* text_input); + +/* Deinitialize and free text input + * @param text_input - Text input instance + * @param callback - callback fn + * @param callback_context - callback context + * @param text - text buffer to use + * @param max_text_length - text buffer length + */ +void text_input_set_result_callback( + TextInput* text_input, + TextInputCallback callback, + void* callback_context, + char* text, + uint8_t max_text_length); + +/* Set text input header text + * @param text input - Text input instance + * @param text - text to be shown + */ +void text_input_set_header_text(TextInput* text_input, const char* text); \ No newline at end of file diff --git a/assets/icons/Common/ButtonCenter_7x7.png b/assets/icons/Common/ButtonCenter_7x7.png new file mode 100644 index 00000000..a66461b2 Binary files /dev/null and b/assets/icons/Common/ButtonCenter_7x7.png differ diff --git a/assets/icons/Common/ButtonLeft_4x7.png b/assets/icons/Common/ButtonLeft_4x7.png new file mode 100644 index 00000000..0b4655d4 Binary files /dev/null and b/assets/icons/Common/ButtonLeft_4x7.png differ diff --git a/assets/icons/Common/ButtonRight_4x7.png b/assets/icons/Common/ButtonRight_4x7.png new file mode 100644 index 00000000..8e1c74c1 Binary files /dev/null and b/assets/icons/Common/ButtonRight_4x7.png differ diff --git a/assets/icons/Keyboard/KeyBackspaceSelected_16x9.png b/assets/icons/Keyboard/KeyBackspaceSelected_16x9.png new file mode 100644 index 00000000..7cc0759a Binary files /dev/null and b/assets/icons/Keyboard/KeyBackspaceSelected_16x9.png differ diff --git a/assets/icons/Keyboard/KeyBackspace_16x9.png b/assets/icons/Keyboard/KeyBackspace_16x9.png new file mode 100644 index 00000000..9946232d Binary files /dev/null and b/assets/icons/Keyboard/KeyBackspace_16x9.png differ diff --git a/assets/icons/Keyboard/KeySaveSelected_24x11.png b/assets/icons/Keyboard/KeySaveSelected_24x11.png new file mode 100644 index 00000000..eeb3569d Binary files /dev/null and b/assets/icons/Keyboard/KeySaveSelected_24x11.png differ diff --git a/assets/icons/Keyboard/KeySave_24x11.png b/assets/icons/Keyboard/KeySave_24x11.png new file mode 100644 index 00000000..e7dba987 Binary files /dev/null and b/assets/icons/Keyboard/KeySave_24x11.png differ diff --git a/assets/icons/iButton/DolphinExcited_64x63.png b/assets/icons/iButton/DolphinExcited_64x63.png new file mode 100644 index 00000000..e695c85f Binary files /dev/null and b/assets/icons/iButton/DolphinExcited_64x63.png differ diff --git a/assets/icons/iButton/DolphinMafia_115x62.png b/assets/icons/iButton/DolphinMafia_115x62.png new file mode 100644 index 00000000..66fdb40f Binary files /dev/null and b/assets/icons/iButton/DolphinMafia_115x62.png differ diff --git a/assets/icons/iButton/DolphinWait_61x59.png b/assets/icons/iButton/DolphinWait_61x59.png new file mode 100644 index 00000000..423e0791 Binary files /dev/null and b/assets/icons/iButton/DolphinWait_61x59.png differ diff --git a/assets/icons/iButton/iButtonDolphinSuccess_109x60.png b/assets/icons/iButton/iButtonDolphinSuccess_109x60.png new file mode 100644 index 00000000..f234aba9 Binary files /dev/null and b/assets/icons/iButton/iButtonDolphinSuccess_109x60.png differ diff --git a/assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png b/assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png new file mode 100644 index 00000000..2b4bec7c Binary files /dev/null and b/assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png differ diff --git a/assets/icons/iButton/iButtonKey_49x44.png b/assets/icons/iButton/iButtonKey_49x44.png new file mode 100644 index 00000000..db895ec5 Binary files /dev/null and b/assets/icons/iButton/iButtonKey_49x44.png differ diff --git a/lib/u8g2/u8g2.h b/lib/u8g2/u8g2.h index 349b2c2f..b277d91e 100644 --- a/lib/u8g2/u8g2.h +++ b/lib/u8g2/u8g2.h @@ -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); diff --git a/lib/u8g2/u8g2_font.c b/lib/u8g2/u8g2_font.c index 816667c6..95d38277 100644 --- a/lib/u8g2/u8g2_font.c +++ b/lib/u8g2/u8g2_font.c @@ -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 */ -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); diff --git a/lib/u8g2_vendor/u8g2_vendor.c b/lib/u8g2_vendor/u8g2_vendor.c deleted file mode 100644 index 739a37e3..00000000 --- a/lib/u8g2_vendor/u8g2_vendor.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "u8g2_support.h" -#include "main.h" -#include "cmsis_os.h" -#include "gpio.h" - -#include - -extern SPI_HandleTypeDef hspi1; - -// #define DEBUG 1 - -uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - switch(msg){ - //Initialize SPI peripheral - case U8X8_MSG_GPIO_AND_DELAY_INIT: - /* HAL initialization contains all what we need so we can skip this part. */ - break; - - //Function which implements a delay, arg_int contains the amount of ms - case U8X8_MSG_DELAY_MILLI: - osDelay(arg_int); - break; - - //Function which delays 10us - case U8X8_MSG_DELAY_10MICRO: - delay_us(10); - break; - - //Function which delays 100ns - case U8X8_MSG_DELAY_100NANO: - asm("nop"); - break; - - //Function to define the logic level of the RESET line - case U8X8_MSG_GPIO_RESET: - #ifdef DEBUG - printf("[u8g2] rst %d\n", arg_int); - #endif - - HAL_GPIO_WritePin(DISPLAY_RST_GPIO_Port, DISPLAY_RST_Pin, arg_int ? GPIO_PIN_SET : GPIO_PIN_RESET); - break; - - default: - #ifdef DEBUG - printf("[u8g2] unknown io %d\n", msg); - #endif - - return 0; //A message was received which is not implemented, return 0 to indicate an error - } - - return 1; // command processed successfully. -} - -uint8_t u8x8_hw_spi_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr){ - switch (msg) { - case U8X8_MSG_BYTE_SEND: - #ifdef DEBUG - printf("[u8g2] send %d bytes %02X\n", arg_int, ((uint8_t*)arg_ptr)[0]); - #endif - - HAL_SPI_Transmit(&hspi1, (uint8_t *)arg_ptr, arg_int, 10000); - break; - - case U8X8_MSG_BYTE_SET_DC: - #ifdef DEBUG - printf("[u8g2] dc %d\n", arg_int); - #endif - - HAL_GPIO_WritePin(DISPLAY_DI_GPIO_Port, DISPLAY_DI_Pin, arg_int ? GPIO_PIN_SET : GPIO_PIN_RESET); - break; - - case U8X8_MSG_BYTE_INIT: - #ifdef DEBUG - printf("[u8g2] init\n"); - #endif - HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_RESET); - break; - - case U8X8_MSG_BYTE_START_TRANSFER: - #ifdef DEBUG - printf("[u8g2] start\n"); - #endif - - HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_RESET); - asm("nop"); - break; - - case U8X8_MSG_BYTE_END_TRANSFER: - #ifdef DEBUG - printf("[u8g2] end\n"); - #endif - - asm("nop"); - HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_SET); - break; - - default: - #ifdef DEBUG - printf("[u8g2] unknown xfer %d\n", msg); - #endif - - return 0; - } - - return 1; -} diff --git a/lib/u8g2_vendor/u8g2_vendor.h b/lib/u8g2_vendor/u8g2_vendor.h deleted file mode 100644 index c2581ddb..00000000 --- a/lib/u8g2_vendor/u8g2_vendor.h +++ /dev/null @@ -1,4 +0,0 @@ -#include "u8g2/u8g2.h" - -uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr); -uint8_t u8x8_hw_spi_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);