mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2025-10-09 06:32:50 +02:00
* 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>
146 lines
4.5 KiB
C
146 lines
4.5 KiB
C
#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;
|
|
const Icon* data;
|
|
} IconDefinition;
|
|
|
|
#define ICON_DEF(icon) \
|
|
(IconDefinition) { \
|
|
.name = #icon, .data = &I_##icon \
|
|
}
|
|
|
|
static const IconDefinition builtin_icons[] = {
|
|
ICON_DEF(DolphinWait_59x54),
|
|
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));
|
|
|
|
for(size_t i = 0; i < COUNT_OF(builtin_icons); i++) {
|
|
if(strcmp(icon_name, builtin_icons[i].name) == 0) {
|
|
mjs_return(mjs, mjs_mk_foreign(mjs, (void*)builtin_icons[i].data));
|
|
return;
|
|
}
|
|
}
|
|
|
|
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 js_icon;
|
|
}
|
|
|
|
static void js_gui_icon_destroy(void* 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 = {
|
|
"gui__icon",
|
|
js_gui_icon_create,
|
|
js_gui_icon_destroy,
|
|
NULL,
|
|
};
|
|
|
|
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
|
.appid = PLUGIN_APP_ID,
|
|
.ep_api_version = PLUGIN_API_VERSION,
|
|
.entry_point = &js_gui_icon_desc,
|
|
};
|
|
|
|
const FlipperAppPluginDescriptor* js_gui_icon_ep(void) {
|
|
return &plugin_descriptor;
|
|
}
|