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;
|
||||
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
@ -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);
|
||||
}
|
||||
}
|
@ -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,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);
|
||||
|
@ -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);
|