[FL-2627] Flipper applications: SDK, build and debug system (#1387)
* Added support for running applications from SD card (FAPs - Flipper Application Packages) * Added plugin_dist target for fbt to build FAPs * All apps of type FlipperAppType.EXTERNAL and FlipperAppType.PLUGIN are built as FAPs by default * Updated VSCode configuration for new fbt features - re-deploy stock configuration to use them * Added debugging support for FAPs with fbt debug & VSCode * Added public firmware API with automated versioning Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: SG <who.just.the.doctor@gmail.com> Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
committed by
Aleksandr Kutuzov
co-authored by
hedger
あく
parent
0f6f9ad52e
commit
b9a766d909
@@ -0,0 +1,36 @@
|
||||
App(
|
||||
appid="gui",
|
||||
name="GuiSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="gui_srv",
|
||||
cdefines=["SRV_GUI"],
|
||||
requires=[
|
||||
"input",
|
||||
"notification",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
sdk_headers=[
|
||||
"gui.h",
|
||||
"elements.h",
|
||||
"view_dispatcher.h",
|
||||
"view_stack.h",
|
||||
"modules/button_menu.h",
|
||||
"modules/byte_input.h",
|
||||
"modules/popup.h",
|
||||
"modules/text_input.h",
|
||||
"modules/widget.h",
|
||||
"modules/validators.h",
|
||||
"modules/file_browser.h",
|
||||
"modules/button_panel.h",
|
||||
"modules/variable_item_list.h",
|
||||
"modules/file_browser_worker.h",
|
||||
"modules/menu.h",
|
||||
"modules/dialog_ex.h",
|
||||
"modules/loading.h",
|
||||
"modules/text_box.h",
|
||||
"modules/submenu.h",
|
||||
"modules/widget_elements/widget_element.h",
|
||||
"modules/empty_screen.h",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,389 @@
|
||||
#include "canvas_i.h"
|
||||
#include "icon_i.h"
|
||||
#include "icon_animation_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stdint.h>
|
||||
#include <u8g2_glue.h>
|
||||
|
||||
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 = malloc(sizeof(Canvas));
|
||||
|
||||
// Setup u8g2
|
||||
u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
|
||||
canvas->orientation = CanvasOrientationHorizontal;
|
||||
// Initialize display
|
||||
u8g2_InitDisplay(&canvas->fb);
|
||||
// Wake up display
|
||||
u8g2_SetPowerSave(&canvas->fb, 0);
|
||||
|
||||
// Clear buffer and send to device
|
||||
canvas_clear(canvas);
|
||||
canvas_commit(canvas);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
void canvas_free(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
free(canvas);
|
||||
}
|
||||
|
||||
void canvas_reset(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_set_font_direction(canvas, CanvasDirectionLeftToRight);
|
||||
}
|
||||
|
||||
void canvas_commit(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
u8g2_SendBuffer(&canvas->fb);
|
||||
}
|
||||
|
||||
uint8_t* canvas_get_buffer(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
return u8g2_GetBufferPtr(&canvas->fb);
|
||||
}
|
||||
|
||||
size_t canvas_get_buffer_size(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
return u8g2_GetBufferTileWidth(&canvas->fb) * u8g2_GetBufferTileHeight(&canvas->fb) * 8;
|
||||
}
|
||||
|
||||
void canvas_frame_set(
|
||||
Canvas* canvas,
|
||||
uint8_t offset_x,
|
||||
uint8_t offset_y,
|
||||
uint8_t width,
|
||||
uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
canvas->offset_x = offset_x;
|
||||
canvas->offset_y = offset_y;
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
}
|
||||
|
||||
uint8_t canvas_width(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
return canvas->width;
|
||||
}
|
||||
|
||||
uint8_t canvas_height(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
return canvas->height;
|
||||
}
|
||||
|
||||
uint8_t canvas_current_font_height(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
uint8_t font_height = u8g2_GetMaxCharHeight(&canvas->fb);
|
||||
|
||||
if(canvas->fb.font == u8g2_font_haxrcorp4089_tr) {
|
||||
font_height += 1;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void canvas_set_color(Canvas* canvas, Color color) {
|
||||
furi_assert(canvas);
|
||||
u8g2_SetDrawColor(&canvas->fb, color);
|
||||
}
|
||||
|
||||
void canvas_set_font_direction(Canvas* canvas, CanvasDirection dir) {
|
||||
furi_assert(canvas);
|
||||
u8g2_SetFontDirection(&canvas->fb, dir);
|
||||
}
|
||||
|
||||
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);
|
||||
if(font == FontPrimary) {
|
||||
u8g2_SetFont(&canvas->fb, u8g2_font_helvB08_tr);
|
||||
} else if(font == FontSecondary) {
|
||||
u8g2_SetFont(&canvas->fb, u8g2_font_haxrcorp4089_tr);
|
||||
} else if(font == FontKeyboard) {
|
||||
u8g2_SetFont(&canvas->fb, u8g2_font_profont11_mr);
|
||||
} else if(font == FontBigNumbers) {
|
||||
u8g2_SetFont(&canvas->fb, u8g2_font_profont22_tn);
|
||||
} else {
|
||||
furi_crash(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str) {
|
||||
furi_assert(canvas);
|
||||
if(!str) return;
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
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_crash(NULL);
|
||||
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_crash(NULL);
|
||||
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);
|
||||
}
|
||||
|
||||
uint8_t canvas_glyph_width(Canvas* canvas, char symbol) {
|
||||
furi_assert(canvas);
|
||||
return u8g2_GetGlyphWidth(&canvas->fb, symbol);
|
||||
}
|
||||
|
||||
void canvas_draw_bitmap(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
const uint8_t* compressed_bitmap_data) {
|
||||
furi_assert(canvas);
|
||||
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
uint8_t* bitmap_data = NULL;
|
||||
furi_hal_compress_icon_decode(compressed_bitmap_data, &bitmap_data);
|
||||
u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data);
|
||||
}
|
||||
|
||||
void canvas_draw_icon_animation(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
IconAnimation* icon_animation) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(icon_animation);
|
||||
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
uint8_t* icon_data = NULL;
|
||||
furi_hal_compress_icon_decode(icon_animation_get_data(icon_animation), &icon_data);
|
||||
u8g2_DrawXBM(
|
||||
&canvas->fb,
|
||||
x,
|
||||
y,
|
||||
icon_animation_get_width(icon_animation),
|
||||
icon_animation_get_height(icon_animation),
|
||||
icon_data);
|
||||
}
|
||||
|
||||
void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(icon);
|
||||
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
uint8_t* icon_data = NULL;
|
||||
furi_hal_compress_icon_decode(icon_get_data(icon), &icon_data);
|
||||
u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data);
|
||||
}
|
||||
|
||||
void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawPixel(&canvas->fb, x, y);
|
||||
}
|
||||
|
||||
void canvas_draw_box(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawBox(&canvas->fb, x, y, width, height);
|
||||
}
|
||||
|
||||
void canvas_draw_rbox(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawRBox(&canvas->fb, x, y, width, height, radius);
|
||||
}
|
||||
|
||||
void canvas_draw_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawFrame(&canvas->fb, x, y, width, height);
|
||||
}
|
||||
|
||||
void canvas_draw_rframe(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawRFrame(&canvas->fb, x, y, width, height, radius);
|
||||
}
|
||||
|
||||
void canvas_draw_line(Canvas* canvas, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
|
||||
furi_assert(canvas);
|
||||
x1 += canvas->offset_x;
|
||||
y1 += canvas->offset_y;
|
||||
x2 += canvas->offset_x;
|
||||
y2 += canvas->offset_y;
|
||||
u8g2_DrawLine(&canvas->fb, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void canvas_draw_circle(Canvas* canvas, uint8_t x, uint8_t y, uint8_t radius) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawCircle(&canvas->fb, x, y, radius, U8G2_DRAW_ALL);
|
||||
}
|
||||
|
||||
void canvas_draw_disc(Canvas* canvas, uint8_t x, uint8_t y, uint8_t radius) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawDisc(&canvas->fb, x, y, radius, U8G2_DRAW_ALL);
|
||||
}
|
||||
|
||||
void canvas_draw_triangle(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t base,
|
||||
uint8_t height,
|
||||
CanvasDirection dir) {
|
||||
furi_assert(canvas);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_line(canvas, x - base / 2, y, x + base / 2, y);
|
||||
canvas_draw_line(canvas, x - base / 2, y, x, y - height + 1);
|
||||
canvas_draw_line(canvas, x, y - height + 1, x + base / 2, y);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_line(canvas, x - base / 2, y, x + base / 2, y);
|
||||
canvas_draw_line(canvas, x - base / 2, y, x, y + height - 1);
|
||||
canvas_draw_line(canvas, x, y + height - 1, x + base / 2, y);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_line(canvas, x, y - base / 2, x, y + base / 2);
|
||||
canvas_draw_line(canvas, x, y - base / 2, x - height + 1, y);
|
||||
canvas_draw_line(canvas, x - height + 1, y, x, y + base / 2);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_line(canvas, x, y - base / 2, x, y + base / 2);
|
||||
canvas_draw_line(canvas, x, y - base / 2, x + height - 1, y);
|
||||
canvas_draw_line(canvas, x + height - 1, y, x, y + base / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void canvas_draw_xbm(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
const uint8_t* bitmap) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawXBM(&canvas->fb, x, y, w, h, bitmap);
|
||||
}
|
||||
|
||||
void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawGlyph(&canvas->fb, x, y, ch);
|
||||
}
|
||||
|
||||
void canvas_set_bitmap_mode(Canvas* canvas, bool alpha) {
|
||||
u8g2_SetBitmapMode(&canvas->fb, alpha ? 1 : 0);
|
||||
}
|
||||
|
||||
void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) {
|
||||
furi_assert(canvas);
|
||||
if(canvas->orientation != orientation) {
|
||||
canvas->orientation = orientation;
|
||||
if(canvas->orientation == CanvasOrientationHorizontal) {
|
||||
FURI_SWAP(canvas->width, canvas->height);
|
||||
u8g2_SetDisplayRotation(&canvas->fb, U8G2_R0);
|
||||
} else if(canvas->orientation == CanvasOrientationVertical) {
|
||||
FURI_SWAP(canvas->width, canvas->height);
|
||||
u8g2_SetDisplayRotation(&canvas->fb, U8G2_R3);
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CanvasOrientation canvas_get_orientation(const Canvas* canvas) {
|
||||
return canvas->orientation;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
/**
|
||||
* @file canvas.h
|
||||
* GUI: Canvas API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <gui/icon_animation.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Color enumeration */
|
||||
typedef enum {
|
||||
ColorWhite = 0x00,
|
||||
ColorBlack = 0x01,
|
||||
} Color;
|
||||
|
||||
/** Fonts enumeration */
|
||||
typedef enum {
|
||||
FontPrimary,
|
||||
FontSecondary,
|
||||
FontKeyboard,
|
||||
FontBigNumbers,
|
||||
|
||||
// Keep last for fonts number calculation
|
||||
FontTotalNumber,
|
||||
} Font;
|
||||
|
||||
/** Alignment enumeration */
|
||||
typedef enum {
|
||||
AlignLeft,
|
||||
AlignRight,
|
||||
AlignTop,
|
||||
AlignBottom,
|
||||
AlignCenter,
|
||||
} Align;
|
||||
|
||||
/** Canvas Orientation */
|
||||
typedef enum {
|
||||
CanvasOrientationHorizontal,
|
||||
CanvasOrientationVertical,
|
||||
} CanvasOrientation;
|
||||
|
||||
/** Font Direction */
|
||||
typedef enum {
|
||||
CanvasDirectionLeftToRight,
|
||||
CanvasDirectionTopToBottom,
|
||||
CanvasDirectionRightToLeft,
|
||||
CanvasDirectionBottomToTop,
|
||||
} CanvasDirection;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Get Canvas width
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return width in pixels.
|
||||
*/
|
||||
uint8_t canvas_width(Canvas* canvas);
|
||||
|
||||
/** Get Canvas height
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return height in pixels.
|
||||
*/
|
||||
uint8_t canvas_height(Canvas* canvas);
|
||||
|
||||
/** Get current font height
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return height in pixels.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
void canvas_clear(Canvas* canvas);
|
||||
|
||||
/** Set drawing color
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param color Color
|
||||
*/
|
||||
void canvas_set_color(Canvas* canvas, Color color);
|
||||
|
||||
/** Set font swap
|
||||
* Argument String Rotation Description
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param dir Direction font
|
||||
*/
|
||||
void canvas_set_font_direction(Canvas* canvas, CanvasDirection dir);
|
||||
|
||||
/** Invert drawing color
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*/
|
||||
void canvas_invert_color(Canvas* canvas);
|
||||
|
||||
/** Set drawing font
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param font Font
|
||||
*/
|
||||
void canvas_set_font(Canvas* canvas, Font font);
|
||||
|
||||
/** Draw string at position of baseline defined by x, y.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x anchor point x coordinate
|
||||
* @param y anchor point y coordinate
|
||||
* @param str C-string
|
||||
*/
|
||||
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)
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x anchor point x coordinate
|
||||
* @param y anchor point y coordinate
|
||||
* @param horizontal horizontal alignment
|
||||
* @param vertical vertical alignment
|
||||
* @param str C-string
|
||||
*/
|
||||
void canvas_draw_str_aligned(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
const char* str);
|
||||
|
||||
/** Get string width
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param str C-string
|
||||
*
|
||||
* @return width in pixels.
|
||||
*/
|
||||
uint16_t canvas_string_width(Canvas* canvas, const char* str);
|
||||
|
||||
/** Get glyph width
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param[in] symbol character
|
||||
*
|
||||
* @return width in pixels
|
||||
*/
|
||||
uint8_t canvas_glyph_width(Canvas* canvas, char symbol);
|
||||
|
||||
/** Draw bitmap picture at position defined by x,y.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param width width of bitmap
|
||||
* @param height height of bitmap
|
||||
* @param compressed_bitmap_data compressed bitmap data
|
||||
*/
|
||||
void canvas_draw_bitmap(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
const uint8_t* compressed_bitmap_data);
|
||||
|
||||
/** Draw animation at position defined by x,y.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param icon_animation IconAnimation instance
|
||||
*/
|
||||
void canvas_draw_icon_animation(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
IconAnimation* icon_animation);
|
||||
|
||||
/** Draw icon at position defined by x,y.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param icon Icon instance
|
||||
*/
|
||||
void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon);
|
||||
|
||||
/** Draw XBM bitmap
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param w bitmap width
|
||||
* @param h bitmap height
|
||||
* @param bitmap pointer to XBM bitmap data
|
||||
*/
|
||||
void canvas_draw_xbm(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
const uint8_t* bitmap);
|
||||
|
||||
/** Draw dot at x,y
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
*/
|
||||
void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y);
|
||||
|
||||
/** Draw box of width, height at x,y
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param width box width
|
||||
* @param height box height
|
||||
*/
|
||||
void canvas_draw_box(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
|
||||
|
||||
/** Draw frame of width, height at x,y
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param width frame width
|
||||
* @param height frame height
|
||||
*/
|
||||
void canvas_draw_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height);
|
||||
|
||||
/** Draw line from x1,y1 to x2,y2
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x1 x1 coordinate
|
||||
* @param y1 y1 coordinate
|
||||
* @param x2 x2 coordinate
|
||||
* @param y2 y2 coordinate
|
||||
*/
|
||||
void canvas_draw_line(Canvas* canvas, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
|
||||
|
||||
/** Draw circle at x,y with radius r
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param r radius
|
||||
*/
|
||||
void canvas_draw_circle(Canvas* canvas, uint8_t x, uint8_t y, uint8_t r);
|
||||
|
||||
/** Draw disc at x,y with radius r
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param r radius
|
||||
*/
|
||||
void canvas_draw_disc(Canvas* canvas, uint8_t x, uint8_t y, uint8_t r);
|
||||
|
||||
/** Draw triangle with given base and height lengths and their intersection coordinate
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate of base and height intersection
|
||||
* @param y y coordinate of base and height intersection
|
||||
* @param base length of triangle side
|
||||
* @param height length of triangle height
|
||||
* @param dir CanvasDirection triangle orientaion
|
||||
*/
|
||||
void canvas_draw_triangle(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t base,
|
||||
uint8_t height,
|
||||
CanvasDirection dir);
|
||||
|
||||
/** Draw glyph
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param ch character
|
||||
*/
|
||||
void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch);
|
||||
|
||||
/** Set transparency mode
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param alpha transparency mode
|
||||
*/
|
||||
void canvas_set_bitmap_mode(Canvas* canvas, bool alpha);
|
||||
|
||||
/** Draw rounded-corner frame of width, height at x,y, with round value raduis
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param width frame width
|
||||
* @param height frame height
|
||||
* @param radius frame corner radius
|
||||
*/
|
||||
void canvas_draw_rframe(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius);
|
||||
|
||||
/** Draw rounded-corner box of width, height at x,y, with round value raduis
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param width box width
|
||||
* @param height box height
|
||||
* @param radius box corner radius
|
||||
*/
|
||||
void canvas_draw_rbox(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @file canvas_i.h
|
||||
* GUI: internal Canvas API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "canvas.h"
|
||||
#include <u8g2.h>
|
||||
|
||||
/** Canvas structure
|
||||
*/
|
||||
struct Canvas {
|
||||
u8g2_t fb;
|
||||
CanvasOrientation orientation;
|
||||
uint8_t offset_x;
|
||||
uint8_t offset_y;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
};
|
||||
|
||||
/** Allocate memory and initialize canvas
|
||||
*
|
||||
* @return Canvas instance
|
||||
*/
|
||||
Canvas* canvas_init();
|
||||
|
||||
/** Free canvas memory
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*/
|
||||
void canvas_free(Canvas* canvas);
|
||||
|
||||
/** Reset canvas drawing tools configuration
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*/
|
||||
void canvas_reset(Canvas* canvas);
|
||||
|
||||
/** Commit canvas. Send buffer to display
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*/
|
||||
void canvas_commit(Canvas* canvas);
|
||||
|
||||
/** Get canvas buffer.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return pointer to buffer
|
||||
*/
|
||||
uint8_t* canvas_get_buffer(Canvas* canvas);
|
||||
|
||||
/** Get canvas buffer size.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return size of canvas in bytes
|
||||
*/
|
||||
size_t canvas_get_buffer_size(Canvas* canvas);
|
||||
|
||||
/** Set drawing region relative to real screen buffer
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param offset_x x coordinate offset
|
||||
* @param offset_y y coordinate offset
|
||||
* @param width width
|
||||
* @param height height
|
||||
*/
|
||||
void canvas_frame_set(
|
||||
Canvas* canvas,
|
||||
uint8_t offset_x,
|
||||
uint8_t offset_y,
|
||||
uint8_t width,
|
||||
uint8_t height);
|
||||
|
||||
/** Set canvas orientation
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param orientation CanvasOrientation
|
||||
*/
|
||||
void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation);
|
||||
|
||||
/** Get canvas orientation
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return CanvasOrientation
|
||||
*/
|
||||
CanvasOrientation canvas_get_orientation(const Canvas* canvas);
|
||||
@@ -0,0 +1,758 @@
|
||||
#include "elements.h"
|
||||
#include "m-core.h"
|
||||
#include <assets_icons.h>
|
||||
#include "furi_hal_resources.h"
|
||||
#include <furi_hal.h>
|
||||
#include "gui/canvas.h"
|
||||
|
||||
#include <gui/icon_i.h>
|
||||
#include <gui/icon_animation_i.h>
|
||||
|
||||
#include <m-string.h>
|
||||
#include <furi.h>
|
||||
#include "canvas_i.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
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, uint8_t x, uint8_t y, uint8_t width, float progress) {
|
||||
furi_assert(canvas);
|
||||
furi_assert((progress >= 0) && (progress <= 1.0));
|
||||
uint8_t height = 9;
|
||||
|
||||
uint8_t progress_length = roundf(progress * (width - 2));
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x + 1, y + 1, width - 2, height - 2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_rframe(canvas, x, y, width, height, 3);
|
||||
|
||||
canvas_draw_box(canvas, x + 1, y + 1, progress_length, height - 2);
|
||||
}
|
||||
|
||||
void elements_scrollbar_pos(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t height,
|
||||
uint16_t pos,
|
||||
uint16_t total) {
|
||||
furi_assert(canvas);
|
||||
// prevent overflows
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x - 3, y, 3, height);
|
||||
// dot line
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
for(uint8_t i = y; i < height + y; i += 2) {
|
||||
canvas_draw_dot(canvas, x - 2, i);
|
||||
}
|
||||
// Position block
|
||||
if(total) {
|
||||
float block_h = ((float)height) / total;
|
||||
canvas_draw_box(canvas, x - 3, y + (block_h * pos), 3, MAX(block_h, 1));
|
||||
}
|
||||
}
|
||||
|
||||
void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total) {
|
||||
furi_assert(canvas);
|
||||
|
||||
uint8_t width = canvas_width(canvas);
|
||||
uint8_t height = canvas_height(canvas);
|
||||
// prevent overflows
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, width - 3, 0, 3, height);
|
||||
// dot line
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
for(uint8_t i = 0; i < height; i += 2) {
|
||||
canvas_draw_dot(canvas, width - 2, i);
|
||||
}
|
||||
// Position block
|
||||
if(total) {
|
||||
float block_h = ((float)height) / total;
|
||||
canvas_draw_box(canvas, width - 3, block_h * pos, 3, MAX(block_h, 1));
|
||||
}
|
||||
}
|
||||
|
||||
void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
|
||||
canvas_draw_line(canvas, x + 2, y, x + width - 2, y);
|
||||
canvas_draw_line(canvas, x + 1, y + height - 1, x + width, y + height - 1);
|
||||
canvas_draw_line(canvas, x + 2, y + height, x + width - 1, y + height);
|
||||
|
||||
canvas_draw_line(canvas, x, y + 2, x, y + height - 2);
|
||||
canvas_draw_line(canvas, x + width - 1, y + 1, x + width - 1, y + height - 2);
|
||||
canvas_draw_line(canvas, x + width, y + 2, x + width, y + height - 2);
|
||||
|
||||
canvas_draw_dot(canvas, x + 1, y + 1);
|
||||
}
|
||||
|
||||
void elements_button_left(Canvas* canvas, const char* str) {
|
||||
const uint8_t button_height = 12;
|
||||
const uint8_t vertical_offset = 3;
|
||||
const uint8_t horizontal_offset = 3;
|
||||
const uint8_t string_width = canvas_string_width(canvas, str);
|
||||
const Icon* icon = &I_ButtonLeft_4x7;
|
||||
const uint8_t icon_h_offset = 3;
|
||||
const uint8_t icon_width_with_offset = icon->width + icon_h_offset;
|
||||
const uint8_t icon_v_offset = icon->height + vertical_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(canvas, x + horizontal_offset, y - icon_v_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 = 12;
|
||||
const uint8_t vertical_offset = 3;
|
||||
const uint8_t horizontal_offset = 3;
|
||||
const uint8_t string_width = canvas_string_width(canvas, str);
|
||||
const Icon* icon = &I_ButtonRight_4x7;
|
||||
const uint8_t icon_h_offset = 3;
|
||||
const uint8_t icon_width_with_offset = icon->width + icon_h_offset;
|
||||
const uint8_t icon_v_offset = icon->height + vertical_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(
|
||||
canvas, x - horizontal_offset - icon->width, y - icon_v_offset, &I_ButtonRight_4x7);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
void elements_button_center(Canvas* canvas, const char* str) {
|
||||
const uint8_t button_height = 12;
|
||||
const uint8_t vertical_offset = 3;
|
||||
const uint8_t horizontal_offset = 1;
|
||||
const uint8_t string_width = canvas_string_width(canvas, str);
|
||||
const Icon* icon = &I_ButtonCenter_7x7;
|
||||
const uint8_t icon_h_offset = 3;
|
||||
const uint8_t icon_width_with_offset = icon->width + icon_h_offset;
|
||||
const uint8_t icon_v_offset = icon->height + vertical_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(canvas, x + horizontal_offset, y - icon_v_offset, &I_ButtonCenter_7x7);
|
||||
canvas_draw_str(
|
||||
canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
static size_t
|
||||
elements_get_max_chars_to_fit(Canvas* canvas, Align horizontal, const char* text, uint8_t x) {
|
||||
const char* end = strchr(text, '\n');
|
||||
if(end == NULL) {
|
||||
end = text + strlen(text);
|
||||
}
|
||||
size_t text_size = end - text;
|
||||
string_t str;
|
||||
string_init_set_str(str, text);
|
||||
string_left(str, text_size);
|
||||
size_t result = 0;
|
||||
|
||||
uint16_t len_px = canvas_string_width(canvas, string_get_cstr(str));
|
||||
uint8_t px_left = 0;
|
||||
if(horizontal == AlignCenter) {
|
||||
if(x > (canvas_width(canvas) / 2)) {
|
||||
px_left = (canvas_width(canvas) - x) * 2;
|
||||
} else {
|
||||
px_left = x * 2;
|
||||
}
|
||||
} else if(horizontal == AlignLeft) {
|
||||
px_left = canvas_width(canvas) - x;
|
||||
} else if(horizontal == AlignRight) {
|
||||
px_left = x;
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
|
||||
if(len_px > px_left) {
|
||||
uint8_t excess_symbols_approximately =
|
||||
roundf((float)(len_px - px_left) / ((float)len_px / (float)text_size));
|
||||
// reduce to 5 to be sure dash fit, and next line will be at least 5 symbols long
|
||||
if(excess_symbols_approximately > 0) {
|
||||
excess_symbols_approximately = MAX(excess_symbols_approximately, 5);
|
||||
result = text_size - excess_symbols_approximately - 1;
|
||||
} else {
|
||||
result = text_size;
|
||||
}
|
||||
} else {
|
||||
result = text_size;
|
||||
}
|
||||
|
||||
string_clear(str);
|
||||
return result;
|
||||
}
|
||||
|
||||
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 lines_count = 0;
|
||||
uint8_t font_height = canvas_current_font_height(canvas);
|
||||
string_t line;
|
||||
|
||||
/* go through text line by line and count lines */
|
||||
for(const char* start = text; start[0];) {
|
||||
size_t chars_fit = elements_get_max_chars_to_fit(canvas, horizontal, start, x);
|
||||
++lines_count;
|
||||
start += chars_fit;
|
||||
start += start[0] == '\n' ? 1 : 0;
|
||||
}
|
||||
|
||||
if(vertical == AlignBottom) {
|
||||
y -= font_height * (lines_count - 1);
|
||||
} else if(vertical == AlignCenter) {
|
||||
y -= (font_height * (lines_count - 1)) / 2;
|
||||
}
|
||||
|
||||
/* go through text line by line and print them */
|
||||
for(const char* start = text; start[0];) {
|
||||
size_t chars_fit = elements_get_max_chars_to_fit(canvas, horizontal, start, x);
|
||||
|
||||
if((start[chars_fit] == '\n') || (start[chars_fit] == 0)) {
|
||||
string_init_printf(line, "%.*s", chars_fit, start);
|
||||
} else if((y + font_height) > canvas_height(canvas)) {
|
||||
string_init_printf(line, "%.*s...\n", chars_fit, start);
|
||||
} else {
|
||||
string_init_printf(line, "%.*s-\n", chars_fit, start);
|
||||
}
|
||||
canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, string_get_cstr(line));
|
||||
string_clear(line);
|
||||
y += font_height;
|
||||
if(y > canvas_height(canvas)) {
|
||||
break;
|
||||
}
|
||||
|
||||
start += chars_fit;
|
||||
start += start[0] == '\n' ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, 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;
|
||||
do {
|
||||
end = strchr(start, '\n');
|
||||
if(end) {
|
||||
string_set_strn(str, start, end - start);
|
||||
} else {
|
||||
string_set_str(str, start);
|
||||
}
|
||||
canvas_draw_str(canvas, x, y, string_get_cstr(str));
|
||||
start = end + 1;
|
||||
y += font_height;
|
||||
} while(end && y < 64);
|
||||
string_clear(str);
|
||||
}
|
||||
|
||||
void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(text);
|
||||
|
||||
uint8_t font_y = canvas_current_font_height(canvas);
|
||||
uint16_t str_width = canvas_string_width(canvas, text);
|
||||
// count \n's
|
||||
uint8_t lines = 1;
|
||||
const char* t = text;
|
||||
while(*t != '\0') {
|
||||
if(*t == '\n') {
|
||||
lines++;
|
||||
uint16_t temp_width = canvas_string_width(canvas, t + 1);
|
||||
str_width = temp_width > str_width ? temp_width : str_width;
|
||||
}
|
||||
t++;
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x, y - font_y, str_width + 8, font_y * lines + 4);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
elements_multiline_text(canvas, x + 4, y - 1, text);
|
||||
elements_frame(canvas, x, y - font_y, str_width + 8, font_y * lines + 4);
|
||||
}
|
||||
|
||||
void elements_slightly_rounded_frame(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
canvas_draw_rframe(canvas, x, y, width, height, 1);
|
||||
}
|
||||
|
||||
void elements_slightly_rounded_box(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
canvas_draw_rbox(canvas, x, y, width, height, 1);
|
||||
}
|
||||
|
||||
void elements_bold_rounded_frame(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x + 2, y + 2, width - 3, height - 3);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_line(canvas, x + 3, y, x + width - 3, y);
|
||||
canvas_draw_line(canvas, x + 2, y + 1, x + width - 2, y + 1);
|
||||
|
||||
canvas_draw_line(canvas, x, y + 3, x, y + height - 3);
|
||||
canvas_draw_line(canvas, x + 1, y + 2, x + 1, y + height - 2);
|
||||
|
||||
canvas_draw_line(canvas, x + width, y + 3, x + width, y + height - 3);
|
||||
canvas_draw_line(canvas, x + width - 1, y + 2, x + width - 1, y + height - 2);
|
||||
|
||||
canvas_draw_line(canvas, x + 3, y + height, x + width - 3, y + height);
|
||||
canvas_draw_line(canvas, x + 2, y + height - 1, x + width - 2, y + height - 1);
|
||||
|
||||
canvas_draw_dot(canvas, x + 2, y + 2);
|
||||
canvas_draw_dot(canvas, x + 3, y + 2);
|
||||
canvas_draw_dot(canvas, x + 2, y + 3);
|
||||
|
||||
canvas_draw_dot(canvas, x + width - 2, y + 2);
|
||||
canvas_draw_dot(canvas, x + width - 3, y + 2);
|
||||
canvas_draw_dot(canvas, x + width - 2, y + 3);
|
||||
|
||||
canvas_draw_dot(canvas, x + 2, y + height - 2);
|
||||
canvas_draw_dot(canvas, x + 3, y + height - 2);
|
||||
canvas_draw_dot(canvas, x + 2, y + height - 3);
|
||||
|
||||
canvas_draw_dot(canvas, x + width - 2, y + height - 2);
|
||||
canvas_draw_dot(canvas, x + width - 3, y + height - 2);
|
||||
canvas_draw_dot(canvas, x + width - 2, y + height - 3);
|
||||
}
|
||||
|
||||
void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
canvas_draw_rframe(canvas, x + 4, y, width, height, 3);
|
||||
uint8_t y_corner = y + height * 2 / 3;
|
||||
canvas_draw_line(canvas, x, y_corner, x + 4, y_corner - 4);
|
||||
canvas_draw_line(canvas, x, y_corner, x + 4, y_corner + 4);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_line(canvas, x + 4, y_corner - 3, x + 4, y_corner + 3);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
void elements_bubble_str(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
const char* text,
|
||||
Align horizontal,
|
||||
Align vertical) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(text);
|
||||
|
||||
uint8_t font_y = canvas_current_font_height(canvas);
|
||||
uint16_t str_width = canvas_string_width(canvas, text);
|
||||
// count \n's
|
||||
uint8_t lines = 1;
|
||||
const char* t = text;
|
||||
while(*t != '\0') {
|
||||
if(*t == '\n') {
|
||||
lines++;
|
||||
uint16_t temp_width = canvas_string_width(canvas, t + 1);
|
||||
str_width = temp_width > str_width ? temp_width : str_width;
|
||||
}
|
||||
t++;
|
||||
}
|
||||
|
||||
uint8_t frame_x = x;
|
||||
uint8_t frame_y = y;
|
||||
uint8_t frame_width = str_width + 8;
|
||||
uint8_t frame_height = font_y * lines + 4;
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, frame_x + 1, frame_y + 1, frame_width - 2, frame_height - 2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_rframe(canvas, frame_x, frame_y, frame_width, frame_height, 1);
|
||||
elements_multiline_text(canvas, x + 4, y - 1 + font_y, text);
|
||||
|
||||
uint8_t x1 = 0;
|
||||
uint8_t x2 = 0;
|
||||
uint8_t x3 = 0;
|
||||
uint8_t y1 = 0;
|
||||
uint8_t y2 = 0;
|
||||
uint8_t y3 = 0;
|
||||
if((horizontal == AlignLeft) && (vertical == AlignTop)) {
|
||||
x1 = frame_x;
|
||||
y1 = frame_y;
|
||||
x2 = frame_x - 4;
|
||||
y2 = frame_y;
|
||||
x3 = frame_x;
|
||||
y3 = frame_y + 4;
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x2 + 2, y2 + 1, 2, 2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else if((horizontal == AlignLeft) && (vertical == AlignCenter)) {
|
||||
x1 = frame_x;
|
||||
y1 = frame_y + (frame_height - 1) / 2 - 4;
|
||||
x2 = frame_x - 4;
|
||||
y2 = frame_y + (frame_height - 1) / 2;
|
||||
x3 = frame_x;
|
||||
y3 = frame_y + (frame_height - 1) / 2 + 4;
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x2 + 2, y2 - 2, 2, 5);
|
||||
canvas_draw_dot(canvas, x2 + 1, y2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else if((horizontal == AlignLeft) && (vertical == AlignBottom)) {
|
||||
x1 = frame_x;
|
||||
y1 = frame_y + (frame_height - 1) - 4;
|
||||
x2 = frame_x - 4;
|
||||
y2 = frame_y + (frame_height - 1);
|
||||
x3 = frame_x;
|
||||
y3 = frame_y + (frame_height - 1);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x2 + 2, y2 - 2, 2, 2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else if((horizontal == AlignRight) && (vertical == AlignTop)) {
|
||||
x1 = frame_x + (frame_width - 1);
|
||||
y1 = frame_y;
|
||||
x2 = frame_x + (frame_width - 1) + 4;
|
||||
y2 = frame_y;
|
||||
x3 = frame_x + (frame_width - 1);
|
||||
y3 = frame_y + 4;
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x2 - 3, y2 + 1, 2, 2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else if((horizontal == AlignRight) && (vertical == AlignCenter)) {
|
||||
x1 = frame_x + (frame_width - 1);
|
||||
y1 = frame_y + (frame_height - 1) / 2 - 4;
|
||||
x2 = frame_x + (frame_width - 1) + 4;
|
||||
y2 = frame_y + (frame_height - 1) / 2;
|
||||
x3 = frame_x + (frame_width - 1);
|
||||
y3 = frame_y + (frame_height - 1) / 2 + 4;
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x2 - 3, y2 - 2, 2, 5);
|
||||
canvas_draw_dot(canvas, x2 - 1, y2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else if((horizontal == AlignRight) && (vertical == AlignBottom)) {
|
||||
x1 = frame_x + (frame_width - 1);
|
||||
y1 = frame_y + (frame_height - 1) - 4;
|
||||
x2 = frame_x + (frame_width - 1) + 4;
|
||||
y2 = frame_y + (frame_height - 1);
|
||||
x3 = frame_x + (frame_width - 1);
|
||||
y3 = frame_y + (frame_height - 1);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x2 - 3, y2 - 2, 2, 2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else if((horizontal == AlignCenter) && (vertical == AlignTop)) {
|
||||
x1 = frame_x + (frame_width - 1) / 2 - 4;
|
||||
y1 = frame_y;
|
||||
x2 = frame_x + (frame_width - 1) / 2;
|
||||
y2 = frame_y - 4;
|
||||
x3 = frame_x + (frame_width - 1) / 2 + 4;
|
||||
y3 = frame_y;
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x2 - 2, y2 + 2, 5, 2);
|
||||
canvas_draw_dot(canvas, x2, y2 + 1);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else if((horizontal == AlignCenter) && (vertical == AlignBottom)) {
|
||||
x1 = frame_x + (frame_width - 1) / 2 - 4;
|
||||
y1 = frame_y + (frame_height - 1);
|
||||
x2 = frame_x + (frame_width - 1) / 2;
|
||||
y2 = frame_y + (frame_height - 1) + 4;
|
||||
x3 = frame_x + (frame_width - 1) / 2 + 4;
|
||||
y3 = frame_y + (frame_height - 1);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x2 - 2, y2 - 3, 5, 2);
|
||||
canvas_draw_dot(canvas, x2, y2 - 1);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_line(canvas, x3, y3, x1, y1);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_line(canvas, x1, y1, x2, y2);
|
||||
canvas_draw_line(canvas, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(string);
|
||||
|
||||
uint16_t len_px = canvas_string_width(canvas, string_get_cstr(string));
|
||||
if(len_px > width) {
|
||||
width -= canvas_string_width(canvas, "...");
|
||||
do {
|
||||
string_left(string, string_size(string) - 1);
|
||||
len_px = canvas_string_width(canvas, string_get_cstr(string));
|
||||
} while(len_px > 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,
|
||||
bool strip_to_dots) {
|
||||
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;
|
||||
uint16_t dots_width = canvas_string_width(canvas, "...");
|
||||
|
||||
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) {
|
||||
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(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 = 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 {
|
||||
if((i == line_num - 1) && strip_to_dots) {
|
||||
uint8_t next_symbol_width = canvas_glyph_width(canvas, line[i].text[j]);
|
||||
if(line[i].x + next_symbol_width + dots_width > x + width) {
|
||||
canvas_draw_str(canvas, line[i].x, line[i].y, "...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* @file elements.h
|
||||
* GUI: Elements API
|
||||
*
|
||||
* Canvas helpers and UI building blocks.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <m-string.h>
|
||||
#include "canvas.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
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 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 (0.0 - 1.0)
|
||||
*/
|
||||
void elements_progress_bar(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, float progress);
|
||||
|
||||
/** Draw scrollbar on canvas at specific position.
|
||||
*
|
||||
* @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,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t height,
|
||||
uint16_t pos,
|
||||
uint16_t total);
|
||||
|
||||
/** Draw scrollbar on canvas.
|
||||
* @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 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 canvas Canvas instance
|
||||
* @param str button text
|
||||
*/
|
||||
void elements_button_left(Canvas* canvas, const char* str);
|
||||
|
||||
/** Draw button in right corner
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param str button text
|
||||
*/
|
||||
void elements_button_right(Canvas* canvas, const char* str);
|
||||
|
||||
/** Draw button in center
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param str button text
|
||||
*/
|
||||
void elements_button_center(Canvas* canvas, const char* str);
|
||||
|
||||
/** Draw aligned multiline text
|
||||
*
|
||||
* @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,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
const char* text);
|
||||
|
||||
/** Draw multiline text
|
||||
*
|
||||
* @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 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 canvas Canvas instance
|
||||
* @param x, y top left corner coordinates
|
||||
* @param width, height size of frame
|
||||
*/
|
||||
void elements_slightly_rounded_frame(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height);
|
||||
|
||||
/** Draw slightly rounded 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,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height);
|
||||
|
||||
/** Draw bold rounded frame
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x, y top left corner coordinates
|
||||
* @param width, height size of frame
|
||||
*/
|
||||
void elements_bold_rounded_frame(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height);
|
||||
|
||||
/** Draw bubble frame for text
|
||||
*
|
||||
* @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);
|
||||
|
||||
/** Draw bubble frame for text with corner
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x left x coordinates
|
||||
* @param y top y coordinate
|
||||
* @param width bubble width
|
||||
* @param height bubble height
|
||||
* @param horizontal horizontal aligning
|
||||
* @param vertical aligning
|
||||
*/
|
||||
void elements_bubble_str(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
const char* text,
|
||||
Align horizontal,
|
||||
Align vertical);
|
||||
|
||||
/** Trim string buffer to fit width in pixels
|
||||
*
|
||||
* @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
|
||||
* @param strip_to_dots Strip text to ... if does not fit to width
|
||||
*/
|
||||
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,
|
||||
bool strip_to_dots);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,522 @@
|
||||
#include "gui/canvas.h"
|
||||
#include "gui_i.h"
|
||||
|
||||
#define TAG "GuiSrv"
|
||||
|
||||
ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) {
|
||||
// Iterating backward
|
||||
ViewPortArray_it_t it;
|
||||
ViewPortArray_it_last(it, array);
|
||||
while(!ViewPortArray_end_p(it)) {
|
||||
ViewPort* view_port = *ViewPortArray_ref(it);
|
||||
if(view_port_is_enabled(view_port)) {
|
||||
return view_port;
|
||||
}
|
||||
ViewPortArray_previous(it);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void gui_update(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_DRAW);
|
||||
}
|
||||
|
||||
void gui_input_events_callback(const void* value, void* ctx) {
|
||||
furi_assert(value);
|
||||
furi_assert(ctx);
|
||||
|
||||
Gui* gui = ctx;
|
||||
|
||||
furi_message_queue_put(gui->input_queue, value, FuriWaitForever);
|
||||
furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_INPUT);
|
||||
}
|
||||
|
||||
// Only Fullscreen supports vertical display for now
|
||||
bool gui_redraw_fs(Gui* gui) {
|
||||
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
|
||||
canvas_frame_set(gui->canvas, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT);
|
||||
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]);
|
||||
if(view_port) {
|
||||
view_port_draw(view_port, gui->canvas);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void gui_redraw_status_bar(Gui* gui, bool need_attention) {
|
||||
ViewPortArray_it_t it;
|
||||
uint8_t left_used = 0;
|
||||
uint8_t right_used = 0;
|
||||
uint8_t width;
|
||||
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
|
||||
canvas_frame_set(
|
||||
gui->canvas, GUI_STATUS_BAR_X, GUI_STATUS_BAR_Y, GUI_DISPLAY_WIDTH, GUI_STATUS_BAR_HEIGHT);
|
||||
|
||||
/* for support black theme - paint white area and
|
||||
* draw icon with transparent white color
|
||||
*/
|
||||
canvas_set_color(gui->canvas, ColorWhite);
|
||||
canvas_draw_box(gui->canvas, 1, 1, 9, 7);
|
||||
canvas_draw_box(gui->canvas, 7, 3, 58, 6);
|
||||
canvas_draw_box(gui->canvas, 61, 1, 32, 7);
|
||||
canvas_draw_box(gui->canvas, 89, 3, 38, 6);
|
||||
canvas_set_color(gui->canvas, ColorBlack);
|
||||
canvas_set_bitmap_mode(gui->canvas, 1);
|
||||
canvas_draw_icon(gui->canvas, 0, 0, &I_Background_128x11);
|
||||
canvas_set_bitmap_mode(gui->canvas, 0);
|
||||
|
||||
// Right side
|
||||
uint8_t x = GUI_DISPLAY_WIDTH - 1;
|
||||
ViewPortArray_it(it, gui->layers[GuiLayerStatusBarRight]);
|
||||
while(!ViewPortArray_end_p(it) && right_used < GUI_STATUS_BAR_WIDTH) {
|
||||
ViewPort* view_port = *ViewPortArray_ref(it);
|
||||
if(view_port_is_enabled(view_port)) {
|
||||
width = view_port_get_width(view_port);
|
||||
if(!width) width = 8;
|
||||
// Recalculate next position
|
||||
right_used += (width + 2);
|
||||
x -= (width + 2);
|
||||
// Prepare work area background
|
||||
canvas_frame_set(
|
||||
gui->canvas,
|
||||
x - 1,
|
||||
GUI_STATUS_BAR_Y + 1,
|
||||
width + 2,
|
||||
GUI_STATUS_BAR_WORKAREA_HEIGHT + 2);
|
||||
canvas_set_color(gui->canvas, ColorWhite);
|
||||
canvas_draw_box(
|
||||
gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas));
|
||||
canvas_set_color(gui->canvas, ColorBlack);
|
||||
// ViewPort draw
|
||||
canvas_frame_set(
|
||||
gui->canvas, x, GUI_STATUS_BAR_Y + 2, width, GUI_STATUS_BAR_WORKAREA_HEIGHT);
|
||||
view_port_draw(view_port, gui->canvas);
|
||||
}
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
// Draw frame around icons on the right
|
||||
if(right_used) {
|
||||
canvas_frame_set(
|
||||
gui->canvas,
|
||||
GUI_DISPLAY_WIDTH - 3 - right_used,
|
||||
GUI_STATUS_BAR_Y,
|
||||
right_used + 3,
|
||||
GUI_STATUS_BAR_HEIGHT);
|
||||
canvas_set_color(gui->canvas, ColorBlack);
|
||||
canvas_draw_rframe(
|
||||
gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas), 1);
|
||||
canvas_draw_line(
|
||||
gui->canvas,
|
||||
canvas_width(gui->canvas) - 2,
|
||||
1,
|
||||
canvas_width(gui->canvas) - 2,
|
||||
canvas_height(gui->canvas) - 2);
|
||||
canvas_draw_line(
|
||||
gui->canvas,
|
||||
1,
|
||||
canvas_height(gui->canvas) - 2,
|
||||
canvas_width(gui->canvas) - 2,
|
||||
canvas_height(gui->canvas) - 2);
|
||||
}
|
||||
|
||||
// Left side
|
||||
x = 2;
|
||||
ViewPortArray_it(it, gui->layers[GuiLayerStatusBarLeft]);
|
||||
while(!ViewPortArray_end_p(it) && (right_used + left_used) < GUI_STATUS_BAR_WIDTH) {
|
||||
ViewPort* view_port = *ViewPortArray_ref(it);
|
||||
if(view_port_is_enabled(view_port)) {
|
||||
width = view_port_get_width(view_port);
|
||||
if(!width) width = 8;
|
||||
// Prepare work area background
|
||||
canvas_frame_set(
|
||||
gui->canvas,
|
||||
x - 1,
|
||||
GUI_STATUS_BAR_Y + 1,
|
||||
width + 2,
|
||||
GUI_STATUS_BAR_WORKAREA_HEIGHT + 2);
|
||||
canvas_set_color(gui->canvas, ColorWhite);
|
||||
canvas_draw_box(
|
||||
gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas));
|
||||
canvas_set_color(gui->canvas, ColorBlack);
|
||||
// ViewPort draw
|
||||
canvas_frame_set(
|
||||
gui->canvas, x, GUI_STATUS_BAR_Y + 2, width, GUI_STATUS_BAR_WORKAREA_HEIGHT);
|
||||
view_port_draw(view_port, gui->canvas);
|
||||
// Recalculate next position
|
||||
left_used += (width + 2);
|
||||
x += (width + 2);
|
||||
}
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
// Extra notification
|
||||
if(need_attention) {
|
||||
width = icon_get_width(&I_Attention_5x8);
|
||||
// Prepare work area background
|
||||
canvas_frame_set(
|
||||
gui->canvas,
|
||||
x - 1,
|
||||
GUI_STATUS_BAR_Y + 1,
|
||||
width + 2,
|
||||
GUI_STATUS_BAR_WORKAREA_HEIGHT + 2);
|
||||
canvas_set_color(gui->canvas, ColorWhite);
|
||||
canvas_draw_box(gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas));
|
||||
canvas_set_color(gui->canvas, ColorBlack);
|
||||
// Draw Icon
|
||||
canvas_frame_set(
|
||||
gui->canvas, x, GUI_STATUS_BAR_Y + 2, width, GUI_STATUS_BAR_WORKAREA_HEIGHT);
|
||||
canvas_draw_icon(gui->canvas, 0, 0, &I_Attention_5x8);
|
||||
// Recalculate next position
|
||||
left_used += (width + 2);
|
||||
x += (width + 2);
|
||||
}
|
||||
// Draw frame around icons on the left
|
||||
if(left_used) {
|
||||
canvas_frame_set(gui->canvas, 0, 0, left_used + 3, GUI_STATUS_BAR_HEIGHT);
|
||||
canvas_draw_rframe(
|
||||
gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas), 1);
|
||||
canvas_draw_line(
|
||||
gui->canvas,
|
||||
canvas_width(gui->canvas) - 2,
|
||||
1,
|
||||
canvas_width(gui->canvas) - 2,
|
||||
canvas_height(gui->canvas) - 2);
|
||||
canvas_draw_line(
|
||||
gui->canvas,
|
||||
1,
|
||||
canvas_height(gui->canvas) - 2,
|
||||
canvas_width(gui->canvas) - 2,
|
||||
canvas_height(gui->canvas) - 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool gui_redraw_window(Gui* gui) {
|
||||
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
|
||||
canvas_frame_set(gui->canvas, GUI_WINDOW_X, GUI_WINDOW_Y, GUI_WINDOW_WIDTH, GUI_WINDOW_HEIGHT);
|
||||
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]);
|
||||
if(view_port) {
|
||||
view_port_draw(view_port, gui->canvas);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool gui_redraw_desktop(Gui* gui) {
|
||||
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
|
||||
canvas_frame_set(gui->canvas, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT);
|
||||
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
|
||||
if(view_port) {
|
||||
view_port_draw(view_port, gui->canvas);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void gui_redraw(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
gui_lock(gui);
|
||||
|
||||
canvas_reset(gui->canvas);
|
||||
|
||||
if(gui->lockdown) {
|
||||
gui_redraw_desktop(gui);
|
||||
bool need_attention =
|
||||
(gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 ||
|
||||
gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]) != 0);
|
||||
gui_redraw_status_bar(gui, need_attention);
|
||||
} else {
|
||||
if(!gui_redraw_fs(gui)) {
|
||||
if(!gui_redraw_window(gui)) {
|
||||
gui_redraw_desktop(gui);
|
||||
}
|
||||
gui_redraw_status_bar(gui, false);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_commit(gui->canvas);
|
||||
for
|
||||
M_EACH(p, gui->canvas_callback_pair, CanvasCallbackPairArray_t) {
|
||||
p->callback(
|
||||
canvas_get_buffer(gui->canvas), canvas_get_buffer_size(gui->canvas), p->context);
|
||||
}
|
||||
gui_unlock(gui);
|
||||
}
|
||||
|
||||
void gui_input(Gui* gui, InputEvent* input_event) {
|
||||
furi_assert(gui);
|
||||
furi_assert(input_event);
|
||||
|
||||
// Check input complementarity
|
||||
uint8_t key_bit = (1 << input_event->key);
|
||||
if(input_event->type == InputTypeRelease) {
|
||||
gui->ongoing_input &= ~key_bit;
|
||||
} else if(input_event->type == InputTypePress) {
|
||||
gui->ongoing_input |= key_bit;
|
||||
} else if(!(gui->ongoing_input & key_bit)) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"non-complementary input, discarding key: %s type: %s, sequence: %p",
|
||||
input_get_key_name(input_event->key),
|
||||
input_get_type_name(input_event->type),
|
||||
input_event->sequence);
|
||||
return;
|
||||
}
|
||||
|
||||
gui_lock(gui);
|
||||
|
||||
ViewPort* view_port = NULL;
|
||||
|
||||
if(gui->lockdown) {
|
||||
view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
|
||||
} else {
|
||||
view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]);
|
||||
if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]);
|
||||
if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
|
||||
}
|
||||
|
||||
if(!(gui->ongoing_input & ~key_bit) && input_event->type == InputTypePress) {
|
||||
gui->ongoing_input_view_port = view_port;
|
||||
}
|
||||
|
||||
if(view_port && view_port == gui->ongoing_input_view_port) {
|
||||
view_port_input(view_port, input_event);
|
||||
} else if(gui->ongoing_input_view_port && input_event->type == InputTypeRelease) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"ViewPort changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port",
|
||||
gui->ongoing_input_view_port,
|
||||
view_port,
|
||||
input_get_key_name(input_event->key),
|
||||
input_get_type_name(input_event->type),
|
||||
input_event->sequence);
|
||||
view_port_input(gui->ongoing_input_view_port, input_event);
|
||||
} else {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"ViewPort changed while key press %p -> %p. Discarding key: %s, type: %s, sequence: %p",
|
||||
gui->ongoing_input_view_port,
|
||||
view_port,
|
||||
input_get_key_name(input_event->key),
|
||||
input_get_type_name(input_event->type),
|
||||
input_event->sequence);
|
||||
}
|
||||
|
||||
gui_unlock(gui);
|
||||
}
|
||||
|
||||
void gui_lock(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
furi_check(furi_mutex_acquire(gui->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void gui_unlock(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
furi_check(furi_mutex_release(gui->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
furi_check(layer < GuiLayerMAX);
|
||||
// Only fullscreen supports Vertical orientation for now
|
||||
furi_assert(
|
||||
(layer == GuiLayerFullscreen) || (view_port->orientation != ViewPortOrientationVertical));
|
||||
|
||||
gui_lock(gui);
|
||||
// Verify that view port is not yet added
|
||||
ViewPortArray_it_t it;
|
||||
for(size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_it(it, gui->layers[i]);
|
||||
while(!ViewPortArray_end_p(it)) {
|
||||
furi_assert(*ViewPortArray_ref(it) != view_port);
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
}
|
||||
// Add view port and link with gui
|
||||
ViewPortArray_push_back(gui->layers[layer], view_port);
|
||||
view_port_gui_set(view_port, gui);
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
void gui_remove_view_port(Gui* gui, ViewPort* view_port) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
|
||||
gui_lock(gui);
|
||||
view_port_gui_set(view_port, NULL);
|
||||
ViewPortArray_it_t it;
|
||||
for(size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_it(it, gui->layers[i]);
|
||||
while(!ViewPortArray_end_p(it)) {
|
||||
if(*ViewPortArray_ref(it) == view_port) {
|
||||
ViewPortArray_remove(gui->layers[i], it);
|
||||
} else {
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(gui->ongoing_input_view_port == view_port) {
|
||||
gui->ongoing_input_view_port = NULL;
|
||||
}
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
|
||||
gui_lock(gui);
|
||||
// Remove
|
||||
GuiLayer layer = GuiLayerMAX;
|
||||
ViewPortArray_it_t it;
|
||||
for(size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_it(it, gui->layers[i]);
|
||||
while(!ViewPortArray_end_p(it)) {
|
||||
if(*ViewPortArray_ref(it) == view_port) {
|
||||
ViewPortArray_remove(gui->layers[i], it);
|
||||
furi_assert(layer == GuiLayerMAX);
|
||||
layer = i;
|
||||
} else {
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_assert(layer != GuiLayerMAX);
|
||||
// Return to the top
|
||||
ViewPortArray_push_back(gui->layers[layer], view_port);
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
|
||||
gui_lock(gui);
|
||||
// Remove
|
||||
GuiLayer layer = GuiLayerMAX;
|
||||
ViewPortArray_it_t it;
|
||||
for(size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_it(it, gui->layers[i]);
|
||||
while(!ViewPortArray_end_p(it)) {
|
||||
if(*ViewPortArray_ref(it) == view_port) {
|
||||
ViewPortArray_remove(gui->layers[i], it);
|
||||
furi_assert(layer == GuiLayerMAX);
|
||||
layer = i;
|
||||
} else {
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_assert(layer != GuiLayerMAX);
|
||||
// Return to the top
|
||||
ViewPortArray_push_at(gui->layers[layer], 0, view_port);
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) {
|
||||
furi_assert(gui);
|
||||
|
||||
const CanvasCallbackPair p = {callback, context};
|
||||
|
||||
gui_lock(gui);
|
||||
furi_assert(CanvasCallbackPairArray_count(gui->canvas_callback_pair, p) == 0);
|
||||
CanvasCallbackPairArray_push_back(gui->canvas_callback_pair, p);
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) {
|
||||
furi_assert(gui);
|
||||
|
||||
const CanvasCallbackPair p = {callback, context};
|
||||
|
||||
gui_lock(gui);
|
||||
furi_assert(CanvasCallbackPairArray_count(gui->canvas_callback_pair, p) == 1);
|
||||
CanvasCallbackPairArray_remove_val(gui->canvas_callback_pair, p);
|
||||
gui_unlock(gui);
|
||||
}
|
||||
|
||||
size_t gui_get_framebuffer_size(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
return canvas_get_buffer_size(gui->canvas);
|
||||
}
|
||||
|
||||
void gui_set_lockdown(Gui* gui, bool lockdown) {
|
||||
furi_assert(gui);
|
||||
|
||||
gui_lock(gui);
|
||||
gui->lockdown = lockdown;
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
Gui* gui_alloc() {
|
||||
Gui* gui = malloc(sizeof(Gui));
|
||||
// Thread ID
|
||||
gui->thread_id = furi_thread_get_current_id();
|
||||
// Allocate mutex
|
||||
gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
furi_check(gui->mutex);
|
||||
// Layers
|
||||
for(size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_init(gui->layers[i]);
|
||||
}
|
||||
// Drawing canvas
|
||||
gui->canvas = canvas_init();
|
||||
CanvasCallbackPairArray_init(gui->canvas_callback_pair);
|
||||
|
||||
// Input
|
||||
gui->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
gui->input_events = furi_record_open(RECORD_INPUT_EVENTS);
|
||||
|
||||
furi_check(gui->input_events);
|
||||
furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui);
|
||||
|
||||
return gui;
|
||||
}
|
||||
|
||||
int32_t gui_srv(void* p) {
|
||||
UNUSED(p);
|
||||
Gui* gui = gui_alloc();
|
||||
|
||||
furi_record_create(RECORD_GUI, gui);
|
||||
|
||||
while(1) {
|
||||
uint32_t flags =
|
||||
furi_thread_flags_wait(GUI_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
|
||||
// Process and dispatch input
|
||||
if(flags & GUI_THREAD_FLAG_INPUT) {
|
||||
// Process till queue become empty
|
||||
InputEvent input_event;
|
||||
while(furi_message_queue_get(gui->input_queue, &input_event, 0) == FuriStatusOk) {
|
||||
gui_input(gui, &input_event);
|
||||
}
|
||||
}
|
||||
// Process and dispatch draw call
|
||||
if(flags & GUI_THREAD_FLAG_DRAW) {
|
||||
// Clear flags that arrived on input step
|
||||
furi_thread_flags_clear(GUI_THREAD_FLAG_DRAW);
|
||||
gui_redraw(gui);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* @file gui.h
|
||||
* GUI: main API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "view_port.h"
|
||||
#include "canvas.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Gui layers */
|
||||
typedef enum {
|
||||
GuiLayerDesktop, /**< Desktop layer for internal use. Like fullscreen but with status bar */
|
||||
|
||||
GuiLayerWindow, /**< Window layer, status bar is shown */
|
||||
|
||||
GuiLayerStatusBarLeft, /**< Status bar left-side layer, auto-layout */
|
||||
GuiLayerStatusBarRight, /**< Status bar right-side layer, auto-layout */
|
||||
|
||||
GuiLayerFullscreen, /**< Fullscreen layer, no status bar */
|
||||
|
||||
GuiLayerMAX /**< Don't use or move, special value */
|
||||
} GuiLayer;
|
||||
|
||||
/** Gui Canvas Commit Callback */
|
||||
typedef void (*GuiCanvasCommitCallback)(uint8_t* data, size_t size, void* context);
|
||||
|
||||
#define RECORD_GUI "gui"
|
||||
|
||||
typedef struct Gui Gui;
|
||||
|
||||
/** Add view_port to view_port tree
|
||||
*
|
||||
* @remark thread safe
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param view_port ViewPort instance
|
||||
* @param[in] layer GuiLayer where to place view_port
|
||||
*/
|
||||
void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer);
|
||||
|
||||
/** Remove view_port from rendering tree
|
||||
*
|
||||
* @remark thread safe
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void gui_remove_view_port(Gui* gui, ViewPort* view_port);
|
||||
|
||||
/** Send ViewPort to the front
|
||||
*
|
||||
* Places selected ViewPort to the top of the drawing stack
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port);
|
||||
|
||||
/** Send ViewPort to the back
|
||||
*
|
||||
* Places selected ViewPort to the bottom of the drawing stack
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port);
|
||||
|
||||
/** Add gui canvas commit callback
|
||||
*
|
||||
* This callback will be called upon Canvas commit Callback dispatched from GUI
|
||||
* thread and is time critical
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param callback GuiCanvasCommitCallback
|
||||
* @param context GuiCanvasCommitCallback context
|
||||
*/
|
||||
void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context);
|
||||
|
||||
/** Remove gui canvas commit callback
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param callback GuiCanvasCommitCallback
|
||||
* @param context GuiCanvasCommitCallback context
|
||||
*/
|
||||
void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context);
|
||||
|
||||
/** Get gui canvas frame buffer size
|
||||
* *
|
||||
* @param gui Gui instance
|
||||
* @return size_t size of frame buffer in bytes
|
||||
*/
|
||||
size_t gui_get_framebuffer_size(Gui* gui);
|
||||
|
||||
/** Set lockdown mode
|
||||
*
|
||||
* When lockdown mode is enabled, only GuiLayerDesktop is shown.
|
||||
* This feature prevents services from showing sensitive information when flipper is locked.
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param lockdown bool, true if enabled
|
||||
*/
|
||||
void gui_set_lockdown(Gui* gui, bool lockdown);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @file gui_i.h
|
||||
* GUI: main API internals
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gui.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <m-array.h>
|
||||
#include <m-algo.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "canvas.h"
|
||||
#include "canvas_i.h"
|
||||
#include "view_port.h"
|
||||
#include "view_port_i.h"
|
||||
|
||||
#define GUI_DISPLAY_WIDTH 128
|
||||
#define GUI_DISPLAY_HEIGHT 64
|
||||
|
||||
#define GUI_STATUS_BAR_X 0
|
||||
#define GUI_STATUS_BAR_Y 0
|
||||
#define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH
|
||||
/* 0-1 pixels for upper thin frame
|
||||
* 2-9 pixels for icons (battery, sd card, etc)
|
||||
* 10-12 pixels for lower bold line */
|
||||
#define GUI_STATUS_BAR_HEIGHT 13
|
||||
/* icon itself area (battery, sd card, etc) excluding frame.
|
||||
* painted 2 pixels below GUI_STATUS_BAR_X.
|
||||
*/
|
||||
#define GUI_STATUS_BAR_WORKAREA_HEIGHT 8
|
||||
|
||||
#define GUI_WINDOW_X 0
|
||||
#define GUI_WINDOW_Y GUI_STATUS_BAR_HEIGHT
|
||||
#define GUI_WINDOW_WIDTH GUI_DISPLAY_WIDTH
|
||||
#define GUI_WINDOW_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_WINDOW_Y)
|
||||
|
||||
#define GUI_THREAD_FLAG_DRAW (1 << 0)
|
||||
#define GUI_THREAD_FLAG_INPUT (1 << 1)
|
||||
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT)
|
||||
|
||||
ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST);
|
||||
|
||||
typedef struct {
|
||||
GuiCanvasCommitCallback callback;
|
||||
void* context;
|
||||
} CanvasCallbackPair;
|
||||
|
||||
ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST);
|
||||
|
||||
#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST)
|
||||
|
||||
ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t);
|
||||
|
||||
/** Gui structure */
|
||||
struct Gui {
|
||||
// Thread and lock
|
||||
FuriThreadId thread_id;
|
||||
FuriMutex* mutex;
|
||||
|
||||
// Layers and Canvas
|
||||
bool lockdown;
|
||||
ViewPortArray_t layers[GuiLayerMAX];
|
||||
Canvas* canvas;
|
||||
CanvasCallbackPairArray_t canvas_callback_pair;
|
||||
|
||||
// Input
|
||||
FuriMessageQueue* input_queue;
|
||||
FuriPubSub* input_events;
|
||||
uint8_t ongoing_input;
|
||||
ViewPort* ongoing_input_view_port;
|
||||
};
|
||||
|
||||
ViewPort* gui_view_port_find_enabled(ViewPortArray_t array);
|
||||
|
||||
/** Update GUI, request redraw
|
||||
*
|
||||
* @param gui Gui instance
|
||||
*/
|
||||
void gui_update(Gui* gui);
|
||||
|
||||
void gui_input_events_callback(const void* value, void* ctx);
|
||||
|
||||
void gui_lock(Gui* gui);
|
||||
|
||||
void gui_unlock(Gui* gui);
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "icon_i.h"
|
||||
|
||||
uint8_t icon_get_width(const Icon* instance) {
|
||||
return instance->width;
|
||||
}
|
||||
|
||||
uint8_t icon_get_height(const Icon* instance) {
|
||||
return instance->height;
|
||||
}
|
||||
|
||||
const uint8_t* icon_get_data(const Icon* instance) {
|
||||
return instance->frames[0];
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file icon.h
|
||||
* GUI: Icon API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Icon Icon;
|
||||
|
||||
/** Get icon width
|
||||
*
|
||||
* @param[in] instance pointer to Icon data
|
||||
*
|
||||
* @return width in pixels
|
||||
*/
|
||||
uint8_t icon_get_width(const Icon* instance);
|
||||
|
||||
/** Get icon height
|
||||
*
|
||||
* @param[in] instance pointer to Icon data
|
||||
*
|
||||
* @return height in pixels
|
||||
*/
|
||||
uint8_t icon_get_height(const Icon* instance);
|
||||
|
||||
/** Get Icon XBM bitmap data
|
||||
*
|
||||
* @param[in] instance pointer to Icon data
|
||||
*
|
||||
* @return pointer to XBM bitmap data
|
||||
*/
|
||||
const uint8_t* icon_get_data(const Icon* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "icon_animation_i.h"
|
||||
#include "icon_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
IconAnimation* icon_animation_alloc(const Icon* icon) {
|
||||
furi_assert(icon);
|
||||
IconAnimation* instance = malloc(sizeof(IconAnimation));
|
||||
instance->icon = icon;
|
||||
instance->timer =
|
||||
furi_timer_alloc(icon_animation_timer_callback, FuriTimerTypePeriodic, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void icon_animation_free(IconAnimation* instance) {
|
||||
furi_assert(instance);
|
||||
icon_animation_stop(instance);
|
||||
while(xTimerIsTimerActive(instance->timer) == pdTRUE) furi_delay_tick(1);
|
||||
furi_timer_free(instance->timer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void icon_animation_set_update_callback(
|
||||
IconAnimation* instance,
|
||||
IconAnimationCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
instance->callback = callback;
|
||||
instance->callback_context = context;
|
||||
}
|
||||
|
||||
const uint8_t* icon_animation_get_data(IconAnimation* instance) {
|
||||
return instance->icon->frames[instance->frame];
|
||||
}
|
||||
|
||||
void icon_animation_next_frame(IconAnimation* instance) {
|
||||
furi_assert(instance);
|
||||
instance->frame = (instance->frame + 1) % instance->icon->frame_count;
|
||||
}
|
||||
|
||||
void icon_animation_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
IconAnimation* instance = context;
|
||||
|
||||
if(!instance->animating) return;
|
||||
|
||||
icon_animation_next_frame(instance);
|
||||
if(instance->callback) {
|
||||
instance->callback(instance, instance->callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t icon_animation_get_width(IconAnimation* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->icon->width;
|
||||
}
|
||||
|
||||
uint8_t icon_animation_get_height(IconAnimation* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->icon->height;
|
||||
}
|
||||
|
||||
void icon_animation_start(IconAnimation* instance) {
|
||||
furi_assert(instance);
|
||||
if(!instance->animating) {
|
||||
instance->animating = true;
|
||||
furi_assert(instance->icon->frame_rate);
|
||||
furi_check(
|
||||
xTimerChangePeriod(
|
||||
instance->timer,
|
||||
(furi_kernel_get_tick_frequency() / instance->icon->frame_rate),
|
||||
portMAX_DELAY) == pdPASS);
|
||||
}
|
||||
}
|
||||
|
||||
void icon_animation_stop(IconAnimation* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->animating) {
|
||||
instance->animating = false;
|
||||
furi_check(xTimerStop(instance->timer, portMAX_DELAY) == pdPASS);
|
||||
instance->frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool icon_animation_is_last_frame(IconAnimation* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->icon->frame_count - instance->frame <= 1;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @file icon_animation.h
|
||||
* GUI: IconAnimation API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Icon Animation */
|
||||
typedef struct IconAnimation IconAnimation;
|
||||
|
||||
/** Icon Animation Callback. Used for update notification */
|
||||
typedef void (*IconAnimationCallback)(IconAnimation* instance, void* context);
|
||||
|
||||
/** Allocate icon animation instance with const icon data.
|
||||
*
|
||||
* always returns Icon or stops system if not enough memory
|
||||
*
|
||||
* @param[in] icon pointer to Icon data
|
||||
*
|
||||
* @return IconAnimation instance
|
||||
*/
|
||||
IconAnimation* icon_animation_alloc(const Icon* icon);
|
||||
|
||||
/** Release icon animation instance
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
*/
|
||||
void icon_animation_free(IconAnimation* instance);
|
||||
|
||||
/** Set IconAnimation update callback
|
||||
*
|
||||
* Normally you do not need to use this function, use view_tie_icon_animation
|
||||
* instead.
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
* @param[in] callback IconAnimationCallback
|
||||
* @param context callback context
|
||||
*/
|
||||
void icon_animation_set_update_callback(
|
||||
IconAnimation* instance,
|
||||
IconAnimationCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Get icon animation width
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
*
|
||||
* @return width in pixels
|
||||
*/
|
||||
uint8_t icon_animation_get_width(IconAnimation* instance);
|
||||
|
||||
/** Get icon animation height
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
*
|
||||
* @return height in pixels
|
||||
*/
|
||||
uint8_t icon_animation_get_height(IconAnimation* instance);
|
||||
|
||||
/** Start icon animation
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
*/
|
||||
void icon_animation_start(IconAnimation* instance);
|
||||
|
||||
/** Stop icon animation
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
*/
|
||||
void icon_animation_stop(IconAnimation* instance);
|
||||
|
||||
/** Returns true if current frame is a last one
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
*
|
||||
* @return true if last frame
|
||||
*/
|
||||
bool icon_animation_is_last_frame(IconAnimation* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file icon_animation_i.h
|
||||
* GUI: internal IconAnimation API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "icon_animation.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
struct IconAnimation {
|
||||
const Icon* icon;
|
||||
uint8_t frame;
|
||||
bool animating;
|
||||
FuriTimer* timer;
|
||||
IconAnimationCallback callback;
|
||||
void* callback_context;
|
||||
};
|
||||
|
||||
/** Get pointer to current frame data
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
*
|
||||
* @return pointer to current frame XBM bitmap data
|
||||
*/
|
||||
const uint8_t* icon_animation_get_data(IconAnimation* instance);
|
||||
|
||||
/** Advance to next frame
|
||||
*
|
||||
* @param instance IconAnimation instance
|
||||
*/
|
||||
void icon_animation_next_frame(IconAnimation* instance);
|
||||
|
||||
/** IconAnimation timer callback
|
||||
*
|
||||
* @param context pointer to IconAnimation
|
||||
*/
|
||||
void icon_animation_timer_callback(void* context);
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @file icon_i.h
|
||||
* GUI: internal Icon API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "icon.h"
|
||||
|
||||
struct Icon {
|
||||
const uint8_t width;
|
||||
const uint8_t height;
|
||||
const uint8_t frame_count;
|
||||
const uint8_t frame_rate;
|
||||
const uint8_t* const* frames;
|
||||
};
|
||||
@@ -0,0 +1,343 @@
|
||||
#include "button_menu.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/elements.h"
|
||||
#include "input/input.h"
|
||||
#include <m-array.h>
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define ITEM_FIRST_OFFSET 17
|
||||
#define ITEM_NEXT_OFFSET 4
|
||||
#define ITEM_HEIGHT 14
|
||||
#define ITEM_WIDTH 64
|
||||
#define BUTTONS_PER_SCREEN 6
|
||||
|
||||
struct ButtonMenuItem {
|
||||
const char* label;
|
||||
int32_t index;
|
||||
ButtonMenuItemCallback callback;
|
||||
ButtonMenuItemType type;
|
||||
void* callback_context;
|
||||
};
|
||||
|
||||
ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST);
|
||||
|
||||
struct ButtonMenu {
|
||||
View* view;
|
||||
bool freeze_input;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
ButtonMenuItemArray_t items;
|
||||
uint8_t position;
|
||||
const char* header;
|
||||
} ButtonMenuModel;
|
||||
|
||||
static void button_menu_draw_control_button(
|
||||
Canvas* canvas,
|
||||
uint8_t item_position,
|
||||
const char* text,
|
||||
bool selected) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(text);
|
||||
|
||||
uint8_t item_x = 0;
|
||||
uint8_t item_y = ITEM_FIRST_OFFSET + (item_position * (ITEM_HEIGHT + ITEM_NEXT_OFFSET));
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
if(selected) {
|
||||
elements_slightly_rounded_box(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
item_x + (ITEM_WIDTH / 2),
|
||||
item_y + (ITEM_HEIGHT / 2),
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
text);
|
||||
}
|
||||
|
||||
static void button_menu_draw_common_button(
|
||||
Canvas* canvas,
|
||||
uint8_t item_position,
|
||||
const char* text,
|
||||
bool selected) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(text);
|
||||
|
||||
uint8_t item_x = 0;
|
||||
uint8_t item_y = ITEM_FIRST_OFFSET + (item_position * (ITEM_HEIGHT + ITEM_NEXT_OFFSET));
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
if(selected) {
|
||||
canvas_draw_rbox(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
canvas_draw_rframe(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5);
|
||||
}
|
||||
|
||||
string_t disp_str;
|
||||
string_init_set_str(disp_str, text);
|
||||
elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6);
|
||||
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
item_x + (ITEM_WIDTH / 2),
|
||||
item_y + (ITEM_HEIGHT / 2),
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
string_get_cstr(disp_str));
|
||||
|
||||
string_clear(disp_str);
|
||||
}
|
||||
|
||||
static void button_menu_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(_model);
|
||||
|
||||
ButtonMenuModel* model = (ButtonMenuModel*)_model;
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
uint8_t item_position = 0;
|
||||
int8_t active_screen = model->position / BUTTONS_PER_SCREEN;
|
||||
size_t items_size = ButtonMenuItemArray_size(model->items);
|
||||
int8_t max_screen = ((int16_t)items_size - 1) / BUTTONS_PER_SCREEN;
|
||||
ButtonMenuItemArray_it_t it;
|
||||
|
||||
if(active_screen > 0) {
|
||||
canvas_draw_icon(canvas, 28, 1, &I_InfraredArrowUp_4x8);
|
||||
}
|
||||
|
||||
if(max_screen > active_screen) {
|
||||
canvas_draw_icon(canvas, 28, 123, &I_InfraredArrowDown_4x8);
|
||||
}
|
||||
|
||||
if(model->header) {
|
||||
string_t disp_str;
|
||||
string_init_set_str(disp_str, model->header);
|
||||
elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 32, 10, AlignCenter, AlignCenter, string_get_cstr(disp_str));
|
||||
string_clear(disp_str);
|
||||
}
|
||||
|
||||
for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it);
|
||||
ButtonMenuItemArray_next(it), ++item_position) {
|
||||
if(active_screen == (item_position / BUTTONS_PER_SCREEN)) {
|
||||
if(ButtonMenuItemArray_cref(it)->type == ButtonMenuItemTypeControl) {
|
||||
button_menu_draw_control_button(
|
||||
canvas,
|
||||
item_position % BUTTONS_PER_SCREEN,
|
||||
ButtonMenuItemArray_cref(it)->label,
|
||||
(item_position == model->position));
|
||||
} else if(ButtonMenuItemArray_cref(it)->type == ButtonMenuItemTypeCommon) {
|
||||
button_menu_draw_common_button(
|
||||
canvas,
|
||||
item_position % BUTTONS_PER_SCREEN,
|
||||
ButtonMenuItemArray_cref(it)->label,
|
||||
(item_position == model->position));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void button_menu_process_up(ButtonMenu* button_menu) {
|
||||
furi_assert(button_menu);
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
if(model->position > 0) {
|
||||
model->position--;
|
||||
} else {
|
||||
model->position = ButtonMenuItemArray_size(model->items) - 1;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void button_menu_process_down(ButtonMenu* button_menu) {
|
||||
furi_assert(button_menu);
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
if(model->position < (ButtonMenuItemArray_size(model->items) - 1)) {
|
||||
model->position++;
|
||||
} else {
|
||||
model->position = 0;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void button_menu_process_ok(ButtonMenu* button_menu, InputType type) {
|
||||
furi_assert(button_menu);
|
||||
|
||||
ButtonMenuItem* item = NULL;
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
if(model->position < (ButtonMenuItemArray_size(model->items))) {
|
||||
item = ButtonMenuItemArray_get(model->items, model->position);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if(item) {
|
||||
if(item->type == ButtonMenuItemTypeControl) {
|
||||
if(type == InputTypeShort) {
|
||||
if(item && item->callback) {
|
||||
item->callback(item->callback_context, item->index, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(item->type == ButtonMenuItemTypeCommon) {
|
||||
if((type == InputTypePress) || (type == InputTypeRelease)) {
|
||||
if(item && item->callback) {
|
||||
item->callback(item->callback_context, item->index, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool button_menu_view_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
|
||||
ButtonMenu* button_menu = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->key == InputKeyOk) {
|
||||
if((event->type == InputTypeRelease) || (event->type == InputTypePress)) {
|
||||
consumed = true;
|
||||
button_menu->freeze_input = (event->type == InputTypePress);
|
||||
button_menu_process_ok(button_menu, event->type);
|
||||
} else if(event->type == InputTypeShort) {
|
||||
consumed = true;
|
||||
button_menu_process_ok(button_menu, event->type);
|
||||
}
|
||||
}
|
||||
|
||||
if(!button_menu->freeze_input &&
|
||||
((event->type == InputTypeRepeat) || (event->type == InputTypeShort))) {
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
consumed = true;
|
||||
button_menu_process_up(button_menu);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
consumed = true;
|
||||
button_menu_process_down(button_menu);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
View* button_menu_get_view(ButtonMenu* button_menu) {
|
||||
furi_assert(button_menu);
|
||||
return button_menu->view;
|
||||
}
|
||||
|
||||
void button_menu_reset(ButtonMenu* button_menu) {
|
||||
furi_assert(button_menu);
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
ButtonMenuItemArray_reset(model->items);
|
||||
model->position = 0;
|
||||
model->header = NULL;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void button_menu_set_header(ButtonMenu* button_menu, const char* header) {
|
||||
furi_assert(button_menu);
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
model->header = header;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
ButtonMenuItem* button_menu_add_item(
|
||||
ButtonMenu* button_menu,
|
||||
const char* label,
|
||||
int32_t index,
|
||||
ButtonMenuItemCallback callback,
|
||||
ButtonMenuItemType type,
|
||||
void* callback_context) {
|
||||
ButtonMenuItem* item = NULL;
|
||||
furi_assert(label);
|
||||
furi_assert(button_menu);
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
item = ButtonMenuItemArray_push_new(model->items);
|
||||
item->label = label;
|
||||
item->index = index;
|
||||
item->type = type;
|
||||
item->callback = callback;
|
||||
item->callback_context = callback_context;
|
||||
return true;
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
ButtonMenu* button_menu_alloc(void) {
|
||||
ButtonMenu* button_menu = malloc(sizeof(ButtonMenu));
|
||||
button_menu->view = view_alloc();
|
||||
view_set_orientation(button_menu->view, ViewOrientationVertical);
|
||||
view_set_context(button_menu->view, button_menu);
|
||||
view_allocate_model(button_menu->view, ViewModelTypeLocking, sizeof(ButtonMenuModel));
|
||||
view_set_draw_callback(button_menu->view, button_menu_view_draw_callback);
|
||||
view_set_input_callback(button_menu->view, button_menu_view_input_callback);
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
ButtonMenuItemArray_init(model->items);
|
||||
model->position = 0;
|
||||
model->header = NULL;
|
||||
return true;
|
||||
});
|
||||
|
||||
button_menu->freeze_input = false;
|
||||
return button_menu;
|
||||
}
|
||||
|
||||
void button_menu_free(ButtonMenu* button_menu) {
|
||||
furi_assert(button_menu);
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
ButtonMenuItemArray_clear(model->items);
|
||||
return true;
|
||||
});
|
||||
view_free(button_menu->view);
|
||||
free(button_menu);
|
||||
}
|
||||
|
||||
void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index) {
|
||||
furi_assert(button_menu);
|
||||
|
||||
with_view_model(
|
||||
button_menu->view, (ButtonMenuModel * model) {
|
||||
uint8_t item_position = 0;
|
||||
ButtonMenuItemArray_it_t it;
|
||||
for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it);
|
||||
ButtonMenuItemArray_next(it), ++item_position) {
|
||||
if((uint32_t)ButtonMenuItemArray_cref(it)->index == index) {
|
||||
model->position = item_position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @file button_menu.h
|
||||
* GUI: ButtonMenu view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** ButtonMenu anonymous structure */
|
||||
typedef struct ButtonMenu ButtonMenu;
|
||||
|
||||
/** ButtonMenuItem anonymous structure */
|
||||
typedef struct ButtonMenuItem ButtonMenuItem;
|
||||
|
||||
/** Callback for any button menu actions */
|
||||
typedef void (*ButtonMenuItemCallback)(void* context, int32_t index, InputType type);
|
||||
|
||||
/** Type of button. Difference in drawing buttons. */
|
||||
typedef enum {
|
||||
ButtonMenuItemTypeCommon,
|
||||
ButtonMenuItemTypeControl,
|
||||
} ButtonMenuItemType;
|
||||
|
||||
/** Get button menu view
|
||||
*
|
||||
* @param button_menu ButtonMenu instance
|
||||
*
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* button_menu_get_view(ButtonMenu* button_menu);
|
||||
|
||||
/** Clean button menu
|
||||
*
|
||||
* @param button_menu ButtonMenu instance
|
||||
*/
|
||||
void button_menu_reset(ButtonMenu* button_menu);
|
||||
|
||||
/** Add item to button menu instance
|
||||
*
|
||||
* @param button_menu ButtonMenu instance
|
||||
* @param label text inside new button
|
||||
* @param index value to distinct between buttons inside
|
||||
* ButtonMenuItemCallback
|
||||
* @param callback The callback
|
||||
* @param type type of button to create. Differ by button
|
||||
* drawing. Control buttons have no frames, and
|
||||
* have more squared borders.
|
||||
* @param callback_context The callback context
|
||||
*
|
||||
* @return pointer to just-created item
|
||||
*/
|
||||
ButtonMenuItem* button_menu_add_item(
|
||||
ButtonMenu* button_menu,
|
||||
const char* label,
|
||||
int32_t index,
|
||||
ButtonMenuItemCallback callback,
|
||||
ButtonMenuItemType type,
|
||||
void* callback_context);
|
||||
|
||||
/** Allocate and initialize new instance of ButtonMenu model
|
||||
*
|
||||
* @return just-created ButtonMenu model
|
||||
*/
|
||||
ButtonMenu* button_menu_alloc(void);
|
||||
|
||||
/** Free ButtonMenu element
|
||||
*
|
||||
* @param button_menu ButtonMenu instance
|
||||
*/
|
||||
void button_menu_free(ButtonMenu* button_menu);
|
||||
|
||||
/** Set ButtonMenu header on top of canvas
|
||||
*
|
||||
* @param button_menu ButtonMenu instance
|
||||
* @param header header on the top of button menu
|
||||
*/
|
||||
void button_menu_set_header(ButtonMenu* button_menu, const char* header);
|
||||
|
||||
/** Set selected item
|
||||
*
|
||||
* @param button_menu ButtonMenu instance
|
||||
* @param index index of ButtonMenu to be selected
|
||||
*/
|
||||
void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,383 @@
|
||||
#include "button_panel.h"
|
||||
#include "furi_hal_resources.h"
|
||||
#include "gui/canvas.h"
|
||||
#include <m-array.h>
|
||||
#include <m-i-list.h>
|
||||
#include <m-list.h>
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
// uint16_t to support multi-screen, wide button panel
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
Font font;
|
||||
const char* str;
|
||||
} LabelElement;
|
||||
|
||||
LIST_DEF(LabelList, LabelElement, M_POD_OPLIST)
|
||||
#define M_OPL_LabelList_t() LIST_OPLIST(LabelList)
|
||||
|
||||
typedef struct {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
const Icon* name;
|
||||
const Icon* name_selected;
|
||||
} IconElement;
|
||||
|
||||
typedef struct ButtonItem {
|
||||
uint32_t index;
|
||||
ButtonItemCallback callback;
|
||||
IconElement icon;
|
||||
void* callback_context;
|
||||
} ButtonItem;
|
||||
|
||||
ARRAY_DEF(ButtonArray, ButtonItem*, M_PTR_OPLIST);
|
||||
#define M_OPL_ButtonArray_t() ARRAY_OPLIST(ButtonArray, M_PTR_OPLIST)
|
||||
ARRAY_DEF(ButtonMatrix, ButtonArray_t);
|
||||
#define M_OPL_ButtonMatrix_t() ARRAY_OPLIST(ButtonMatrix, M_OPL_ButtonArray_t())
|
||||
|
||||
struct ButtonPanel {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
ButtonMatrix_t button_matrix;
|
||||
LabelList_t labels;
|
||||
uint16_t reserve_x;
|
||||
uint16_t reserve_y;
|
||||
uint16_t selected_item_x;
|
||||
uint16_t selected_item_y;
|
||||
} ButtonPanelModel;
|
||||
|
||||
static ButtonItem** button_panel_get_item(ButtonPanelModel* model, size_t x, size_t y);
|
||||
static void button_panel_process_up(ButtonPanel* button_panel);
|
||||
static void button_panel_process_down(ButtonPanel* button_panel);
|
||||
static void button_panel_process_left(ButtonPanel* button_panel);
|
||||
static void button_panel_process_right(ButtonPanel* button_panel);
|
||||
static void button_panel_process_ok(ButtonPanel* button_panel);
|
||||
static void button_panel_view_draw_callback(Canvas* canvas, void* _model);
|
||||
static bool button_panel_view_input_callback(InputEvent* event, void* context);
|
||||
|
||||
ButtonPanel* button_panel_alloc() {
|
||||
ButtonPanel* button_panel = malloc(sizeof(ButtonPanel));
|
||||
button_panel->view = view_alloc();
|
||||
view_set_orientation(button_panel->view, ViewOrientationVertical);
|
||||
view_set_context(button_panel->view, button_panel);
|
||||
view_allocate_model(button_panel->view, ViewModelTypeLocking, sizeof(ButtonPanelModel));
|
||||
view_set_draw_callback(button_panel->view, button_panel_view_draw_callback);
|
||||
view_set_input_callback(button_panel->view, button_panel_view_input_callback);
|
||||
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
model->reserve_x = 0;
|
||||
model->reserve_y = 0;
|
||||
model->selected_item_x = 0;
|
||||
model->selected_item_y = 0;
|
||||
ButtonMatrix_init(model->button_matrix);
|
||||
LabelList_init(model->labels);
|
||||
return true;
|
||||
});
|
||||
|
||||
return button_panel;
|
||||
}
|
||||
|
||||
void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t reserve_y) {
|
||||
furi_check(reserve_x > 0);
|
||||
furi_check(reserve_y > 0);
|
||||
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
model->reserve_x = reserve_x;
|
||||
model->reserve_y = reserve_y;
|
||||
ButtonMatrix_reserve(model->button_matrix, model->reserve_y);
|
||||
for(size_t i = 0; i > model->reserve_y; ++i) {
|
||||
ButtonArray_t* array = ButtonMatrix_get(model->button_matrix, i);
|
||||
ButtonArray_init(*array);
|
||||
ButtonArray_reserve(*array, reserve_x);
|
||||
// TODO: do we need to clear allocated memory of ptr-s to ButtonItem ??
|
||||
}
|
||||
LabelList_init(model->labels);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void button_panel_free(ButtonPanel* button_panel) {
|
||||
furi_assert(button_panel);
|
||||
|
||||
button_panel_reset(button_panel);
|
||||
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
LabelList_clear(model->labels);
|
||||
ButtonMatrix_clear(model->button_matrix);
|
||||
return true;
|
||||
});
|
||||
|
||||
view_free(button_panel->view);
|
||||
free(button_panel);
|
||||
}
|
||||
|
||||
void button_panel_reset(ButtonPanel* button_panel) {
|
||||
furi_assert(button_panel);
|
||||
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
for(size_t x = 0; x < model->reserve_x; ++x) {
|
||||
for(size_t y = 0; y < model->reserve_y; ++y) {
|
||||
ButtonItem** button_item = button_panel_get_item(model, x, y);
|
||||
free(*button_item);
|
||||
*button_item = NULL;
|
||||
}
|
||||
}
|
||||
model->reserve_x = 0;
|
||||
model->reserve_y = 0;
|
||||
LabelList_reset(model->labels);
|
||||
ButtonMatrix_reset(model->button_matrix);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static ButtonItem** button_panel_get_item(ButtonPanelModel* model, size_t x, size_t y) {
|
||||
furi_assert(model);
|
||||
|
||||
furi_check(x < model->reserve_x);
|
||||
furi_check(y < model->reserve_y);
|
||||
ButtonArray_t* button_array = ButtonMatrix_safe_get(model->button_matrix, x);
|
||||
ButtonItem** button_item = ButtonArray_safe_get(*button_array, y);
|
||||
return button_item;
|
||||
}
|
||||
|
||||
void button_panel_add_item(
|
||||
ButtonPanel* button_panel,
|
||||
uint32_t index,
|
||||
uint16_t matrix_place_x,
|
||||
uint16_t matrix_place_y,
|
||||
uint16_t x,
|
||||
uint16_t y,
|
||||
const Icon* icon_name,
|
||||
const Icon* icon_name_selected,
|
||||
ButtonItemCallback callback,
|
||||
void* callback_context) {
|
||||
furi_assert(button_panel);
|
||||
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
ButtonItem** button_item_ptr =
|
||||
button_panel_get_item(model, matrix_place_x, matrix_place_y);
|
||||
furi_check(*button_item_ptr == NULL);
|
||||
*button_item_ptr = malloc(sizeof(ButtonItem));
|
||||
ButtonItem* button_item = *button_item_ptr;
|
||||
button_item->callback = callback;
|
||||
button_item->callback_context = callback_context;
|
||||
button_item->icon.x = x;
|
||||
button_item->icon.y = y;
|
||||
button_item->icon.name = icon_name;
|
||||
button_item->icon.name_selected = icon_name_selected;
|
||||
button_item->index = index;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
View* button_panel_get_view(ButtonPanel* button_panel) {
|
||||
furi_assert(button_panel);
|
||||
return button_panel->view;
|
||||
}
|
||||
|
||||
static void button_panel_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(_model);
|
||||
|
||||
ButtonPanelModel* model = _model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
for(size_t x = 0; x < model->reserve_x; ++x) {
|
||||
for(size_t y = 0; y < model->reserve_y; ++y) {
|
||||
ButtonItem* button_item = *button_panel_get_item(model, x, y);
|
||||
const Icon* icon_name = button_item->icon.name;
|
||||
if((model->selected_item_x == x) && (model->selected_item_y == y)) {
|
||||
icon_name = button_item->icon.name_selected;
|
||||
}
|
||||
canvas_draw_icon(canvas, button_item->icon.x, button_item->icon.y, icon_name);
|
||||
}
|
||||
}
|
||||
|
||||
for
|
||||
M_EACH(label, model->labels, LabelList_t) {
|
||||
canvas_set_font(canvas, label->font);
|
||||
canvas_draw_str(canvas, label->x, label->y, label->str);
|
||||
}
|
||||
}
|
||||
|
||||
static void button_panel_process_down(ButtonPanel* button_panel) {
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
uint16_t new_selected_item_x = model->selected_item_x;
|
||||
uint16_t new_selected_item_y = model->selected_item_y;
|
||||
size_t i;
|
||||
|
||||
if(new_selected_item_y >= (model->reserve_y - 1)) return false;
|
||||
|
||||
++new_selected_item_y;
|
||||
|
||||
for(i = 0; i < model->reserve_x; ++i) {
|
||||
new_selected_item_x = (model->selected_item_x + i) % model->reserve_x;
|
||||
if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i == model->reserve_x) return false;
|
||||
|
||||
model->selected_item_x = new_selected_item_x;
|
||||
model->selected_item_y = new_selected_item_y;
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void button_panel_process_up(ButtonPanel* button_panel) {
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
size_t new_selected_item_x = model->selected_item_x;
|
||||
size_t new_selected_item_y = model->selected_item_y;
|
||||
size_t i;
|
||||
|
||||
if(new_selected_item_y <= 0) return false;
|
||||
|
||||
--new_selected_item_y;
|
||||
|
||||
for(i = 0; i < model->reserve_x; ++i) {
|
||||
new_selected_item_x = (model->selected_item_x + i) % model->reserve_x;
|
||||
if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i == model->reserve_x) return false;
|
||||
|
||||
model->selected_item_x = new_selected_item_x;
|
||||
model->selected_item_y = new_selected_item_y;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void button_panel_process_left(ButtonPanel* button_panel) {
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
size_t new_selected_item_x = model->selected_item_x;
|
||||
size_t new_selected_item_y = model->selected_item_y;
|
||||
size_t i;
|
||||
|
||||
if(new_selected_item_x <= 0) return false;
|
||||
|
||||
--new_selected_item_x;
|
||||
|
||||
for(i = 0; i < model->reserve_y; ++i) {
|
||||
new_selected_item_y = (model->selected_item_y + i) % model->reserve_y;
|
||||
if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i == model->reserve_y) return false;
|
||||
|
||||
model->selected_item_x = new_selected_item_x;
|
||||
model->selected_item_y = new_selected_item_y;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void button_panel_process_right(ButtonPanel* button_panel) {
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
uint16_t new_selected_item_x = model->selected_item_x;
|
||||
uint16_t new_selected_item_y = model->selected_item_y;
|
||||
size_t i;
|
||||
|
||||
if(new_selected_item_x >= (model->reserve_x - 1)) return false;
|
||||
|
||||
++new_selected_item_x;
|
||||
|
||||
for(i = 0; i < model->reserve_y; ++i) {
|
||||
new_selected_item_y = (model->selected_item_y + i) % model->reserve_y;
|
||||
if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i == model->reserve_y) return false;
|
||||
|
||||
model->selected_item_x = new_selected_item_x;
|
||||
model->selected_item_y = new_selected_item_y;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void button_panel_process_ok(ButtonPanel* button_panel) {
|
||||
ButtonItem* button_item = NULL;
|
||||
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
button_item =
|
||||
*button_panel_get_item(model, model->selected_item_x, model->selected_item_y);
|
||||
return true;
|
||||
});
|
||||
|
||||
if(button_item && button_item->callback) {
|
||||
button_item->callback(button_item->callback_context, button_item->index);
|
||||
}
|
||||
}
|
||||
|
||||
static bool button_panel_view_input_callback(InputEvent* event, void* context) {
|
||||
ButtonPanel* button_panel = context;
|
||||
furi_assert(button_panel);
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
consumed = true;
|
||||
button_panel_process_up(button_panel);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
consumed = true;
|
||||
button_panel_process_down(button_panel);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
consumed = true;
|
||||
button_panel_process_left(button_panel);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
consumed = true;
|
||||
button_panel_process_right(button_panel);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
consumed = true;
|
||||
button_panel_process_ok(button_panel);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void button_panel_add_label(
|
||||
ButtonPanel* button_panel,
|
||||
uint16_t x,
|
||||
uint16_t y,
|
||||
Font font,
|
||||
const char* label_str) {
|
||||
furi_assert(button_panel);
|
||||
|
||||
with_view_model(
|
||||
button_panel->view, (ButtonPanelModel * model) {
|
||||
LabelElement* label = LabelList_push_raw(model->labels);
|
||||
label->x = x;
|
||||
label->y = y;
|
||||
label->font = font;
|
||||
label->str = label_str;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @file button_panel.h
|
||||
* GUI: ButtonPanel view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Button panel module descriptor */
|
||||
typedef struct ButtonPanel ButtonPanel;
|
||||
|
||||
/** Callback type to call for handling selecting button_panel items */
|
||||
typedef void (*ButtonItemCallback)(void* context, uint32_t index);
|
||||
|
||||
/** Allocate new button_panel module.
|
||||
*
|
||||
* @return ButtonPanel instance
|
||||
*/
|
||||
ButtonPanel* button_panel_alloc(void);
|
||||
|
||||
/** Free button_panel module.
|
||||
*
|
||||
* @param button_panel ButtonPanel instance
|
||||
*/
|
||||
void button_panel_free(ButtonPanel* button_panel);
|
||||
|
||||
/** Free items from button_panel module. Preallocated matrix stays unchanged.
|
||||
*
|
||||
* @param button_panel ButtonPanel instance
|
||||
*/
|
||||
void button_panel_reset(ButtonPanel* button_panel);
|
||||
|
||||
/** Reserve space for adding items.
|
||||
*
|
||||
* One does not simply use button_panel_add_item() without this function. It
|
||||
* should be allocated space for it first.
|
||||
*
|
||||
* @param button_panel ButtonPanel instance
|
||||
* @param reserve_x number of columns in button_panel
|
||||
* @param reserve_y number of rows in button_panel
|
||||
*/
|
||||
void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t reserve_y);
|
||||
|
||||
/** Add item to button_panel module.
|
||||
*
|
||||
* Have to set element in bounds of allocated size by X and by Y.
|
||||
*
|
||||
* @param button_panel ButtonPanel instance
|
||||
* @param index value to pass to callback
|
||||
* @param matrix_place_x coordinates by x-axis on virtual grid, it
|
||||
* is only used for naviagation
|
||||
* @param matrix_place_y coordinates by y-axis on virtual grid, it
|
||||
* is only used for naviagation
|
||||
* @param x x-coordinate to draw icon on
|
||||
* @param y y-coordinate to draw icon on
|
||||
* @param icon_name name of the icon to draw
|
||||
* @param icon_name_selected name of the icon to draw when current
|
||||
* element is selected
|
||||
* @param callback function to call when specific element is
|
||||
* selected (pressed Ok on selected item)
|
||||
* @param callback_context context to pass to callback
|
||||
*/
|
||||
void button_panel_add_item(
|
||||
ButtonPanel* button_panel,
|
||||
uint32_t index,
|
||||
uint16_t matrix_place_x,
|
||||
uint16_t matrix_place_y,
|
||||
uint16_t x,
|
||||
uint16_t y,
|
||||
const Icon* icon_name,
|
||||
const Icon* icon_name_selected,
|
||||
ButtonItemCallback callback,
|
||||
void* callback_context);
|
||||
|
||||
/** Get button_panel view.
|
||||
*
|
||||
* @param button_panel ButtonPanel instance
|
||||
*
|
||||
* @return acquired view
|
||||
*/
|
||||
View* button_panel_get_view(ButtonPanel* button_panel);
|
||||
|
||||
/** Add label to button_panel module.
|
||||
*
|
||||
* @param button_panel ButtonPanel instance
|
||||
* @param x x-coordinate to place label
|
||||
* @param y y-coordinate to place label
|
||||
* @param font font to write label with
|
||||
* @param label_str string label to write
|
||||
*/
|
||||
void button_panel_add_label(
|
||||
ButtonPanel* button_panel,
|
||||
uint16_t x,
|
||||
uint16_t y,
|
||||
Font font,
|
||||
const char* label_str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,781 @@
|
||||
#include "byte_input.h"
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
struct ByteInput {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const uint8_t value;
|
||||
const uint8_t x;
|
||||
const uint8_t y;
|
||||
} ByteInputKey;
|
||||
|
||||
typedef struct {
|
||||
const char* header;
|
||||
uint8_t* bytes;
|
||||
uint8_t bytes_count;
|
||||
|
||||
ByteInputCallback input_callback;
|
||||
ByteChangedCallback changed_callback;
|
||||
void* callback_context;
|
||||
|
||||
bool selected_high_nibble;
|
||||
uint8_t selected_byte;
|
||||
int8_t selected_row; // row -1 - input, row 0 & 1 - keyboard
|
||||
uint8_t selected_column;
|
||||
uint8_t first_visible_byte;
|
||||
} ByteInputModel;
|
||||
|
||||
static const uint8_t keyboard_origin_x = 7;
|
||||
static const uint8_t keyboard_origin_y = 31;
|
||||
static const uint8_t keyboard_row_count = 2;
|
||||
static const uint8_t enter_symbol = '\r';
|
||||
static const uint8_t backspace_symbol = '\b';
|
||||
static const uint8_t max_drawable_bytes = 8;
|
||||
|
||||
static const ByteInputKey keyboard_keys_row_1[] = {
|
||||
{'0', 0, 12},
|
||||
{'1', 11, 12},
|
||||
{'2', 22, 12},
|
||||
{'3', 33, 12},
|
||||
{'4', 44, 12},
|
||||
{'5', 55, 12},
|
||||
{'6', 66, 12},
|
||||
{'7', 77, 12},
|
||||
{backspace_symbol, 103, 4},
|
||||
};
|
||||
|
||||
static const ByteInputKey keyboard_keys_row_2[] = {
|
||||
{'8', 0, 26},
|
||||
{'9', 11, 26},
|
||||
{'A', 22, 26},
|
||||
{'B', 33, 26},
|
||||
{'C', 44, 26},
|
||||
{'D', 55, 26},
|
||||
{'E', 66, 26},
|
||||
{'F', 77, 26},
|
||||
{enter_symbol, 95, 17},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get row size
|
||||
*
|
||||
* @param row_index Index of row
|
||||
* @return uint8_t Row size
|
||||
*/
|
||||
static uint8_t byte_input_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(ByteInputKey);
|
||||
break;
|
||||
case 2:
|
||||
row_size = sizeof(keyboard_keys_row_2) / sizeof(ByteInputKey);
|
||||
break;
|
||||
}
|
||||
|
||||
return row_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get row pointer
|
||||
*
|
||||
* @param row_index Index of row
|
||||
* @return const ByteInputKey* Row pointer
|
||||
*/
|
||||
static const ByteInputKey* byte_input_get_row(uint8_t row_index) {
|
||||
const ByteInputKey* row = NULL;
|
||||
|
||||
switch(row_index + 1) {
|
||||
case 1:
|
||||
row = keyboard_keys_row_1;
|
||||
break;
|
||||
case 2:
|
||||
row = keyboard_keys_row_2;
|
||||
break;
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get text from nibble
|
||||
*
|
||||
* @param byte byte value
|
||||
* @param high_nibble Get from high nibble, otherwise low nibble
|
||||
* @return char nibble text
|
||||
*/
|
||||
static char byte_input_get_nibble_text(uint8_t byte, bool high_nibble) {
|
||||
if(high_nibble) {
|
||||
byte = byte >> 4;
|
||||
}
|
||||
byte = byte & 0x0F;
|
||||
|
||||
switch(byte & 0x0F) {
|
||||
case 0x0:
|
||||
case 0x1:
|
||||
case 0x2:
|
||||
case 0x3:
|
||||
case 0x4:
|
||||
case 0x5:
|
||||
case 0x6:
|
||||
case 0x7:
|
||||
case 0x8:
|
||||
case 0x9:
|
||||
byte = byte + '0';
|
||||
break;
|
||||
case 0xA:
|
||||
case 0xB:
|
||||
case 0xC:
|
||||
case 0xD:
|
||||
case 0xE:
|
||||
case 0xF:
|
||||
byte = byte - 0xA + 'A';
|
||||
break;
|
||||
default:
|
||||
byte = '!';
|
||||
break;
|
||||
}
|
||||
|
||||
return byte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw input box (common view)
|
||||
*
|
||||
* @param canvas
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) {
|
||||
const uint8_t text_x = 8;
|
||||
const uint8_t text_y = 25;
|
||||
|
||||
elements_slightly_rounded_frame(canvas, 6, 14, 116, 15);
|
||||
|
||||
canvas_draw_icon(canvas, 2, 19, &I_ButtonLeftSmall_3x5);
|
||||
canvas_draw_icon(canvas, 123, 19, &I_ButtonRightSmall_3x5);
|
||||
|
||||
for(uint8_t i = model->first_visible_byte;
|
||||
i < model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes);
|
||||
i++) {
|
||||
uint8_t byte_position = i - model->first_visible_byte;
|
||||
|
||||
if(i == model->selected_byte) {
|
||||
canvas_draw_frame(canvas, text_x + byte_position * 14, text_y - 9, 15, 11);
|
||||
|
||||
if(model->selected_high_nibble) {
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 8 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], false));
|
||||
canvas_draw_box(canvas, text_x + 1 + byte_position * 14, text_y - 8, 7, 9);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_line(
|
||||
canvas,
|
||||
text_x + 14 + byte_position * 14,
|
||||
text_y - 6,
|
||||
text_x + 14 + byte_position * 14,
|
||||
text_y - 2);
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 2 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], true));
|
||||
canvas_invert_color(canvas);
|
||||
} else {
|
||||
canvas_draw_box(canvas, text_x + 7 + byte_position * 14, text_y - 8, 7, 9);
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 2 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], true));
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_line(
|
||||
canvas,
|
||||
text_x + byte_position * 14,
|
||||
text_y - 6,
|
||||
text_x + byte_position * 14,
|
||||
text_y - 2);
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 8 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], false));
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
} else {
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 2 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], true));
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 8 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], false));
|
||||
}
|
||||
}
|
||||
|
||||
if(model->bytes_count - model->first_visible_byte > max_drawable_bytes) {
|
||||
canvas_draw_icon(canvas, 123, 21, &I_ButtonRightSmall_3x5);
|
||||
}
|
||||
|
||||
if(model->first_visible_byte > 0) {
|
||||
canvas_draw_icon(canvas, 1, 21, &I_ButtonLeftSmall_3x5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw input box (selected view)
|
||||
*
|
||||
* @param canvas
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_draw_input_selected(Canvas* canvas, ByteInputModel* model) {
|
||||
const uint8_t text_x = 7;
|
||||
const uint8_t text_y = 25;
|
||||
|
||||
canvas_draw_box(canvas, 0, 12, 127, 19);
|
||||
canvas_invert_color(canvas);
|
||||
|
||||
elements_slightly_rounded_frame(canvas, 6, 14, 115, 15);
|
||||
canvas_draw_icon(canvas, 2, 19, &I_ButtonLeftSmall_3x5);
|
||||
canvas_draw_icon(canvas, 122, 19, &I_ButtonRightSmall_3x5);
|
||||
|
||||
for(uint8_t i = model->first_visible_byte;
|
||||
i < model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes);
|
||||
i++) {
|
||||
uint8_t byte_position = i - model->first_visible_byte;
|
||||
|
||||
if(i == model->selected_byte) {
|
||||
canvas_draw_box(canvas, text_x + 1 + byte_position * 14, text_y - 9, 13, 11);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 2 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], true));
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 8 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], false));
|
||||
canvas_invert_color(canvas);
|
||||
} else {
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 2 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], true));
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
text_x + 8 + byte_position * 14,
|
||||
text_y,
|
||||
byte_input_get_nibble_text(model->bytes[i], false));
|
||||
}
|
||||
}
|
||||
|
||||
if(model->bytes_count - model->first_visible_byte > max_drawable_bytes) {
|
||||
canvas_draw_icon(canvas, 123, 21, &I_ButtonRightSmall_3x5);
|
||||
}
|
||||
|
||||
if(model->first_visible_byte > 0) {
|
||||
canvas_draw_icon(canvas, 1, 21, &I_ButtonLeftSmall_3x5);
|
||||
}
|
||||
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set nibble at position
|
||||
*
|
||||
* @param data where to set nibble
|
||||
* @param position byte position
|
||||
* @param value char value
|
||||
* @param high_nibble set high nibble
|
||||
*/
|
||||
static void byte_input_set_nibble(uint8_t* data, uint8_t position, char value, bool high_nibble) {
|
||||
switch(value) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
value = value - '0';
|
||||
break;
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
value = value - 'A' + 10;
|
||||
break;
|
||||
default:
|
||||
value = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if(high_nibble) {
|
||||
data[position] &= 0x0F;
|
||||
data[position] |= value << 4;
|
||||
} else {
|
||||
data[position] &= 0xF0;
|
||||
data[position] |= value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief What currently selected
|
||||
*
|
||||
* @return true - keyboard selected, false - input selected
|
||||
*/
|
||||
static bool byte_input_keyboard_selected(ByteInputModel* model) {
|
||||
return model->selected_row >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Do transition from keyboard
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_transition_from_keyboard(ByteInputModel* model) {
|
||||
model->selected_row += 1;
|
||||
model->selected_high_nibble = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increase selected byte position
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_inc_selected_byte(ByteInputModel* model) {
|
||||
if(model->selected_byte < model->bytes_count - 1) {
|
||||
model->selected_byte += 1;
|
||||
|
||||
if(model->bytes_count > max_drawable_bytes) {
|
||||
if(model->selected_byte - model->first_visible_byte > (max_drawable_bytes - 2)) {
|
||||
if(model->first_visible_byte < model->bytes_count - max_drawable_bytes) {
|
||||
model->first_visible_byte++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decrease selected byte position
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_dec_selected_byte(ByteInputModel* model) {
|
||||
if(model->selected_byte > 0) {
|
||||
model->selected_byte -= 1;
|
||||
|
||||
if(model->selected_byte - model->first_visible_byte < 1) {
|
||||
if(model->first_visible_byte > 0) {
|
||||
model->first_visible_byte--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Call input callback
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_call_input_callback(ByteInputModel* model) {
|
||||
if(model->input_callback != NULL) {
|
||||
model->input_callback(model->callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Call changed callback
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_call_changed_callback(ByteInputModel* model) {
|
||||
if(model->changed_callback != NULL) {
|
||||
model->changed_callback(model->callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear selected byte
|
||||
*/
|
||||
|
||||
static void byte_input_clear_selected_byte(ByteInputModel* model) {
|
||||
model->bytes[model->selected_byte] = 0;
|
||||
model->selected_high_nibble = true;
|
||||
byte_input_dec_selected_byte(model);
|
||||
byte_input_call_changed_callback(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle up button
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_handle_up(ByteInputModel* model) {
|
||||
if(model->selected_row > -1) {
|
||||
model->selected_row -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle down button
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_handle_down(ByteInputModel* model) {
|
||||
if(byte_input_keyboard_selected(model)) {
|
||||
if(model->selected_row < keyboard_row_count - 1) {
|
||||
model->selected_row += 1;
|
||||
}
|
||||
} else {
|
||||
byte_input_transition_from_keyboard(model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle left button
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_handle_left(ByteInputModel* model) {
|
||||
if(byte_input_keyboard_selected(model)) {
|
||||
if(model->selected_column > 0) {
|
||||
model->selected_column -= 1;
|
||||
} else {
|
||||
model->selected_column = byte_input_get_row_size(model->selected_row) - 1;
|
||||
}
|
||||
} else {
|
||||
byte_input_dec_selected_byte(model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle right button
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_handle_right(ByteInputModel* model) {
|
||||
if(byte_input_keyboard_selected(model)) {
|
||||
if(model->selected_column < byte_input_get_row_size(model->selected_row) - 1) {
|
||||
model->selected_column += 1;
|
||||
} else {
|
||||
model->selected_column = 0;
|
||||
}
|
||||
} else {
|
||||
byte_input_inc_selected_byte(model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle OK button
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
static void byte_input_handle_ok(ByteInputModel* model) {
|
||||
if(byte_input_keyboard_selected(model)) {
|
||||
uint8_t value = byte_input_get_row(model->selected_row)[model->selected_column].value;
|
||||
|
||||
if(value == enter_symbol) {
|
||||
byte_input_call_input_callback(model);
|
||||
} else if(value == backspace_symbol) {
|
||||
byte_input_clear_selected_byte(model);
|
||||
} else {
|
||||
byte_input_set_nibble(
|
||||
model->bytes, model->selected_byte, value, model->selected_high_nibble);
|
||||
if(model->selected_high_nibble == true) {
|
||||
model->selected_high_nibble = false;
|
||||
} else {
|
||||
byte_input_inc_selected_byte(model);
|
||||
model->selected_high_nibble = true;
|
||||
}
|
||||
byte_input_call_changed_callback(model);
|
||||
}
|
||||
} else {
|
||||
byte_input_transition_from_keyboard(model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw callback
|
||||
*
|
||||
* @param canvas
|
||||
* @param _model
|
||||
*/
|
||||
static void byte_input_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
ByteInputModel* model = _model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_str(canvas, 2, 9, model->header);
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
|
||||
if(model->selected_row == -1) {
|
||||
byte_input_draw_input_selected(canvas, model);
|
||||
} else {
|
||||
byte_input_draw_input(canvas, model);
|
||||
}
|
||||
|
||||
for(uint8_t row = 0; row < keyboard_row_count; row++) {
|
||||
const uint8_t column_count = byte_input_get_row_size(row);
|
||||
const ByteInputKey* keys = byte_input_get_row(row);
|
||||
|
||||
for(size_t column = 0; column < column_count; column++) {
|
||||
if(keys[column].value == enter_symbol) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
if(model->selected_row == row && model->selected_column == column) {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySaveSelected_24x11);
|
||||
} else {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySave_24x11);
|
||||
}
|
||||
} else if(keys[column].value == backspace_symbol) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
if(model->selected_row == row && model->selected_column == column) {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeyBackspaceSelected_16x9);
|
||||
} else {
|
||||
canvas_draw_icon(
|
||||
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 - 3,
|
||||
keyboard_origin_y + keys[column].y - 10,
|
||||
11,
|
||||
13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else if(model->selected_row == -1 && row == 0 && model->selected_column == column) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_frame(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x - 3,
|
||||
keyboard_origin_y + keys[column].y - 10,
|
||||
11,
|
||||
13);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
keys[column].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Input callback
|
||||
*
|
||||
* @param event
|
||||
* @param context
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
static bool byte_input_view_input_callback(InputEvent* event, void* context) {
|
||||
ByteInput* byte_input = context;
|
||||
furi_assert(byte_input);
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
||||
switch(event->key) {
|
||||
case InputKeyLeft:
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
byte_input_handle_left(model);
|
||||
return true;
|
||||
});
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyRight:
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
byte_input_handle_right(model);
|
||||
return true;
|
||||
});
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyUp:
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
byte_input_handle_up(model);
|
||||
return true;
|
||||
});
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyDown:
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
byte_input_handle_down(model);
|
||||
return true;
|
||||
});
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyOk:
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
byte_input_handle_ok(model);
|
||||
return true;
|
||||
});
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if((event->type == InputTypeLong || event->type == InputTypeRepeat) &&
|
||||
event->key == InputKeyBack) {
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
byte_input_clear_selected_byte(model);
|
||||
return true;
|
||||
});
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all input-related data in model
|
||||
*
|
||||
* @param model ByteInputModel
|
||||
*/
|
||||
static void byte_input_reset_model_input_data(ByteInputModel* model) {
|
||||
model->bytes = NULL;
|
||||
model->bytes_count = 0;
|
||||
model->selected_high_nibble = true;
|
||||
model->selected_byte = 0;
|
||||
model->selected_row = 0;
|
||||
model->selected_column = 0;
|
||||
model->first_visible_byte = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate and initialize byte input. This byte input is used to enter bytes.
|
||||
*
|
||||
* @return ByteInput instance pointer
|
||||
*/
|
||||
ByteInput* byte_input_alloc() {
|
||||
ByteInput* byte_input = malloc(sizeof(ByteInput));
|
||||
byte_input->view = view_alloc();
|
||||
view_set_context(byte_input->view, byte_input);
|
||||
view_allocate_model(byte_input->view, ViewModelTypeLocking, sizeof(ByteInputModel));
|
||||
view_set_draw_callback(byte_input->view, byte_input_view_draw_callback);
|
||||
view_set_input_callback(byte_input->view, byte_input_view_input_callback);
|
||||
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
model->header = "";
|
||||
model->input_callback = NULL;
|
||||
model->changed_callback = NULL;
|
||||
model->callback_context = NULL;
|
||||
byte_input_reset_model_input_data(model);
|
||||
return true;
|
||||
});
|
||||
|
||||
return byte_input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deinitialize and free byte input
|
||||
*
|
||||
* @param byte_input Byte input instance
|
||||
*/
|
||||
void byte_input_free(ByteInput* byte_input) {
|
||||
furi_assert(byte_input);
|
||||
view_free(byte_input->view);
|
||||
free(byte_input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get byte input view
|
||||
*
|
||||
* @param byte_input byte input instance
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* byte_input_get_view(ByteInput* byte_input) {
|
||||
furi_assert(byte_input);
|
||||
return byte_input->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deinitialize and free byte input
|
||||
*
|
||||
* @param byte_input byte input instance
|
||||
* @param input_callback input callback fn
|
||||
* @param changed_callback changed callback fn
|
||||
* @param callback_context callback context
|
||||
* @param bytes buffer to use
|
||||
* @param bytes_count buffer length
|
||||
*/
|
||||
void byte_input_set_result_callback(
|
||||
ByteInput* byte_input,
|
||||
ByteInputCallback input_callback,
|
||||
ByteChangedCallback changed_callback,
|
||||
void* callback_context,
|
||||
uint8_t* bytes,
|
||||
uint8_t bytes_count) {
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
byte_input_reset_model_input_data(model);
|
||||
model->input_callback = input_callback;
|
||||
model->changed_callback = changed_callback;
|
||||
model->callback_context = callback_context;
|
||||
model->bytes = bytes;
|
||||
model->bytes_count = bytes_count;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set byte input header text
|
||||
*
|
||||
* @param byte_input byte input instance
|
||||
* @param text text to be shown
|
||||
*/
|
||||
void byte_input_set_header_text(ByteInput* byte_input, const char* text) {
|
||||
with_view_model(
|
||||
byte_input->view, (ByteInputModel * model) {
|
||||
model->header = text;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @file byte_input.h
|
||||
* GUI: ByteInput keyboard view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Byte input anonymous structure */
|
||||
typedef struct ByteInput ByteInput;
|
||||
|
||||
/** callback that is executed on save button press */
|
||||
typedef void (*ByteInputCallback)(void* context);
|
||||
|
||||
/** callback that is executed when byte buffer is changed */
|
||||
typedef void (*ByteChangedCallback)(void* context);
|
||||
|
||||
/** Allocate and initialize byte input. This byte input is used to enter bytes.
|
||||
*
|
||||
* @return ByteInput instance pointer
|
||||
*/
|
||||
ByteInput* byte_input_alloc();
|
||||
|
||||
/** Deinitialize and free byte input
|
||||
*
|
||||
* @param byte_input Byte input instance
|
||||
*/
|
||||
void byte_input_free(ByteInput* byte_input);
|
||||
|
||||
/** Get byte input view
|
||||
*
|
||||
* @param byte_input byte input instance
|
||||
*
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* byte_input_get_view(ByteInput* byte_input);
|
||||
|
||||
/** Set byte input result callback
|
||||
*
|
||||
* @param byte_input byte input instance
|
||||
* @param input_callback input callback fn
|
||||
* @param changed_callback changed callback fn
|
||||
* @param callback_context callback context
|
||||
* @param bytes buffer to use
|
||||
* @param bytes_count buffer length
|
||||
*/
|
||||
void byte_input_set_result_callback(
|
||||
ByteInput* byte_input,
|
||||
ByteInputCallback input_callback,
|
||||
ByteChangedCallback changed_callback,
|
||||
void* callback_context,
|
||||
uint8_t* bytes,
|
||||
uint8_t bytes_count);
|
||||
|
||||
/** Set byte input header text
|
||||
*
|
||||
* @param byte_input byte input instance
|
||||
* @param text text to be shown
|
||||
*/
|
||||
void byte_input_set_header_text(ByteInput* byte_input, const char* text);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,303 @@
|
||||
#include "dialog_ex.h"
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
struct DialogEx {
|
||||
View* view;
|
||||
void* context;
|
||||
DialogExResultCallback callback;
|
||||
bool enable_extended_events;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* text;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
Align horizontal;
|
||||
Align vertical;
|
||||
} TextElement;
|
||||
|
||||
typedef struct {
|
||||
int8_t x;
|
||||
int8_t y;
|
||||
const Icon* icon;
|
||||
} 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);
|
||||
|
||||
if(model->icon.icon != NULL) {
|
||||
canvas_draw_icon(canvas, model->icon.x, model->icon.y, model->icon.icon);
|
||||
}
|
||||
|
||||
// Draw header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->header.text != NULL) {
|
||||
elements_multiline_text_aligned(
|
||||
canvas,
|
||||
model->header.x,
|
||||
model->header.y,
|
||||
model->header.horizontal,
|
||||
model->header.vertical,
|
||||
model->header.text);
|
||||
}
|
||||
|
||||
// Draw text
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(model->text.text != NULL) {
|
||||
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;
|
||||
return true;
|
||||
});
|
||||
|
||||
if(dialog_ex->callback) {
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyLeft && left_text != NULL) {
|
||||
dialog_ex->callback(DialogExResultLeft, dialog_ex->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyOk && center_text != NULL) {
|
||||
dialog_ex->callback(DialogExResultCenter, dialog_ex->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyRight && right_text != NULL) {
|
||||
dialog_ex->callback(DialogExResultRight, dialog_ex->context);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(event->type == InputTypePress && dialog_ex->enable_extended_events) {
|
||||
if(event->key == InputKeyLeft && left_text != NULL) {
|
||||
dialog_ex->callback(DialogExPressLeft, dialog_ex->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyOk && center_text != NULL) {
|
||||
dialog_ex->callback(DialogExPressCenter, dialog_ex->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyRight && right_text != NULL) {
|
||||
dialog_ex->callback(DialogExPressRight, dialog_ex->context);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(event->type == InputTypeRelease && dialog_ex->enable_extended_events) {
|
||||
if(event->key == InputKeyLeft && left_text != NULL) {
|
||||
dialog_ex->callback(DialogExReleaseLeft, dialog_ex->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyOk && center_text != NULL) {
|
||||
dialog_ex->callback(DialogExReleaseCenter, dialog_ex->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyRight && right_text != NULL) {
|
||||
dialog_ex->callback(DialogExReleaseRight, dialog_ex->context);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
DialogEx* dialog_ex_alloc() {
|
||||
DialogEx* dialog_ex = malloc(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;
|
||||
|
||||
model->icon.x = 0;
|
||||
model->icon.y = 0;
|
||||
model->icon.icon = NULL;
|
||||
|
||||
model->left_text = NULL;
|
||||
model->center_text = NULL;
|
||||
model->right_text = NULL;
|
||||
|
||||
return true;
|
||||
});
|
||||
dialog_ex->enable_extended_events = false;
|
||||
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;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon) {
|
||||
furi_assert(dialog_ex);
|
||||
with_view_model(
|
||||
dialog_ex->view, (DialogExModel * model) {
|
||||
model->icon.x = x;
|
||||
model->icon.y = y;
|
||||
model->icon.icon = icon;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void dialog_ex_reset(DialogEx* dialog_ex) {
|
||||
furi_assert(dialog_ex);
|
||||
TextElement clean_text_el = {
|
||||
.text = NULL, .x = 0, .y = 0, .horizontal = AlignLeft, .vertical = AlignLeft};
|
||||
IconElement clean_icon_el = {.icon = NULL, .x = 0, .y = 0};
|
||||
with_view_model(
|
||||
dialog_ex->view, (DialogExModel * model) {
|
||||
model->header = clean_text_el;
|
||||
model->text = clean_text_el;
|
||||
model->icon = clean_icon_el;
|
||||
model->left_text = NULL;
|
||||
model->center_text = NULL;
|
||||
model->right_text = NULL;
|
||||
return true;
|
||||
});
|
||||
dialog_ex->context = NULL;
|
||||
dialog_ex->callback = NULL;
|
||||
}
|
||||
|
||||
void dialog_ex_enable_extended_events(DialogEx* dialog_ex) {
|
||||
furi_assert(dialog_ex);
|
||||
dialog_ex->enable_extended_events = true;
|
||||
}
|
||||
|
||||
void dialog_ex_disable_extended_events(DialogEx* dialog_ex) {
|
||||
furi_assert(dialog_ex);
|
||||
dialog_ex->enable_extended_events = false;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* @file dialog_ex.h
|
||||
* GUI: DialogEx view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Dialog anonymous structure */
|
||||
typedef struct DialogEx DialogEx;
|
||||
|
||||
/** DialogEx result */
|
||||
typedef enum {
|
||||
DialogExResultLeft,
|
||||
DialogExResultCenter,
|
||||
DialogExResultRight,
|
||||
DialogExPressLeft,
|
||||
DialogExPressCenter,
|
||||
DialogExPressRight,
|
||||
DialogExReleaseLeft,
|
||||
DialogExReleaseCenter,
|
||||
DialogExReleaseRight,
|
||||
} 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
|
||||
*
|
||||
* @return DialogEx instance
|
||||
*/
|
||||
DialogEx* dialog_ex_alloc();
|
||||
|
||||
/** Deinitialize and free dialog
|
||||
*
|
||||
* @param dialog_ex DialogEx instance
|
||||
*/
|
||||
void dialog_ex_free(DialogEx* dialog_ex);
|
||||
|
||||
/** Get dialog view
|
||||
*
|
||||
* @param dialog_ex 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_ex DialogEx instance
|
||||
* @param text text to be shown, can be multiline
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param horizontal horizontal text aligment
|
||||
* @param vertical 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_ex DialogEx instance
|
||||
* @param text text to be shown, can be multiline
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param horizontal horizontal text aligment
|
||||
* @param vertical 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_ex DialogEx instance
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param icon The icon
|
||||
* @param name icon to be shown
|
||||
*/
|
||||
void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon);
|
||||
|
||||
/** Set left button text
|
||||
*
|
||||
* If text is null, left button will not be rendered and processed
|
||||
*
|
||||
* @param dialog_ex 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_ex 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_ex DialogEx instance
|
||||
* @param text text to be shown
|
||||
*/
|
||||
void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text);
|
||||
|
||||
/** Clean dialog
|
||||
*
|
||||
* @param dialog_ex DialogEx instance
|
||||
*/
|
||||
void dialog_ex_reset(DialogEx* dialog_ex);
|
||||
|
||||
/** Enable press/release events
|
||||
*
|
||||
* @param dialog_ex DialogEx instance
|
||||
*/
|
||||
void dialog_ex_enable_extended_events(DialogEx* dialog_ex);
|
||||
|
||||
/** Disable press/release events
|
||||
*
|
||||
* @param dialog_ex DialogEx instance
|
||||
*/
|
||||
void dialog_ex_disable_extended_events(DialogEx* dialog_ex);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "empty_screen.h"
|
||||
#include <furi.h>
|
||||
|
||||
struct EmptyScreen {
|
||||
View* view;
|
||||
};
|
||||
|
||||
static void empty_screen_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
UNUSED(_model);
|
||||
canvas_clear(canvas);
|
||||
}
|
||||
|
||||
static bool empty_screen_view_input_callback(InputEvent* event, void* context) {
|
||||
UNUSED(event);
|
||||
UNUSED(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
EmptyScreen* empty_screen_alloc() {
|
||||
EmptyScreen* empty_screen = malloc(sizeof(EmptyScreen));
|
||||
empty_screen->view = view_alloc();
|
||||
view_set_context(empty_screen->view, empty_screen);
|
||||
view_set_draw_callback(empty_screen->view, empty_screen_view_draw_callback);
|
||||
view_set_input_callback(empty_screen->view, empty_screen_view_input_callback);
|
||||
return empty_screen;
|
||||
}
|
||||
|
||||
void empty_screen_free(EmptyScreen* empty_screen) {
|
||||
furi_assert(empty_screen);
|
||||
view_free(empty_screen->view);
|
||||
free(empty_screen);
|
||||
}
|
||||
|
||||
View* empty_screen_get_view(EmptyScreen* empty_screen) {
|
||||
furi_assert(empty_screen);
|
||||
return empty_screen->view;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @file empty_screen.h
|
||||
* GUI: EmptyScreen view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Empty screen anonymous structure */
|
||||
typedef struct EmptyScreen EmptyScreen;
|
||||
|
||||
/** Allocate and initialize empty screen
|
||||
*
|
||||
* This empty screen used to ask simple questions like Yes/
|
||||
*
|
||||
* @return EmptyScreen instance
|
||||
*/
|
||||
EmptyScreen* empty_screen_alloc();
|
||||
|
||||
/** Deinitialize and free empty screen
|
||||
*
|
||||
* @param empty_screen Empty screen instance
|
||||
*/
|
||||
void empty_screen_free(EmptyScreen* empty_screen);
|
||||
|
||||
/** Get empty screen view
|
||||
*
|
||||
* @param empty_screen Empty screen instance
|
||||
*
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* empty_screen_get_view(EmptyScreen* empty_screen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,595 @@
|
||||
#include "file_browser.h"
|
||||
#include "assets_icons.h"
|
||||
#include "file_browser_worker.h"
|
||||
#include <core/check.h>
|
||||
#include <core/common_defines.h>
|
||||
#include <core/log.h>
|
||||
#include "furi_hal_resources.h"
|
||||
#include "m-string.h"
|
||||
#include <m-array.h>
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
#include "toolbox/path.h"
|
||||
|
||||
#define LIST_ITEMS 5u
|
||||
#define MAX_LEN_PX 110
|
||||
#define FRAME_HEIGHT 12
|
||||
#define Y_OFFSET 3
|
||||
|
||||
#define ITEM_LIST_LEN_MAX 50
|
||||
|
||||
#define CUSTOM_ICON_MAX_SIZE 32
|
||||
|
||||
typedef enum {
|
||||
BrowserItemTypeLoading,
|
||||
BrowserItemTypeBack,
|
||||
BrowserItemTypeFolder,
|
||||
BrowserItemTypeFile,
|
||||
} BrowserItemType;
|
||||
|
||||
typedef struct {
|
||||
string_t path;
|
||||
BrowserItemType type;
|
||||
uint8_t* custom_icon_data;
|
||||
string_t display_name;
|
||||
} BrowserItem_t;
|
||||
|
||||
static void BrowserItem_t_init(BrowserItem_t* obj) {
|
||||
obj->type = BrowserItemTypeLoading;
|
||||
string_init(obj->path);
|
||||
string_init(obj->display_name);
|
||||
obj->custom_icon_data = NULL;
|
||||
}
|
||||
|
||||
static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) {
|
||||
obj->type = src->type;
|
||||
string_init_set(obj->path, src->path);
|
||||
string_init_set(obj->display_name, src->display_name);
|
||||
if(src->custom_icon_data) {
|
||||
obj->custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE);
|
||||
memcpy(obj->custom_icon_data, src->custom_icon_data, CUSTOM_ICON_MAX_SIZE);
|
||||
} else {
|
||||
obj->custom_icon_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) {
|
||||
obj->type = src->type;
|
||||
string_set(obj->path, src->path);
|
||||
string_set(obj->display_name, src->display_name);
|
||||
if(src->custom_icon_data) {
|
||||
obj->custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE);
|
||||
memcpy(obj->custom_icon_data, src->custom_icon_data, CUSTOM_ICON_MAX_SIZE);
|
||||
} else {
|
||||
obj->custom_icon_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void BrowserItem_t_clear(BrowserItem_t* obj) {
|
||||
string_clear(obj->path);
|
||||
string_clear(obj->display_name);
|
||||
if(obj->custom_icon_data) {
|
||||
free(obj->custom_icon_data);
|
||||
}
|
||||
}
|
||||
|
||||
ARRAY_DEF(
|
||||
items_array,
|
||||
BrowserItem_t,
|
||||
(INIT(API_2(BrowserItem_t_init)),
|
||||
SET(API_6(BrowserItem_t_set)),
|
||||
INIT_SET(API_6(BrowserItem_t_init_set)),
|
||||
CLEAR(API_2(BrowserItem_t_clear))))
|
||||
|
||||
struct FileBrowser {
|
||||
View* view;
|
||||
BrowserWorker* worker;
|
||||
const char* ext_filter;
|
||||
bool skip_assets;
|
||||
bool hide_ext;
|
||||
|
||||
FileBrowserCallback callback;
|
||||
void* context;
|
||||
|
||||
FileBrowserLoadItemCallback item_callback;
|
||||
void* item_context;
|
||||
|
||||
string_ptr result_path;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
items_array_t items;
|
||||
|
||||
bool is_root;
|
||||
bool folder_loading;
|
||||
bool list_loading;
|
||||
uint32_t item_cnt;
|
||||
int32_t item_idx;
|
||||
int32_t array_offset;
|
||||
int32_t list_offset;
|
||||
|
||||
const Icon* file_icon;
|
||||
bool hide_ext;
|
||||
} FileBrowserModel;
|
||||
|
||||
static const Icon* BrowserItemIcons[] = {
|
||||
[BrowserItemTypeLoading] = &I_loading_10px,
|
||||
[BrowserItemTypeBack] = &I_back_10px,
|
||||
[BrowserItemTypeFolder] = &I_dir_10px,
|
||||
[BrowserItemTypeFile] = &I_unknown_10px,
|
||||
};
|
||||
|
||||
static void file_browser_view_draw_callback(Canvas* canvas, void* _model);
|
||||
static bool file_browser_view_input_callback(InputEvent* event, void* context);
|
||||
|
||||
static void
|
||||
browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root);
|
||||
static void browser_list_load_cb(void* context, uint32_t list_load_offset);
|
||||
static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last);
|
||||
static void browser_long_load_cb(void* context);
|
||||
|
||||
FileBrowser* file_browser_alloc(string_ptr result_path) {
|
||||
furi_assert(result_path);
|
||||
FileBrowser* browser = malloc(sizeof(FileBrowser));
|
||||
browser->view = view_alloc();
|
||||
view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(FileBrowserModel));
|
||||
view_set_context(browser->view, browser);
|
||||
view_set_draw_callback(browser->view, file_browser_view_draw_callback);
|
||||
view_set_input_callback(browser->view, file_browser_view_input_callback);
|
||||
|
||||
browser->result_path = result_path;
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
items_array_init(model->items);
|
||||
return false;
|
||||
});
|
||||
|
||||
return browser;
|
||||
}
|
||||
|
||||
void file_browser_free(FileBrowser* browser) {
|
||||
furi_assert(browser);
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
items_array_clear(model->items);
|
||||
return false;
|
||||
});
|
||||
|
||||
view_free(browser->view);
|
||||
free(browser);
|
||||
}
|
||||
|
||||
View* file_browser_get_view(FileBrowser* browser) {
|
||||
furi_assert(browser);
|
||||
return browser->view;
|
||||
}
|
||||
|
||||
void file_browser_configure(
|
||||
FileBrowser* browser,
|
||||
const char* extension,
|
||||
bool skip_assets,
|
||||
const Icon* file_icon,
|
||||
bool hide_ext) {
|
||||
furi_assert(browser);
|
||||
|
||||
browser->ext_filter = extension;
|
||||
browser->skip_assets = skip_assets;
|
||||
browser->hide_ext = hide_ext;
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
model->file_icon = file_icon;
|
||||
model->hide_ext = hide_ext;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void file_browser_start(FileBrowser* browser, string_t path) {
|
||||
furi_assert(browser);
|
||||
browser->worker = file_browser_worker_alloc(path, browser->ext_filter, browser->skip_assets);
|
||||
file_browser_worker_set_callback_context(browser->worker, browser);
|
||||
file_browser_worker_set_folder_callback(browser->worker, browser_folder_open_cb);
|
||||
file_browser_worker_set_list_callback(browser->worker, browser_list_load_cb);
|
||||
file_browser_worker_set_item_callback(browser->worker, browser_list_item_cb);
|
||||
file_browser_worker_set_long_load_callback(browser->worker, browser_long_load_cb);
|
||||
}
|
||||
|
||||
void file_browser_stop(FileBrowser* browser) {
|
||||
furi_assert(browser);
|
||||
file_browser_worker_free(browser->worker);
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
items_array_reset(model->items);
|
||||
model->item_cnt = 0;
|
||||
model->item_idx = 0;
|
||||
model->array_offset = 0;
|
||||
model->list_offset = 0;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context) {
|
||||
browser->context = context;
|
||||
browser->callback = callback;
|
||||
}
|
||||
|
||||
void file_browser_set_item_callback(
|
||||
FileBrowser* browser,
|
||||
FileBrowserLoadItemCallback callback,
|
||||
void* context) {
|
||||
browser->item_context = context;
|
||||
browser->item_callback = callback;
|
||||
}
|
||||
|
||||
static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) {
|
||||
size_t array_size = items_array_size(model->items);
|
||||
|
||||
if((idx >= (uint32_t)model->array_offset + array_size) ||
|
||||
(idx < (uint32_t)model->array_offset)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool browser_is_list_load_required(FileBrowserModel* model) {
|
||||
size_t array_size = items_array_size(model->items);
|
||||
uint32_t item_cnt = (model->is_root) ? model->item_cnt : model->item_cnt - 1;
|
||||
|
||||
if((model->list_loading) || (array_size >= item_cnt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if((model->array_offset > 0) &&
|
||||
(model->item_idx < (model->array_offset + ITEM_LIST_LEN_MAX / 4))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(((model->array_offset + array_size) < item_cnt) &&
|
||||
(model->item_idx > (int32_t)(model->array_offset + array_size - ITEM_LIST_LEN_MAX / 4))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void browser_update_offset(FileBrowser* browser) {
|
||||
furi_assert(browser);
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
uint16_t bounds = model->item_cnt > (LIST_ITEMS - 1) ? 2 : model->item_cnt;
|
||||
|
||||
if((model->item_cnt > (LIST_ITEMS - 1)) &&
|
||||
(model->item_idx >= ((int32_t)model->item_cnt - 1))) {
|
||||
model->list_offset = model->item_idx - (LIST_ITEMS - 1);
|
||||
} else if(model->list_offset < model->item_idx - bounds) {
|
||||
model->list_offset = CLAMP(
|
||||
model->item_idx - (int32_t)(LIST_ITEMS - 2),
|
||||
(int32_t)model->item_cnt - bounds,
|
||||
0);
|
||||
} else if(model->list_offset > model->item_idx - bounds) {
|
||||
model->list_offset =
|
||||
CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) {
|
||||
furi_assert(context);
|
||||
FileBrowser* browser = (FileBrowser*)context;
|
||||
|
||||
int32_t load_offset = 0;
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
items_array_reset(model->items);
|
||||
if(is_root) {
|
||||
model->item_cnt = item_cnt;
|
||||
model->item_idx = (file_idx > 0) ? file_idx : 0;
|
||||
load_offset =
|
||||
CLAMP(model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0);
|
||||
} else {
|
||||
model->item_cnt = item_cnt + 1;
|
||||
model->item_idx = file_idx + 1;
|
||||
load_offset = CLAMP(
|
||||
model->item_idx - ITEM_LIST_LEN_MAX / 2 - 1, (int32_t)model->item_cnt - 1, 0);
|
||||
}
|
||||
model->array_offset = 0;
|
||||
model->list_offset = 0;
|
||||
model->is_root = is_root;
|
||||
model->list_loading = true;
|
||||
model->folder_loading = false;
|
||||
return true;
|
||||
});
|
||||
browser_update_offset(browser);
|
||||
|
||||
file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX);
|
||||
}
|
||||
|
||||
static void browser_list_load_cb(void* context, uint32_t list_load_offset) {
|
||||
furi_assert(context);
|
||||
FileBrowser* browser = (FileBrowser*)context;
|
||||
|
||||
BrowserItem_t back_item;
|
||||
BrowserItem_t_init(&back_item);
|
||||
back_item.type = BrowserItemTypeBack;
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
items_array_reset(model->items);
|
||||
model->array_offset = list_load_offset;
|
||||
if(!model->is_root) {
|
||||
if(list_load_offset == 0) {
|
||||
items_array_push_back(model->items, back_item);
|
||||
} else {
|
||||
model->array_offset += 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
BrowserItem_t_clear(&back_item);
|
||||
}
|
||||
|
||||
static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) {
|
||||
furi_assert(context);
|
||||
FileBrowser* browser = (FileBrowser*)context;
|
||||
|
||||
BrowserItem_t item;
|
||||
item.custom_icon_data = NULL;
|
||||
|
||||
if(!is_last) {
|
||||
string_init_set(item.path, item_path);
|
||||
string_init(item.display_name);
|
||||
if(is_folder) {
|
||||
item.type = BrowserItemTypeFolder;
|
||||
} else {
|
||||
item.type = BrowserItemTypeFile;
|
||||
if(browser->item_callback) {
|
||||
item.custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE);
|
||||
if(!browser->item_callback(
|
||||
item_path,
|
||||
browser->item_context,
|
||||
&item.custom_icon_data,
|
||||
item.display_name)) {
|
||||
free(item.custom_icon_data);
|
||||
item.custom_icon_data = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(string_empty_p(item.display_name)) {
|
||||
path_extract_filename(
|
||||
item_path,
|
||||
item.display_name,
|
||||
(browser->hide_ext) && (item.type == BrowserItemTypeFile));
|
||||
}
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
items_array_push_back(model->items, item);
|
||||
// TODO: calculate if element is visible
|
||||
return true;
|
||||
});
|
||||
string_clear(item.display_name);
|
||||
string_clear(item.path);
|
||||
} else {
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
model->list_loading = false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void browser_long_load_cb(void* context) {
|
||||
furi_assert(context);
|
||||
FileBrowser* browser = (FileBrowser*)context;
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
model->folder_loading = true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void browser_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(
|
||||
canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT, (scrollbar ? 122 : 127), FRAME_HEIGHT);
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_dot(canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT);
|
||||
canvas_draw_dot(canvas, 1, Y_OFFSET + idx * FRAME_HEIGHT);
|
||||
canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + 1);
|
||||
|
||||
canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1));
|
||||
canvas_draw_dot(canvas, scrollbar ? 121 : 126, Y_OFFSET + idx * FRAME_HEIGHT);
|
||||
canvas_draw_dot(
|
||||
canvas, scrollbar ? 121 : 126, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1));
|
||||
}
|
||||
|
||||
static void browser_draw_loading(Canvas* canvas, FileBrowserModel* model) {
|
||||
UNUSED(model);
|
||||
|
||||
uint8_t x = 128 / 2 - 24 / 2;
|
||||
uint8_t y = 64 / 2 - 24 / 2;
|
||||
|
||||
canvas_draw_icon(canvas, x, y, &A_Loading_24);
|
||||
}
|
||||
|
||||
static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) {
|
||||
uint32_t array_size = items_array_size(model->items);
|
||||
bool show_scrollbar = model->item_cnt > LIST_ITEMS;
|
||||
|
||||
string_t filename;
|
||||
string_init(filename);
|
||||
|
||||
for(uint32_t i = 0; i < MIN(model->item_cnt, LIST_ITEMS); i++) {
|
||||
int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u);
|
||||
|
||||
BrowserItemType item_type = BrowserItemTypeLoading;
|
||||
uint8_t* custom_icon_data = NULL;
|
||||
|
||||
if(browser_is_item_in_array(model, idx)) {
|
||||
BrowserItem_t* item = items_array_get(
|
||||
model->items, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0));
|
||||
item_type = item->type;
|
||||
string_set(filename, item->display_name);
|
||||
if(item_type == BrowserItemTypeFile) {
|
||||
custom_icon_data = item->custom_icon_data;
|
||||
}
|
||||
} else {
|
||||
string_set_str(filename, "---");
|
||||
}
|
||||
|
||||
if(item_type == BrowserItemTypeBack) {
|
||||
string_set_str(filename, ". .");
|
||||
}
|
||||
|
||||
elements_string_fit_width(
|
||||
canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX));
|
||||
|
||||
if(model->item_idx == idx) {
|
||||
browser_draw_frame(canvas, i, show_scrollbar);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
if(custom_icon_data) {
|
||||
// Currently only 10*10 icons are supported
|
||||
canvas_draw_bitmap(
|
||||
canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, 10, 10, custom_icon_data);
|
||||
} else if((item_type == BrowserItemTypeFile) && (model->file_icon)) {
|
||||
canvas_draw_icon(canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, model->file_icon);
|
||||
} else if(BrowserItemIcons[item_type] != NULL) {
|
||||
canvas_draw_icon(
|
||||
canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]);
|
||||
}
|
||||
canvas_draw_str(canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, string_get_cstr(filename));
|
||||
}
|
||||
|
||||
if(show_scrollbar) {
|
||||
elements_scrollbar_pos(
|
||||
canvas,
|
||||
126,
|
||||
Y_OFFSET,
|
||||
canvas_height(canvas) - Y_OFFSET,
|
||||
model->item_idx,
|
||||
model->item_cnt);
|
||||
}
|
||||
|
||||
string_clear(filename);
|
||||
}
|
||||
|
||||
static void file_browser_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
FileBrowserModel* model = _model;
|
||||
|
||||
if(model->folder_loading) {
|
||||
browser_draw_loading(canvas, model);
|
||||
} else {
|
||||
browser_draw_list(canvas, model);
|
||||
}
|
||||
}
|
||||
|
||||
static bool file_browser_view_input_callback(InputEvent* event, void* context) {
|
||||
FileBrowser* browser = context;
|
||||
furi_assert(browser);
|
||||
bool consumed = false;
|
||||
bool is_loading = false;
|
||||
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
is_loading = model->folder_loading;
|
||||
return false;
|
||||
});
|
||||
|
||||
if(is_loading) {
|
||||
return false;
|
||||
} else if(event->key == InputKeyUp || event->key == InputKeyDown) {
|
||||
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->item_idx =
|
||||
((model->item_idx - 1) + model->item_cnt) % model->item_cnt;
|
||||
if(browser_is_list_load_required(model)) {
|
||||
model->list_loading = true;
|
||||
int32_t load_offset = CLAMP(
|
||||
model->item_idx - ITEM_LIST_LEN_MAX / 4 * 3,
|
||||
(int32_t)model->item_cnt - ITEM_LIST_LEN_MAX,
|
||||
0);
|
||||
file_browser_worker_load(
|
||||
browser->worker, load_offset, ITEM_LIST_LEN_MAX);
|
||||
}
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->item_idx = (model->item_idx + 1) % model->item_cnt;
|
||||
if(browser_is_list_load_required(model)) {
|
||||
model->list_loading = true;
|
||||
int32_t load_offset = CLAMP(
|
||||
model->item_idx - ITEM_LIST_LEN_MAX / 4 * 1,
|
||||
(int32_t)model->item_cnt - ITEM_LIST_LEN_MAX,
|
||||
0);
|
||||
file_browser_worker_load(
|
||||
browser->worker, load_offset, ITEM_LIST_LEN_MAX);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
browser_update_offset(browser);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event->key == InputKeyOk) {
|
||||
if(event->type == InputTypeShort) {
|
||||
BrowserItem_t* selected_item = NULL;
|
||||
int32_t select_index = 0;
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
if(browser_is_item_in_array(model, model->item_idx)) {
|
||||
selected_item =
|
||||
items_array_get(model->items, model->item_idx - model->array_offset);
|
||||
select_index = model->item_idx;
|
||||
if((!model->is_root) && (select_index > 0)) {
|
||||
select_index -= 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if(selected_item) {
|
||||
if(selected_item->type == BrowserItemTypeBack) {
|
||||
file_browser_worker_folder_exit(browser->worker);
|
||||
} else if(selected_item->type == BrowserItemTypeFolder) {
|
||||
file_browser_worker_folder_enter(
|
||||
browser->worker, selected_item->path, select_index);
|
||||
} else if(selected_item->type == BrowserItemTypeFile) {
|
||||
string_set(browser->result_path, selected_item->path);
|
||||
if(browser->callback) {
|
||||
browser->callback(browser->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(event->type == InputTypeShort) {
|
||||
bool is_root = false;
|
||||
with_view_model(
|
||||
browser->view, (FileBrowserModel * model) {
|
||||
is_root = model->is_root;
|
||||
return false;
|
||||
});
|
||||
if(!is_root) {
|
||||
file_browser_worker_folder_exit(browser->worker);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @file file_browser.h
|
||||
* GUI: FileBrowser view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "m-string.h"
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FileBrowser FileBrowser;
|
||||
typedef void (*FileBrowserCallback)(void* context);
|
||||
|
||||
typedef bool (
|
||||
*FileBrowserLoadItemCallback)(string_t path, void* context, uint8_t** icon, string_t item_name);
|
||||
|
||||
FileBrowser* file_browser_alloc(string_ptr result_path);
|
||||
|
||||
void file_browser_free(FileBrowser* browser);
|
||||
|
||||
View* file_browser_get_view(FileBrowser* browser);
|
||||
|
||||
void file_browser_configure(
|
||||
FileBrowser* browser,
|
||||
const char* extension,
|
||||
bool skip_assets,
|
||||
const Icon* file_icon,
|
||||
bool hide_ext);
|
||||
|
||||
void file_browser_start(FileBrowser* browser, string_t path);
|
||||
|
||||
void file_browser_stop(FileBrowser* browser);
|
||||
|
||||
void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context);
|
||||
|
||||
void file_browser_set_item_callback(
|
||||
FileBrowser* browser,
|
||||
FileBrowserLoadItemCallback callback,
|
||||
void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,458 @@
|
||||
#include "file_browser_worker.h"
|
||||
#include <core/check.h>
|
||||
#include <core/common_defines.h>
|
||||
#include "m-string.h"
|
||||
#include "storage/filesystem_api_defines.h"
|
||||
#include <m-array.h>
|
||||
#include <stdbool.h>
|
||||
#include <storage/storage.h>
|
||||
#include <furi.h>
|
||||
#include <stddef.h>
|
||||
#include "toolbox/path.h"
|
||||
|
||||
#define TAG "BrowserWorker"
|
||||
|
||||
#define ASSETS_DIR "assets"
|
||||
#define BROWSER_ROOT STORAGE_ANY_PATH_PREFIX
|
||||
#define FILE_NAME_LEN_MAX 256
|
||||
#define LONG_LOAD_THRESHOLD 100
|
||||
|
||||
typedef enum {
|
||||
WorkerEvtStop = (1 << 0),
|
||||
WorkerEvtLoad = (1 << 1),
|
||||
WorkerEvtFolderEnter = (1 << 2),
|
||||
WorkerEvtFolderExit = (1 << 3),
|
||||
WorkerEvtFolderRefresh = (1 << 4),
|
||||
WorkerEvtConfigChange = (1 << 5),
|
||||
} WorkerEvtFlags;
|
||||
|
||||
#define WORKER_FLAGS_ALL \
|
||||
(WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \
|
||||
WorkerEvtFolderRefresh | WorkerEvtConfigChange)
|
||||
|
||||
ARRAY_DEF(idx_last_array, int32_t)
|
||||
|
||||
struct BrowserWorker {
|
||||
FuriThread* thread;
|
||||
|
||||
string_t filter_extension;
|
||||
string_t path_next;
|
||||
int32_t item_sel_idx;
|
||||
uint32_t load_offset;
|
||||
uint32_t load_count;
|
||||
bool skip_assets;
|
||||
idx_last_array_t idx_last;
|
||||
|
||||
void* cb_ctx;
|
||||
BrowserWorkerFolderOpenCallback folder_cb;
|
||||
BrowserWorkerListLoadCallback list_load_cb;
|
||||
BrowserWorkerListItemCallback list_item_cb;
|
||||
BrowserWorkerLongLoadCallback long_load_cb;
|
||||
};
|
||||
|
||||
static bool browser_path_is_file(string_t path) {
|
||||
bool state = false;
|
||||
FileInfo file_info;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) {
|
||||
if((file_info.flags & FSF_DIRECTORY) == 0) {
|
||||
state = true;
|
||||
}
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool browser_path_trim(string_t path) {
|
||||
bool is_root = false;
|
||||
size_t filename_start = string_search_rchar(path, '/');
|
||||
string_left(path, filename_start);
|
||||
if((string_empty_p(path)) || (filename_start == STRING_FAILURE)) {
|
||||
string_set_str(path, BROWSER_ROOT);
|
||||
is_root = true;
|
||||
}
|
||||
return is_root;
|
||||
}
|
||||
|
||||
static bool browser_filter_by_name(BrowserWorker* browser, string_t name, bool is_folder) {
|
||||
if(is_folder) {
|
||||
// Skip assets folders (if enabled)
|
||||
if(browser->skip_assets) {
|
||||
return ((string_cmp_str(name, ASSETS_DIR) == 0) ? (false) : (true));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Filter files by extension
|
||||
if((string_empty_p(browser->filter_extension)) ||
|
||||
(string_cmp_str(browser->filter_extension, "*") == 0)) {
|
||||
return true;
|
||||
}
|
||||
if(string_end_with_string_p(name, browser->filter_extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool browser_folder_check_and_switch(string_t path) {
|
||||
FileInfo file_info;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
bool is_root = false;
|
||||
|
||||
if(string_search_rchar(path, '/') == 0) {
|
||||
is_root = true;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
// Check if folder is existing and navigate back if not
|
||||
if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) {
|
||||
if(file_info.flags & FSF_DIRECTORY) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(is_root) {
|
||||
break;
|
||||
}
|
||||
is_root = browser_path_trim(path);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return is_root;
|
||||
}
|
||||
|
||||
static bool browser_folder_init(
|
||||
BrowserWorker* browser,
|
||||
string_t path,
|
||||
string_t filename,
|
||||
uint32_t* item_cnt,
|
||||
int32_t* file_idx) {
|
||||
bool state = false;
|
||||
FileInfo file_info;
|
||||
uint32_t total_files_cnt = 0;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* directory = storage_file_alloc(storage);
|
||||
|
||||
char name_temp[FILE_NAME_LEN_MAX];
|
||||
string_t name_str;
|
||||
string_init(name_str);
|
||||
|
||||
*item_cnt = 0;
|
||||
*file_idx = -1;
|
||||
|
||||
if(storage_dir_open(directory, string_get_cstr(path))) {
|
||||
state = true;
|
||||
while(1) {
|
||||
if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) {
|
||||
break;
|
||||
}
|
||||
if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) {
|
||||
total_files_cnt++;
|
||||
string_set_str(name_str, name_temp);
|
||||
if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) {
|
||||
if(!string_empty_p(filename)) {
|
||||
if(string_cmp(name_str, filename) == 0) {
|
||||
*file_idx = *item_cnt;
|
||||
}
|
||||
}
|
||||
(*item_cnt)++;
|
||||
}
|
||||
if(total_files_cnt == LONG_LOAD_THRESHOLD) {
|
||||
// There are too many files in folder and counting them will take some time - send callback to app
|
||||
if(browser->long_load_cb) {
|
||||
browser->long_load_cb(browser->cb_ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(name_str);
|
||||
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool
|
||||
browser_folder_load(BrowserWorker* browser, string_t path, uint32_t offset, uint32_t count) {
|
||||
FileInfo file_info;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* directory = storage_file_alloc(storage);
|
||||
|
||||
char name_temp[FILE_NAME_LEN_MAX];
|
||||
string_t name_str;
|
||||
string_init(name_str);
|
||||
|
||||
uint32_t items_cnt = 0;
|
||||
|
||||
do {
|
||||
if(!storage_dir_open(directory, string_get_cstr(path))) {
|
||||
break;
|
||||
}
|
||||
|
||||
items_cnt = 0;
|
||||
while(items_cnt < offset) {
|
||||
if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) {
|
||||
break;
|
||||
}
|
||||
if(storage_file_get_error(directory) == FSE_OK) {
|
||||
string_set_str(name_str, name_temp);
|
||||
if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) {
|
||||
items_cnt++;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(items_cnt != offset) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(browser->list_load_cb) {
|
||||
browser->list_load_cb(browser->cb_ctx, offset);
|
||||
}
|
||||
|
||||
items_cnt = 0;
|
||||
while(items_cnt < count) {
|
||||
if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) {
|
||||
break;
|
||||
}
|
||||
if(storage_file_get_error(directory) == FSE_OK) {
|
||||
string_set_str(name_str, name_temp);
|
||||
if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) {
|
||||
string_printf(name_str, "%s/%s", string_get_cstr(path), name_temp);
|
||||
if(browser->list_item_cb) {
|
||||
browser->list_item_cb(
|
||||
browser->cb_ctx, name_str, (file_info.flags & FSF_DIRECTORY), false);
|
||||
}
|
||||
items_cnt++;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(browser->list_item_cb) {
|
||||
browser->list_item_cb(browser->cb_ctx, NULL, false, true);
|
||||
}
|
||||
} while(0);
|
||||
|
||||
string_clear(name_str);
|
||||
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return (items_cnt == count);
|
||||
}
|
||||
|
||||
static int32_t browser_worker(void* context) {
|
||||
BrowserWorker* browser = (BrowserWorker*)context;
|
||||
furi_assert(browser);
|
||||
FURI_LOG_D(TAG, "Start");
|
||||
|
||||
uint32_t items_cnt = 0;
|
||||
string_t path;
|
||||
string_init_set_str(path, BROWSER_ROOT);
|
||||
browser->item_sel_idx = -1;
|
||||
|
||||
string_t filename;
|
||||
string_init(filename);
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange);
|
||||
|
||||
while(1) {
|
||||
uint32_t flags =
|
||||
furi_thread_flags_wait(WORKER_FLAGS_ALL, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_assert((flags & FuriFlagError) == 0);
|
||||
|
||||
if(flags & WorkerEvtConfigChange) {
|
||||
// If start path is a path to the file - try finding index of this file in a folder
|
||||
if(browser_path_is_file(browser->path_next)) {
|
||||
path_extract_filename(browser->path_next, filename, false);
|
||||
}
|
||||
idx_last_array_reset(browser->idx_last);
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter);
|
||||
}
|
||||
|
||||
if(flags & WorkerEvtFolderEnter) {
|
||||
string_set(path, browser->path_next);
|
||||
bool is_root = browser_folder_check_and_switch(path);
|
||||
|
||||
// Push previous selected item index to history array
|
||||
idx_last_array_push_back(browser->idx_last, browser->item_sel_idx);
|
||||
|
||||
int32_t file_idx = 0;
|
||||
browser_folder_init(browser, path, filename, &items_cnt, &file_idx);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Enter folder: %s items: %u idx: %d",
|
||||
string_get_cstr(path),
|
||||
items_cnt,
|
||||
file_idx);
|
||||
if(browser->folder_cb) {
|
||||
browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root);
|
||||
}
|
||||
string_reset(filename);
|
||||
}
|
||||
|
||||
if(flags & WorkerEvtFolderExit) {
|
||||
browser_path_trim(path);
|
||||
bool is_root = browser_folder_check_and_switch(path);
|
||||
|
||||
int32_t file_idx = 0;
|
||||
browser_folder_init(browser, path, filename, &items_cnt, &file_idx);
|
||||
if(idx_last_array_size(browser->idx_last) > 0) {
|
||||
// Pop previous selected item index from history array
|
||||
idx_last_array_pop_back(&file_idx, browser->idx_last);
|
||||
}
|
||||
FURI_LOG_D(
|
||||
TAG, "Exit to: %s items: %u idx: %d", string_get_cstr(path), items_cnt, file_idx);
|
||||
if(browser->folder_cb) {
|
||||
browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root);
|
||||
}
|
||||
}
|
||||
|
||||
if(flags & WorkerEvtFolderRefresh) {
|
||||
bool is_root = browser_folder_check_and_switch(path);
|
||||
|
||||
int32_t file_idx = 0;
|
||||
string_reset(filename);
|
||||
browser_folder_init(browser, path, filename, &items_cnt, &file_idx);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Refresh folder: %s items: %u idx: %d",
|
||||
string_get_cstr(path),
|
||||
items_cnt,
|
||||
browser->item_sel_idx);
|
||||
if(browser->folder_cb) {
|
||||
browser->folder_cb(browser->cb_ctx, items_cnt, browser->item_sel_idx, is_root);
|
||||
}
|
||||
}
|
||||
|
||||
if(flags & WorkerEvtLoad) {
|
||||
FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count);
|
||||
browser_folder_load(browser, path, browser->load_offset, browser->load_count);
|
||||
}
|
||||
|
||||
if(flags & WorkerEvtStop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(filename);
|
||||
string_clear(path);
|
||||
|
||||
FURI_LOG_D(TAG, "End");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets) {
|
||||
BrowserWorker* browser = malloc(sizeof(BrowserWorker));
|
||||
|
||||
idx_last_array_init(browser->idx_last);
|
||||
|
||||
string_init_set_str(browser->filter_extension, filter_ext);
|
||||
browser->skip_assets = skip_assets;
|
||||
string_init_set(browser->path_next, path);
|
||||
|
||||
browser->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(browser->thread, "BrowserWorker");
|
||||
furi_thread_set_stack_size(browser->thread, 2048);
|
||||
furi_thread_set_context(browser->thread, browser);
|
||||
furi_thread_set_callback(browser->thread, browser_worker);
|
||||
furi_thread_start(browser->thread);
|
||||
|
||||
return browser;
|
||||
}
|
||||
|
||||
void file_browser_worker_free(BrowserWorker* browser) {
|
||||
furi_assert(browser);
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtStop);
|
||||
furi_thread_join(browser->thread);
|
||||
furi_thread_free(browser->thread);
|
||||
|
||||
string_clear(browser->filter_extension);
|
||||
string_clear(browser->path_next);
|
||||
|
||||
idx_last_array_clear(browser->idx_last);
|
||||
|
||||
free(browser);
|
||||
}
|
||||
|
||||
void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context) {
|
||||
furi_assert(browser);
|
||||
browser->cb_ctx = context;
|
||||
}
|
||||
|
||||
void file_browser_worker_set_folder_callback(
|
||||
BrowserWorker* browser,
|
||||
BrowserWorkerFolderOpenCallback cb) {
|
||||
furi_assert(browser);
|
||||
browser->folder_cb = cb;
|
||||
}
|
||||
|
||||
void file_browser_worker_set_list_callback(
|
||||
BrowserWorker* browser,
|
||||
BrowserWorkerListLoadCallback cb) {
|
||||
furi_assert(browser);
|
||||
browser->list_load_cb = cb;
|
||||
}
|
||||
|
||||
void file_browser_worker_set_item_callback(
|
||||
BrowserWorker* browser,
|
||||
BrowserWorkerListItemCallback cb) {
|
||||
furi_assert(browser);
|
||||
browser->list_item_cb = cb;
|
||||
}
|
||||
|
||||
void file_browser_worker_set_long_load_callback(
|
||||
BrowserWorker* browser,
|
||||
BrowserWorkerLongLoadCallback cb) {
|
||||
furi_assert(browser);
|
||||
browser->long_load_cb = cb;
|
||||
}
|
||||
|
||||
void file_browser_worker_set_config(
|
||||
BrowserWorker* browser,
|
||||
string_t path,
|
||||
const char* filter_ext,
|
||||
bool skip_assets) {
|
||||
furi_assert(browser);
|
||||
string_set(browser->path_next, path);
|
||||
string_set_str(browser->filter_extension, filter_ext);
|
||||
browser->skip_assets = skip_assets;
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange);
|
||||
}
|
||||
|
||||
void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) {
|
||||
furi_assert(browser);
|
||||
string_set(browser->path_next, path);
|
||||
browser->item_sel_idx = item_idx;
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter);
|
||||
}
|
||||
|
||||
void file_browser_worker_folder_exit(BrowserWorker* browser) {
|
||||
furi_assert(browser);
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderExit);
|
||||
}
|
||||
|
||||
void file_browser_worker_folder_refresh(BrowserWorker* browser, int32_t item_idx) {
|
||||
furi_assert(browser);
|
||||
browser->item_sel_idx = item_idx;
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderRefresh);
|
||||
}
|
||||
|
||||
void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) {
|
||||
furi_assert(browser);
|
||||
browser->load_offset = offset;
|
||||
browser->load_count = count;
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtLoad);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "m-string.h"
|
||||
#include <gui/view.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct BrowserWorker BrowserWorker;
|
||||
typedef void (*BrowserWorkerFolderOpenCallback)(
|
||||
void* context,
|
||||
uint32_t item_cnt,
|
||||
int32_t file_idx,
|
||||
bool is_root);
|
||||
typedef void (*BrowserWorkerListLoadCallback)(void* context, uint32_t list_load_offset);
|
||||
typedef void (*BrowserWorkerListItemCallback)(
|
||||
void* context,
|
||||
string_t item_path,
|
||||
bool is_folder,
|
||||
bool is_last);
|
||||
typedef void (*BrowserWorkerLongLoadCallback)(void* context);
|
||||
|
||||
BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets);
|
||||
|
||||
void file_browser_worker_free(BrowserWorker* browser);
|
||||
|
||||
void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context);
|
||||
|
||||
void file_browser_worker_set_folder_callback(
|
||||
BrowserWorker* browser,
|
||||
BrowserWorkerFolderOpenCallback cb);
|
||||
|
||||
void file_browser_worker_set_list_callback(
|
||||
BrowserWorker* browser,
|
||||
BrowserWorkerListLoadCallback cb);
|
||||
|
||||
void file_browser_worker_set_item_callback(
|
||||
BrowserWorker* browser,
|
||||
BrowserWorkerListItemCallback cb);
|
||||
|
||||
void file_browser_worker_set_long_load_callback(
|
||||
BrowserWorker* browser,
|
||||
BrowserWorkerLongLoadCallback cb);
|
||||
|
||||
void file_browser_worker_set_config(
|
||||
BrowserWorker* browser,
|
||||
string_t path,
|
||||
const char* filter_ext,
|
||||
bool skip_assets);
|
||||
|
||||
void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx);
|
||||
|
||||
void file_browser_worker_folder_exit(BrowserWorker* browser);
|
||||
|
||||
void file_browser_worker_folder_refresh(BrowserWorker* browser, int32_t item_idx);
|
||||
|
||||
void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <stdint.h>
|
||||
#include <furi.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/icon_animation.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/canvas.h>
|
||||
#include <gui/view.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#include "loading.h"
|
||||
|
||||
struct Loading {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
IconAnimation* icon;
|
||||
} LoadingModel;
|
||||
|
||||
static void loading_draw_callback(Canvas* canvas, void* _model) {
|
||||
LoadingModel* model = (LoadingModel*)_model;
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 0, 0, canvas_width(canvas), canvas_height(canvas));
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
uint8_t x = canvas_width(canvas) / 2 - 24 / 2;
|
||||
uint8_t y = canvas_height(canvas) / 2 - 24 / 2;
|
||||
|
||||
canvas_draw_icon(canvas, x, y, &A_Loading_24);
|
||||
|
||||
canvas_draw_icon_animation(canvas, x, y, model->icon);
|
||||
}
|
||||
|
||||
static bool loading_input_callback(InputEvent* event, void* context) {
|
||||
UNUSED(event);
|
||||
furi_assert(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void loading_enter_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Loading* instance = context;
|
||||
LoadingModel* model = view_get_model(instance->view);
|
||||
/* using Loading View in conjunction with several
|
||||
* Stack View obligates to reassign
|
||||
* Update callback, as it can be rewritten
|
||||
*/
|
||||
view_tie_icon_animation(instance->view, model->icon);
|
||||
icon_animation_start(model->icon);
|
||||
view_commit_model(instance->view, false);
|
||||
}
|
||||
|
||||
static void loading_exit_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Loading* instance = context;
|
||||
LoadingModel* model = view_get_model(instance->view);
|
||||
icon_animation_stop(model->icon);
|
||||
view_commit_model(instance->view, false);
|
||||
}
|
||||
|
||||
Loading* loading_alloc(void) {
|
||||
Loading* instance = malloc(sizeof(Loading));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(LoadingModel));
|
||||
LoadingModel* model = view_get_model(instance->view);
|
||||
model->icon = icon_animation_alloc(&A_Loading_24);
|
||||
view_tie_icon_animation(instance->view, model->icon);
|
||||
view_commit_model(instance->view, false);
|
||||
|
||||
view_set_context(instance->view, instance);
|
||||
view_set_draw_callback(instance->view, loading_draw_callback);
|
||||
view_set_input_callback(instance->view, loading_input_callback);
|
||||
view_set_enter_callback(instance->view, loading_enter_callback);
|
||||
view_set_exit_callback(instance->view, loading_exit_callback);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void loading_free(Loading* instance) {
|
||||
LoadingModel* model = view_get_model(instance->view);
|
||||
icon_animation_free(model->icon);
|
||||
view_commit_model(instance->view, false);
|
||||
|
||||
furi_assert(instance);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* loading_get_view(Loading* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->view);
|
||||
return instance->view;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Loading anonymous structure */
|
||||
typedef struct Loading Loading;
|
||||
|
||||
/** Allocate and initialize
|
||||
*
|
||||
* This View used to show system is doing some processing
|
||||
*
|
||||
* @return Loading View instance
|
||||
*/
|
||||
Loading* loading_alloc();
|
||||
|
||||
/** Deinitialize and free Loading View
|
||||
*
|
||||
* @param instance Loading instance
|
||||
*/
|
||||
void loading_free(Loading* instance);
|
||||
|
||||
/** Get Loading view
|
||||
*
|
||||
* @param instance Loading instance
|
||||
*
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* loading_get_view(Loading* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,267 @@
|
||||
#include "menu.h"
|
||||
|
||||
#include <m-array.h>
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
struct Menu {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* label;
|
||||
IconAnimation* icon;
|
||||
uint32_t index;
|
||||
MenuItemCallback callback;
|
||||
void* callback_context;
|
||||
} MenuItem;
|
||||
|
||||
ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST);
|
||||
|
||||
#define M_OPL_MenuItemArray_t() ARRAY_OPLIST(MenuItemArray, M_POD_OPLIST)
|
||||
|
||||
typedef struct {
|
||||
MenuItemArray_t items;
|
||||
size_t position;
|
||||
} MenuModel;
|
||||
|
||||
static void menu_process_up(Menu* menu);
|
||||
static void menu_process_down(Menu* menu);
|
||||
static void menu_process_ok(Menu* menu);
|
||||
|
||||
static void menu_draw_callback(Canvas* canvas, void* _model) {
|
||||
MenuModel* model = _model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
size_t position = model->position;
|
||||
size_t items_count = MenuItemArray_size(model->items);
|
||||
if(items_count) {
|
||||
MenuItem* item;
|
||||
size_t shift_position;
|
||||
// First line
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
shift_position = (0 + position + items_count - 1) % items_count;
|
||||
item = MenuItemArray_get(model->items, shift_position);
|
||||
if(item->icon) {
|
||||
canvas_draw_icon_animation(canvas, 4, 3, item->icon);
|
||||
}
|
||||
canvas_draw_str(canvas, 22, 14, item->label);
|
||||
// Second line main
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
shift_position = (1 + position + items_count - 1) % items_count;
|
||||
item = MenuItemArray_get(model->items, shift_position);
|
||||
if(item->icon) {
|
||||
canvas_draw_icon_animation(canvas, 4, 25, item->icon);
|
||||
}
|
||||
canvas_draw_str(canvas, 22, 36, item->label);
|
||||
// Third line
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
shift_position = (2 + position + items_count - 1) % items_count;
|
||||
item = MenuItemArray_get(model->items, shift_position);
|
||||
if(item->icon) {
|
||||
canvas_draw_icon_animation(canvas, 4, 47, item->icon);
|
||||
}
|
||||
canvas_draw_str(canvas, 22, 58, item->label);
|
||||
// Frame and scrollbar
|
||||
elements_frame(canvas, 0, 21, 128 - 5, 21);
|
||||
elements_scrollbar(canvas, position, items_count);
|
||||
} else {
|
||||
canvas_draw_str(canvas, 2, 32, "Empty");
|
||||
elements_scrollbar(canvas, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool menu_input_callback(InputEvent* event, void* context) {
|
||||
Menu* menu = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyUp) {
|
||||
consumed = true;
|
||||
menu_process_up(menu);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
consumed = true;
|
||||
menu_process_down(menu);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
consumed = true;
|
||||
menu_process_ok(menu);
|
||||
}
|
||||
} else if(event->type == InputTypeRepeat) {
|
||||
if(event->key == InputKeyUp) {
|
||||
consumed = true;
|
||||
menu_process_up(menu);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
consumed = true;
|
||||
menu_process_down(menu);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static void menu_enter(void* context) {
|
||||
Menu* menu = context;
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
MenuItem* item = MenuItemArray_get(model->items, model->position);
|
||||
if(item && item->icon) {
|
||||
icon_animation_start(item->icon);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
static void menu_exit(void* context) {
|
||||
Menu* menu = context;
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
MenuItem* item = MenuItemArray_get(model->items, model->position);
|
||||
if(item && item->icon) {
|
||||
icon_animation_stop(item->icon);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
Menu* menu_alloc() {
|
||||
Menu* menu = malloc(sizeof(Menu));
|
||||
menu->view = view_alloc(menu->view);
|
||||
view_set_context(menu->view, menu);
|
||||
view_allocate_model(menu->view, ViewModelTypeLocking, sizeof(MenuModel));
|
||||
view_set_draw_callback(menu->view, menu_draw_callback);
|
||||
view_set_input_callback(menu->view, menu_input_callback);
|
||||
view_set_enter_callback(menu->view, menu_enter);
|
||||
view_set_exit_callback(menu->view, menu_exit);
|
||||
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
MenuItemArray_init(model->items);
|
||||
model->position = 0;
|
||||
return true;
|
||||
});
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
void menu_free(Menu* menu) {
|
||||
furi_assert(menu);
|
||||
menu_reset(menu);
|
||||
view_free(menu->view);
|
||||
free(menu);
|
||||
}
|
||||
|
||||
View* menu_get_view(Menu* menu) {
|
||||
furi_assert(menu);
|
||||
return (menu->view);
|
||||
}
|
||||
|
||||
void menu_add_item(
|
||||
Menu* menu,
|
||||
const char* label,
|
||||
const Icon* icon,
|
||||
uint32_t index,
|
||||
MenuItemCallback callback,
|
||||
void* context) {
|
||||
furi_assert(menu);
|
||||
furi_assert(label);
|
||||
|
||||
MenuItem* item = NULL;
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
item = MenuItemArray_push_new(model->items);
|
||||
item->label = label;
|
||||
item->icon = icon ? icon_animation_alloc(icon) : icon_animation_alloc(&A_Plugins_14);
|
||||
view_tie_icon_animation(menu->view, item->icon);
|
||||
item->index = index;
|
||||
item->callback = callback;
|
||||
item->callback_context = context;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void menu_reset(Menu* menu) {
|
||||
furi_assert(menu);
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
for
|
||||
M_EACH(item, model->items, MenuItemArray_t) {
|
||||
icon_animation_stop(item->icon);
|
||||
icon_animation_free(item->icon);
|
||||
}
|
||||
|
||||
MenuItemArray_reset(model->items);
|
||||
model->position = 0;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void menu_set_selected_item(Menu* menu, uint32_t index) {
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
if(index >= MenuItemArray_size(model->items)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
model->position = index;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void menu_process_up(Menu* menu) {
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
MenuItem* item = MenuItemArray_get(model->items, model->position);
|
||||
if(item && item->icon) {
|
||||
icon_animation_stop(item->icon);
|
||||
}
|
||||
|
||||
if(model->position > 0) {
|
||||
model->position--;
|
||||
} else {
|
||||
model->position = MenuItemArray_size(model->items) - 1;
|
||||
}
|
||||
|
||||
item = MenuItemArray_get(model->items, model->position);
|
||||
if(item && item->icon) {
|
||||
icon_animation_start(item->icon);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void menu_process_down(Menu* menu) {
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
MenuItem* item = MenuItemArray_get(model->items, model->position);
|
||||
if(item && item->icon) {
|
||||
icon_animation_stop(item->icon);
|
||||
}
|
||||
|
||||
if(model->position < MenuItemArray_size(model->items) - 1) {
|
||||
model->position++;
|
||||
} else {
|
||||
model->position = 0;
|
||||
}
|
||||
|
||||
item = MenuItemArray_get(model->items, model->position);
|
||||
if(item && item->icon) {
|
||||
icon_animation_start(item->icon);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void menu_process_ok(Menu* menu) {
|
||||
MenuItem* item = NULL;
|
||||
with_view_model(
|
||||
menu->view, (MenuModel * model) {
|
||||
if(model->position < MenuItemArray_size(model->items)) {
|
||||
item = MenuItemArray_get(model->items, model->position);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if(item && item->callback) {
|
||||
item->callback(item->callback_context, item->index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file menu.h
|
||||
* GUI: Menu view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Menu anonymous structure */
|
||||
typedef struct Menu Menu;
|
||||
|
||||
/** Menu Item Callback */
|
||||
typedef void (*MenuItemCallback)(void* context, uint32_t index);
|
||||
|
||||
/** Menu allocation and initialization
|
||||
*
|
||||
* @return Menu instance
|
||||
*/
|
||||
Menu* menu_alloc();
|
||||
|
||||
/** Free menu
|
||||
*
|
||||
* @param menu Menu instance
|
||||
*/
|
||||
void menu_free(Menu* menu);
|
||||
|
||||
/** Get Menu view
|
||||
*
|
||||
* @param menu Menu instance
|
||||
*
|
||||
* @return View instance
|
||||
*/
|
||||
View* menu_get_view(Menu* menu);
|
||||
|
||||
/** Add item to menu
|
||||
*
|
||||
* @param menu Menu instance
|
||||
* @param label menu item string label
|
||||
* @param icon IconAnimation instance
|
||||
* @param index menu item index
|
||||
* @param callback MenuItemCallback instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void menu_add_item(
|
||||
Menu* menu,
|
||||
const char* label,
|
||||
const Icon* icon,
|
||||
uint32_t index,
|
||||
MenuItemCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Clean menu
|
||||
* @note this function does not free menu instance
|
||||
*
|
||||
* @param menu Menu instance
|
||||
*/
|
||||
void menu_reset(Menu* menu);
|
||||
|
||||
/** Set current menu item
|
||||
*
|
||||
* @param menu Menu instance
|
||||
* @param index The index
|
||||
*/
|
||||
void menu_set_selected_item(Menu* menu, uint32_t index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,246 @@
|
||||
#include "popup.h"
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
struct Popup {
|
||||
View* view;
|
||||
void* context;
|
||||
PopupCallback callback;
|
||||
|
||||
FuriTimer* 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 {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
const Icon* icon;
|
||||
} 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);
|
||||
|
||||
if(model->icon.icon != NULL) {
|
||||
canvas_draw_icon(canvas, model->icon.x, model->icon.y, model->icon.icon);
|
||||
}
|
||||
|
||||
// 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->type == InputTypeShort && 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 / furi_kernel_get_tick_frequency());
|
||||
if(timer_period == 0) timer_period = 1;
|
||||
|
||||
if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) {
|
||||
furi_assert(0);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void popup_stop_timer(void* context) {
|
||||
Popup* popup = context;
|
||||
furi_timer_stop(popup->timer);
|
||||
}
|
||||
|
||||
Popup* popup_alloc() {
|
||||
Popup* popup = malloc(sizeof(Popup));
|
||||
popup->view = view_alloc();
|
||||
popup->timer = furi_timer_alloc(popup_timer_callback, FuriTimerTypeOnce, popup);
|
||||
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;
|
||||
|
||||
model->icon.x = 0;
|
||||
model->icon.y = 0;
|
||||
model->icon.icon = NULL;
|
||||
return true;
|
||||
});
|
||||
return popup;
|
||||
}
|
||||
|
||||
void popup_free(Popup* popup) {
|
||||
furi_assert(popup);
|
||||
furi_timer_free(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;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void popup_set_icon(Popup* popup, uint8_t x, uint8_t y, const Icon* icon) {
|
||||
furi_assert(popup);
|
||||
with_view_model(
|
||||
popup->view, (PopupModel * model) {
|
||||
model->icon.x = x;
|
||||
model->icon.y = y;
|
||||
model->icon.icon = icon;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void popup_reset(Popup* popup) {
|
||||
furi_assert(popup);
|
||||
|
||||
with_view_model(
|
||||
popup->view, (PopupModel * model) {
|
||||
memset(&model->header, 0, sizeof(model->header));
|
||||
memset(&model->text, 0, sizeof(model->text));
|
||||
memset(&model->icon, 0, sizeof(model->icon));
|
||||
return false;
|
||||
});
|
||||
popup->callback = NULL;
|
||||
popup->context = NULL;
|
||||
popup->timer_enabled = false;
|
||||
popup->timer_period_in_ms = 0;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @file popup.h
|
||||
* GUI: Popup view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** 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/
|
||||
*
|
||||
* @return Popup instance
|
||||
*/
|
||||
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 callback PopupCallback
|
||||
*/
|
||||
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 x position
|
||||
* @param y y position
|
||||
* @param horizontal horizontal alignment
|
||||
* @param vertical vertical 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 x position
|
||||
* @param y y position
|
||||
* @param horizontal horizontal alignment
|
||||
* @param vertical vertical aligment
|
||||
*/
|
||||
void popup_set_text(
|
||||
Popup* popup,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical);
|
||||
|
||||
/** Set popup icon
|
||||
*
|
||||
* If icon position is negative, popup icon will not be rendered
|
||||
*
|
||||
* @param popup Popup instance
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param icon pointer to Icon data
|
||||
*/
|
||||
void popup_set_icon(Popup* popup, uint8_t x, uint8_t y, const Icon* icon);
|
||||
|
||||
/** 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);
|
||||
|
||||
/** Reset popup instance state
|
||||
*
|
||||
* @param popup Popup instance
|
||||
*/
|
||||
void popup_reset(Popup* popup);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,296 @@
|
||||
#include "submenu.h"
|
||||
|
||||
#include <m-array.h>
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
struct Submenu {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* label;
|
||||
uint32_t index;
|
||||
SubmenuItemCallback callback;
|
||||
void* callback_context;
|
||||
} SubmenuItem;
|
||||
|
||||
ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST);
|
||||
|
||||
typedef struct {
|
||||
SubmenuItemArray_t items;
|
||||
const char* header;
|
||||
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);
|
||||
|
||||
uint8_t position = 0;
|
||||
SubmenuItemArray_it_t it;
|
||||
|
||||
if(model->header) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 4, 11, model->header);
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
|
||||
SubmenuItemArray_next(it)) {
|
||||
uint8_t item_position = position - model->window_position;
|
||||
uint8_t items_on_screen = model->header ? 3 : 4;
|
||||
uint8_t y_offset = model->header ? 16 : 0;
|
||||
|
||||
if(item_position < items_on_screen) {
|
||||
if(position == model->position) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
elements_slightly_rounded_box(
|
||||
canvas,
|
||||
0,
|
||||
y_offset + (item_position * item_height) + 1,
|
||||
item_width,
|
||||
item_height - 2);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
string_t disp_str;
|
||||
string_init_set_str(disp_str, SubmenuItemArray_cref(it)->label);
|
||||
elements_string_fit_width(canvas, disp_str, item_width - 20);
|
||||
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
6,
|
||||
y_offset + (item_position * item_height) + item_height - 4,
|
||||
string_get_cstr(disp_str));
|
||||
|
||||
string_clear(disp_str);
|
||||
}
|
||||
|
||||
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->type == InputTypeShort) {
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
consumed = true;
|
||||
submenu_process_up(submenu);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
consumed = true;
|
||||
submenu_process_down(submenu);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
consumed = true;
|
||||
submenu_process_ok(submenu);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event->type == InputTypeRepeat) {
|
||||
if(event->key == InputKeyUp) {
|
||||
consumed = true;
|
||||
submenu_process_up(submenu);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
consumed = true;
|
||||
submenu_process_down(submenu);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
Submenu* submenu_alloc() {
|
||||
Submenu* submenu = malloc(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;
|
||||
model->window_position = 0;
|
||||
model->header = NULL;
|
||||
return true;
|
||||
});
|
||||
|
||||
return submenu;
|
||||
}
|
||||
|
||||
void submenu_free(Submenu* submenu) {
|
||||
furi_assert(submenu);
|
||||
|
||||
with_view_model(
|
||||
submenu->view, (SubmenuModel * model) {
|
||||
SubmenuItemArray_clear(model->items);
|
||||
return true;
|
||||
});
|
||||
view_free(submenu->view);
|
||||
free(submenu);
|
||||
}
|
||||
|
||||
View* submenu_get_view(Submenu* submenu) {
|
||||
furi_assert(submenu);
|
||||
return submenu->view;
|
||||
}
|
||||
|
||||
void submenu_add_item(
|
||||
Submenu* submenu,
|
||||
const char* label,
|
||||
uint32_t index,
|
||||
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->index = index;
|
||||
item->callback = callback;
|
||||
item->callback_context = callback_context;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void submenu_reset(Submenu* submenu) {
|
||||
furi_assert(submenu);
|
||||
|
||||
with_view_model(
|
||||
submenu->view, (SubmenuModel * model) {
|
||||
SubmenuItemArray_reset(model->items);
|
||||
model->position = 0;
|
||||
model->window_position = 0;
|
||||
model->header = NULL;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void submenu_set_selected_item(Submenu* submenu, uint32_t index) {
|
||||
with_view_model(
|
||||
submenu->view, (SubmenuModel * model) {
|
||||
uint32_t position = 0;
|
||||
SubmenuItemArray_it_t it;
|
||||
for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
|
||||
SubmenuItemArray_next(it)) {
|
||||
if(index == SubmenuItemArray_cref(it)->index) {
|
||||
break;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
if(position >= SubmenuItemArray_size(model->items)) {
|
||||
position = 0;
|
||||
}
|
||||
|
||||
model->position = position;
|
||||
model->window_position = position;
|
||||
|
||||
if(model->window_position > 0) {
|
||||
model->window_position -= 1;
|
||||
}
|
||||
|
||||
uint8_t items_on_screen = model->header ? 3 : 4;
|
||||
|
||||
if(SubmenuItemArray_size(model->items) <= items_on_screen) {
|
||||
model->window_position = 0;
|
||||
} else {
|
||||
if(model->window_position >=
|
||||
(SubmenuItemArray_size(model->items) - items_on_screen)) {
|
||||
model->window_position =
|
||||
(SubmenuItemArray_size(model->items) - items_on_screen);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void submenu_process_up(Submenu* submenu) {
|
||||
with_view_model(
|
||||
submenu->view, (SubmenuModel * model) {
|
||||
uint8_t items_on_screen = model->header ? 3 : 4;
|
||||
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;
|
||||
if(model->position > (items_on_screen - 1)) {
|
||||
model->window_position = model->position - (items_on_screen - 1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void submenu_process_down(Submenu* submenu) {
|
||||
with_view_model(
|
||||
submenu->view, (SubmenuModel * model) {
|
||||
uint8_t items_on_screen = model->header ? 3 : 4;
|
||||
if(model->position < (SubmenuItemArray_size(model->items) - 1)) {
|
||||
model->position++;
|
||||
if((model->position - model->window_position) > (items_on_screen - 2) &&
|
||||
model->window_position <
|
||||
(SubmenuItemArray_size(model->items) - items_on_screen)) {
|
||||
model->window_position++;
|
||||
}
|
||||
} else {
|
||||
model->position = 0;
|
||||
model->window_position = 0;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if(item && item->callback) {
|
||||
item->callback(item->callback_context, item->index);
|
||||
}
|
||||
}
|
||||
|
||||
void submenu_set_header(Submenu* submenu, const char* header) {
|
||||
furi_assert(submenu);
|
||||
|
||||
with_view_model(
|
||||
submenu->view, (SubmenuModel * model) {
|
||||
model->header = header;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @file submenu.h
|
||||
* GUI: SubMenu view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Submenu anonymous structure */
|
||||
typedef struct Submenu Submenu;
|
||||
typedef void (*SubmenuItemCallback)(void* context, uint32_t index);
|
||||
|
||||
/** Allocate and initialize submenu
|
||||
*
|
||||
* This submenu is used to select one option
|
||||
*
|
||||
* @return Submenu instance
|
||||
*/
|
||||
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 index menu item index, used for callback, may be
|
||||
* the same with other items
|
||||
* @param callback menu item callback
|
||||
* @param callback_context menu item callback context
|
||||
*/
|
||||
void submenu_add_item(
|
||||
Submenu* submenu,
|
||||
const char* label,
|
||||
uint32_t index,
|
||||
SubmenuItemCallback callback,
|
||||
void* callback_context);
|
||||
|
||||
/** Remove all items from submenu
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
*/
|
||||
void submenu_reset(Submenu* submenu);
|
||||
|
||||
/** Set submenu item selector
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
* @param index The index
|
||||
*/
|
||||
void submenu_set_selected_item(Submenu* submenu, uint32_t index);
|
||||
|
||||
/** Set optional header for submenu
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
* @param header header to set
|
||||
*/
|
||||
void submenu_set_header(Submenu* submenu, const char* header);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,214 @@
|
||||
#include "text_box.h"
|
||||
#include "gui/canvas.h"
|
||||
#include <m-string.h>
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct TextBox {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* text;
|
||||
char* text_pos;
|
||||
string_t text_formatted;
|
||||
int32_t scroll_pos;
|
||||
int32_t scroll_num;
|
||||
TextBoxFont font;
|
||||
TextBoxFocus focus;
|
||||
bool formatted;
|
||||
} TextBoxModel;
|
||||
|
||||
static void text_box_process_down(TextBox* text_box) {
|
||||
with_view_model(
|
||||
text_box->view, (TextBoxModel * model) {
|
||||
if(model->scroll_pos < model->scroll_num - 1) {
|
||||
model->scroll_pos++;
|
||||
// Search next line start
|
||||
while(*model->text_pos++ != '\n')
|
||||
;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void text_box_process_up(TextBox* text_box) {
|
||||
with_view_model(
|
||||
text_box->view, (TextBoxModel * model) {
|
||||
if(model->scroll_pos > 0) {
|
||||
model->scroll_pos--;
|
||||
// Reach last symbol of previous line
|
||||
model->text_pos--;
|
||||
// Search prevous line start
|
||||
while((model->text_pos != model->text) && (*(--model->text_pos) != '\n'))
|
||||
;
|
||||
if(*model->text_pos == '\n') {
|
||||
model->text_pos++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) {
|
||||
size_t i = 0;
|
||||
size_t line_width = 0;
|
||||
const char* str = model->text;
|
||||
size_t line_num = 0;
|
||||
|
||||
const size_t text_width = 120;
|
||||
|
||||
while(str[i] != '\0') {
|
||||
char symb = str[i++];
|
||||
if(symb != '\n') {
|
||||
size_t glyph_width = canvas_glyph_width(canvas, symb);
|
||||
if(line_width + glyph_width > text_width) {
|
||||
line_num++;
|
||||
line_width = 0;
|
||||
string_push_back(model->text_formatted, '\n');
|
||||
}
|
||||
line_width += glyph_width;
|
||||
} else {
|
||||
line_num++;
|
||||
line_width = 0;
|
||||
}
|
||||
string_push_back(model->text_formatted, symb);
|
||||
}
|
||||
line_num++;
|
||||
model->text = string_get_cstr(model->text_formatted);
|
||||
model->text_pos = (char*)model->text;
|
||||
if(model->focus == TextBoxFocusEnd && line_num > 5) {
|
||||
// Set text position to 5th line from the end
|
||||
for(uint8_t i = 0; i < line_num - 5; i++) {
|
||||
while(*model->text_pos++ != '\n') {
|
||||
};
|
||||
}
|
||||
model->scroll_num = line_num - 4;
|
||||
model->scroll_pos = line_num - 5;
|
||||
} else {
|
||||
model->scroll_num = MAX(line_num - 4, 0u);
|
||||
model->scroll_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void text_box_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
TextBoxModel* model = _model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
if(model->font == TextBoxFontText) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
} else if(model->font == TextBoxFontHex) {
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
}
|
||||
|
||||
if(!model->formatted) {
|
||||
text_box_insert_endline(canvas, model);
|
||||
model->formatted = true;
|
||||
}
|
||||
|
||||
elements_slightly_rounded_frame(canvas, 0, 0, 124, 64);
|
||||
elements_multiline_text(canvas, 3, 11, model->text_pos);
|
||||
elements_scrollbar(canvas, model->scroll_pos, model->scroll_num);
|
||||
}
|
||||
|
||||
static bool text_box_view_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
TextBox* text_box = context;
|
||||
bool consumed = false;
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyDown) {
|
||||
text_box_process_down(text_box);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyUp) {
|
||||
text_box_process_up(text_box);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
TextBox* text_box_alloc() {
|
||||
TextBox* text_box = malloc(sizeof(TextBox));
|
||||
text_box->view = view_alloc();
|
||||
view_set_context(text_box->view, text_box);
|
||||
view_allocate_model(text_box->view, ViewModelTypeLocking, sizeof(TextBoxModel));
|
||||
view_set_draw_callback(text_box->view, text_box_view_draw_callback);
|
||||
view_set_input_callback(text_box->view, text_box_view_input_callback);
|
||||
|
||||
with_view_model(
|
||||
text_box->view, (TextBoxModel * model) {
|
||||
model->text = NULL;
|
||||
string_init_set_str(model->text_formatted, "");
|
||||
model->formatted = false;
|
||||
model->font = TextBoxFontText;
|
||||
return true;
|
||||
});
|
||||
|
||||
return text_box;
|
||||
}
|
||||
|
||||
void text_box_free(TextBox* text_box) {
|
||||
furi_assert(text_box);
|
||||
|
||||
with_view_model(
|
||||
text_box->view, (TextBoxModel * model) {
|
||||
string_clear(model->text_formatted);
|
||||
return true;
|
||||
});
|
||||
view_free(text_box->view);
|
||||
free(text_box);
|
||||
}
|
||||
|
||||
View* text_box_get_view(TextBox* text_box) {
|
||||
furi_assert(text_box);
|
||||
return text_box->view;
|
||||
}
|
||||
|
||||
void text_box_reset(TextBox* text_box) {
|
||||
furi_assert(text_box);
|
||||
|
||||
with_view_model(
|
||||
text_box->view, (TextBoxModel * model) {
|
||||
model->text = NULL;
|
||||
string_set_str(model->text_formatted, "");
|
||||
model->font = TextBoxFontText;
|
||||
model->focus = TextBoxFocusStart;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void text_box_set_text(TextBox* text_box, const char* text) {
|
||||
furi_assert(text_box);
|
||||
furi_assert(text);
|
||||
|
||||
with_view_model(
|
||||
text_box->view, (TextBoxModel * model) {
|
||||
model->text = text;
|
||||
string_reset(model->text_formatted);
|
||||
string_reserve(model->text_formatted, strlen(text));
|
||||
model->formatted = false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void text_box_set_font(TextBox* text_box, TextBoxFont font) {
|
||||
furi_assert(text_box);
|
||||
|
||||
with_view_model(
|
||||
text_box->view, (TextBoxModel * model) {
|
||||
model->font = font;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void text_box_set_focus(TextBox* text_box, TextBoxFocus focus) {
|
||||
furi_assert(text_box);
|
||||
|
||||
with_view_model(
|
||||
text_box->view, (TextBoxModel * model) {
|
||||
model->focus = focus;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @file text_box.h
|
||||
* GUI: TextBox view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** TextBox anonymous structure */
|
||||
typedef struct TextBox TextBox;
|
||||
|
||||
typedef enum {
|
||||
TextBoxFontText,
|
||||
TextBoxFontHex,
|
||||
} TextBoxFont;
|
||||
|
||||
typedef enum {
|
||||
TextBoxFocusStart,
|
||||
TextBoxFocusEnd,
|
||||
} TextBoxFocus;
|
||||
|
||||
/** Allocate and initialize text_box
|
||||
*
|
||||
* @return TextBox instance
|
||||
*/
|
||||
TextBox* text_box_alloc();
|
||||
|
||||
/** Deinitialize and free text_box
|
||||
*
|
||||
* @param text_box text_box instance
|
||||
*/
|
||||
void text_box_free(TextBox* text_box);
|
||||
|
||||
/** Get text_box view
|
||||
*
|
||||
* @param text_box TextBox instance
|
||||
*
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* text_box_get_view(TextBox* text_box);
|
||||
|
||||
/** Clean text_box
|
||||
*
|
||||
* @param text_box TextBox instance
|
||||
*/
|
||||
void text_box_reset(TextBox* text_box);
|
||||
|
||||
/** Set text for text_box
|
||||
*
|
||||
* @param text_box TextBox instance
|
||||
* @param text text to set
|
||||
*/
|
||||
void text_box_set_text(TextBox* text_box, const char* text);
|
||||
|
||||
/** Set TextBox font
|
||||
*
|
||||
* @param text_box TextBox instance
|
||||
* @param font TextBoxFont instance
|
||||
*/
|
||||
void text_box_set_font(TextBox* text_box, TextBoxFont font);
|
||||
|
||||
/** Set TextBox focus
|
||||
* @note Use to display from start or from end
|
||||
*
|
||||
* @param text_box TextBox instance
|
||||
* @param focus TextBoxFocus instance
|
||||
*/
|
||||
void text_box_set_focus(TextBox* text_box, TextBoxFocus focus);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,561 @@
|
||||
#include "text_input.h"
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
struct TextInput {
|
||||
View* view;
|
||||
FuriTimer* timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char text;
|
||||
const uint8_t x;
|
||||
const uint8_t y;
|
||||
} TextInputKey;
|
||||
|
||||
typedef struct {
|
||||
const char* header;
|
||||
char* text_buffer;
|
||||
size_t text_buffer_size;
|
||||
bool clear_default_text;
|
||||
|
||||
TextInputCallback callback;
|
||||
void* callback_context;
|
||||
|
||||
uint8_t selected_row;
|
||||
uint8_t selected_column;
|
||||
|
||||
TextInputValidatorCallback validator_callback;
|
||||
void* validator_callback_context;
|
||||
string_t validator_text;
|
||||
bool valadator_message_visible;
|
||||
} 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},
|
||||
{'0', 91, 8},
|
||||
{'1', 100, 8},
|
||||
{'2', 110, 8},
|
||||
{'3', 120, 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},
|
||||
{BACKSPACE_KEY, 82, 12},
|
||||
{'4', 100, 20},
|
||||
{'5', 110, 20},
|
||||
{'6', 120, 20},
|
||||
};
|
||||
|
||||
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},
|
||||
{'_', 64, 32},
|
||||
{ENTER_KEY, 74, 23},
|
||||
{'7', 100, 32},
|
||||
{'8', 110, 32},
|
||||
{'9', 120, 32},
|
||||
};
|
||||
|
||||
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 char get_selected_char(TextInputModel* model) {
|
||||
return get_row(model->selected_row)[model->selected_column].text;
|
||||
}
|
||||
|
||||
static bool char_is_lowercase(char letter) {
|
||||
return (letter >= 0x61 && letter <= 0x7A);
|
||||
}
|
||||
|
||||
static char char_to_uppercase(const char letter) {
|
||||
if(letter == '_') {
|
||||
return 0x20;
|
||||
} else if(isalpha(letter)) {
|
||||
return (letter - 0x20);
|
||||
} else {
|
||||
return letter;
|
||||
}
|
||||
}
|
||||
|
||||
static void text_input_backspace_cb(TextInputModel* model) {
|
||||
uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
|
||||
if(text_length > 0) {
|
||||
model->text_buffer[text_length - 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void text_input_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
TextInputModel* model = _model;
|
||||
uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
|
||||
uint8_t needed_string_width = canvas_width(canvas) - 8;
|
||||
uint8_t start_pos = 4;
|
||||
|
||||
const char* text = model->text_buffer;
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_str(canvas, 2, 8, model->header);
|
||||
elements_slightly_rounded_frame(canvas, 1, 12, 126, 15);
|
||||
|
||||
if(canvas_string_width(canvas, text) > needed_string_width) {
|
||||
canvas_draw_str(canvas, start_pos, 22, "...");
|
||||
start_pos += 6;
|
||||
needed_string_width -= 8;
|
||||
}
|
||||
|
||||
while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) {
|
||||
text++;
|
||||
}
|
||||
|
||||
if(model->clear_default_text) {
|
||||
elements_slightly_rounded_box(
|
||||
canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 22, "|");
|
||||
canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 22, "|");
|
||||
}
|
||||
canvas_draw_str(canvas, start_pos, 22, text);
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
|
||||
for(uint8_t row = 0; row <= keyboard_row_count; row++) {
|
||||
const uint8_t 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(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySaveSelected_24x11);
|
||||
} else {
|
||||
canvas_draw_icon(
|
||||
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(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeyBackspaceSelected_16x9);
|
||||
} else {
|
||||
canvas_draw_icon(
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(model->valadator_message_visible) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 8, 10, 110, 48);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42);
|
||||
canvas_draw_rframe(canvas, 8, 8, 112, 50, 3);
|
||||
canvas_draw_rframe(canvas, 9, 9, 110, 48, 2);
|
||||
elements_multiline_text(canvas, 62, 20, string_get_cstr(model->validator_text));
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
static void text_input_handle_up(TextInput* text_input, TextInputModel* model) {
|
||||
UNUSED(text_input);
|
||||
if(model->selected_row > 0) {
|
||||
model->selected_row--;
|
||||
if(model->selected_column > get_row_size(model->selected_row) - 6) {
|
||||
model->selected_column = model->selected_column + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void text_input_handle_down(TextInput* text_input, TextInputModel* model) {
|
||||
UNUSED(text_input);
|
||||
if(model->selected_row < keyboard_row_count - 1) {
|
||||
model->selected_row++;
|
||||
if(model->selected_column > get_row_size(model->selected_row) - 4) {
|
||||
model->selected_column = model->selected_column - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void text_input_handle_left(TextInput* text_input, TextInputModel* model) {
|
||||
UNUSED(text_input);
|
||||
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, TextInputModel* model) {
|
||||
UNUSED(text_input);
|
||||
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, TextInputModel* model, bool shift) {
|
||||
char selected = get_selected_char(model);
|
||||
uint8_t text_length = strlen(model->text_buffer);
|
||||
|
||||
if(shift) {
|
||||
selected = char_to_uppercase(selected);
|
||||
}
|
||||
|
||||
if(selected == ENTER_KEY) {
|
||||
if(model->validator_callback &&
|
||||
(!model->validator_callback(
|
||||
model->text_buffer, model->validator_text, model->validator_callback_context))) {
|
||||
model->valadator_message_visible = true;
|
||||
furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4);
|
||||
} else if(model->callback != 0 && text_length > 0) {
|
||||
model->callback(model->callback_context);
|
||||
}
|
||||
} else if(selected == BACKSPACE_KEY) {
|
||||
text_input_backspace_cb(model);
|
||||
} else if(text_length < (model->text_buffer_size - 1)) {
|
||||
if(model->clear_default_text) {
|
||||
text_length = 0;
|
||||
}
|
||||
if(text_length == 0 && char_is_lowercase(selected)) {
|
||||
selected = char_to_uppercase(selected);
|
||||
}
|
||||
model->text_buffer[text_length] = selected;
|
||||
model->text_buffer[text_length + 1] = 0;
|
||||
}
|
||||
model->clear_default_text = false;
|
||||
}
|
||||
|
||||
static bool text_input_view_input_callback(InputEvent* event, void* context) {
|
||||
TextInput* text_input = context;
|
||||
furi_assert(text_input);
|
||||
|
||||
bool consumed = false;
|
||||
|
||||
// Acquire model
|
||||
TextInputModel* model = view_get_model(text_input->view);
|
||||
|
||||
if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
|
||||
model->valadator_message_visible) {
|
||||
model->valadator_message_visible = false;
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
consumed = true;
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
text_input_handle_up(text_input, model);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
text_input_handle_down(text_input, model);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
text_input_handle_left(text_input, model);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
text_input_handle_right(text_input, model);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
text_input_handle_ok(text_input, model, false);
|
||||
break;
|
||||
default:
|
||||
consumed = false;
|
||||
break;
|
||||
}
|
||||
} else if(event->type == InputTypeLong) {
|
||||
consumed = true;
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
text_input_handle_up(text_input, model);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
text_input_handle_down(text_input, model);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
text_input_handle_left(text_input, model);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
text_input_handle_right(text_input, model);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
text_input_handle_ok(text_input, model, true);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
text_input_backspace_cb(model);
|
||||
break;
|
||||
default:
|
||||
consumed = false;
|
||||
break;
|
||||
}
|
||||
} else if(event->type == InputTypeRepeat) {
|
||||
consumed = true;
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
text_input_handle_up(text_input, model);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
text_input_handle_down(text_input, model);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
text_input_handle_left(text_input, model);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
text_input_handle_right(text_input, model);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
text_input_backspace_cb(model);
|
||||
break;
|
||||
default:
|
||||
consumed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Commit model
|
||||
view_commit_model(text_input->view, consumed);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void text_input_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
TextInput* text_input = context;
|
||||
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
model->valadator_message_visible = false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
TextInput* text_input_alloc() {
|
||||
TextInput* text_input = malloc(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);
|
||||
|
||||
text_input->timer = furi_timer_alloc(text_input_timer_callback, FuriTimerTypeOnce, text_input);
|
||||
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
string_init(model->validator_text);
|
||||
return false;
|
||||
});
|
||||
|
||||
text_input_reset(text_input);
|
||||
|
||||
return text_input;
|
||||
}
|
||||
|
||||
void text_input_free(TextInput* text_input) {
|
||||
furi_assert(text_input);
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
string_clear(model->validator_text);
|
||||
return false;
|
||||
});
|
||||
|
||||
// Send stop command
|
||||
furi_timer_stop(text_input->timer);
|
||||
// Release allocated memory
|
||||
furi_timer_free(text_input->timer);
|
||||
|
||||
view_free(text_input->view);
|
||||
|
||||
free(text_input);
|
||||
}
|
||||
|
||||
void text_input_reset(TextInput* text_input) {
|
||||
furi_assert(text_input);
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
model->text_buffer_size = 0;
|
||||
model->header = "";
|
||||
model->selected_row = 0;
|
||||
model->selected_column = 0;
|
||||
model->clear_default_text = false;
|
||||
model->text_buffer = NULL;
|
||||
model->text_buffer_size = 0;
|
||||
model->callback = NULL;
|
||||
model->callback_context = NULL;
|
||||
model->validator_callback = NULL;
|
||||
model->validator_callback_context = NULL;
|
||||
string_reset(model->validator_text);
|
||||
model->valadator_message_visible = false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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_buffer,
|
||||
size_t text_buffer_size,
|
||||
bool clear_default_text) {
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
model->callback = callback;
|
||||
model->callback_context = callback_context;
|
||||
model->text_buffer = text_buffer;
|
||||
model->text_buffer_size = text_buffer_size;
|
||||
model->clear_default_text = clear_default_text;
|
||||
if(text_buffer && text_buffer[0] != '\0') {
|
||||
// Set focus on Save
|
||||
model->selected_row = 2;
|
||||
model->selected_column = 8;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void text_input_set_validator(
|
||||
TextInput* text_input,
|
||||
TextInputValidatorCallback callback,
|
||||
void* callback_context) {
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
model->validator_callback = callback;
|
||||
model->validator_callback_context = callback_context;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input) {
|
||||
TextInputValidatorCallback validator_callback = NULL;
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
validator_callback = model->validator_callback;
|
||||
return false;
|
||||
});
|
||||
return validator_callback;
|
||||
}
|
||||
|
||||
void* text_input_get_validator_callback_context(TextInput* text_input) {
|
||||
void* validator_callback_context = NULL;
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
validator_callback_context = model->validator_callback_context;
|
||||
return false;
|
||||
});
|
||||
return validator_callback_context;
|
||||
}
|
||||
|
||||
void text_input_set_header_text(TextInput* text_input, const char* text) {
|
||||
with_view_model(
|
||||
text_input->view, (TextInputModel * model) {
|
||||
model->header = text;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file text_input.h
|
||||
* GUI: TextInput keybord view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "validators.h"
|
||||
#include <m-string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Text input anonymous structure */
|
||||
typedef struct TextInput TextInput;
|
||||
typedef void (*TextInputCallback)(void* context);
|
||||
typedef bool (*TextInputValidatorCallback)(const char* text, string_t error, void* context);
|
||||
|
||||
/** Allocate and initialize text input
|
||||
*
|
||||
* This text input is used to enter string
|
||||
*
|
||||
* @return TextInput instance
|
||||
*/
|
||||
TextInput* text_input_alloc();
|
||||
|
||||
/** Deinitialize and free text input
|
||||
*
|
||||
* @param text_input TextInput instance
|
||||
*/
|
||||
void text_input_free(TextInput* text_input);
|
||||
|
||||
/** Clean text input view Note: this function does not free memory
|
||||
*
|
||||
* @param text_input Text input instance
|
||||
*/
|
||||
void text_input_reset(TextInput* text_input);
|
||||
|
||||
/** Get text input view
|
||||
*
|
||||
* @param text_input TextInput instance
|
||||
*
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* text_input_get_view(TextInput* text_input);
|
||||
|
||||
/** Set text input result callback
|
||||
*
|
||||
* @param text_input TextInput instance
|
||||
* @param callback callback fn
|
||||
* @param callback_context callback context
|
||||
* @param text_buffer pointer to YOUR text buffer, that we going
|
||||
* to modify
|
||||
* @param text_buffer_size YOUR text buffer size in bytes. Max string
|
||||
* length will be text_buffer_size-1.
|
||||
* @param clear_default_text clear text from text_buffer on first OK
|
||||
* event
|
||||
*/
|
||||
void text_input_set_result_callback(
|
||||
TextInput* text_input,
|
||||
TextInputCallback callback,
|
||||
void* callback_context,
|
||||
char* text_buffer,
|
||||
size_t text_buffer_size,
|
||||
bool clear_default_text);
|
||||
|
||||
void text_input_set_validator(
|
||||
TextInput* text_input,
|
||||
TextInputValidatorCallback callback,
|
||||
void* callback_context);
|
||||
|
||||
TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input);
|
||||
|
||||
void* text_input_get_validator_callback_context(TextInput* text_input);
|
||||
|
||||
/** Set text input header text
|
||||
*
|
||||
* @param text_input TextInput instance
|
||||
* @param text text to be shown
|
||||
*/
|
||||
void text_input_set_header_text(TextInput* text_input, const char* text);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
#include <furi.h>
|
||||
#include "validators.h"
|
||||
#include <storage/storage.h>
|
||||
|
||||
struct ValidatorIsFile {
|
||||
char* app_path_folder;
|
||||
const char* app_extension;
|
||||
char* current_name;
|
||||
};
|
||||
|
||||
bool validator_is_file_callback(const char* text, string_t error, void* context) {
|
||||
furi_assert(context);
|
||||
ValidatorIsFile* instance = context;
|
||||
|
||||
if(instance->current_name != NULL) {
|
||||
if(strcmp(instance->current_name, text) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
string_t path;
|
||||
string_init_printf(path, "%s/%s%s", instance->app_path_folder, text, instance->app_extension);
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_common_stat(storage, string_get_cstr(path), NULL) == FSE_OK) {
|
||||
ret = false;
|
||||
string_printf(error, "This name\nexists!\nChoose\nanother one.");
|
||||
} else {
|
||||
ret = true;
|
||||
}
|
||||
string_clear(path);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ValidatorIsFile* validator_is_file_alloc_init(
|
||||
const char* app_path_folder,
|
||||
const char* app_extension,
|
||||
const char* current_name) {
|
||||
ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile));
|
||||
|
||||
instance->app_path_folder = strdup(app_path_folder);
|
||||
instance->app_extension = app_extension;
|
||||
if(current_name != NULL) {
|
||||
instance->current_name = strdup(current_name);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void validator_is_file_free(ValidatorIsFile* instance) {
|
||||
furi_assert(instance);
|
||||
free(instance->app_path_folder);
|
||||
free(instance->current_name);
|
||||
free(instance);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <m-string.h>
|
||||
#include <core/common_defines.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
typedef struct ValidatorIsFile ValidatorIsFile;
|
||||
|
||||
ValidatorIsFile* validator_is_file_alloc_init(
|
||||
const char* app_path_folder,
|
||||
const char* app_extension,
|
||||
const char* current_name);
|
||||
|
||||
void validator_is_file_free(ValidatorIsFile* instance);
|
||||
|
||||
bool validator_is_file_callback(const char* text, string_t error, void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,388 @@
|
||||
#include "variable_item_list.h"
|
||||
#include "gui/canvas.h"
|
||||
#include <m-array.h>
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct VariableItem {
|
||||
const char* label;
|
||||
uint8_t current_value_index;
|
||||
string_t current_value_text;
|
||||
uint8_t values_count;
|
||||
VariableItemChangeCallback change_callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST);
|
||||
|
||||
struct VariableItemList {
|
||||
View* view;
|
||||
VariableItemListEnterCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
VariableItemArray_t items;
|
||||
uint8_t position;
|
||||
uint8_t window_position;
|
||||
} VariableItemListModel;
|
||||
|
||||
static void variable_item_list_process_up(VariableItemList* variable_item_list);
|
||||
static void variable_item_list_process_down(VariableItemList* variable_item_list);
|
||||
static void variable_item_list_process_left(VariableItemList* variable_item_list);
|
||||
static void variable_item_list_process_right(VariableItemList* variable_item_list);
|
||||
static void variable_item_list_process_ok(VariableItemList* variable_item_list);
|
||||
|
||||
static void variable_item_list_draw_callback(Canvas* canvas, void* _model) {
|
||||
VariableItemListModel* model = _model;
|
||||
|
||||
const uint8_t item_height = 16;
|
||||
const uint8_t item_width = 123;
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
uint8_t position = 0;
|
||||
VariableItemArray_it_t it;
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it);
|
||||
VariableItemArray_next(it)) {
|
||||
uint8_t item_position = position - model->window_position;
|
||||
uint8_t items_on_screen = 4;
|
||||
uint8_t y_offset = 0;
|
||||
|
||||
if(item_position < items_on_screen) {
|
||||
const VariableItem* item = VariableItemArray_cref(it);
|
||||
uint8_t item_y = y_offset + (item_position * item_height);
|
||||
uint8_t item_text_y = item_y + item_height - 4;
|
||||
|
||||
if(position == model->position) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
elements_slightly_rounded_box(canvas, 0, item_y + 1, item_width, item_height - 2);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
canvas_draw_str(canvas, 6, item_text_y, item->label);
|
||||
|
||||
if(item->current_value_index > 0) {
|
||||
canvas_draw_str(canvas, 73, item_text_y, "<");
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
(115 + 73) / 2 + 1,
|
||||
item_text_y,
|
||||
AlignCenter,
|
||||
AlignBottom,
|
||||
string_get_cstr(item->current_value_text));
|
||||
|
||||
if(item->current_value_index < (item->values_count - 1)) {
|
||||
canvas_draw_str(canvas, 115, item_text_y, ">");
|
||||
}
|
||||
}
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items));
|
||||
}
|
||||
|
||||
void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) {
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
uint8_t position = index;
|
||||
if(position >= VariableItemArray_size(model->items)) {
|
||||
position = 0;
|
||||
}
|
||||
|
||||
model->position = position;
|
||||
model->window_position = position;
|
||||
|
||||
if(model->window_position > 0) {
|
||||
model->window_position -= 1;
|
||||
}
|
||||
|
||||
if(VariableItemArray_size(model->items) <= 4) {
|
||||
model->window_position = 0;
|
||||
} else {
|
||||
if(model->window_position >= (VariableItemArray_size(model->items) - 4)) {
|
||||
model->window_position = (VariableItemArray_size(model->items) - 4);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list) {
|
||||
VariableItemListModel* model = view_get_model(variable_item_list->view);
|
||||
uint8_t idx = model->position;
|
||||
view_commit_model(variable_item_list->view, false);
|
||||
return idx;
|
||||
}
|
||||
|
||||
static bool variable_item_list_input_callback(InputEvent* event, void* context) {
|
||||
VariableItemList* variable_item_list = context;
|
||||
furi_assert(variable_item_list);
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
consumed = true;
|
||||
variable_item_list_process_up(variable_item_list);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
consumed = true;
|
||||
variable_item_list_process_down(variable_item_list);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
consumed = true;
|
||||
variable_item_list_process_left(variable_item_list);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
consumed = true;
|
||||
variable_item_list_process_right(variable_item_list);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
variable_item_list_process_ok(variable_item_list);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event->type == InputTypeRepeat) {
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
consumed = true;
|
||||
variable_item_list_process_up(variable_item_list);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
consumed = true;
|
||||
variable_item_list_process_down(variable_item_list);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
consumed = true;
|
||||
variable_item_list_process_left(variable_item_list);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
consumed = true;
|
||||
variable_item_list_process_right(variable_item_list);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void variable_item_list_process_up(VariableItemList* variable_item_list) {
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
uint8_t items_on_screen = 4;
|
||||
if(model->position > 0) {
|
||||
model->position--;
|
||||
if(((model->position - model->window_position) < 1) &&
|
||||
model->window_position > 0) {
|
||||
model->window_position--;
|
||||
}
|
||||
} else {
|
||||
model->position = VariableItemArray_size(model->items) - 1;
|
||||
if(model->position > (items_on_screen - 1)) {
|
||||
model->window_position = model->position - (items_on_screen - 1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void variable_item_list_process_down(VariableItemList* variable_item_list) {
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
uint8_t items_on_screen = 4;
|
||||
if(model->position < (VariableItemArray_size(model->items) - 1)) {
|
||||
model->position++;
|
||||
if((model->position - model->window_position) > (items_on_screen - 2) &&
|
||||
model->window_position <
|
||||
(VariableItemArray_size(model->items) - items_on_screen)) {
|
||||
model->window_position++;
|
||||
}
|
||||
} else {
|
||||
model->position = 0;
|
||||
model->window_position = 0;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
VariableItem* variable_item_list_get_selected_item(VariableItemListModel* model) {
|
||||
VariableItem* item = NULL;
|
||||
|
||||
VariableItemArray_it_t it;
|
||||
uint8_t position = 0;
|
||||
for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it);
|
||||
VariableItemArray_next(it)) {
|
||||
if(position == model->position) {
|
||||
break;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
item = VariableItemArray_ref(it);
|
||||
|
||||
furi_assert(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
void variable_item_list_process_left(VariableItemList* variable_item_list) {
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
VariableItem* item = variable_item_list_get_selected_item(model);
|
||||
if(item->current_value_index > 0) {
|
||||
item->current_value_index--;
|
||||
if(item->change_callback) {
|
||||
item->change_callback(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void variable_item_list_process_right(VariableItemList* variable_item_list) {
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
VariableItem* item = variable_item_list_get_selected_item(model);
|
||||
if(item->current_value_index < (item->values_count - 1)) {
|
||||
item->current_value_index++;
|
||||
if(item->change_callback) {
|
||||
item->change_callback(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void variable_item_list_process_ok(VariableItemList* variable_item_list) {
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
if(variable_item_list->callback) {
|
||||
variable_item_list->callback(variable_item_list->context, model->position);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
VariableItemList* variable_item_list_alloc() {
|
||||
VariableItemList* variable_item_list = malloc(sizeof(VariableItemList));
|
||||
variable_item_list->view = view_alloc();
|
||||
view_set_context(variable_item_list->view, variable_item_list);
|
||||
view_allocate_model(
|
||||
variable_item_list->view, ViewModelTypeLocking, sizeof(VariableItemListModel));
|
||||
view_set_draw_callback(variable_item_list->view, variable_item_list_draw_callback);
|
||||
view_set_input_callback(variable_item_list->view, variable_item_list_input_callback);
|
||||
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
VariableItemArray_init(model->items);
|
||||
model->position = 0;
|
||||
model->window_position = 0;
|
||||
return true;
|
||||
});
|
||||
|
||||
return variable_item_list;
|
||||
}
|
||||
|
||||
void variable_item_list_free(VariableItemList* variable_item_list) {
|
||||
furi_assert(variable_item_list);
|
||||
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
VariableItemArray_it_t it;
|
||||
for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it);
|
||||
VariableItemArray_next(it)) {
|
||||
string_clear(VariableItemArray_ref(it)->current_value_text);
|
||||
}
|
||||
VariableItemArray_clear(model->items);
|
||||
return false;
|
||||
});
|
||||
view_free(variable_item_list->view);
|
||||
free(variable_item_list);
|
||||
}
|
||||
|
||||
void variable_item_list_reset(VariableItemList* variable_item_list) {
|
||||
furi_assert(variable_item_list);
|
||||
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
VariableItemArray_it_t it;
|
||||
for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it);
|
||||
VariableItemArray_next(it)) {
|
||||
string_clear(VariableItemArray_ref(it)->current_value_text);
|
||||
}
|
||||
VariableItemArray_reset(model->items);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
View* variable_item_list_get_view(VariableItemList* variable_item_list) {
|
||||
furi_assert(variable_item_list);
|
||||
return variable_item_list->view;
|
||||
}
|
||||
|
||||
VariableItem* variable_item_list_add(
|
||||
VariableItemList* variable_item_list,
|
||||
const char* label,
|
||||
uint8_t values_count,
|
||||
VariableItemChangeCallback change_callback,
|
||||
void* context) {
|
||||
VariableItem* item = NULL;
|
||||
furi_assert(label);
|
||||
furi_assert(variable_item_list);
|
||||
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
item = VariableItemArray_push_new(model->items);
|
||||
item->label = label;
|
||||
item->values_count = values_count;
|
||||
item->change_callback = change_callback;
|
||||
item->context = context;
|
||||
item->current_value_index = 0;
|
||||
string_init(item->current_value_text);
|
||||
return true;
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void variable_item_list_set_enter_callback(
|
||||
VariableItemList* variable_item_list,
|
||||
VariableItemListEnterCallback callback,
|
||||
void* context) {
|
||||
furi_assert(callback);
|
||||
with_view_model(
|
||||
variable_item_list->view, (VariableItemListModel * model) {
|
||||
UNUSED(model);
|
||||
variable_item_list->callback = callback;
|
||||
variable_item_list->context = context;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index) {
|
||||
item->current_value_index = current_value_index;
|
||||
}
|
||||
|
||||
void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text) {
|
||||
string_set_str(item->current_value_text, current_value_text);
|
||||
}
|
||||
|
||||
uint8_t variable_item_get_current_value_index(VariableItem* item) {
|
||||
return item->current_value_index;
|
||||
}
|
||||
|
||||
void* variable_item_get_context(VariableItem* item) {
|
||||
return item->context;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @file variable_item_list.h
|
||||
* GUI: VariableItemList view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct VariableItemList VariableItemList;
|
||||
typedef struct VariableItem VariableItem;
|
||||
typedef void (*VariableItemChangeCallback)(VariableItem* item);
|
||||
typedef void (*VariableItemListEnterCallback)(void* context, uint32_t index);
|
||||
|
||||
/** Allocate and initialize VariableItemList
|
||||
*
|
||||
* @return VariableItemList*
|
||||
*/
|
||||
VariableItemList* variable_item_list_alloc();
|
||||
|
||||
/** Deinitialize and free VariableItemList
|
||||
*
|
||||
* @param variable_item_list VariableItemList instance
|
||||
*/
|
||||
void variable_item_list_free(VariableItemList* variable_item_list);
|
||||
|
||||
/** Clear all elements from list
|
||||
*
|
||||
* @param variable_item_list VariableItemList instance
|
||||
*/
|
||||
void variable_item_list_reset(VariableItemList* variable_item_list);
|
||||
|
||||
/** Get VariableItemList View instance
|
||||
*
|
||||
* @param variable_item_list VariableItemList instance
|
||||
*
|
||||
* @return View instance
|
||||
*/
|
||||
View* variable_item_list_get_view(VariableItemList* variable_item_list);
|
||||
|
||||
/** Add item to VariableItemList
|
||||
*
|
||||
* @param variable_item_list VariableItemList instance
|
||||
* @param label item name
|
||||
* @param values_count item values count
|
||||
* @param change_callback called on value change in gui
|
||||
* @param context item context
|
||||
*
|
||||
* @return VariableItem* item instance
|
||||
*/
|
||||
VariableItem* variable_item_list_add(
|
||||
VariableItemList* variable_item_list,
|
||||
const char* label,
|
||||
uint8_t values_count,
|
||||
VariableItemChangeCallback change_callback,
|
||||
void* context);
|
||||
|
||||
/** Set enter callback
|
||||
*
|
||||
* @param variable_item_list VariableItemList instance
|
||||
* @param callback VariableItemListEnterCallback instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void variable_item_list_set_enter_callback(
|
||||
VariableItemList* variable_item_list,
|
||||
VariableItemListEnterCallback callback,
|
||||
void* context);
|
||||
|
||||
void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index);
|
||||
|
||||
uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list);
|
||||
|
||||
/** Set item current selected index
|
||||
*
|
||||
* @param item VariableItem* instance
|
||||
* @param current_value_index The current value index
|
||||
*/
|
||||
void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index);
|
||||
|
||||
/** Set item current selected text
|
||||
*
|
||||
* @param item VariableItem* instance
|
||||
* @param current_value_text The current value text
|
||||
*/
|
||||
void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text);
|
||||
|
||||
/** Get item current selected index
|
||||
*
|
||||
* @param item VariableItem* instance
|
||||
*
|
||||
* @return uint8_t current selected index
|
||||
*/
|
||||
uint8_t variable_item_get_current_value_index(VariableItem* item);
|
||||
|
||||
/** Get item context
|
||||
*
|
||||
* @param item VariableItem* instance
|
||||
*
|
||||
* @return void* item context
|
||||
*/
|
||||
void* variable_item_get_context(VariableItem* item);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,208 @@
|
||||
#include <furi.h>
|
||||
#include "widget.h"
|
||||
#include <m-array.h>
|
||||
#include "widget_elements/widget_element_i.h"
|
||||
|
||||
ARRAY_DEF(ElementArray, WidgetElement*, M_PTR_OPLIST);
|
||||
|
||||
struct Widget {
|
||||
View* view;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
ElementArray_t element;
|
||||
} GuiWidgetModel;
|
||||
|
||||
static void gui_widget_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
GuiWidgetModel* model = _model;
|
||||
canvas_clear(canvas);
|
||||
|
||||
// Draw all elements
|
||||
ElementArray_it_t it;
|
||||
ElementArray_it(it, model->element);
|
||||
while(!ElementArray_end_p(it)) {
|
||||
WidgetElement* element = *ElementArray_ref(it);
|
||||
if(element->draw != NULL) {
|
||||
element->draw(canvas, element);
|
||||
}
|
||||
ElementArray_next(it);
|
||||
}
|
||||
}
|
||||
|
||||
static bool gui_widget_view_input_callback(InputEvent* event, void* context) {
|
||||
Widget* widget = context;
|
||||
bool consumed = false;
|
||||
|
||||
// Call all Widget Elements input handlers
|
||||
with_view_model(
|
||||
widget->view, (GuiWidgetModel * model) {
|
||||
ElementArray_it_t it;
|
||||
ElementArray_it(it, model->element);
|
||||
while(!ElementArray_end_p(it)) {
|
||||
WidgetElement* element = *ElementArray_ref(it);
|
||||
if(element->input != NULL) {
|
||||
consumed |= element->input(event, element);
|
||||
}
|
||||
ElementArray_next(it);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
Widget* widget_alloc() {
|
||||
Widget* widget = malloc(sizeof(Widget));
|
||||
widget->view = view_alloc();
|
||||
view_set_context(widget->view, widget);
|
||||
view_allocate_model(widget->view, ViewModelTypeLocking, sizeof(GuiWidgetModel));
|
||||
view_set_draw_callback(widget->view, gui_widget_view_draw_callback);
|
||||
view_set_input_callback(widget->view, gui_widget_view_input_callback);
|
||||
|
||||
with_view_model(
|
||||
widget->view, (GuiWidgetModel * model) {
|
||||
ElementArray_init(model->element);
|
||||
return true;
|
||||
});
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
void widget_reset(Widget* widget) {
|
||||
furi_assert(widget);
|
||||
|
||||
with_view_model(
|
||||
widget->view, (GuiWidgetModel * model) {
|
||||
ElementArray_it_t it;
|
||||
ElementArray_it(it, model->element);
|
||||
while(!ElementArray_end_p(it)) {
|
||||
WidgetElement* element = *ElementArray_ref(it);
|
||||
furi_assert(element->free);
|
||||
element->free(element);
|
||||
ElementArray_next(it);
|
||||
}
|
||||
ElementArray_reset(model->element);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void widget_free(Widget* widget) {
|
||||
furi_assert(widget);
|
||||
// Free all elements
|
||||
widget_reset(widget);
|
||||
// Free elements container
|
||||
with_view_model(
|
||||
widget->view, (GuiWidgetModel * model) {
|
||||
ElementArray_clear(model->element);
|
||||
return true;
|
||||
});
|
||||
|
||||
view_free(widget->view);
|
||||
free(widget);
|
||||
}
|
||||
|
||||
View* widget_get_view(Widget* widget) {
|
||||
furi_assert(widget);
|
||||
return widget->view;
|
||||
}
|
||||
|
||||
static void widget_add_element(Widget* widget, WidgetElement* element) {
|
||||
furi_assert(widget);
|
||||
furi_assert(element);
|
||||
|
||||
with_view_model(
|
||||
widget->view, (GuiWidgetModel * model) {
|
||||
element->parent = widget;
|
||||
ElementArray_push_back(model->element, element);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void widget_add_string_multiline_element(
|
||||
Widget* widget,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
Font font,
|
||||
const char* text) {
|
||||
furi_assert(widget);
|
||||
WidgetElement* string_multiline_element =
|
||||
widget_element_string_multiline_create(x, y, horizontal, vertical, font, text);
|
||||
widget_add_element(widget, string_multiline_element);
|
||||
}
|
||||
|
||||
void widget_add_string_element(
|
||||
Widget* widget,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
Font font,
|
||||
const char* text) {
|
||||
furi_assert(widget);
|
||||
WidgetElement* string_element =
|
||||
widget_element_string_create(x, y, horizontal, vertical, font, text);
|
||||
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,
|
||||
bool strip_to_dots) {
|
||||
furi_assert(widget);
|
||||
WidgetElement* text_box_element = widget_element_text_box_create(
|
||||
x, y, width, height, horizontal, vertical, text, strip_to_dots);
|
||||
widget_add_element(widget, text_box_element);
|
||||
}
|
||||
|
||||
void widget_add_text_scroll_element(
|
||||
Widget* widget,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
const char* text) {
|
||||
furi_assert(widget);
|
||||
WidgetElement* text_scroll_element =
|
||||
widget_element_text_scroll_create(x, y, width, height, text);
|
||||
widget_add_element(widget, text_scroll_element);
|
||||
}
|
||||
|
||||
void widget_add_button_element(
|
||||
Widget* widget,
|
||||
GuiButtonType button_type,
|
||||
const char* text,
|
||||
ButtonCallback callback,
|
||||
void* context) {
|
||||
furi_assert(widget);
|
||||
WidgetElement* button_element =
|
||||
widget_element_button_create(button_type, text, callback, context);
|
||||
widget_add_element(widget, button_element);
|
||||
}
|
||||
|
||||
void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon) {
|
||||
furi_assert(widget);
|
||||
furi_assert(icon);
|
||||
WidgetElement* icon_element = widget_element_icon_create(x, y, icon);
|
||||
widget_add_element(widget, icon_element);
|
||||
}
|
||||
|
||||
void widget_add_frame_element(
|
||||
Widget* widget,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius) {
|
||||
furi_assert(widget);
|
||||
WidgetElement* frame_element = widget_element_frame_create(x, y, width, height, radius);
|
||||
widget_add_element(widget, frame_element);
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* @file widget.h
|
||||
* GUI: Widget view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <gui/view.h>
|
||||
#include "widget_elements/widget_element.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Widget Widget;
|
||||
typedef struct WidgetElement WidgetElement;
|
||||
|
||||
/** Allocate Widget that holds Widget Elements
|
||||
*
|
||||
* @return Widget instance
|
||||
*/
|
||||
Widget* widget_alloc();
|
||||
|
||||
/** Free Widget
|
||||
* @note this function free allocated Widget Elements
|
||||
*
|
||||
* @param widget Widget instance
|
||||
*/
|
||||
void widget_free(Widget* widget);
|
||||
|
||||
/** Reset Widget
|
||||
*
|
||||
* @param widget Widget instance
|
||||
*/
|
||||
void widget_reset(Widget* widget);
|
||||
|
||||
/** Get Widget view
|
||||
*
|
||||
* @param widget Widget instance
|
||||
*
|
||||
* @return View instance
|
||||
*/
|
||||
View* widget_get_view(Widget* widget);
|
||||
|
||||
/** Add Multi String Element
|
||||
*
|
||||
* @param widget Widget instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param horizontal Align instance
|
||||
* @param vertical Align instance
|
||||
* @param font Font instance
|
||||
* @param[in] text The text
|
||||
*/
|
||||
void widget_add_string_multiline_element(
|
||||
Widget* widget,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
Font font,
|
||||
const char* text);
|
||||
|
||||
/** Add String Element
|
||||
*
|
||||
* @param widget Widget instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param horizontal Align instance
|
||||
* @param vertical Align instance
|
||||
* @param font Font instance
|
||||
* @param[in] text The text
|
||||
*/
|
||||
void widget_add_string_element(
|
||||
Widget* widget,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
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
|
||||
* @param strip_to_dots Strip text to ... if does not fit to width
|
||||
*/
|
||||
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,
|
||||
bool strip_to_dots);
|
||||
|
||||
/** Add Text Scroll 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[in] text Formatted text. Default format: align left, Secondary font.
|
||||
* The following formats are available:
|
||||
* "\e#Bold text" - sets bold font before until next '\n' symbol
|
||||
* "\ecCenter-aligned text" - sets center horizontal align until the next '\n' symbol
|
||||
* "\erRight-aligned text" - sets right horizontal align until the next '\n' symbol
|
||||
*/
|
||||
void widget_add_text_scroll_element(
|
||||
Widget* widget,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
const char* text);
|
||||
|
||||
/** Add Button Element
|
||||
*
|
||||
* @param widget Widget instance
|
||||
* @param button_type GuiButtonType instance
|
||||
* @param text text on allocated button
|
||||
* @param callback ButtonCallback instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void widget_add_button_element(
|
||||
Widget* widget,
|
||||
GuiButtonType button_type,
|
||||
const char* text,
|
||||
ButtonCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Add Icon Element
|
||||
*
|
||||
* @param widget Widget instance
|
||||
* @param x top left x coordinate
|
||||
* @param y top left y coordinate
|
||||
* @param icon Icon instance
|
||||
*/
|
||||
void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon);
|
||||
|
||||
/** Add Frame Element
|
||||
*
|
||||
* @param widget Widget instance
|
||||
* @param x top left x coordinate
|
||||
* @param y top left y coordinate
|
||||
* @param width frame width
|
||||
* @param height frame height
|
||||
* @param radius frame radius
|
||||
*/
|
||||
void widget_add_frame_element(
|
||||
Widget* widget,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file widget_element_i.h
|
||||
* GUI: internal Widget Element API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
GuiButtonTypeLeft,
|
||||
GuiButtonTypeCenter,
|
||||
GuiButtonTypeRight,
|
||||
} GuiButtonType;
|
||||
|
||||
typedef void (*ButtonCallback)(GuiButtonType result, InputType type, void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "widget_element_i.h"
|
||||
#include <gui/elements.h>
|
||||
#include <m-string.h>
|
||||
|
||||
typedef struct {
|
||||
GuiButtonType button_type;
|
||||
string_t text;
|
||||
ButtonCallback callback;
|
||||
void* context;
|
||||
} GuiButtonModel;
|
||||
|
||||
static void gui_button_draw(Canvas* canvas, WidgetElement* element) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(element);
|
||||
GuiButtonModel* model = element->model;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
if(model->button_type == GuiButtonTypeLeft) {
|
||||
elements_button_left(canvas, string_get_cstr(model->text));
|
||||
} else if(model->button_type == GuiButtonTypeRight) {
|
||||
elements_button_right(canvas, string_get_cstr(model->text));
|
||||
} else if(model->button_type == GuiButtonTypeCenter) {
|
||||
elements_button_center(canvas, string_get_cstr(model->text));
|
||||
}
|
||||
}
|
||||
|
||||
static bool gui_button_input(InputEvent* event, WidgetElement* element) {
|
||||
GuiButtonModel* model = element->model;
|
||||
bool consumed = false;
|
||||
|
||||
if(model->callback == NULL) return consumed;
|
||||
|
||||
if((model->button_type == GuiButtonTypeLeft) && (event->key == InputKeyLeft)) {
|
||||
model->callback(model->button_type, event->type, model->context);
|
||||
consumed = true;
|
||||
} else if((model->button_type == GuiButtonTypeRight) && (event->key == InputKeyRight)) {
|
||||
model->callback(model->button_type, event->type, model->context);
|
||||
consumed = true;
|
||||
} else if((model->button_type == GuiButtonTypeCenter) && (event->key == InputKeyOk)) {
|
||||
model->callback(model->button_type, event->type, model->context);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static void gui_button_free(WidgetElement* gui_button) {
|
||||
furi_assert(gui_button);
|
||||
|
||||
GuiButtonModel* model = gui_button->model;
|
||||
string_clear(model->text);
|
||||
free(gui_button->model);
|
||||
free(gui_button);
|
||||
}
|
||||
|
||||
WidgetElement* widget_element_button_create(
|
||||
GuiButtonType button_type,
|
||||
const char* text,
|
||||
ButtonCallback callback,
|
||||
void* context) {
|
||||
// Allocate and init model
|
||||
GuiButtonModel* model = malloc(sizeof(GuiButtonModel));
|
||||
model->button_type = button_type;
|
||||
model->callback = callback;
|
||||
model->context = context;
|
||||
string_init_set_str(model->text, text);
|
||||
|
||||
// Allocate and init Element
|
||||
WidgetElement* gui_button = malloc(sizeof(WidgetElement));
|
||||
gui_button->parent = NULL;
|
||||
gui_button->input = gui_button_input;
|
||||
gui_button->draw = gui_button_draw;
|
||||
gui_button->free = gui_button_free;
|
||||
gui_button->model = model;
|
||||
|
||||
return gui_button;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "widget_element_i.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t radius;
|
||||
} GuiFrameModel;
|
||||
|
||||
static void gui_frame_draw(Canvas* canvas, WidgetElement* element) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(element);
|
||||
GuiFrameModel* model = element->model;
|
||||
canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius);
|
||||
}
|
||||
|
||||
static void gui_frame_free(WidgetElement* gui_frame) {
|
||||
furi_assert(gui_frame);
|
||||
|
||||
free(gui_frame->model);
|
||||
free(gui_frame);
|
||||
}
|
||||
|
||||
WidgetElement* widget_element_frame_create(
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius) {
|
||||
// Allocate and init model
|
||||
GuiFrameModel* model = malloc(sizeof(GuiFrameModel));
|
||||
model->x = x;
|
||||
model->y = y;
|
||||
model->width = width;
|
||||
model->height = height;
|
||||
model->radius = radius;
|
||||
|
||||
// Allocate and init Element
|
||||
WidgetElement* gui_frame = malloc(sizeof(WidgetElement));
|
||||
gui_frame->parent = NULL;
|
||||
gui_frame->input = NULL;
|
||||
gui_frame->draw = gui_frame_draw;
|
||||
gui_frame->free = gui_frame_free;
|
||||
gui_frame->model = model;
|
||||
|
||||
return gui_frame;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @file widget_element_i.h
|
||||
* GUI: internal Widget Element API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <gui/view.h>
|
||||
#include <input/input.h>
|
||||
#include "widget_element.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct WidgetElement WidgetElement;
|
||||
typedef struct Widget Widget;
|
||||
|
||||
struct WidgetElement {
|
||||
// generic draw and input callbacks
|
||||
void (*draw)(Canvas* canvas, WidgetElement* element);
|
||||
bool (*input)(InputEvent* event, WidgetElement* element);
|
||||
|
||||
// free callback
|
||||
void (*free)(WidgetElement* element);
|
||||
|
||||
// generic model holder
|
||||
void* model;
|
||||
FuriMutex* model_mutex;
|
||||
|
||||
// pointer to widget that hold our element
|
||||
Widget* parent;
|
||||
};
|
||||
|
||||
/** Create multi string element */
|
||||
WidgetElement* widget_element_string_multiline_create(
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
Font font,
|
||||
const char* text);
|
||||
|
||||
/** Create string element */
|
||||
WidgetElement* widget_element_string_create(
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
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,
|
||||
bool strip_to_dots);
|
||||
|
||||
/** Create button element */
|
||||
WidgetElement* widget_element_button_create(
|
||||
GuiButtonType button_type,
|
||||
const char* text,
|
||||
ButtonCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Create icon element */
|
||||
WidgetElement* widget_element_icon_create(uint8_t x, uint8_t y, const Icon* icon);
|
||||
|
||||
/** Create frame element */
|
||||
WidgetElement* widget_element_frame_create(
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius);
|
||||
|
||||
WidgetElement* widget_element_text_scroll_create(
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
const char* text);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "widget_element_i.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
const Icon* icon;
|
||||
} GuiIconModel;
|
||||
|
||||
static void gui_icon_draw(Canvas* canvas, WidgetElement* element) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(element);
|
||||
GuiIconModel* model = element->model;
|
||||
|
||||
if(model->icon) {
|
||||
canvas_draw_icon(canvas, model->x, model->y, model->icon);
|
||||
}
|
||||
}
|
||||
|
||||
static void gui_icon_free(WidgetElement* gui_icon) {
|
||||
furi_assert(gui_icon);
|
||||
|
||||
free(gui_icon->model);
|
||||
free(gui_icon);
|
||||
}
|
||||
|
||||
WidgetElement* widget_element_icon_create(uint8_t x, uint8_t y, const Icon* icon) {
|
||||
furi_assert(icon);
|
||||
|
||||
// Allocate and init model
|
||||
GuiIconModel* model = malloc(sizeof(GuiIconModel));
|
||||
model->x = x;
|
||||
model->y = y;
|
||||
model->icon = icon;
|
||||
|
||||
// Allocate and init Element
|
||||
WidgetElement* gui_icon = malloc(sizeof(WidgetElement));
|
||||
gui_icon->parent = NULL;
|
||||
gui_icon->input = NULL;
|
||||
gui_icon->draw = gui_icon_draw;
|
||||
gui_icon->free = gui_icon_free;
|
||||
gui_icon->model = model;
|
||||
|
||||
return gui_icon;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "widget_element_i.h"
|
||||
#include <m-string.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
Align horizontal;
|
||||
Align vertical;
|
||||
Font font;
|
||||
string_t text;
|
||||
} GuiStringModel;
|
||||
|
||||
static void gui_string_draw(Canvas* canvas, WidgetElement* element) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(element);
|
||||
GuiStringModel* model = element->model;
|
||||
|
||||
if(string_size(model->text)) {
|
||||
canvas_set_font(canvas, model->font);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
model->x,
|
||||
model->y,
|
||||
model->horizontal,
|
||||
model->vertical,
|
||||
string_get_cstr(model->text));
|
||||
}
|
||||
}
|
||||
|
||||
static void gui_string_free(WidgetElement* gui_string) {
|
||||
furi_assert(gui_string);
|
||||
|
||||
GuiStringModel* model = gui_string->model;
|
||||
string_clear(model->text);
|
||||
free(gui_string->model);
|
||||
free(gui_string);
|
||||
}
|
||||
|
||||
WidgetElement* widget_element_string_create(
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
Font font,
|
||||
const char* text) {
|
||||
furi_assert(text);
|
||||
|
||||
// Allocate and init model
|
||||
GuiStringModel* model = malloc(sizeof(GuiStringModel));
|
||||
model->x = x;
|
||||
model->y = y;
|
||||
model->horizontal = horizontal;
|
||||
model->vertical = vertical;
|
||||
model->font = font;
|
||||
string_init_set_str(model->text, text);
|
||||
|
||||
// Allocate and init Element
|
||||
WidgetElement* gui_string = malloc(sizeof(WidgetElement));
|
||||
gui_string->parent = NULL;
|
||||
gui_string->input = NULL;
|
||||
gui_string->draw = gui_string_draw;
|
||||
gui_string->free = gui_string_free;
|
||||
gui_string->model = model;
|
||||
|
||||
return gui_string;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "widget_element_i.h"
|
||||
#include <m-string.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
Align horizontal;
|
||||
Align vertical;
|
||||
Font font;
|
||||
string_t text;
|
||||
} GuiStringMultiLineModel;
|
||||
|
||||
static void gui_string_multiline_draw(Canvas* canvas, WidgetElement* element) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(element);
|
||||
GuiStringMultiLineModel* model = element->model;
|
||||
|
||||
if(string_size(model->text)) {
|
||||
canvas_set_font(canvas, model->font);
|
||||
elements_multiline_text_aligned(
|
||||
canvas,
|
||||
model->x,
|
||||
model->y,
|
||||
model->horizontal,
|
||||
model->vertical,
|
||||
string_get_cstr(model->text));
|
||||
}
|
||||
}
|
||||
|
||||
static void gui_string_multiline_free(WidgetElement* gui_string) {
|
||||
furi_assert(gui_string);
|
||||
|
||||
GuiStringMultiLineModel* model = gui_string->model;
|
||||
string_clear(model->text);
|
||||
free(gui_string->model);
|
||||
free(gui_string);
|
||||
}
|
||||
|
||||
WidgetElement* widget_element_string_multiline_create(
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
Font font,
|
||||
const char* text) {
|
||||
furi_assert(text);
|
||||
|
||||
// Allocate and init model
|
||||
GuiStringMultiLineModel* model = malloc(sizeof(GuiStringMultiLineModel));
|
||||
model->x = x;
|
||||
model->y = y;
|
||||
model->horizontal = horizontal;
|
||||
model->vertical = vertical;
|
||||
model->font = font;
|
||||
string_init_set_str(model->text, text);
|
||||
|
||||
// Allocate and init Element
|
||||
WidgetElement* gui_string = malloc(sizeof(WidgetElement));
|
||||
gui_string->parent = NULL;
|
||||
gui_string->input = NULL;
|
||||
gui_string->draw = gui_string_multiline_draw;
|
||||
gui_string->free = gui_string_multiline_free;
|
||||
gui_string->model = model;
|
||||
|
||||
return gui_string;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#include "widget_element_i.h"
|
||||
#include <m-string.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
Align horizontal;
|
||||
Align vertical;
|
||||
string_t text;
|
||||
bool strip_to_dots;
|
||||
} 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),
|
||||
model->strip_to_dots);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
bool strip_to_dots) {
|
||||
furi_assert(text);
|
||||
|
||||
// Allocate and init model
|
||||
GuiTextBoxModel* model = malloc(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);
|
||||
model->strip_to_dots = strip_to_dots;
|
||||
|
||||
// Allocate and init Element
|
||||
WidgetElement* gui_string = malloc(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;
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
#include "widget_element_i.h"
|
||||
#include <m-string.h>
|
||||
#include <gui/elements.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#define WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET (4)
|
||||
|
||||
typedef struct {
|
||||
Font font;
|
||||
Align horizontal;
|
||||
string_t text;
|
||||
} TextScrollLineArray;
|
||||
|
||||
ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST)
|
||||
|
||||
typedef struct {
|
||||
TextScrollLineArray_t line_array;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
string_t text;
|
||||
uint8_t scroll_pos_total;
|
||||
uint8_t scroll_pos_current;
|
||||
bool text_formatted;
|
||||
} WidgetElementTextScrollModel;
|
||||
|
||||
static bool
|
||||
widget_element_text_scroll_process_ctrl_symbols(TextScrollLineArray* line, string_t text) {
|
||||
bool processed = false;
|
||||
|
||||
do {
|
||||
if(string_get_char(text, 0) != '\e') break;
|
||||
char ctrl_symbol = string_get_char(text, 1);
|
||||
if(ctrl_symbol == 'c') {
|
||||
line->horizontal = AlignCenter;
|
||||
} else if(ctrl_symbol == 'r') {
|
||||
line->horizontal = AlignRight;
|
||||
} else if(ctrl_symbol == '#') {
|
||||
line->font = FontPrimary;
|
||||
}
|
||||
string_right(text, 2);
|
||||
processed = true;
|
||||
} while(false);
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
void widget_element_text_scroll_add_line(WidgetElement* element, TextScrollLineArray* line) {
|
||||
WidgetElementTextScrollModel* model = element->model;
|
||||
TextScrollLineArray new_line;
|
||||
new_line.font = line->font;
|
||||
new_line.horizontal = line->horizontal;
|
||||
string_init_set(new_line.text, line->text);
|
||||
TextScrollLineArray_push_back(model->line_array, new_line);
|
||||
}
|
||||
|
||||
static void widget_element_text_scroll_fill_lines(Canvas* canvas, WidgetElement* element) {
|
||||
WidgetElementTextScrollModel* model = element->model;
|
||||
TextScrollLineArray line_tmp;
|
||||
bool all_text_processed = false;
|
||||
string_init(line_tmp.text);
|
||||
bool reached_new_line = true;
|
||||
uint16_t total_height = 0;
|
||||
|
||||
while(!all_text_processed) {
|
||||
if(reached_new_line) {
|
||||
// Set default line properties
|
||||
line_tmp.font = FontSecondary;
|
||||
line_tmp.horizontal = AlignLeft;
|
||||
string_reset(line_tmp.text);
|
||||
// Process control symbols
|
||||
while(widget_element_text_scroll_process_ctrl_symbols(&line_tmp, model->text))
|
||||
;
|
||||
}
|
||||
// Set canvas font
|
||||
canvas_set_font(canvas, line_tmp.font);
|
||||
CanvasFontParameters* params = canvas_get_font_params(canvas, line_tmp.font);
|
||||
total_height += params->height;
|
||||
if(total_height > model->height) {
|
||||
model->scroll_pos_total++;
|
||||
}
|
||||
|
||||
uint8_t line_width = 0;
|
||||
uint16_t char_i = 0;
|
||||
while(true) {
|
||||
char next_char = string_get_char(model->text, char_i++);
|
||||
if(next_char == '\0') {
|
||||
string_push_back(line_tmp.text, '\0');
|
||||
widget_element_text_scroll_add_line(element, &line_tmp);
|
||||
total_height += params->leading_default - params->height;
|
||||
all_text_processed = true;
|
||||
break;
|
||||
} else if(next_char == '\n') {
|
||||
string_push_back(line_tmp.text, '\0');
|
||||
widget_element_text_scroll_add_line(element, &line_tmp);
|
||||
string_right(model->text, char_i);
|
||||
total_height += params->leading_default - params->height;
|
||||
reached_new_line = true;
|
||||
break;
|
||||
} else {
|
||||
line_width += canvas_glyph_width(canvas, next_char);
|
||||
if(line_width > model->width) {
|
||||
string_push_back(line_tmp.text, '\0');
|
||||
widget_element_text_scroll_add_line(element, &line_tmp);
|
||||
string_right(model->text, char_i - 1);
|
||||
string_reset(line_tmp.text);
|
||||
total_height += params->leading_default - params->height;
|
||||
reached_new_line = false;
|
||||
break;
|
||||
} else {
|
||||
string_push_back(line_tmp.text, next_char);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(line_tmp.text);
|
||||
}
|
||||
|
||||
static void widget_element_text_scroll_draw(Canvas* canvas, WidgetElement* element) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(element);
|
||||
|
||||
furi_mutex_acquire(element->model_mutex, FuriWaitForever);
|
||||
|
||||
WidgetElementTextScrollModel* model = element->model;
|
||||
if(!model->text_formatted) {
|
||||
widget_element_text_scroll_fill_lines(canvas, element);
|
||||
model->text_formatted = true;
|
||||
}
|
||||
|
||||
uint8_t y = model->y;
|
||||
uint8_t x = model->x;
|
||||
uint16_t curr_line = 0;
|
||||
if(TextScrollLineArray_size(model->line_array)) {
|
||||
TextScrollLineArray_it_t it;
|
||||
for(TextScrollLineArray_it(it, model->line_array); !TextScrollLineArray_end_p(it);
|
||||
TextScrollLineArray_next(it), curr_line++) {
|
||||
if(curr_line < model->scroll_pos_current) continue;
|
||||
TextScrollLineArray* line = TextScrollLineArray_ref(it);
|
||||
CanvasFontParameters* params = canvas_get_font_params(canvas, line->font);
|
||||
if(y + params->descender > model->y + model->height) break;
|
||||
canvas_set_font(canvas, line->font);
|
||||
if(line->horizontal == AlignLeft) {
|
||||
x = model->x;
|
||||
} else if(line->horizontal == AlignCenter) {
|
||||
x = (model->x + model->width) / 2;
|
||||
} else if(line->horizontal == AlignRight) {
|
||||
x = model->x + model->width;
|
||||
}
|
||||
canvas_draw_str_aligned(
|
||||
canvas, x, y, line->horizontal, AlignTop, string_get_cstr(line->text));
|
||||
y += params->leading_default;
|
||||
}
|
||||
// Draw scroll bar
|
||||
if(model->scroll_pos_total > 1) {
|
||||
elements_scrollbar_pos(
|
||||
canvas,
|
||||
model->x + model->width + WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET,
|
||||
model->y,
|
||||
model->height,
|
||||
model->scroll_pos_current,
|
||||
model->scroll_pos_total);
|
||||
}
|
||||
}
|
||||
|
||||
furi_mutex_release(element->model_mutex);
|
||||
}
|
||||
|
||||
static bool widget_element_text_scroll_input(InputEvent* event, WidgetElement* element) {
|
||||
furi_assert(event);
|
||||
furi_assert(element);
|
||||
|
||||
furi_mutex_acquire(element->model_mutex, FuriWaitForever);
|
||||
|
||||
WidgetElementTextScrollModel* model = element->model;
|
||||
bool consumed = false;
|
||||
|
||||
if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
|
||||
if(event->key == InputKeyUp) {
|
||||
if(model->scroll_pos_current > 0) {
|
||||
model->scroll_pos_current--;
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
if((model->scroll_pos_total > 1) &&
|
||||
(model->scroll_pos_current < model->scroll_pos_total - 1)) {
|
||||
model->scroll_pos_current++;
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
furi_mutex_release(element->model_mutex);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static void widget_element_text_scroll_free(WidgetElement* text_scroll) {
|
||||
furi_assert(text_scroll);
|
||||
|
||||
WidgetElementTextScrollModel* model = text_scroll->model;
|
||||
TextScrollLineArray_it_t it;
|
||||
for(TextScrollLineArray_it(it, model->line_array); !TextScrollLineArray_end_p(it);
|
||||
TextScrollLineArray_next(it)) {
|
||||
TextScrollLineArray* line = TextScrollLineArray_ref(it);
|
||||
string_clear(line->text);
|
||||
}
|
||||
TextScrollLineArray_clear(model->line_array);
|
||||
string_clear(model->text);
|
||||
free(text_scroll->model);
|
||||
furi_mutex_free(text_scroll->model_mutex);
|
||||
free(text_scroll);
|
||||
}
|
||||
|
||||
WidgetElement* widget_element_text_scroll_create(
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
const char* text) {
|
||||
furi_assert(text);
|
||||
|
||||
// Allocate and init model
|
||||
WidgetElementTextScrollModel* model = malloc(sizeof(WidgetElementTextScrollModel));
|
||||
model->x = x;
|
||||
model->y = y;
|
||||
model->width = width - WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET;
|
||||
model->height = height;
|
||||
model->scroll_pos_current = 0;
|
||||
model->scroll_pos_total = 1;
|
||||
TextScrollLineArray_init(model->line_array);
|
||||
string_init_set_str(model->text, text);
|
||||
|
||||
WidgetElement* text_scroll = malloc(sizeof(WidgetElement));
|
||||
text_scroll->parent = NULL;
|
||||
text_scroll->draw = widget_element_text_scroll_draw;
|
||||
text_scroll->input = widget_element_text_scroll_input;
|
||||
text_scroll->free = widget_element_text_scroll_free;
|
||||
text_scroll->model = model;
|
||||
text_scroll->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
return text_scroll;
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
#include "scene_manager_i.h"
|
||||
#include <furi.h>
|
||||
|
||||
SceneManager* scene_manager_alloc(const SceneManagerHandlers* app_scene_handlers, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
SceneManager* scene_manager = malloc(sizeof(SceneManager));
|
||||
// Set SceneManager context and scene handlers
|
||||
scene_manager->context = context;
|
||||
scene_manager->scene_handlers = app_scene_handlers;
|
||||
// Allocate all scenes
|
||||
scene_manager->scene = malloc(sizeof(AppScene) * app_scene_handlers->scene_num);
|
||||
// Initialize ScaneManager array for navigation
|
||||
SceneManagerIdStack_init(scene_manager->scene_id_stack);
|
||||
|
||||
return scene_manager;
|
||||
}
|
||||
|
||||
void scene_manager_free(SceneManager* scene_manager) {
|
||||
furi_assert(scene_manager);
|
||||
|
||||
// Clear ScaneManager array
|
||||
SceneManagerIdStack_clear(scene_manager->scene_id_stack);
|
||||
// Clear allocated scenes
|
||||
free(scene_manager->scene);
|
||||
// Free SceneManager structure
|
||||
free(scene_manager);
|
||||
}
|
||||
|
||||
void scene_manager_set_scene_state(SceneManager* scene_manager, uint32_t scene_id, uint32_t state) {
|
||||
furi_assert(scene_manager);
|
||||
furi_assert(scene_id < scene_manager->scene_handlers->scene_num);
|
||||
|
||||
scene_manager->scene[scene_id].state = state;
|
||||
}
|
||||
|
||||
uint32_t scene_manager_get_scene_state(SceneManager* scene_manager, uint32_t scene_id) {
|
||||
furi_assert(scene_manager);
|
||||
furi_assert(scene_id < scene_manager->scene_handlers->scene_num);
|
||||
|
||||
return scene_manager->scene[scene_id].state;
|
||||
}
|
||||
|
||||
bool scene_manager_handle_custom_event(SceneManager* scene_manager, uint32_t custom_event) {
|
||||
furi_assert(scene_manager);
|
||||
|
||||
SceneManagerEvent event = {
|
||||
.type = SceneManagerEventTypeCustom,
|
||||
.event = custom_event,
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t* scene_id_p = SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
uint32_t scene_id = *scene_id_p;
|
||||
result = scene_manager->scene_handlers->on_event_handlers[scene_id](
|
||||
scene_manager->context, event);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool scene_manager_handle_back_event(SceneManager* scene_manager) {
|
||||
furi_assert(scene_manager);
|
||||
|
||||
SceneManagerEvent event = {
|
||||
.type = SceneManagerEventTypeBack,
|
||||
};
|
||||
|
||||
bool consumed = false;
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t* scene_id_p = SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
uint32_t scene_id = *scene_id_p;
|
||||
consumed = scene_manager->scene_handlers->on_event_handlers[scene_id](
|
||||
scene_manager->context, event);
|
||||
}
|
||||
|
||||
if(!consumed) {
|
||||
consumed = scene_manager_previous_scene(scene_manager);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void scene_manager_handle_tick_event(SceneManager* scene_manager) {
|
||||
furi_assert(scene_manager);
|
||||
|
||||
SceneManagerEvent event = {
|
||||
.type = SceneManagerEventTypeTick,
|
||||
};
|
||||
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t* scene_id_p = SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
uint32_t scene_id = *scene_id_p;
|
||||
scene_manager->scene_handlers->on_event_handlers[scene_id](scene_manager->context, event);
|
||||
}
|
||||
}
|
||||
|
||||
void scene_manager_next_scene(SceneManager* scene_manager, uint32_t next_scene_id) {
|
||||
furi_assert(scene_manager);
|
||||
furi_assert(next_scene_id < scene_manager->scene_handlers->scene_num);
|
||||
|
||||
// Check if it is not the first scene
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t cur_scene_id = *SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
scene_manager->scene_handlers->on_exit_handlers[cur_scene_id](scene_manager->context);
|
||||
}
|
||||
// Add next scene and run on_enter
|
||||
SceneManagerIdStack_push_back(scene_manager->scene_id_stack, next_scene_id);
|
||||
scene_manager->scene_handlers->on_enter_handlers[next_scene_id](scene_manager->context);
|
||||
}
|
||||
|
||||
bool scene_manager_previous_scene(SceneManager* scene_manager) {
|
||||
furi_assert(scene_manager);
|
||||
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t cur_scene_id = 0;
|
||||
SceneManagerIdStack_pop_back(&cur_scene_id, scene_manager->scene_id_stack);
|
||||
|
||||
// Handle exit from start scene separately
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) == 0) {
|
||||
scene_manager->scene_handlers->on_exit_handlers[cur_scene_id](scene_manager->context);
|
||||
return false;
|
||||
}
|
||||
uint32_t prev_scene_id = *SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
scene_manager->scene_handlers->on_exit_handlers[cur_scene_id](scene_manager->context);
|
||||
scene_manager->scene_handlers->on_enter_handlers[prev_scene_id](scene_manager->context);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool scene_manager_search_and_switch_to_previous_scene(
|
||||
SceneManager* scene_manager,
|
||||
uint32_t scene_id) {
|
||||
furi_assert(scene_manager);
|
||||
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t prev_scene_id = 0;
|
||||
uint32_t cur_scene_id = *SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
SceneManagerIdStack_it_t scene_it;
|
||||
SceneManagerIdStack_it_last(scene_it, scene_manager->scene_id_stack);
|
||||
// Search scene with given id in navigation stack
|
||||
bool scene_found = false;
|
||||
while(!scene_found) {
|
||||
SceneManagerIdStack_previous(scene_it);
|
||||
if(SceneManagerIdStack_end_p(scene_it)) {
|
||||
return false;
|
||||
}
|
||||
prev_scene_id = *SceneManagerIdStack_ref(scene_it);
|
||||
if(prev_scene_id == scene_id) {
|
||||
scene_found = true;
|
||||
}
|
||||
}
|
||||
// Remove all scene id from navigation stack
|
||||
SceneManagerIdStack_next(scene_it);
|
||||
SceneManagerIdStack_pop_until(scene_manager->scene_id_stack, scene_it);
|
||||
|
||||
scene_manager->scene_handlers->on_exit_handlers[cur_scene_id](scene_manager->context);
|
||||
scene_manager->scene_handlers->on_enter_handlers[prev_scene_id](scene_manager->context);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool scene_manager_search_and_switch_to_previous_scene_one_of(
|
||||
SceneManager* scene_manager,
|
||||
const uint32_t* scene_ids,
|
||||
size_t scene_ids_size) {
|
||||
furi_assert(scene_manager);
|
||||
furi_assert(scene_ids);
|
||||
bool scene_found = false;
|
||||
|
||||
for(size_t i = 0; i < scene_ids_size; ++i) {
|
||||
const uint32_t scene_id = scene_ids[i];
|
||||
if(scene_manager_has_previous_scene(scene_manager, scene_id)) {
|
||||
scene_manager_search_and_switch_to_previous_scene(scene_manager, scene_id);
|
||||
scene_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return scene_found;
|
||||
}
|
||||
|
||||
bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id) {
|
||||
furi_assert(scene_manager);
|
||||
bool scene_found = false;
|
||||
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t prev_scene_id;
|
||||
SceneManagerIdStack_it_t scene_it;
|
||||
SceneManagerIdStack_it_last(scene_it, scene_manager->scene_id_stack);
|
||||
|
||||
// Perform search in scene stack
|
||||
while(!scene_found) {
|
||||
SceneManagerIdStack_previous(scene_it);
|
||||
if(SceneManagerIdStack_end_p(scene_it)) {
|
||||
break;
|
||||
}
|
||||
prev_scene_id = *SceneManagerIdStack_ref(scene_it);
|
||||
if(prev_scene_id == scene_id) {
|
||||
scene_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return scene_found;
|
||||
}
|
||||
|
||||
bool scene_manager_search_and_switch_to_another_scene(
|
||||
SceneManager* scene_manager,
|
||||
uint32_t scene_id) {
|
||||
furi_assert(scene_manager);
|
||||
furi_assert(scene_id < scene_manager->scene_handlers->scene_num);
|
||||
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t cur_scene_id = *SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
SceneManagerIdStack_it_t scene_it;
|
||||
SceneManagerIdStack_it(scene_it, scene_manager->scene_id_stack);
|
||||
SceneManagerIdStack_next(scene_it);
|
||||
// Remove all scene id from navigation stack until first scene
|
||||
SceneManagerIdStack_pop_until(scene_manager->scene_id_stack, scene_it);
|
||||
// Add next scene
|
||||
SceneManagerIdStack_push_back(scene_manager->scene_id_stack, scene_id);
|
||||
|
||||
scene_manager->scene_handlers->on_exit_handlers[cur_scene_id](scene_manager->context);
|
||||
scene_manager->scene_handlers->on_enter_handlers[scene_id](scene_manager->context);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void scene_manager_stop(SceneManager* scene_manager) {
|
||||
furi_assert(scene_manager);
|
||||
|
||||
if(SceneManagerIdStack_size(scene_manager->scene_id_stack) > 0) {
|
||||
uint32_t cur_scene_id = *SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
scene_manager->scene_handlers->on_exit_handlers[cur_scene_id](scene_manager->context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* @file scene_manager.h
|
||||
* GUI: SceneManager API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Scene Manager events type */
|
||||
typedef enum {
|
||||
SceneManagerEventTypeCustom,
|
||||
SceneManagerEventTypeBack,
|
||||
SceneManagerEventTypeTick,
|
||||
} SceneManagerEventType;
|
||||
|
||||
/** Scene Manager event
|
||||
*/
|
||||
typedef struct {
|
||||
SceneManagerEventType type;
|
||||
uint32_t event;
|
||||
} SceneManagerEvent;
|
||||
|
||||
/** Prototype for Scene on_enter handler */
|
||||
typedef void (*AppSceneOnEnterCallback)(void* context);
|
||||
|
||||
/** Prototype for Scene on_event handler */
|
||||
typedef bool (*AppSceneOnEventCallback)(void* context, SceneManagerEvent event);
|
||||
|
||||
/** Prototype for Scene on_exit handler */
|
||||
typedef void (*AppSceneOnExitCallback)(void* context);
|
||||
|
||||
/** Scene Manager configuration structure
|
||||
* Contains array of Scene handlers
|
||||
*/
|
||||
typedef struct {
|
||||
const AppSceneOnEnterCallback* on_enter_handlers;
|
||||
const AppSceneOnEventCallback* on_event_handlers;
|
||||
const AppSceneOnExitCallback* on_exit_handlers;
|
||||
const uint32_t scene_num;
|
||||
} SceneManagerHandlers;
|
||||
|
||||
typedef struct SceneManager SceneManager;
|
||||
|
||||
/** Set Scene state
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @param scene_id Scene ID
|
||||
* @param state Scene new state
|
||||
*/
|
||||
void scene_manager_set_scene_state(SceneManager* scene_manager, uint32_t scene_id, uint32_t state);
|
||||
|
||||
/** Get Scene state
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @param scene_id Scene ID
|
||||
*
|
||||
* @return Scene state
|
||||
*/
|
||||
uint32_t scene_manager_get_scene_state(SceneManager* scene_manager, uint32_t scene_id);
|
||||
|
||||
/** Scene Manager allocation and configuration
|
||||
*
|
||||
* Scene Manager allocates all scenes internally
|
||||
*
|
||||
* @param app_scene_handlers SceneManagerHandlers instance
|
||||
* @param context context to be set on Scene handlers calls
|
||||
*
|
||||
* @return SceneManager instance
|
||||
*/
|
||||
SceneManager* scene_manager_alloc(const SceneManagerHandlers* app_scene_handlers, void* context);
|
||||
|
||||
/** Free Scene Manager with allocated Scenes
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
*/
|
||||
void scene_manager_free(SceneManager* scene_manager);
|
||||
|
||||
/** Custom event handler
|
||||
*
|
||||
* Calls Scene event handler with Custom event parameter
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @param custom_event Custom event code
|
||||
*
|
||||
* @return true if event was consumed, false otherwise
|
||||
*/
|
||||
bool scene_manager_handle_custom_event(SceneManager* scene_manager, uint32_t custom_event);
|
||||
|
||||
/** Back event handler
|
||||
*
|
||||
* Calls Scene event handler with Back event parameter
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
*
|
||||
* @return true if event was consumed, false otherwise
|
||||
*/
|
||||
bool scene_manager_handle_back_event(SceneManager* scene_manager);
|
||||
|
||||
/** Tick event handler
|
||||
*
|
||||
* Calls Scene event handler with Tick event parameter
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @return true if event was consumed, false otherwise
|
||||
*/
|
||||
void scene_manager_handle_tick_event(SceneManager* scene_manager);
|
||||
|
||||
/** Add and run next Scene
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @param next_scene_id next Scene ID
|
||||
*/
|
||||
void scene_manager_next_scene(SceneManager* scene_manager, uint32_t next_scene_id);
|
||||
|
||||
/** Run previous Scene
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
*
|
||||
* @return true if previous scene was found, false otherwise
|
||||
*/
|
||||
bool scene_manager_previous_scene(SceneManager* scene_manager);
|
||||
|
||||
/** Search previous Scene
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @param scene_id Scene ID
|
||||
*
|
||||
* @return true if previous scene was found, false otherwise
|
||||
*/
|
||||
bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id);
|
||||
|
||||
/** Search and switch to previous Scene
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @param scene_id Scene ID
|
||||
*
|
||||
* @return true if previous scene was found, false otherwise
|
||||
*/
|
||||
bool scene_manager_search_and_switch_to_previous_scene(
|
||||
SceneManager* scene_manager,
|
||||
uint32_t scene_id);
|
||||
|
||||
/** Search and switch to previous Scene, multiple choice
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @param scene_ids Array of scene IDs
|
||||
* @param scene_ids_size Array of scene IDs size
|
||||
*
|
||||
* @return true if one of previous scenes was found, false otherwise
|
||||
*/
|
||||
bool scene_manager_search_and_switch_to_previous_scene_one_of(
|
||||
SceneManager* scene_manager,
|
||||
const uint32_t* scene_ids,
|
||||
size_t scene_ids_size);
|
||||
|
||||
/** Clear Scene stack and switch to another Scene
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @param scene_id Scene ID
|
||||
*
|
||||
* @return true if previous scene was found, false otherwise
|
||||
*/
|
||||
bool scene_manager_search_and_switch_to_another_scene(
|
||||
SceneManager* scene_manager,
|
||||
uint32_t scene_id);
|
||||
|
||||
/** Exit from current scene
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
*/
|
||||
void scene_manager_stop(SceneManager* scene_manager);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file scene_manager_i.h
|
||||
* GUI: internal SceneManager API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene_manager.h"
|
||||
#include <m-array.h>
|
||||
|
||||
ARRAY_DEF(SceneManagerIdStack, uint32_t, M_DEFAULT_OPLIST);
|
||||
|
||||
typedef struct {
|
||||
uint32_t state;
|
||||
} AppScene;
|
||||
|
||||
struct SceneManager {
|
||||
SceneManagerIdStack_t scene_id_stack;
|
||||
const SceneManagerHandlers* scene_handlers;
|
||||
AppScene* scene;
|
||||
void* context;
|
||||
};
|
||||
@@ -0,0 +1,189 @@
|
||||
#include "view_i.h"
|
||||
|
||||
View* view_alloc() {
|
||||
View* view = malloc(sizeof(View));
|
||||
view->orientation = ViewOrientationHorizontal;
|
||||
return view;
|
||||
}
|
||||
|
||||
void view_free(View* view) {
|
||||
furi_assert(view);
|
||||
view_free_model(view);
|
||||
free(view);
|
||||
}
|
||||
|
||||
void view_tie_icon_animation(View* view, IconAnimation* icon_animation) {
|
||||
furi_assert(view);
|
||||
icon_animation_set_update_callback(icon_animation, view_icon_animation_callback, view);
|
||||
}
|
||||
|
||||
void view_set_draw_callback(View* view, ViewDrawCallback callback) {
|
||||
furi_assert(view);
|
||||
furi_assert(view->draw_callback == NULL);
|
||||
view->draw_callback = callback;
|
||||
}
|
||||
|
||||
void view_set_input_callback(View* view, ViewInputCallback callback) {
|
||||
furi_assert(view);
|
||||
furi_assert(view->input_callback == NULL);
|
||||
view->input_callback = callback;
|
||||
}
|
||||
|
||||
void view_set_custom_callback(View* view, ViewCustomCallback callback) {
|
||||
furi_assert(view);
|
||||
furi_assert(callback);
|
||||
view->custom_callback = callback;
|
||||
}
|
||||
|
||||
void view_set_previous_callback(View* view, ViewNavigationCallback callback) {
|
||||
furi_assert(view);
|
||||
view->previous_callback = callback;
|
||||
}
|
||||
|
||||
void view_set_enter_callback(View* view, ViewCallback callback) {
|
||||
furi_assert(view);
|
||||
view->enter_callback = callback;
|
||||
}
|
||||
|
||||
void view_set_exit_callback(View* view, ViewCallback callback) {
|
||||
furi_assert(view);
|
||||
view->exit_callback = callback;
|
||||
}
|
||||
|
||||
void view_set_update_callback(View* view, ViewUpdateCallback callback) {
|
||||
furi_assert(view);
|
||||
view->update_callback = callback;
|
||||
}
|
||||
|
||||
void view_set_update_callback_context(View* view, void* context) {
|
||||
furi_assert(view);
|
||||
view->update_callback_context = context;
|
||||
}
|
||||
|
||||
void view_set_context(View* view, void* context) {
|
||||
furi_assert(view);
|
||||
furi_assert(context);
|
||||
view->context = context;
|
||||
}
|
||||
|
||||
void view_set_orientation(View* view, ViewOrientation orientation) {
|
||||
furi_assert(view);
|
||||
view->orientation = orientation;
|
||||
}
|
||||
|
||||
void view_allocate_model(View* view, ViewModelType type, size_t size) {
|
||||
furi_assert(view);
|
||||
furi_assert(size > 0);
|
||||
furi_assert(view->model_type == ViewModelTypeNone);
|
||||
furi_assert(view->model == NULL);
|
||||
view->model_type = type;
|
||||
if(view->model_type == ViewModelTypeLockFree) {
|
||||
view->model = malloc(size);
|
||||
} else if(view->model_type == ViewModelTypeLocking) {
|
||||
ViewModelLocking* model = malloc(sizeof(ViewModelLocking));
|
||||
model->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
furi_check(model->mutex);
|
||||
model->data = malloc(size);
|
||||
view->model = model;
|
||||
} else {
|
||||
furi_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void view_free_model(View* view) {
|
||||
furi_assert(view);
|
||||
if(view->model_type == ViewModelTypeNone) {
|
||||
return;
|
||||
} else if(view->model_type == ViewModelTypeLockFree) {
|
||||
free(view->model);
|
||||
} else if(view->model_type == ViewModelTypeLocking) {
|
||||
ViewModelLocking* model = view->model;
|
||||
furi_mutex_free(model->mutex);
|
||||
free(model->data);
|
||||
free(model);
|
||||
view->model = NULL;
|
||||
} else {
|
||||
furi_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void* view_get_model(View* view) {
|
||||
furi_assert(view);
|
||||
if(view->model_type == ViewModelTypeLocking) {
|
||||
ViewModelLocking* model = (ViewModelLocking*)(view->model);
|
||||
furi_check(furi_mutex_acquire(model->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
return model->data;
|
||||
}
|
||||
return view->model;
|
||||
}
|
||||
|
||||
void view_commit_model(View* view, bool update) {
|
||||
furi_assert(view);
|
||||
view_unlock_model(view);
|
||||
if(update && view->update_callback) {
|
||||
view->update_callback(view, view->update_callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
void view_icon_animation_callback(IconAnimation* instance, void* context) {
|
||||
UNUSED(instance);
|
||||
furi_assert(context);
|
||||
View* view = context;
|
||||
if(view->update_callback) {
|
||||
view->update_callback(view, view->update_callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
void view_unlock_model(View* view) {
|
||||
furi_assert(view);
|
||||
if(view->model_type == ViewModelTypeLocking) {
|
||||
ViewModelLocking* model = (ViewModelLocking*)(view->model);
|
||||
furi_check(furi_mutex_release(model->mutex) == FuriStatusOk);
|
||||
}
|
||||
}
|
||||
|
||||
void view_draw(View* view, Canvas* canvas) {
|
||||
furi_assert(view);
|
||||
if(view->draw_callback) {
|
||||
void* data = view_get_model(view);
|
||||
view->draw_callback(canvas, data);
|
||||
view_unlock_model(view);
|
||||
}
|
||||
}
|
||||
|
||||
bool view_input(View* view, InputEvent* event) {
|
||||
furi_assert(view);
|
||||
if(view->input_callback) {
|
||||
return view->input_callback(event, view->context);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool view_custom(View* view, uint32_t event) {
|
||||
furi_assert(view);
|
||||
if(view->custom_callback) {
|
||||
return view->custom_callback(event, view->context);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t view_previous(View* view) {
|
||||
furi_assert(view);
|
||||
if(view->previous_callback) {
|
||||
return view->previous_callback(view->context);
|
||||
} else {
|
||||
return VIEW_IGNORE;
|
||||
}
|
||||
}
|
||||
|
||||
void view_enter(View* view) {
|
||||
furi_assert(view);
|
||||
if(view->enter_callback) view->enter_callback(view->context);
|
||||
}
|
||||
|
||||
void view_exit(View* view) {
|
||||
furi_assert(view);
|
||||
if(view->exit_callback) view->exit_callback(view->context);
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* @file view.h
|
||||
* GUI: View API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <input/input.h>
|
||||
|
||||
#include "icon_animation.h"
|
||||
#include "canvas.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Hides drawing view_port */
|
||||
#define VIEW_NONE 0xFFFFFFFF
|
||||
|
||||
/** Ignore navigation event */
|
||||
#define VIEW_IGNORE 0xFFFFFFFE
|
||||
|
||||
typedef enum {
|
||||
ViewOrientationHorizontal,
|
||||
ViewOrientationVertical,
|
||||
} ViewOrientation;
|
||||
|
||||
/** View, anonymous type */
|
||||
typedef struct View View;
|
||||
|
||||
/** View Draw callback
|
||||
* @param canvas, pointer to canvas
|
||||
* @param view_model, pointer to context
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewDrawCallback)(Canvas* canvas, void* model);
|
||||
|
||||
/** View Input callback
|
||||
* @param event, pointer to input event data
|
||||
* @param context, pointer to context
|
||||
* @return true if event handled, false if event ignored
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef bool (*ViewInputCallback)(InputEvent* event, void* context);
|
||||
|
||||
/** View Custom callback
|
||||
* @param event, number of custom event
|
||||
* @param context, pointer to context
|
||||
* @return true if event handled, false if event ignored
|
||||
*/
|
||||
typedef bool (*ViewCustomCallback)(uint32_t event, void* context);
|
||||
|
||||
/** View navigation callback
|
||||
* @param context, pointer to context
|
||||
* @return next view id
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef uint32_t (*ViewNavigationCallback)(void* context);
|
||||
|
||||
/** View callback
|
||||
* @param context, pointer to context
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewCallback)(void* context);
|
||||
|
||||
/** View Update Callback Called upon model change, need to be propagated to GUI
|
||||
* throw ViewPort update
|
||||
* @param view, pointer to view
|
||||
* @param context, pointer to context
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewUpdateCallback)(View* view, void* context);
|
||||
|
||||
/** View model types */
|
||||
typedef enum {
|
||||
/** Model is not allocated */
|
||||
ViewModelTypeNone,
|
||||
/** Model consist of atomic types and/or partial update is not critical for rendering.
|
||||
* Lock free.
|
||||
*/
|
||||
ViewModelTypeLockFree,
|
||||
/** Model access is guarded with mutex.
|
||||
* Locking gui thread.
|
||||
*/
|
||||
ViewModelTypeLocking,
|
||||
} ViewModelType;
|
||||
|
||||
/** Allocate and init View
|
||||
* @return View instance
|
||||
*/
|
||||
View* view_alloc();
|
||||
|
||||
/** Free View
|
||||
*
|
||||
* @param view instance
|
||||
*/
|
||||
void view_free(View* view);
|
||||
|
||||
/** Tie IconAnimation with View
|
||||
*
|
||||
* @param view View instance
|
||||
* @param icon_animation IconAnimation instance
|
||||
*/
|
||||
void view_tie_icon_animation(View* view, IconAnimation* icon_animation);
|
||||
|
||||
/** Set View Draw callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param callback draw callback
|
||||
*/
|
||||
void view_set_draw_callback(View* view, ViewDrawCallback callback);
|
||||
|
||||
/** Set View Input callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param callback input callback
|
||||
*/
|
||||
void view_set_input_callback(View* view, ViewInputCallback callback);
|
||||
|
||||
/** Set View Custom callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param callback input callback
|
||||
*/
|
||||
void view_set_custom_callback(View* view, ViewCustomCallback callback);
|
||||
|
||||
/** Set Navigation Previous callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param callback input callback
|
||||
*/
|
||||
void view_set_previous_callback(View* view, ViewNavigationCallback callback);
|
||||
|
||||
/** Set Enter callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param callback callback
|
||||
*/
|
||||
void view_set_enter_callback(View* view, ViewCallback callback);
|
||||
|
||||
/** Set Exit callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param callback callback
|
||||
*/
|
||||
void view_set_exit_callback(View* view, ViewCallback callback);
|
||||
|
||||
/** Set Update callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param callback callback
|
||||
*/
|
||||
void view_set_update_callback(View* view, ViewUpdateCallback callback);
|
||||
|
||||
/** Set View Draw callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param context context for callbacks
|
||||
*/
|
||||
void view_set_update_callback_context(View* view, void* context);
|
||||
|
||||
/** Set View Draw callback
|
||||
*
|
||||
* @param view View instance
|
||||
* @param context context for callbacks
|
||||
*/
|
||||
void view_set_context(View* view, void* context);
|
||||
|
||||
/** Set View Orientation
|
||||
*
|
||||
* @param view View instance
|
||||
* @param orientation either vertical or horizontal
|
||||
*/
|
||||
void view_set_orientation(View* view, ViewOrientation orientation);
|
||||
|
||||
/** Allocate view model.
|
||||
*
|
||||
* @param view View instance
|
||||
* @param type View Model Type
|
||||
* @param size size
|
||||
*/
|
||||
void view_allocate_model(View* view, ViewModelType type, size_t size);
|
||||
|
||||
/** Free view model data memory.
|
||||
*
|
||||
* @param view View instance
|
||||
*/
|
||||
void view_free_model(View* view);
|
||||
|
||||
/** Get view model data
|
||||
*
|
||||
* @param view View instance
|
||||
*
|
||||
* @return pointer to model data
|
||||
* @warning Don't forget to commit model changes
|
||||
*/
|
||||
void* view_get_model(View* view);
|
||||
|
||||
/** Commit view model
|
||||
*
|
||||
* @param view View instance
|
||||
* @param update true if you want to emit view update, false otherwise
|
||||
*/
|
||||
void view_commit_model(View* view, bool update);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define with_view_model_cpp(view, type, var, function_body) \
|
||||
{ \
|
||||
type* p = static_cast<type*>(view_get_model(view)); \
|
||||
bool update = [&](type * var) function_body(p); \
|
||||
view_commit_model(view, update); \
|
||||
}
|
||||
#else
|
||||
/** With clause for view model
|
||||
*
|
||||
* @param view View instance pointer
|
||||
* @param function_body a (){} lambda declaration, executed within you
|
||||
* parent function context
|
||||
*
|
||||
* @return true if you want to emit view update, false otherwise
|
||||
*/
|
||||
#define with_view_model(view, function_body) \
|
||||
{ \
|
||||
void* p = view_get_model(view); \
|
||||
bool update = ({ bool __fn__ function_body __fn__; })(p); \
|
||||
view_commit_model(view, update); \
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,358 @@
|
||||
#include "view_dispatcher_i.h"
|
||||
|
||||
#define TAG "ViewDispatcher"
|
||||
|
||||
ViewDispatcher* view_dispatcher_alloc() {
|
||||
ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher));
|
||||
|
||||
view_dispatcher->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(
|
||||
view_dispatcher->view_port, view_dispatcher_draw_callback, view_dispatcher);
|
||||
view_port_input_callback_set(
|
||||
view_dispatcher->view_port, view_dispatcher_input_callback, view_dispatcher);
|
||||
view_port_enabled_set(view_dispatcher->view_port, false);
|
||||
|
||||
ViewDict_init(view_dispatcher->views);
|
||||
|
||||
return view_dispatcher;
|
||||
}
|
||||
|
||||
void view_dispatcher_free(ViewDispatcher* view_dispatcher) {
|
||||
// Detach from gui
|
||||
if(view_dispatcher->gui) {
|
||||
gui_remove_view_port(view_dispatcher->gui, view_dispatcher->view_port);
|
||||
}
|
||||
// Crash if not all views were freed
|
||||
furi_assert(ViewDict_size(view_dispatcher->views) == 0);
|
||||
|
||||
ViewDict_clear(view_dispatcher->views);
|
||||
// Free ViewPort
|
||||
view_port_free(view_dispatcher->view_port);
|
||||
// Free internal queue
|
||||
if(view_dispatcher->queue) {
|
||||
furi_message_queue_free(view_dispatcher->queue);
|
||||
}
|
||||
// Free dispatcher
|
||||
free(view_dispatcher);
|
||||
}
|
||||
|
||||
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(view_dispatcher->queue == NULL);
|
||||
view_dispatcher->queue = furi_message_queue_alloc(16, sizeof(ViewDispatcherMessage));
|
||||
}
|
||||
|
||||
void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher, void* context) {
|
||||
furi_assert(view_dispatcher);
|
||||
view_dispatcher->event_context = context;
|
||||
}
|
||||
|
||||
void view_dispatcher_set_navigation_event_callback(
|
||||
ViewDispatcher* view_dispatcher,
|
||||
ViewDispatcherNavigationEventCallback callback) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(callback);
|
||||
view_dispatcher->navigation_event_callback = callback;
|
||||
}
|
||||
|
||||
void view_dispatcher_set_custom_event_callback(
|
||||
ViewDispatcher* view_dispatcher,
|
||||
ViewDispatcherCustomEventCallback callback) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(callback);
|
||||
view_dispatcher->custom_event_callback = callback;
|
||||
}
|
||||
|
||||
void view_dispatcher_set_tick_event_callback(
|
||||
ViewDispatcher* view_dispatcher,
|
||||
ViewDispatcherTickEventCallback callback,
|
||||
uint32_t tick_period) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(callback);
|
||||
view_dispatcher->tick_event_callback = callback;
|
||||
view_dispatcher->tick_period = tick_period;
|
||||
}
|
||||
|
||||
void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(view_dispatcher->queue);
|
||||
|
||||
uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever :
|
||||
view_dispatcher->tick_period;
|
||||
ViewDispatcherMessage message;
|
||||
while(1) {
|
||||
if(furi_message_queue_get(view_dispatcher->queue, &message, tick_period) != FuriStatusOk) {
|
||||
view_dispatcher_handle_tick_event(view_dispatcher);
|
||||
continue;
|
||||
}
|
||||
if(message.type == ViewDispatcherMessageTypeStop) {
|
||||
break;
|
||||
} else if(message.type == ViewDispatcherMessageTypeInput) {
|
||||
view_dispatcher_handle_input(view_dispatcher, &message.input);
|
||||
} else if(message.type == ViewDispatcherMessageTypeCustomEvent) {
|
||||
view_dispatcher_handle_custom_event(view_dispatcher, message.custom_event);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait till all input events delivered
|
||||
while(view_dispatcher->ongoing_input) {
|
||||
furi_message_queue_get(view_dispatcher->queue, &message, FuriWaitForever);
|
||||
if(message.type == ViewDispatcherMessageTypeInput) {
|
||||
uint8_t key_bit = (1 << message.input.key);
|
||||
if(message.input.type == InputTypePress) {
|
||||
view_dispatcher->ongoing_input |= key_bit;
|
||||
} else if(message.input.type == InputTypeRelease) {
|
||||
view_dispatcher->ongoing_input &= ~key_bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_stop(ViewDispatcher* view_dispatcher) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(view_dispatcher->queue);
|
||||
ViewDispatcherMessage message;
|
||||
message.type = ViewDispatcherMessageTypeStop;
|
||||
furi_check(
|
||||
furi_message_queue_put(view_dispatcher->queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void view_dispatcher_add_view(ViewDispatcher* view_dispatcher, uint32_t view_id, View* view) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(view);
|
||||
// Check if view id is not used and register view
|
||||
furi_check(ViewDict_get(view_dispatcher->views, view_id) == NULL);
|
||||
|
||||
// Lock gui
|
||||
if(view_dispatcher->gui) {
|
||||
gui_lock(view_dispatcher->gui);
|
||||
}
|
||||
|
||||
ViewDict_set_at(view_dispatcher->views, view_id, view);
|
||||
view_set_update_callback(view, view_dispatcher_update);
|
||||
view_set_update_callback_context(view, view_dispatcher);
|
||||
|
||||
// Unlock gui
|
||||
if(view_dispatcher->gui) {
|
||||
gui_unlock(view_dispatcher->gui);
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_remove_view(ViewDispatcher* view_dispatcher, uint32_t view_id) {
|
||||
furi_assert(view_dispatcher);
|
||||
|
||||
// Lock gui
|
||||
if(view_dispatcher->gui) {
|
||||
gui_lock(view_dispatcher->gui);
|
||||
}
|
||||
// Get View by ID
|
||||
View* view = *ViewDict_get(view_dispatcher->views, view_id);
|
||||
|
||||
// Disable the view if it is active
|
||||
if(view_dispatcher->current_view == view) {
|
||||
view_dispatcher_set_current_view(view_dispatcher, NULL);
|
||||
}
|
||||
// Check if view is recieving input
|
||||
if(view_dispatcher->ongoing_input_view == view) {
|
||||
view_dispatcher->ongoing_input_view = NULL;
|
||||
}
|
||||
// Remove view
|
||||
ViewDict_erase(view_dispatcher->views, view_id);
|
||||
|
||||
view_set_update_callback(view, NULL);
|
||||
view_set_update_callback_context(view, NULL);
|
||||
|
||||
// Unlock gui
|
||||
if(view_dispatcher->gui) {
|
||||
gui_unlock(view_dispatcher->gui);
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t view_id) {
|
||||
furi_assert(view_dispatcher);
|
||||
if(view_id == VIEW_NONE) {
|
||||
view_dispatcher_set_current_view(view_dispatcher, NULL);
|
||||
} else if(view_id == VIEW_IGNORE) {
|
||||
} else {
|
||||
View** view_pp = ViewDict_get(view_dispatcher->views, view_id);
|
||||
furi_check(view_pp != NULL);
|
||||
view_dispatcher_set_current_view(view_dispatcher, *view_pp);
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_send_to_front(ViewDispatcher* view_dispatcher) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(view_dispatcher->gui);
|
||||
gui_view_port_send_to_front(view_dispatcher->gui, view_dispatcher->view_port);
|
||||
}
|
||||
|
||||
void view_dispatcher_send_to_back(ViewDispatcher* view_dispatcher) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(view_dispatcher->gui);
|
||||
gui_view_port_send_to_back(view_dispatcher->gui, view_dispatcher->view_port);
|
||||
}
|
||||
|
||||
void view_dispatcher_attach_to_gui(
|
||||
ViewDispatcher* view_dispatcher,
|
||||
Gui* gui,
|
||||
ViewDispatcherType type) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(view_dispatcher->gui == NULL);
|
||||
furi_assert(gui);
|
||||
|
||||
if(type == ViewDispatcherTypeDesktop) {
|
||||
gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerDesktop);
|
||||
} else if(type == ViewDispatcherTypeWindow) {
|
||||
gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerWindow);
|
||||
} else if(type == ViewDispatcherTypeFullscreen) {
|
||||
gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerFullscreen);
|
||||
} else {
|
||||
furi_check(NULL);
|
||||
}
|
||||
view_dispatcher->gui = gui;
|
||||
}
|
||||
|
||||
void view_dispatcher_draw_callback(Canvas* canvas, void* context) {
|
||||
ViewDispatcher* view_dispatcher = context;
|
||||
if(view_dispatcher->current_view) {
|
||||
view_draw(view_dispatcher->current_view, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_input_callback(InputEvent* event, void* context) {
|
||||
ViewDispatcher* view_dispatcher = context;
|
||||
if(view_dispatcher->queue) {
|
||||
ViewDispatcherMessage message;
|
||||
message.type = ViewDispatcherMessageTypeInput;
|
||||
message.input = *event;
|
||||
furi_check(
|
||||
furi_message_queue_put(view_dispatcher->queue, &message, FuriWaitForever) ==
|
||||
FuriStatusOk);
|
||||
} else {
|
||||
view_dispatcher_handle_input(view_dispatcher, event);
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event) {
|
||||
// Check input complementarity
|
||||
uint8_t key_bit = (1 << event->key);
|
||||
if(event->type == InputTypePress) {
|
||||
view_dispatcher->ongoing_input |= key_bit;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
view_dispatcher->ongoing_input &= ~key_bit;
|
||||
} else if(!(view_dispatcher->ongoing_input & key_bit)) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"non-complementary input, discarding key: %s, type: %s, sequence: %p",
|
||||
input_get_key_name(event->key),
|
||||
input_get_type_name(event->type),
|
||||
event->sequence);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set ongoing input view if this is event is first press event
|
||||
if(!(view_dispatcher->ongoing_input & ~key_bit) && event->type == InputTypePress) {
|
||||
view_dispatcher->ongoing_input_view = view_dispatcher->current_view;
|
||||
}
|
||||
|
||||
// Deliver event
|
||||
if(view_dispatcher->current_view &&
|
||||
view_dispatcher->ongoing_input_view == view_dispatcher->current_view) {
|
||||
// Dispatch input to current view
|
||||
bool is_consumed = view_input(view_dispatcher->current_view, event);
|
||||
|
||||
// Navigate if input is not consumed
|
||||
if(!is_consumed && (event->key == InputKeyBack) &&
|
||||
(event->type == InputTypeShort || event->type == InputTypeLong)) {
|
||||
// Navigate to previous
|
||||
uint32_t view_id = view_previous(view_dispatcher->current_view);
|
||||
if(view_id != VIEW_IGNORE) {
|
||||
// Switch to returned view
|
||||
view_dispatcher_switch_to_view(view_dispatcher, view_id);
|
||||
} else if(view_dispatcher->navigation_event_callback) {
|
||||
// Dispatch navigation event
|
||||
if(!view_dispatcher->navigation_event_callback(view_dispatcher->event_context)) {
|
||||
// TODO: should we allow view_dispatcher to stop without navigation_event_callback?
|
||||
view_dispatcher_stop(view_dispatcher);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(view_dispatcher->ongoing_input_view && event->type == InputTypeRelease) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"View changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port",
|
||||
view_dispatcher->ongoing_input_view,
|
||||
view_dispatcher->current_view,
|
||||
input_get_key_name(event->key),
|
||||
input_get_type_name(event->type),
|
||||
event->sequence);
|
||||
view_input(view_dispatcher->ongoing_input_view, event);
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_handle_tick_event(ViewDispatcher* view_dispatcher) {
|
||||
if(view_dispatcher->tick_event_callback) {
|
||||
view_dispatcher->tick_event_callback(view_dispatcher->event_context);
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_handle_custom_event(ViewDispatcher* view_dispatcher, uint32_t event) {
|
||||
bool is_consumed = false;
|
||||
if(view_dispatcher->current_view) {
|
||||
is_consumed = view_custom(view_dispatcher->current_view, event);
|
||||
}
|
||||
// If custom event is not consumed in View, call callback
|
||||
if(!is_consumed && view_dispatcher->custom_event_callback) {
|
||||
is_consumed =
|
||||
view_dispatcher->custom_event_callback(view_dispatcher->event_context, event);
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t event) {
|
||||
furi_assert(view_dispatcher);
|
||||
furi_assert(view_dispatcher->queue);
|
||||
|
||||
ViewDispatcherMessage message;
|
||||
message.type = ViewDispatcherMessageTypeCustomEvent;
|
||||
message.custom_event = event;
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(view_dispatcher->queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* view) {
|
||||
furi_assert(view_dispatcher);
|
||||
// Dispatch view exit event
|
||||
if(view_dispatcher->current_view) {
|
||||
view_exit(view_dispatcher->current_view);
|
||||
}
|
||||
// Set current view
|
||||
view_dispatcher->current_view = view;
|
||||
// Dispatch view enter event
|
||||
if(view_dispatcher->current_view) {
|
||||
if(view->orientation == ViewOrientationVertical)
|
||||
view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVertical);
|
||||
else if(view->orientation == ViewOrientationHorizontal)
|
||||
view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationHorizontal);
|
||||
view_enter(view_dispatcher->current_view);
|
||||
view_port_enabled_set(view_dispatcher->view_port, true);
|
||||
view_port_update(view_dispatcher->view_port);
|
||||
} else {
|
||||
view_port_enabled_set(view_dispatcher->view_port, false);
|
||||
if(view_dispatcher->queue) {
|
||||
view_dispatcher_stop(view_dispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void view_dispatcher_update(View* view, void* context) {
|
||||
furi_assert(view);
|
||||
furi_assert(context);
|
||||
|
||||
ViewDispatcher* view_dispatcher = context;
|
||||
|
||||
if(view_dispatcher->current_view == view) {
|
||||
view_port_update(view_dispatcher->view_port);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @file view_dispatcher.h
|
||||
* GUI: ViewDispatcher API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "view.h"
|
||||
#include "gui.h"
|
||||
#include "scene_manager.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** ViewDispatcher view_port placement */
|
||||
typedef enum {
|
||||
ViewDispatcherTypeDesktop, /**< Desktop layer: fullscreen with status bar on top of it. For internal usage. */
|
||||
ViewDispatcherTypeWindow, /**< Window layer: with status bar */
|
||||
ViewDispatcherTypeFullscreen /**< Fullscreen layer: without status bar */
|
||||
} ViewDispatcherType;
|
||||
|
||||
typedef struct ViewDispatcher ViewDispatcher;
|
||||
|
||||
/** Prototype for custom event callback */
|
||||
typedef bool (*ViewDispatcherCustomEventCallback)(void* context, uint32_t event);
|
||||
|
||||
/** Prototype for navigation event callback */
|
||||
typedef bool (*ViewDispatcherNavigationEventCallback)(void* context);
|
||||
|
||||
/** Prototype for tick event callback */
|
||||
typedef void (*ViewDispatcherTickEventCallback)(void* context);
|
||||
|
||||
/** Allocate ViewDispatcher instance
|
||||
*
|
||||
* @return pointer to ViewDispatcher instance
|
||||
*/
|
||||
ViewDispatcher* view_dispatcher_alloc();
|
||||
|
||||
/** Free ViewDispatcher instance
|
||||
*
|
||||
* @param view_dispatcher pointer to ViewDispatcher
|
||||
*/
|
||||
void view_dispatcher_free(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Enable queue support
|
||||
*
|
||||
* If queue enabled all input and custom events will be dispatched throw
|
||||
* internal queue
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*/
|
||||
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Send custom event
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param[in] event The event
|
||||
*/
|
||||
void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t event);
|
||||
|
||||
/** Set custom event handler
|
||||
*
|
||||
* Called on Custom Event, if it is not consumed by view
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param callback ViewDispatcherCustomEventCallback instance
|
||||
*/
|
||||
void view_dispatcher_set_custom_event_callback(
|
||||
ViewDispatcher* view_dispatcher,
|
||||
ViewDispatcherCustomEventCallback callback);
|
||||
|
||||
/** Set navigation event handler
|
||||
*
|
||||
* Called on Input Short Back Event, if it is not consumed by view
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param callback ViewDispatcherNavigationEventCallback instance
|
||||
*/
|
||||
void view_dispatcher_set_navigation_event_callback(
|
||||
ViewDispatcher* view_dispatcher,
|
||||
ViewDispatcherNavigationEventCallback callback);
|
||||
|
||||
/** Set tick event handler
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param callback ViewDispatcherTickEventCallback
|
||||
* @param tick_period callback call period
|
||||
*/
|
||||
void view_dispatcher_set_tick_event_callback(
|
||||
ViewDispatcher* view_dispatcher,
|
||||
ViewDispatcherTickEventCallback callback,
|
||||
uint32_t tick_period);
|
||||
|
||||
/** Set event callback context
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher, void* context);
|
||||
|
||||
/** Run ViewDispatcher
|
||||
*
|
||||
* Use only after queue enabled
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*/
|
||||
void view_dispatcher_run(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Stop ViewDispatcher
|
||||
*
|
||||
* Use only after queue enabled
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*/
|
||||
void view_dispatcher_stop(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Add view to ViewDispatcher
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param view_id View id to register
|
||||
* @param view View instance
|
||||
*/
|
||||
void view_dispatcher_add_view(ViewDispatcher* view_dispatcher, uint32_t view_id, View* view);
|
||||
|
||||
/** Remove view from ViewDispatcher
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param view_id View id to remove
|
||||
*/
|
||||
void view_dispatcher_remove_view(ViewDispatcher* view_dispatcher, uint32_t view_id);
|
||||
|
||||
/** Switch to View
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param view_id View id to register
|
||||
* @warning switching may be delayed till input events complementarity
|
||||
* reached
|
||||
*/
|
||||
void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t view_id);
|
||||
|
||||
/** Send ViewPort of this ViewDispatcher instance to front
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*/
|
||||
void view_dispatcher_send_to_front(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Send ViewPort of this ViewDispatcher instance to back
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
*/
|
||||
void view_dispatcher_send_to_back(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Attach ViewDispatcher to GUI
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param gui GUI instance to attach to
|
||||
* @param[in] type The type
|
||||
*/
|
||||
void view_dispatcher_attach_to_gui(
|
||||
ViewDispatcher* view_dispatcher,
|
||||
Gui* gui,
|
||||
ViewDispatcherType type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file view_dispatcher_i.h
|
||||
* GUI: ViewDispatcher API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <m-dict.h>
|
||||
|
||||
#include "view_dispatcher.h"
|
||||
#include "view_i.h"
|
||||
#include "gui_i.h"
|
||||
|
||||
DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST)
|
||||
|
||||
struct ViewDispatcher {
|
||||
FuriMessageQueue* queue;
|
||||
Gui* gui;
|
||||
ViewPort* view_port;
|
||||
ViewDict_t views;
|
||||
|
||||
View* current_view;
|
||||
|
||||
View* ongoing_input_view;
|
||||
uint8_t ongoing_input;
|
||||
|
||||
ViewDispatcherCustomEventCallback custom_event_callback;
|
||||
ViewDispatcherNavigationEventCallback navigation_event_callback;
|
||||
ViewDispatcherTickEventCallback tick_event_callback;
|
||||
uint32_t tick_period;
|
||||
void* event_context;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
ViewDispatcherMessageTypeInput,
|
||||
ViewDispatcherMessageTypeCustomEvent,
|
||||
ViewDispatcherMessageTypeStop,
|
||||
} ViewDispatcherMessageType;
|
||||
|
||||
typedef struct {
|
||||
ViewDispatcherMessageType type;
|
||||
union {
|
||||
InputEvent input;
|
||||
uint32_t custom_event;
|
||||
};
|
||||
} ViewDispatcherMessage;
|
||||
|
||||
/** ViewPort Draw Callback */
|
||||
void view_dispatcher_draw_callback(Canvas* canvas, void* context);
|
||||
|
||||
/** ViewPort Input Callback */
|
||||
void view_dispatcher_input_callback(InputEvent* event, void* context);
|
||||
|
||||
/** Input handler */
|
||||
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event);
|
||||
|
||||
/** Tick handler */
|
||||
void view_dispatcher_handle_tick_event(ViewDispatcher* view_dispatcher);
|
||||
|
||||
/** Custom event handler */
|
||||
void view_dispatcher_handle_custom_event(ViewDispatcher* view_dispatcher, uint32_t event);
|
||||
|
||||
/** Set current view, dispatches view enter and exit */
|
||||
void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* view);
|
||||
|
||||
/** ViewDispatcher update event */
|
||||
void view_dispatcher_update(View* view, void* context);
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @file view_i.h
|
||||
* GUI: internal View API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "view.h"
|
||||
#include <furi.h>
|
||||
|
||||
typedef struct {
|
||||
void* data;
|
||||
FuriMutex* mutex;
|
||||
} ViewModelLocking;
|
||||
|
||||
struct View {
|
||||
ViewDrawCallback draw_callback;
|
||||
ViewInputCallback input_callback;
|
||||
ViewCustomCallback custom_callback;
|
||||
|
||||
ViewModelType model_type;
|
||||
ViewNavigationCallback previous_callback;
|
||||
ViewCallback enter_callback;
|
||||
ViewCallback exit_callback;
|
||||
ViewOrientation orientation;
|
||||
|
||||
ViewUpdateCallback update_callback;
|
||||
void* update_callback_context;
|
||||
|
||||
void* model;
|
||||
void* context;
|
||||
};
|
||||
|
||||
/** IconAnimation tie callback */
|
||||
void view_icon_animation_callback(IconAnimation* instance, void* context);
|
||||
|
||||
/** Unlock model */
|
||||
void view_unlock_model(View* view);
|
||||
|
||||
/** Draw Callback for View dispatcher */
|
||||
void view_draw(View* view, Canvas* canvas);
|
||||
|
||||
/** Input Callback for View dispatcher */
|
||||
bool view_input(View* view, InputEvent* event);
|
||||
|
||||
/** Custom Callback for View dispatcher */
|
||||
bool view_custom(View* view, uint32_t event);
|
||||
|
||||
/** Previous Callback for View dispatcher */
|
||||
uint32_t view_previous(View* view);
|
||||
|
||||
/** Enter Callback for View dispatcher */
|
||||
void view_enter(View* view);
|
||||
|
||||
/** Exit Callback for View dispatcher */
|
||||
void view_exit(View* view);
|
||||
@@ -0,0 +1,139 @@
|
||||
#include "view_port_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "gui.h"
|
||||
#include "gui_i.h"
|
||||
|
||||
// TODO add mutex to view_port ops
|
||||
|
||||
static void view_port_rotate_buttons(InputEvent* event) {
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
event->key = InputKeyRight;
|
||||
break;
|
||||
case InputKeyDown:
|
||||
event->key = InputKeyLeft;
|
||||
break;
|
||||
case InputKeyRight:
|
||||
event->key = InputKeyDown;
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
event->key = InputKeyUp;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void view_port_setup_canvas_orientation(const ViewPort* view_port, Canvas* canvas) {
|
||||
if(view_port->orientation == ViewPortOrientationHorizontal) {
|
||||
canvas_set_orientation(canvas, CanvasOrientationHorizontal);
|
||||
} else if(view_port->orientation == ViewPortOrientationVertical) {
|
||||
canvas_set_orientation(canvas, CanvasOrientationVertical);
|
||||
}
|
||||
}
|
||||
|
||||
ViewPort* view_port_alloc() {
|
||||
ViewPort* view_port = malloc(sizeof(ViewPort));
|
||||
view_port->orientation = ViewPortOrientationHorizontal;
|
||||
view_port->is_enabled = true;
|
||||
return view_port;
|
||||
}
|
||||
|
||||
void view_port_free(ViewPort* view_port) {
|
||||
furi_assert(view_port);
|
||||
furi_check(view_port->gui == NULL);
|
||||
free(view_port);
|
||||
}
|
||||
|
||||
void view_port_set_width(ViewPort* view_port, uint8_t width) {
|
||||
furi_assert(view_port);
|
||||
view_port->width = width;
|
||||
}
|
||||
|
||||
uint8_t view_port_get_width(ViewPort* view_port) {
|
||||
furi_assert(view_port);
|
||||
return view_port->width;
|
||||
}
|
||||
|
||||
void view_port_set_height(ViewPort* view_port, uint8_t height) {
|
||||
furi_assert(view_port);
|
||||
view_port->height = height;
|
||||
}
|
||||
|
||||
uint8_t view_port_get_height(ViewPort* view_port) {
|
||||
furi_assert(view_port);
|
||||
return view_port->height;
|
||||
}
|
||||
|
||||
void view_port_enabled_set(ViewPort* view_port, bool enabled) {
|
||||
furi_assert(view_port);
|
||||
if(view_port->is_enabled != enabled) {
|
||||
view_port->is_enabled = enabled;
|
||||
if(view_port->gui) gui_update(view_port->gui);
|
||||
}
|
||||
}
|
||||
|
||||
bool view_port_is_enabled(ViewPort* view_port) {
|
||||
furi_assert(view_port);
|
||||
return view_port->is_enabled;
|
||||
}
|
||||
|
||||
void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context) {
|
||||
furi_assert(view_port);
|
||||
view_port->draw_callback = callback;
|
||||
view_port->draw_callback_context = context;
|
||||
}
|
||||
|
||||
void view_port_input_callback_set(
|
||||
ViewPort* view_port,
|
||||
ViewPortInputCallback callback,
|
||||
void* context) {
|
||||
furi_assert(view_port);
|
||||
view_port->input_callback = callback;
|
||||
view_port->input_callback_context = context;
|
||||
}
|
||||
|
||||
void view_port_update(ViewPort* view_port) {
|
||||
furi_assert(view_port);
|
||||
if(view_port->gui && view_port->is_enabled) gui_update(view_port->gui);
|
||||
}
|
||||
|
||||
void view_port_gui_set(ViewPort* view_port, Gui* gui) {
|
||||
furi_assert(view_port);
|
||||
view_port->gui = gui;
|
||||
}
|
||||
|
||||
void view_port_draw(ViewPort* view_port, Canvas* canvas) {
|
||||
furi_assert(view_port);
|
||||
furi_assert(canvas);
|
||||
furi_check(view_port->gui);
|
||||
|
||||
if(view_port->draw_callback) {
|
||||
view_port_setup_canvas_orientation(view_port, canvas);
|
||||
view_port->draw_callback(canvas, view_port->draw_callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
void view_port_input(ViewPort* view_port, InputEvent* event) {
|
||||
furi_assert(view_port);
|
||||
furi_assert(event);
|
||||
furi_check(view_port->gui);
|
||||
|
||||
if(view_port->input_callback) {
|
||||
if(view_port_get_orientation(view_port) == ViewPortOrientationVertical) {
|
||||
view_port_rotate_buttons(event);
|
||||
}
|
||||
view_port->input_callback(event, view_port->input_callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
void view_port_set_orientation(ViewPort* view_port, ViewPortOrientation orientation) {
|
||||
furi_assert(view_port);
|
||||
view_port->orientation = orientation;
|
||||
}
|
||||
|
||||
ViewPortOrientation view_port_get_orientation(const ViewPort* view_port) {
|
||||
return view_port->orientation;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @file view_port.h
|
||||
* GUI: ViewPort API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <input/input.h>
|
||||
#include "canvas.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct ViewPort ViewPort;
|
||||
|
||||
typedef enum {
|
||||
ViewPortOrientationHorizontal,
|
||||
ViewPortOrientationVertical,
|
||||
} ViewPortOrientation;
|
||||
|
||||
/** ViewPort Draw callback
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewPortDrawCallback)(Canvas* canvas, void* context);
|
||||
|
||||
/** ViewPort Input callback
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewPortInputCallback)(InputEvent* event, void* context);
|
||||
|
||||
/** ViewPort allocator
|
||||
*
|
||||
* always returns view_port or stops system if not enough memory.
|
||||
*
|
||||
* @return ViewPort instance
|
||||
*/
|
||||
ViewPort* view_port_alloc();
|
||||
|
||||
/** ViewPort deallocator
|
||||
*
|
||||
* Ensure that view_port was unregistered in GUI system before use.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void view_port_free(ViewPort* view_port);
|
||||
|
||||
/** Set view_port width.
|
||||
*
|
||||
* Will be used to limit canvas drawing area and autolayout feature.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
* @param width wanted width, 0 - auto.
|
||||
*/
|
||||
void view_port_set_width(ViewPort* view_port, uint8_t width);
|
||||
uint8_t view_port_get_width(ViewPort* view_port);
|
||||
|
||||
/** Set view_port height.
|
||||
*
|
||||
* Will be used to limit canvas drawing area and autolayout feature.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
* @param height wanted height, 0 - auto.
|
||||
*/
|
||||
void view_port_set_height(ViewPort* view_port, uint8_t height);
|
||||
uint8_t view_port_get_height(ViewPort* view_port);
|
||||
|
||||
/** Enable or disable view_port rendering.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
* @param enabled Indicates if enabled
|
||||
* @warning automatically dispatches update event
|
||||
*/
|
||||
void view_port_enabled_set(ViewPort* view_port, bool enabled);
|
||||
bool view_port_is_enabled(ViewPort* view_port);
|
||||
|
||||
/** ViewPort event callbacks
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
* @param callback appropriate callback function
|
||||
* @param context context to pass to callback
|
||||
*/
|
||||
void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context);
|
||||
void view_port_input_callback_set(
|
||||
ViewPort* view_port,
|
||||
ViewPortInputCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Emit update signal to GUI system.
|
||||
*
|
||||
* Rendering will happen later after GUI system process signal.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void view_port_update(ViewPort* view_port);
|
||||
|
||||
/** Set ViewPort orientation.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
* @param orientation display orientation, horizontal or vertical.
|
||||
*/
|
||||
void view_port_set_orientation(ViewPort* view_port, ViewPortOrientation orientation);
|
||||
ViewPortOrientation view_port_get_orientation(const ViewPort* view_port);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file view_port_i.h
|
||||
* GUI: internal ViewPort API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gui_i.h"
|
||||
#include "view_port.h"
|
||||
|
||||
struct ViewPort {
|
||||
Gui* gui;
|
||||
bool is_enabled;
|
||||
ViewPortOrientation orientation;
|
||||
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
|
||||
ViewPortDrawCallback draw_callback;
|
||||
void* draw_callback_context;
|
||||
|
||||
ViewPortInputCallback input_callback;
|
||||
void* input_callback_context;
|
||||
};
|
||||
|
||||
/** Set GUI reference.
|
||||
*
|
||||
* To be used by GUI, called upon view_port tree insert
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
* @param gui gui instance pointer
|
||||
*/
|
||||
void view_port_gui_set(ViewPort* view_port, Gui* gui);
|
||||
|
||||
/** Process draw call. Calls draw callback.
|
||||
*
|
||||
* To be used by GUI, called on tree redraw.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
* @param canvas canvas to draw at
|
||||
*/
|
||||
void view_port_draw(ViewPort* view_port, Canvas* canvas);
|
||||
|
||||
/** Process input. Calls input callback.
|
||||
*
|
||||
* To be used by GUI, called on input dispatch.
|
||||
*
|
||||
* @param view_port ViewPort instance
|
||||
* @param event pointer to input event
|
||||
*/
|
||||
void view_port_input(ViewPort* view_port, InputEvent* event);
|
||||
@@ -0,0 +1,172 @@
|
||||
#include "gui/view.h"
|
||||
#include <core/memmgr.h>
|
||||
#include "view_stack.h"
|
||||
#include "view_i.h"
|
||||
|
||||
#define MAX_VIEWS 3
|
||||
|
||||
typedef struct {
|
||||
View* views[MAX_VIEWS];
|
||||
} ViewStackModel;
|
||||
|
||||
struct ViewStack {
|
||||
View* view;
|
||||
};
|
||||
|
||||
static void view_stack_draw(Canvas* canvas, void* model);
|
||||
static bool view_stack_input(InputEvent* event, void* context);
|
||||
|
||||
static void view_stack_update_callback(View* view_top_or_bottom, void* context) {
|
||||
furi_assert(view_top_or_bottom);
|
||||
furi_assert(context);
|
||||
|
||||
View* view_stack_view = context;
|
||||
if(view_stack_view->update_callback) {
|
||||
view_stack_view->update_callback(
|
||||
view_stack_view, view_stack_view->update_callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
static void view_stack_enter(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
ViewStack* view_stack = context;
|
||||
ViewStackModel* model = view_get_model(view_stack->view);
|
||||
|
||||
/* if more than 1 Stack View hold same view they have to reassign update_callback_context */
|
||||
for(int i = 0; i < MAX_VIEWS; ++i) {
|
||||
if(model->views[i]) {
|
||||
view_set_update_callback_context(model->views[i], view_stack->view);
|
||||
if(model->views[i]->enter_callback) {
|
||||
model->views[i]->enter_callback(model->views[i]->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_commit_model(view_stack->view, false);
|
||||
}
|
||||
|
||||
static void view_stack_exit(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
ViewStack* view_stack = context;
|
||||
ViewStackModel* model = view_get_model(view_stack->view);
|
||||
|
||||
for(int i = 0; i < MAX_VIEWS; ++i) {
|
||||
if(model->views[i] && model->views[i]->exit_callback) {
|
||||
model->views[i]->exit_callback(model->views[i]->context);
|
||||
}
|
||||
}
|
||||
|
||||
view_commit_model(view_stack->view, false);
|
||||
}
|
||||
|
||||
ViewStack* view_stack_alloc(void) {
|
||||
ViewStack* view_stack = malloc(sizeof(ViewStack));
|
||||
view_stack->view = view_alloc();
|
||||
|
||||
view_allocate_model(view_stack->view, ViewModelTypeLocking, sizeof(ViewStackModel));
|
||||
view_set_draw_callback(view_stack->view, view_stack_draw);
|
||||
view_set_input_callback(view_stack->view, view_stack_input);
|
||||
view_set_context(view_stack->view, view_stack);
|
||||
view_set_enter_callback(view_stack->view, view_stack_enter);
|
||||
view_set_exit_callback(view_stack->view, view_stack_exit);
|
||||
return view_stack;
|
||||
}
|
||||
|
||||
void view_stack_free(ViewStack* view_stack) {
|
||||
furi_assert(view_stack);
|
||||
|
||||
ViewStackModel* model = view_get_model(view_stack->view);
|
||||
for(int i = 0; i < MAX_VIEWS; ++i) {
|
||||
if(model->views[i]) {
|
||||
view_set_update_callback(model->views[i], NULL);
|
||||
view_set_update_callback_context(model->views[i], NULL);
|
||||
}
|
||||
}
|
||||
view_commit_model(view_stack->view, false);
|
||||
|
||||
view_free(view_stack->view);
|
||||
free(view_stack);
|
||||
}
|
||||
|
||||
static void view_stack_draw(Canvas* canvas, void* _model) {
|
||||
furi_assert(_model);
|
||||
|
||||
ViewStackModel* model = _model;
|
||||
for(int i = 0; i < MAX_VIEWS; ++i) {
|
||||
if(model->views[i]) {
|
||||
view_draw(model->views[i], canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool view_stack_input(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
furi_assert(context);
|
||||
|
||||
ViewStack* view_stack = context;
|
||||
|
||||
bool consumed = false;
|
||||
ViewStackModel* model = view_get_model(view_stack->view);
|
||||
for(int i = MAX_VIEWS - 1; i >= 0; i--) {
|
||||
if(model->views[i] && view_input(model->views[i], event)) {
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
view_commit_model(view_stack->view, false);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void view_stack_add_view(ViewStack* view_stack, View* view) {
|
||||
furi_assert(view_stack);
|
||||
furi_assert(view);
|
||||
|
||||
bool result = false;
|
||||
ViewStackModel* model = view_get_model(view_stack->view);
|
||||
for(int i = 0; i < MAX_VIEWS; ++i) {
|
||||
if(!model->views[i]) {
|
||||
model->views[i] = view;
|
||||
view_set_update_callback(model->views[i], view_stack_update_callback);
|
||||
view_set_update_callback_context(model->views[i], view_stack->view);
|
||||
if(view->enter_callback) {
|
||||
view->enter_callback(view->context);
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
view_commit_model(view_stack->view, result);
|
||||
furi_assert(result);
|
||||
}
|
||||
|
||||
void view_stack_remove_view(ViewStack* view_stack, View* view) {
|
||||
furi_assert(view_stack);
|
||||
furi_assert(view);
|
||||
|
||||
/* Removing view on-the-go is dangerous, but it is protected with
|
||||
* Locking model, so system is consistent at any time. */
|
||||
bool result = false;
|
||||
ViewStackModel* model = view_get_model(view_stack->view);
|
||||
for(int i = 0; i < MAX_VIEWS; ++i) {
|
||||
if(model->views[i] == view) {
|
||||
if(view->exit_callback) {
|
||||
view->exit_callback(view->context);
|
||||
}
|
||||
view_set_update_callback(model->views[i], NULL);
|
||||
view_set_update_callback_context(model->views[i], NULL);
|
||||
model->views[i] = NULL;
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
view_commit_model(view_stack->view, result);
|
||||
furi_assert(result);
|
||||
}
|
||||
|
||||
View* view_stack_get_view(ViewStack* view_stack) {
|
||||
furi_assert(view_stack);
|
||||
return view_stack->view;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @file view_stack.h
|
||||
* GUI: ViewStack API
|
||||
*
|
||||
* ViewStack accumulates several Views in one stack.
|
||||
* Draw callbacks are called sequenctially starting from
|
||||
* first added. Input callbacks are called in reverse order.
|
||||
* Consumed input is not passed on underlying layers.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "view.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** ViewStack, anonymous type. */
|
||||
typedef struct ViewStack ViewStack;
|
||||
|
||||
/** Allocate and init ViewStack
|
||||
*
|
||||
* @return ViewStack instance
|
||||
*/
|
||||
ViewStack* view_stack_alloc(void);
|
||||
|
||||
/** Free ViewStack instance
|
||||
*
|
||||
* @param view_stack instance
|
||||
*/
|
||||
void view_stack_free(ViewStack* view_stack);
|
||||
|
||||
/** Get View of ViewStack.
|
||||
* Should this View to any view manager such as
|
||||
* ViewDispatcher or ViewHolder.
|
||||
*
|
||||
* @param view_stack instance
|
||||
*/
|
||||
View* view_stack_get_view(ViewStack* view_stack);
|
||||
|
||||
/** Add View to ViewStack.
|
||||
* Adds View on top of ViewStack.
|
||||
*
|
||||
* @param view_stack instance
|
||||
* @view view view to add
|
||||
*/
|
||||
void view_stack_add_view(ViewStack* view_stack, View* view);
|
||||
|
||||
/** Remove any View in ViewStack.
|
||||
* If no View to remove found - ignore.
|
||||
*
|
||||
* @param view_stack instance
|
||||
* @view view view to remove
|
||||
*/
|
||||
void view_stack_remove_view(ViewStack* view_stack, View* view);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user