mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-24 03:29:57 -07:00
GUI: Widget view extra options for JS (#4120)
* Fill option for widget frame * Add widget circle element * Add widget line element * Fix missing include for InputType * Fix missing comment * Update api symbols * Load .fxbm from file * Fix copy pasta * Add fill param to example * Fix some comments * Bump JS SDK 0.3 * Fix free * Rename widget frame to rect * Gui: add widget_add_frame_element backward compatibility macros Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
@@ -19,8 +19,8 @@ let jsLogo = icon.getBuiltin("js_script_10px");
|
||||
let stopwatchWidgetElements = [
|
||||
{ element: "string", x: 67, y: 44, align: "bl", font: "big_numbers", text: "00 00" },
|
||||
{ element: "string", x: 77, y: 22, align: "bl", font: "primary", text: "Stopwatch" },
|
||||
{ element: "frame", x: 64, y: 27, w: 28, h: 20, radius: 3 },
|
||||
{ element: "frame", x: 100, y: 27, w: 28, h: 20, radius: 3 },
|
||||
{ element: "frame", x: 64, y: 27, w: 28, h: 20, radius: 3, fill: false },
|
||||
{ element: "frame", x: 100, y: 27, w: 28, h: 20, radius: 3, fill: false },
|
||||
{ element: "icon", x: 0, y: 5, iconData: cuteDolphinWithWatch },
|
||||
{ element: "icon", x: 64, y: 13, iconData: jsLogo },
|
||||
{ element: "button", button: "right", text: "Back" },
|
||||
|
||||
@@ -270,6 +270,7 @@ static const char* extra_features[] = {
|
||||
"gpio-pwm",
|
||||
"gui-widget",
|
||||
"serial-framing",
|
||||
"gui-widget-extras",
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "../../js_modules.h"
|
||||
#include <assets_icons.h>
|
||||
#include <core/dangerous_defines.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <m-list.h>
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
@@ -16,6 +19,26 @@ static const IconDefinition builtin_icons[] = {
|
||||
ICON_DEF(js_script_10px),
|
||||
};
|
||||
|
||||
// Firmware's Icon struct needs a frames array, and uses a small CompressHeader
|
||||
// Here we use a variable size allocation to add the uncompressed data in same allocation
|
||||
// Also use a one-long array pointing to later in the same struct as the frames array
|
||||
// CompressHeader includes a first is_compressed byte so we don't need to compress (.fxbm is uncompressed)
|
||||
typedef struct FURI_PACKED {
|
||||
Icon icon;
|
||||
uint8_t* frames[1];
|
||||
struct {
|
||||
uint8_t is_compressed;
|
||||
uint8_t uncompressed_data[];
|
||||
} frame;
|
||||
} FxbmIconWrapper;
|
||||
|
||||
LIST_DEF(FxbmIconWrapperList, FxbmIconWrapper*, M_PTR_OPLIST); // NOLINT
|
||||
#define M_OPL_FxbmIconWrapperList_t() LIST_OPLIST(FxbmIconWrapperList)
|
||||
|
||||
typedef struct {
|
||||
FxbmIconWrapperList_t fxbm_list;
|
||||
} JsGuiIconInst;
|
||||
|
||||
static void js_gui_icon_get_builtin(struct mjs* mjs) {
|
||||
const char* icon_name;
|
||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&icon_name));
|
||||
@@ -30,17 +53,78 @@ static void js_gui_icon_get_builtin(struct mjs* mjs) {
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "no such built-in icon");
|
||||
}
|
||||
|
||||
static void js_gui_icon_load_fxbm(struct mjs* mjs) {
|
||||
const char* fxbm_path;
|
||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fxbm_path));
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
FxbmIconWrapper* fxbm = NULL;
|
||||
|
||||
do {
|
||||
if(!storage_file_open(file, fxbm_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
break;
|
||||
}
|
||||
|
||||
struct {
|
||||
uint32_t size; // Total following size including width and height values
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
} fxbm_header;
|
||||
if(storage_file_read(file, &fxbm_header, sizeof(fxbm_header)) != sizeof(fxbm_header)) {
|
||||
break;
|
||||
}
|
||||
|
||||
size_t frame_size = fxbm_header.size - sizeof(uint32_t) * 2;
|
||||
fxbm = malloc(sizeof(FxbmIconWrapper) + frame_size);
|
||||
if(storage_file_read(file, fxbm->frame.uncompressed_data, frame_size) != frame_size) {
|
||||
free(fxbm);
|
||||
fxbm = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_CONST_ASSIGN(fxbm->icon.width, fxbm_header.width);
|
||||
FURI_CONST_ASSIGN(fxbm->icon.height, fxbm_header.height);
|
||||
FURI_CONST_ASSIGN(fxbm->icon.frame_count, 1);
|
||||
FURI_CONST_ASSIGN(fxbm->icon.frame_rate, 1);
|
||||
FURI_CONST_ASSIGN_PTR(fxbm->icon.frames, fxbm->frames);
|
||||
fxbm->frames[0] = (void*)&fxbm->frame;
|
||||
fxbm->frame.is_compressed = false;
|
||||
} while(false);
|
||||
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(!fxbm) {
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "could not load .fxbm icon");
|
||||
}
|
||||
|
||||
JsGuiIconInst* js_icon = JS_GET_CONTEXT(mjs);
|
||||
FxbmIconWrapperList_push_back(js_icon->fxbm_list, fxbm);
|
||||
mjs_return(mjs, mjs_mk_foreign(mjs, (void*)&fxbm->icon));
|
||||
}
|
||||
|
||||
static void* js_gui_icon_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||
UNUSED(modules);
|
||||
JsGuiIconInst* js_icon = malloc(sizeof(JsGuiIconInst));
|
||||
FxbmIconWrapperList_init(js_icon->fxbm_list);
|
||||
*object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, *object) {
|
||||
JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_icon));
|
||||
JS_FIELD("getBuiltin", MJS_MK_FN(js_gui_icon_get_builtin));
|
||||
JS_FIELD("loadFxbm", MJS_MK_FN(js_gui_icon_load_fxbm));
|
||||
}
|
||||
return NULL;
|
||||
return js_icon;
|
||||
}
|
||||
|
||||
static void js_gui_icon_destroy(void* inst) {
|
||||
UNUSED(inst);
|
||||
JsGuiIconInst* js_icon = inst;
|
||||
for
|
||||
M_EACH(fxbm, js_icon->fxbm_list, FxbmIconWrapperList_t) {
|
||||
free(*fxbm);
|
||||
}
|
||||
FxbmIconWrapperList_clear(js_icon->fxbm_list);
|
||||
free(js_icon);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_gui_icon_desc = {
|
||||
|
||||
@@ -202,7 +202,7 @@ static bool js_widget_add_child(
|
||||
const Icon* icon = mjs_get_ptr(mjs, icon_data_in);
|
||||
widget_add_icon_element(widget, x, y, icon);
|
||||
|
||||
} else if(strcmp(element_type, "frame") == 0) {
|
||||
} else if(strcmp(element_type, "rect") == 0) {
|
||||
int32_t x, y, w, h;
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
|
||||
@@ -211,7 +211,43 @@ static bool js_widget_add_child(
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius");
|
||||
int32_t radius = mjs_get_int32(mjs, radius_in);
|
||||
widget_add_frame_element(widget, x, y, w, h, radius);
|
||||
mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0);
|
||||
if(!mjs_is_boolean(fill_in))
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill");
|
||||
int32_t fill = mjs_get_bool(mjs, fill_in);
|
||||
widget_add_rect_element(widget, x, y, w, h, radius, fill);
|
||||
|
||||
} else if(strcmp(element_type, "circle") == 0) {
|
||||
int32_t x, y;
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||
mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0);
|
||||
if(!mjs_is_number(radius_in))
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius");
|
||||
int32_t radius = mjs_get_int32(mjs, radius_in);
|
||||
mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0);
|
||||
if(!mjs_is_boolean(fill_in))
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill");
|
||||
int32_t fill = mjs_get_bool(mjs, fill_in);
|
||||
widget_add_circle_element(widget, x, y, radius, fill);
|
||||
|
||||
} else if(strcmp(element_type, "line") == 0) {
|
||||
int32_t x1, y1, x2, y2;
|
||||
mjs_val_t x1_in = mjs_get(mjs, child_obj, "x1", ~0);
|
||||
mjs_val_t y1_in = mjs_get(mjs, child_obj, "y1", ~0);
|
||||
mjs_val_t x2_in = mjs_get(mjs, child_obj, "x2", ~0);
|
||||
mjs_val_t y2_in = mjs_get(mjs, child_obj, "y2", ~0);
|
||||
if(!mjs_is_number(x1_in) || !mjs_is_number(y1_in) || !mjs_is_number(x2_in) ||
|
||||
!mjs_is_number(y2_in))
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element positions");
|
||||
x1 = mjs_get_int32(mjs, x1_in);
|
||||
y1 = mjs_get_int32(mjs, y1_in);
|
||||
x2 = mjs_get_int32(mjs, x2_in);
|
||||
y2 = mjs_get_int32(mjs, y2_in);
|
||||
widget_add_line_element(widget, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -9,3 +9,10 @@ export type IconData = symbol & { "__tag__": "icon" };
|
||||
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
|
||||
*/
|
||||
export declare function getBuiltin(icon: BuiltinIcon): IconData;
|
||||
|
||||
/**
|
||||
* Loads a .fxbm icon (XBM Flipper sprite, from flipperzero-game-engine) for use in GUI
|
||||
* @param path Path to the .fxbm file
|
||||
* @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"`
|
||||
*/
|
||||
export declare function loadFxbm(path: string): IconData;
|
||||
|
||||
@@ -42,7 +42,9 @@ type TextBoxElement = { element: "text_box", stripToDots: boolean } & Position &
|
||||
type TextScrollElement = { element: "text_scroll" } & Position & Size & Text;
|
||||
type ButtonElement = { element: "button", button: "left" | "center" | "right" } & Text;
|
||||
type IconElement = { element: "icon", iconData: IconData } & Position;
|
||||
type FrameElement = { element: "frame", radius: number } & Position & Size;
|
||||
type RectElement = { element: "rect", radius: number, fill: boolean } & Position & Size; /** @version Amended in JS SDK 0.3, extra feature `"gui-widget-extras"` */
|
||||
type CircleElement = { element: "circle", radius: number, fill: boolean } & Position; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */
|
||||
type LineElement = { element: "line", x1: number, y1: number, x2: number, y2: number }; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */
|
||||
|
||||
type Element = StringMultilineElement
|
||||
| StringElement
|
||||
@@ -50,7 +52,9 @@ type Element = StringMultilineElement
|
||||
| TextScrollElement
|
||||
| ButtonElement
|
||||
| IconElement
|
||||
| FrameElement;
|
||||
| RectElement
|
||||
| CircleElement
|
||||
| LineElement;
|
||||
|
||||
type Props = {};
|
||||
type Child = Element;
|
||||
|
||||
Reference in New Issue
Block a user