From 146cd51894a8695d53d3e84ceb67ec6f964bd172 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 27 Oct 2021 21:55:03 +0300 Subject: [PATCH] [FL-1497] GUI textbox element and widget (#792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * canvas: add font parameters * elements: add text box element * widget: add text box element * nfc: rework delete and info scene with text box widget * gui: update documentation * gui: fix canvas_get_font_params return Co-authored-by: あく --- applications/gui/canvas.c | 13 ++ applications/gui/canvas.h | 27 ++- applications/gui/elements.c | 215 ++++++++++++++++++ applications/gui/elements.h | 127 ++++++++--- applications/gui/modules/widget.c | 15 ++ applications/gui/modules/widget.h | 24 ++ .../widget_elements/widget_element_i.h | 10 + .../widget_elements/widget_element_text_box.c | 71 ++++++ applications/nfc/scenes/nfc_scene_delete.c | 4 +- .../nfc/scenes/nfc_scene_device_info.c | 4 +- 10 files changed, 470 insertions(+), 40 deletions(-) mode change 100644 => 100755 applications/gui/elements.c mode change 100644 => 100755 applications/gui/elements.h create mode 100644 applications/gui/modules/widget_elements/widget_element_text_box.c diff --git a/applications/gui/canvas.c b/applications/gui/canvas.c index bfd2a0af..5ab42875 100644 --- a/applications/gui/canvas.c +++ b/applications/gui/canvas.c @@ -6,6 +6,13 @@ #include #include +const CanvasFontParameters canvas_font_params[FontTotalNumber] = { + [FontPrimary] = {.leading_default = 12, .leading_min = 11, .height = 8, .descender = 2}, + [FontSecondary] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2}, + [FontKeyboard] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2}, + [FontBigNumbers] = {.leading_default = 18, .leading_min = 16, .height = 15, .descender = 0}, +}; + Canvas* canvas_init() { Canvas* canvas = furi_alloc(sizeof(Canvas)); @@ -92,6 +99,12 @@ uint8_t canvas_current_font_height(Canvas* canvas) { return font_height; } +CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font) { + furi_assert(canvas); + furi_assert(font < FontTotalNumber); + return (CanvasFontParameters*)&canvas_font_params[font]; +} + void canvas_clear(Canvas* canvas) { furi_assert(canvas); u8g2_ClearBuffer(&canvas->fb); diff --git a/applications/gui/canvas.h b/applications/gui/canvas.h index 59895472..b197e928 100644 --- a/applications/gui/canvas.h +++ b/applications/gui/canvas.h @@ -20,7 +20,15 @@ typedef enum { } Color; /** Fonts enumeration */ -typedef enum { FontPrimary, FontSecondary, FontKeyboard, FontBigNumbers } Font; +typedef enum { + FontPrimary, + FontSecondary, + FontKeyboard, + FontBigNumbers, + + // Keep last for fonts number calculation + FontTotalNumber, +} Font; /** Alignment enumeration */ typedef enum { @@ -45,6 +53,14 @@ typedef enum { CanvasFontDirectionDownToTop, } CanvasFontDirection; +/** Font parameters */ +typedef struct { + uint8_t leading_default; + uint8_t leading_min; + uint8_t height; + uint8_t descender; +} CanvasFontParameters; + /** Canvas anonymouse structure */ typedef struct Canvas Canvas; @@ -72,6 +88,15 @@ uint8_t canvas_height(Canvas* canvas); */ uint8_t canvas_current_font_height(Canvas* canvas); +/** Get font parameters + * + * @param canvas Canvas instance + * @param font Font + * + * @return pointer to CanvasFontParameters structure + */ +CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font); + /** Clear canvas * * @param canvas Canvas instance diff --git a/applications/gui/elements.c b/applications/gui/elements.c old mode 100644 new mode 100755 index 4a577780..0a03ed29 --- a/applications/gui/elements.c +++ b/applications/gui/elements.c @@ -10,6 +10,18 @@ #include #include +#include + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t leading_min; + uint8_t leading_default; + uint8_t height; + uint8_t descender; + uint8_t len; + const char* text; +} ElementTextBoxLine; void elements_progress_bar( Canvas* canvas, @@ -352,3 +364,206 @@ void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) { string_cat(string, "..."); } } + +void elements_text_box( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text) { + furi_assert(canvas); + + ElementTextBoxLine line[ELEMENTS_MAX_LINES_NUM]; + bool bold = false; + bool mono = false; + bool inversed = false; + bool inversed_present = false; + Font current_font = FontSecondary; + Font prev_font = FontSecondary; + CanvasFontParameters* font_params = canvas_get_font_params(canvas, current_font); + + // Fill line parameters + uint8_t line_leading_min = font_params->leading_min; + uint8_t line_leading_default = font_params->leading_default; + uint8_t line_height = font_params->height; + uint8_t line_descender = font_params->descender; + uint8_t line_num = 0; + uint8_t line_width = 0; + uint8_t line_len = 0; + uint8_t total_height_min = 0; + uint8_t total_height_default = 0; + uint16_t i = 0; + bool full_text_processed = false; + + canvas_set_font(canvas, FontSecondary); + + // Fill all lines + line[0].text = text; + for(i = 0; !full_text_processed; i++) { + line_len++; + // Identify line height + if(prev_font != current_font) { + font_params = canvas_get_font_params(canvas, current_font); + line_leading_min = MAX(line_leading_min, font_params->leading_min); + line_leading_default = MAX(line_leading_default, font_params->leading_default); + line_height = MAX(line_height, font_params->height); + line_descender = MAX(line_descender, font_params->descender); + prev_font = current_font; + } + // Set the font + if(text[i] == '\e' && text[i + 1]) { + i++; + line_len++; + if(text[i] == ELEMENTS_BOLD_MARKER) { + if(bold) { + current_font = FontSecondary; + } else { + current_font = FontPrimary; + } + canvas_set_font(canvas, current_font); + bold = !bold; + } + if(text[i] == ELEMENTS_MONO_MARKER) { + if(mono) { + current_font = FontSecondary; + } else { + current_font = FontKeyboard; + } + canvas_set_font(canvas, FontKeyboard); + mono = !mono; + } + if(text[i] == ELEMENTS_INVERSED_MARKER) { + inversed_present = true; + } + continue; + } + if(text[i] != '\n') { + line_width += canvas_glyph_width(canvas, text[i]); + } + // Process new line + if(text[i] == '\n' || text[i] == '\0' || line_width > width) { + if(line_width > width) { + line_width -= canvas_glyph_width(canvas, text[i--]); + line_len--; + } + if(text[i] == '\0') { + full_text_processed = true; + } + if(inversed_present) { + line_leading_min += 1; + line_leading_default += 1; + inversed_present = false; + } + line[line_num].leading_min = line_leading_min; + line[line_num].leading_default = line_leading_default; + line[line_num].height = line_height; + line[line_num].descender = line_descender; + if(total_height_min + line_leading_min > height) { + line_num--; + break; + } + total_height_min += line_leading_min; + total_height_default += line_leading_default; + line[line_num].len = line_len; + if(horizontal == AlignCenter) { + line[line_num].x = x + (width - line_width) / 2; + } else if(horizontal == AlignRight) { + line[line_num].x = x + (width - line_width); + } else { + line[line_num].x = x; + } + line[line_num].y = total_height_min; + line_num++; + if(text[i + 1]) { + line[line_num].text = &text[i + 1]; + } + + line_leading_min = font_params->leading_min; + line_height = font_params->height; + line_descender = font_params->descender; + line_width = 0; + line_len = 0; + } + } + + // Set vertical alignment for all lines + if(full_text_processed) { + if(total_height_default < height) { + if(vertical == AlignTop) { + line[0].y = y + line[0].height; + } else if(vertical == AlignCenter) { + line[0].y = y + line[0].height + (height - total_height_default) / 2; + } else if(vertical == AlignBottom) { + line[0].y = y + line[0].height + (height - total_height_default); + } + if(line_num > 1) { + for(uint8_t i = 1; i < line_num; i++) { + line[i].y = line[i - 1].y + line[i - 1].leading_default; + } + } + } else if(line_num > 1) { + uint8_t free_pixel_num = height - total_height_min; + uint8_t fill_pixel = 0; + uint8_t j = 1; + line[0].y = line[0].height; + while(fill_pixel < free_pixel_num) { + line[j].y = line[j - 1].y + line[j - 1].leading_min + 1; + fill_pixel++; + j = j % (line_num - 1) + 1; + } + } + } + + // Draw line by line + canvas_set_font(canvas, FontSecondary); + bold = false; + mono = false; + inversed = false; + for(uint8_t i = 0; i < line_num; i++) { + for(uint8_t j = 0; j < line[i].len; j++) { + // Process format symbols + if(line[i].text[j] == ELEMENTS_BOLD_MARKER) { + if(bold) { + current_font = FontSecondary; + } else { + current_font = FontPrimary; + } + canvas_set_font(canvas, current_font); + bold = !bold; + continue; + } + if(line[i].text[j] == ELEMENTS_MONO_MARKER) { + if(mono) { + current_font = FontSecondary; + } else { + current_font = FontKeyboard; + } + canvas_set_font(canvas, current_font); + mono = !mono; + continue; + } + if(line[i].text[j] == ELEMENTS_INVERSED_MARKER) { + inversed = !inversed; + continue; + } + if(inversed) { + canvas_draw_box( + canvas, + line[i].x - 1, + line[i].y - line[i].height - 1, + canvas_glyph_width(canvas, line[i].text[j]) + 1, + line[i].height + line[i].descender + 2); + canvas_invert_color(canvas); + canvas_draw_glyph(canvas, line[i].x, line[i].y, line[i].text[j]); + canvas_invert_color(canvas); + } else { + canvas_draw_glyph(canvas, line[i].x, line[i].y, line[i].text[j]); + } + line[i].x += canvas_glyph_width(canvas, line[i].text[j]); + } + } + canvas_set_font(canvas, FontSecondary); +} diff --git a/applications/gui/elements.h b/applications/gui/elements.h old mode 100644 new mode 100755 index 32156dc2..52b26864 --- a/applications/gui/elements.h +++ b/applications/gui/elements.h @@ -16,12 +16,19 @@ extern "C" { #endif +#define ELEMENTS_MAX_LINES_NUM (7) +#define ELEMENTS_BOLD_MARKER '#' +#define ELEMENTS_MONO_MARKER '*' +#define ELEMENTS_INVERSED_MARKER '!' + /** Draw progress bar. - * @param x - progress bar position on X axis - * @param y - progress bar position on Y axis - * @param width - progress bar width - * @param progress - progress in unnamed metric - * @param total - total amount in unnamed metric + * + * @param canvas Canvas instance + * @param x progress bar position on X axis + * @param y progress bar position on Y axis + * @param width progress bar width + * @param progress progress in unnamed metric + * @param total total amount in unnamed metric */ void elements_progress_bar( Canvas* canvas, @@ -32,11 +39,13 @@ void elements_progress_bar( uint8_t total); /** Draw scrollbar on canvas at specific position. - * @param x - scrollbar position on X axis - * @param y - scrollbar position on Y axis - * @param height - scrollbar height - * @param pos - current element - * @param total - total elements + * + * @param canvas Canvas instance + * @param x scrollbar position on X axis + * @param y scrollbar position on Y axis + * @param height scrollbar height + * @param pos current element + * @param total total elements */ void elements_scrollbar_pos( Canvas* canvas, @@ -47,37 +56,49 @@ void elements_scrollbar_pos( uint16_t total); /** Draw scrollbar on canvas. - * width 3px, height equal to canvas height - * @param pos - current element of total elements - * @param total - total elements + * @note width 3px, height equal to canvas height + * + * @param canvas Canvas instance + * @param pos current element of total elements + * @param total total elements */ void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total); /** Draw rounded frame - * @param x, y - top left corner coordinates - * @param width, height - frame width and height + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param width, height frame width and height */ void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); /** Draw button in left corner - * @param str - button text + * + * @param canvas Canvas instance + * @param str button text */ void elements_button_left(Canvas* canvas, const char* str); /** Draw button in right corner - * @param str - button text + * + * @param canvas Canvas instance + * @param str button text */ void elements_button_right(Canvas* canvas, const char* str); /** Draw button in center - * @param str - button text + * + * @param canvas Canvas instance + * @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) + * + * @param canvas Canvas instance + * @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, @@ -88,20 +109,26 @@ void elements_multiline_text_aligned( const char* text); /** Draw multiline text - * @param x, y - top left corner coordinates - * @param text - string (possible multiline) + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param text string (possible multiline) */ void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text); /** Draw framed multiline text - * @param x, y - top left corner coordinates - * @param text - string (possible multiline) + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param text string (possible multiline) */ void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text); /** Draw slightly rounded frame - * @param x, y - top left corner coordinates - * @param width, height - size of frame + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param width, height size of frame */ void elements_slightly_rounded_frame( Canvas* canvas, @@ -111,8 +138,10 @@ void elements_slightly_rounded_frame( uint8_t height); /** Draw slightly rounded box - * @param x, y - top left corner coordinates - * @param width, height - size of box + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param width, height size of box */ void elements_slightly_rounded_box( Canvas* canvas, @@ -122,19 +151,47 @@ void elements_slightly_rounded_box( uint8_t height); /** Draw bubble frame for text - * @param x - left x coordinates - * @param y - top y coordinate - * @param width - bubble width - * @param height - bubble height + * + * @param canvas Canvas instance + * @param x left x coordinates + * @param y top y coordinate + * @param width bubble width + * @param height bubble height */ void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); /** Trim string buffer to fit width in pixels - * @param string - string to trim - * @param width - max width + * + * @param canvas Canvas instance + * @param string string to trim + * @param width max width */ void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width); +/** Draw text box element + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param width width to fit text + * @param height height to fit text + * @param horizontal Align instance + * @param vertical Align instance + * @param[in] text Formatted text. The following formats are available: + * "\e#Bold text\e#" - bold font is used + * "\e*Monospaced text\e*" - monospaced font is used + * "\e#Inversed text\e#" - white text on black background + */ +void elements_text_box( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text); + #ifdef __cplusplus } #endif diff --git a/applications/gui/modules/widget.c b/applications/gui/modules/widget.c index 92ca9133..76779463 100755 --- a/applications/gui/modules/widget.c +++ b/applications/gui/modules/widget.c @@ -146,6 +146,21 @@ void widget_add_string_element( widget_add_element(widget, string_element); } +void widget_add_text_box_element( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text) { + furi_assert(widget); + WidgetElement* text_box_element = + widget_element_text_box_create(x, y, width, height, horizontal, vertical, text); + widget_add_element(widget, text_box_element); +} + void widget_add_button_element( Widget* widget, GuiButtonType button_type, diff --git a/applications/gui/modules/widget.h b/applications/gui/modules/widget.h index 09177c57..af881be1 100755 --- a/applications/gui/modules/widget.h +++ b/applications/gui/modules/widget.h @@ -75,6 +75,30 @@ void widget_add_string_element( Font font, const char* text); +/** Add Text Box Element + * + * @param widget Widget instance + * @param x x coordinate + * @param y y coordinate + * @param width width to fit text + * @param height height to fit text + * @param horizontal Align instance + * @param vertical Align instance + * @param[in] text Formatted text. The following formats are available: + * "\e#Bold text\e#" - bold font is used + * "\e*Monospaced text\e*" - monospaced font is used + * "\e#Inversed text\e#" - white text on black background + */ +void widget_add_text_box_element( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text); + /** Add Button Element * * @param widget Widget instance diff --git a/applications/gui/modules/widget_elements/widget_element_i.h b/applications/gui/modules/widget_elements/widget_element_i.h index bbc58ff9..d7b4e463 100755 --- a/applications/gui/modules/widget_elements/widget_element_i.h +++ b/applications/gui/modules/widget_elements/widget_element_i.h @@ -52,6 +52,16 @@ WidgetElement* widget_element_string_create( Font font, const char* text); +/** Create text box element */ +WidgetElement* widget_element_text_box_create( + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text); + /** Create button element */ WidgetElement* widget_element_button_create( GuiButtonType button_type, diff --git a/applications/gui/modules/widget_elements/widget_element_text_box.c b/applications/gui/modules/widget_elements/widget_element_text_box.c new file mode 100644 index 00000000..9ee33188 --- /dev/null +++ b/applications/gui/modules/widget_elements/widget_element_text_box.c @@ -0,0 +1,71 @@ +#include "widget_element_i.h" +#include +#include + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t width; + uint8_t height; + Align horizontal; + Align vertical; + string_t text; +} GuiTextBoxModel; + +static void gui_text_box_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiTextBoxModel* model = element->model; + + if(string_size(model->text)) { + elements_text_box( + canvas, + model->x, + model->y, + model->width, + model->height, + model->horizontal, + model->vertical, + string_get_cstr(model->text)); + } +} + +static void gui_text_box_free(WidgetElement* gui_string) { + furi_assert(gui_string); + + GuiTextBoxModel* model = gui_string->model; + string_clear(model->text); + free(gui_string->model); + free(gui_string); +} + +WidgetElement* widget_element_text_box_create( + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + Align horizontal, + Align vertical, + const char* text) { + furi_assert(text); + + // Allocate and init model + GuiTextBoxModel* model = furi_alloc(sizeof(GuiTextBoxModel)); + model->x = x; + model->y = y; + model->width = width; + model->height = height; + model->horizontal = horizontal; + model->vertical = vertical; + string_init_set_str(model->text, text); + + // Allocate and init Element + WidgetElement* gui_string = furi_alloc(sizeof(WidgetElement)); + gui_string->parent = NULL; + gui_string->input = NULL; + gui_string->draw = gui_text_box_draw; + gui_string->free = gui_text_box_free; + gui_string->model = model; + + return gui_string; +} diff --git a/applications/nfc/scenes/nfc_scene_delete.c b/applications/nfc/scenes/nfc_scene_delete.c index 993e1e37..0ea1b214 100755 --- a/applications/nfc/scenes/nfc_scene_delete.c +++ b/applications/nfc/scenes/nfc_scene_delete.c @@ -12,8 +12,8 @@ void nfc_scene_delete_on_enter(void* context) { // Setup Custom Widget view char delete_str[64]; - snprintf(delete_str, sizeof(delete_str), "Delete %s", nfc->dev.dev_name); - widget_add_string_element(nfc->widget, 64, 6, AlignCenter, AlignTop, FontPrimary, delete_str); + snprintf(delete_str, sizeof(delete_str), "\e#Delete %s\e#", nfc->dev.dev_name); + widget_add_text_box_element(nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, delete_str); widget_add_button_element( nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_delete_widget_callback, nfc); widget_add_button_element( diff --git a/applications/nfc/scenes/nfc_scene_device_info.c b/applications/nfc/scenes/nfc_scene_device_info.c index 674b3f6d..54196a32 100755 --- a/applications/nfc/scenes/nfc_scene_device_info.c +++ b/applications/nfc/scenes/nfc_scene_device_info.c @@ -35,8 +35,8 @@ void nfc_scene_device_info_on_enter(void* context) { Nfc* nfc = context; // Setup Custom Widget view - widget_add_string_element( - nfc->widget, 64, 6, AlignCenter, AlignTop, FontSecondary, nfc->dev.dev_name); + widget_add_text_box_element( + nfc->widget, 0, 0, 128, 24, AlignCenter, AlignCenter, nfc->dev.dev_name); widget_add_button_element( nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc); widget_add_button_element(