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>
This commit is contained in:
DrZlo13 2021-02-05 09:35:06 +10:00 committed by GitHub
parent 9642f0d529
commit 4341da90dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1768 additions and 156 deletions

1
.github/CODEOWNERS vendored
View file

@ -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

View file

@ -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() {

View file

@ -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

View 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);
}
}

View file

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

View file

@ -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.
*/

View file

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

View file

@ -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

View file

@ -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() {

View file

@ -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
*/

View 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; });
}

View 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);

View 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;
}

View 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);

View 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);
}
}

View 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);

View 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; });
}

View 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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

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

View file

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

View file

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

View file

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