mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-21 00:58:10 -07:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
* @brief Context passed to the generic event callback
|
||||
*/
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
JsEventLoopObjectType object_type;
|
||||
|
||||
struct mjs* mjs;
|
||||
@@ -36,11 +37,6 @@ typedef struct {
|
||||
void* subscriptions; // SubscriptionArray_t, which we can't reference in this definition
|
||||
} JsEventLoopSubscription;
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* loop;
|
||||
struct mjs* mjs;
|
||||
} JsEventLoopTickContext;
|
||||
|
||||
ARRAY_DEF(SubscriptionArray, JsEventLoopSubscription*, M_PTR_OPLIST); //-V575
|
||||
ARRAY_DEF(ContractArray, JsEventLoopContract*, M_PTR_OPLIST); //-V575
|
||||
|
||||
@@ -51,7 +47,6 @@ struct JsEventLoop {
|
||||
FuriEventLoop* loop;
|
||||
SubscriptionArray_t subscriptions;
|
||||
ContractArray_t owned_contracts; //<! Contracts that were produced by this module
|
||||
JsEventLoopTickContext* tick_context;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,7 +55,7 @@ struct JsEventLoop {
|
||||
static void js_event_loop_callback_generic(void* param) {
|
||||
JsEventLoopCallbackContext* context = param;
|
||||
mjs_val_t result;
|
||||
mjs_apply(
|
||||
mjs_err_t error = mjs_apply(
|
||||
context->mjs,
|
||||
&result,
|
||||
context->callback,
|
||||
@@ -68,6 +63,12 @@ static void js_event_loop_callback_generic(void* param) {
|
||||
context->arity,
|
||||
context->arguments);
|
||||
|
||||
bool is_error = strcmp(mjs_strerror(context->mjs, error), "NO_ERROR") != 0;
|
||||
bool asked_to_stop = js_flags_wait(context->mjs, ThreadEventStop, 0) & ThreadEventStop;
|
||||
if(is_error || asked_to_stop) {
|
||||
furi_event_loop_stop(context->event_loop);
|
||||
}
|
||||
|
||||
// save returned args for next call
|
||||
if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return;
|
||||
for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) {
|
||||
@@ -111,11 +112,14 @@ static void js_event_loop_subscription_cancel(struct mjs* mjs) {
|
||||
JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs);
|
||||
|
||||
if(subscription->object_type == JsEventLoopObjectTypeTimer) {
|
||||
// timer operations are deferred, which creates lifetime issues
|
||||
// just stop the timer and let the cleanup routine free everything when the script is done
|
||||
furi_event_loop_timer_stop(subscription->object);
|
||||
} else {
|
||||
furi_event_loop_unsubscribe(subscription->loop, subscription->object);
|
||||
return;
|
||||
}
|
||||
|
||||
furi_event_loop_unsubscribe(subscription->loop, subscription->object);
|
||||
|
||||
free(subscription->context->arguments);
|
||||
free(subscription->context);
|
||||
|
||||
@@ -158,6 +162,7 @@ static void js_event_loop_subscribe(struct mjs* mjs) {
|
||||
mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel));
|
||||
|
||||
// create callback context
|
||||
context->event_loop = module->loop;
|
||||
context->object_type = contract->object_type;
|
||||
context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2;
|
||||
context->arguments = calloc(context->arity, sizeof(mjs_val_t));
|
||||
@@ -333,37 +338,22 @@ static void js_event_loop_queue(struct mjs* mjs) {
|
||||
mjs_return(mjs, queue);
|
||||
}
|
||||
|
||||
static void js_event_loop_tick(void* param) {
|
||||
JsEventLoopTickContext* context = param;
|
||||
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0);
|
||||
if(flags & FuriFlagError) {
|
||||
return;
|
||||
}
|
||||
if(flags & ThreadEventStop) {
|
||||
furi_event_loop_stop(context->loop);
|
||||
mjs_exit(context->mjs);
|
||||
}
|
||||
}
|
||||
|
||||
static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||
UNUSED(modules);
|
||||
mjs_val_t event_loop_obj = mjs_mk_object(mjs);
|
||||
JsEventLoop* module = malloc(sizeof(JsEventLoop));
|
||||
JsEventLoopTickContext* tick_ctx = malloc(sizeof(JsEventLoopTickContext));
|
||||
module->loop = furi_event_loop_alloc();
|
||||
tick_ctx->loop = module->loop;
|
||||
tick_ctx->mjs = mjs;
|
||||
module->tick_context = tick_ctx;
|
||||
furi_event_loop_tick_set(module->loop, 10, js_event_loop_tick, tick_ctx);
|
||||
SubscriptionArray_init(module->subscriptions);
|
||||
ContractArray_init(module->owned_contracts);
|
||||
|
||||
mjs_set(mjs, event_loop_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module));
|
||||
mjs_set(mjs, event_loop_obj, "subscribe", ~0, MJS_MK_FN(js_event_loop_subscribe));
|
||||
mjs_set(mjs, event_loop_obj, "run", ~0, MJS_MK_FN(js_event_loop_run));
|
||||
mjs_set(mjs, event_loop_obj, "stop", ~0, MJS_MK_FN(js_event_loop_stop));
|
||||
mjs_set(mjs, event_loop_obj, "timer", ~0, MJS_MK_FN(js_event_loop_timer));
|
||||
mjs_set(mjs, event_loop_obj, "queue", ~0, MJS_MK_FN(js_event_loop_queue));
|
||||
JS_ASSIGN_MULTI(mjs, event_loop_obj) {
|
||||
JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, module));
|
||||
JS_FIELD("subscribe", MJS_MK_FN(js_event_loop_subscribe));
|
||||
JS_FIELD("run", MJS_MK_FN(js_event_loop_run));
|
||||
JS_FIELD("stop", MJS_MK_FN(js_event_loop_stop));
|
||||
JS_FIELD("timer", MJS_MK_FN(js_event_loop_timer));
|
||||
JS_FIELD("queue", MJS_MK_FN(js_event_loop_queue));
|
||||
}
|
||||
|
||||
*object = event_loop_obj;
|
||||
return module;
|
||||
@@ -418,7 +408,6 @@ static void js_event_loop_destroy(void* inst) {
|
||||
ContractArray_clear(module->owned_contracts);
|
||||
|
||||
furi_event_loop_free(module->loop);
|
||||
free(module->tick_context);
|
||||
free(module);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "../js_modules.h" // IWYU pragma: keep
|
||||
#include "./js_event_loop/js_event_loop.h"
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_pwm.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <expansion/expansion.h>
|
||||
#include <limits.h>
|
||||
@@ -17,6 +18,7 @@ typedef struct {
|
||||
FuriSemaphore* interrupt_semaphore;
|
||||
JsEventLoopContract* interrupt_contract;
|
||||
FuriHalAdcChannel adc_channel;
|
||||
FuriHalPwmOutputId pwm_output;
|
||||
FuriHalAdcHandle* adc_handle;
|
||||
} JsGpioPinInst;
|
||||
|
||||
@@ -231,6 +233,88 @@ static void js_gpio_read_analog(struct mjs* mjs) {
|
||||
mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines whether this pin supports PWM
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
* let gpio = require("gpio");
|
||||
* assert_eq(true, gpio.get("pa4").isPwmSupported());
|
||||
* assert_eq(false, gpio.get("pa5").isPwmSupported());
|
||||
* ```
|
||||
*/
|
||||
static void js_gpio_is_pwm_supported(struct mjs* mjs) {
|
||||
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, manager_data->pwm_output != FuriHalPwmOutputIdNone));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets PWM parameters and starts the PWM
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
* let gpio = require("gpio");
|
||||
* let pa4 = gpio.get("pa4");
|
||||
* pa4.pwmWrite(10000, 50);
|
||||
* ```
|
||||
*/
|
||||
static void js_gpio_pwm_write(struct mjs* mjs) {
|
||||
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
|
||||
int32_t frequency, duty;
|
||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&frequency), JS_ARG_INT32(&duty));
|
||||
if(manager_data->pwm_output == FuriHalPwmOutputIdNone) {
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
|
||||
}
|
||||
|
||||
if(furi_hal_pwm_is_running(manager_data->pwm_output)) {
|
||||
furi_hal_pwm_set_params(manager_data->pwm_output, frequency, duty);
|
||||
} else {
|
||||
furi_hal_pwm_start(manager_data->pwm_output, frequency, duty);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines whether PWM is running
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
* let gpio = require("gpio");
|
||||
* assert_eq(false, gpio.get("pa4").isPwmRunning());
|
||||
* ```
|
||||
*/
|
||||
static void js_gpio_is_pwm_running(struct mjs* mjs) {
|
||||
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
|
||||
if(manager_data->pwm_output == FuriHalPwmOutputIdNone) {
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
|
||||
}
|
||||
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, furi_hal_pwm_is_running(manager_data->pwm_output)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stops PWM
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
* let gpio = require("gpio");
|
||||
* let pa4 = gpio.get("pa4");
|
||||
* pa4.pwmWrite(10000, 50);
|
||||
* pa4.pwmStop();
|
||||
* ```
|
||||
*/
|
||||
static void js_gpio_pwm_stop(struct mjs* mjs) {
|
||||
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
|
||||
if(manager_data->pwm_output != FuriHalPwmOutputIdNone) {
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
|
||||
}
|
||||
|
||||
furi_hal_pwm_stop(manager_data->pwm_output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an object that manages a specified pin.
|
||||
*
|
||||
@@ -269,12 +353,19 @@ static void js_gpio_get(struct mjs* mjs) {
|
||||
manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0);
|
||||
manager_data->adc_handle = module->adc_handle;
|
||||
manager_data->adc_channel = pin_record->channel;
|
||||
mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data));
|
||||
mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init));
|
||||
mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write));
|
||||
mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read));
|
||||
mjs_set(mjs, manager, "readAnalog", ~0, MJS_MK_FN(js_gpio_read_analog));
|
||||
mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt));
|
||||
manager_data->pwm_output = pin_record->pwm_output;
|
||||
JS_ASSIGN_MULTI(mjs, manager) {
|
||||
JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, manager_data));
|
||||
JS_FIELD("init", MJS_MK_FN(js_gpio_init));
|
||||
JS_FIELD("write", MJS_MK_FN(js_gpio_write));
|
||||
JS_FIELD("read", MJS_MK_FN(js_gpio_read));
|
||||
JS_FIELD("readAnalog", MJS_MK_FN(js_gpio_read_analog));
|
||||
JS_FIELD("interrupt", MJS_MK_FN(js_gpio_interrupt));
|
||||
JS_FIELD("isPwmSupported", MJS_MK_FN(js_gpio_is_pwm_supported));
|
||||
JS_FIELD("pwmWrite", MJS_MK_FN(js_gpio_pwm_write));
|
||||
JS_FIELD("isPwmRunning", MJS_MK_FN(js_gpio_is_pwm_running));
|
||||
JS_FIELD("pwmStop", MJS_MK_FN(js_gpio_pwm_stop));
|
||||
}
|
||||
mjs_return(mjs, manager);
|
||||
|
||||
// remember pin
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "../../js_modules.h"
|
||||
#include <assets_icons.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),
|
||||
};
|
||||
|
||||
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_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||
UNUSED(modules);
|
||||
*object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, *object) {
|
||||
JS_FIELD("getBuiltin", MJS_MK_FN(js_gui_icon_get_builtin));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void js_gui_icon_destroy(void* inst) {
|
||||
UNUSED(inst);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -247,6 +247,22 @@ static bool
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the list of children. Not available from JS.
|
||||
*/
|
||||
static bool
|
||||
js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) {
|
||||
data->descriptor->reset_children(data->specific_view, data->custom_data);
|
||||
|
||||
for(size_t i = 0; i < mjs_array_length(mjs, children); i++) {
|
||||
mjs_val_t child = mjs_array_get(mjs, children, i);
|
||||
if(!data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief `View.set`
|
||||
*/
|
||||
@@ -260,6 +276,46 @@ static void js_gui_view_set(struct mjs* mjs) {
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief `View.addChild`
|
||||
*/
|
||||
static void js_gui_view_add_child(struct mjs* mjs) {
|
||||
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
|
||||
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||
|
||||
mjs_val_t child;
|
||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&child));
|
||||
bool success = data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child);
|
||||
UNUSED(success);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief `View.resetChildren`
|
||||
*/
|
||||
static void js_gui_view_reset_children(struct mjs* mjs) {
|
||||
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
|
||||
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||
|
||||
data->descriptor->reset_children(data->specific_view, data->custom_data);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief `View.setChildren`
|
||||
*/
|
||||
static void js_gui_view_set_children(struct mjs* mjs) {
|
||||
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
|
||||
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||
|
||||
mjs_val_t children;
|
||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&children));
|
||||
js_gui_view_internal_set_children(mjs, children, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief `View` destructor
|
||||
*/
|
||||
@@ -283,7 +339,12 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr
|
||||
|
||||
// generic view API
|
||||
mjs_val_t view_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set));
|
||||
JS_ASSIGN_MULTI(mjs, view_obj) {
|
||||
JS_FIELD("set", MJS_MK_FN(js_gui_view_set));
|
||||
JS_FIELD("addChild", MJS_MK_FN(js_gui_view_add_child));
|
||||
JS_FIELD("resetChildren", MJS_MK_FN(js_gui_view_reset_children));
|
||||
JS_FIELD("setChildren", MJS_MK_FN(js_gui_view_set_children));
|
||||
}
|
||||
|
||||
// object data
|
||||
JsGuiViewData* data = malloc(sizeof(JsGuiViewData));
|
||||
@@ -314,7 +375,7 @@ static void js_gui_vf_make(struct mjs* mjs) {
|
||||
*/
|
||||
static void js_gui_vf_make_with(struct mjs* mjs) {
|
||||
mjs_val_t props;
|
||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&props));
|
||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_OBJ(&props));
|
||||
const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs);
|
||||
|
||||
// make the object like normal
|
||||
@@ -334,6 +395,18 @@ static void js_gui_vf_make_with(struct mjs* mjs) {
|
||||
}
|
||||
}
|
||||
|
||||
// assign children
|
||||
if(mjs_nargs(mjs) >= 2) {
|
||||
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||
|
||||
mjs_val_t children = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_array(children))
|
||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 1: expected array");
|
||||
|
||||
if(!js_gui_view_internal_set_children(mjs, children, data)) return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, view_obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ typedef void (*JsViewFree)(void* specific_view);
|
||||
typedef void* (*JsViewCustomMake)(struct mjs* mjs, void* specific_view, mjs_val_t view_obj);
|
||||
/** @brief Context destruction for glue code */
|
||||
typedef void (*JsViewCustomDestroy)(void* specific_view, void* custom_state, FuriEventLoop* loop);
|
||||
/** @brief `addChild` callback for glue code */
|
||||
typedef bool (
|
||||
*JsViewAddChild)(struct mjs* mjs, void* specific_view, void* custom_state, mjs_val_t child_obj);
|
||||
/** @brief `resetChildren` callback for glue code */
|
||||
typedef void (*JsViewResetChildren)(void* specific_view, void* custom_state);
|
||||
|
||||
/**
|
||||
* @brief Descriptor for a JS view
|
||||
@@ -66,15 +71,22 @@ typedef struct {
|
||||
JsViewAlloc alloc;
|
||||
JsViewGetView get_view;
|
||||
JsViewFree free;
|
||||
|
||||
JsViewCustomMake custom_make; // <! May be NULL
|
||||
JsViewCustomDestroy custom_destroy; // <! May be NULL
|
||||
|
||||
JsViewAddChild add_child; // <! May be NULL
|
||||
JsViewResetChildren reset_children; // <! May be NULL
|
||||
|
||||
size_t prop_cnt; //<! Number of properties visible from JS
|
||||
JsViewPropDescriptor props[]; // <! Descriptors of properties visible from JS
|
||||
} JsViewDescriptor;
|
||||
|
||||
// Callback ordering:
|
||||
// alloc -> get_view -> [custom_make (if set)] -> props[i].assign -> [custom_destroy (if_set)] -> free
|
||||
// \_______________ creation ________________/ \___ usage ___/ \_________ destruction _________/
|
||||
// +-> add_child -+
|
||||
// +-> reset_children -+
|
||||
// alloc -> get_view -> custom_make -+-> props[i].assign -+> custom_destroy -> free
|
||||
// \__________ creation __________/ \____ use ____/ \___ destruction ____/
|
||||
|
||||
/**
|
||||
* @brief Creates a JS `ViewFactory` object
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
#include "../../js_modules.h" // IWYU pragma: keep
|
||||
#include "js_gui.h"
|
||||
#include "../js_event_loop/js_event_loop.h"
|
||||
#include <gui/modules/widget.h>
|
||||
|
||||
typedef struct {
|
||||
FuriMessageQueue* queue;
|
||||
JsEventLoopContract contract;
|
||||
} JsWidgetCtx;
|
||||
|
||||
#define QUEUE_LEN 2
|
||||
|
||||
/**
|
||||
* @brief Parses position (X and Y) from an element declaration object
|
||||
*/
|
||||
static bool element_get_position(struct mjs* mjs, mjs_val_t element, int32_t* x, int32_t* y) {
|
||||
mjs_val_t x_in = mjs_get(mjs, element, "x", ~0);
|
||||
mjs_val_t y_in = mjs_get(mjs, element, "y", ~0);
|
||||
if(!mjs_is_number(x_in) || !mjs_is_number(y_in)) return false;
|
||||
*x = mjs_get_int32(mjs, x_in);
|
||||
*y = mjs_get_int32(mjs, y_in);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses size (W and h) from an element declaration object
|
||||
*/
|
||||
static bool element_get_size(struct mjs* mjs, mjs_val_t element, int32_t* w, int32_t* h) {
|
||||
mjs_val_t w_in = mjs_get(mjs, element, "w", ~0);
|
||||
mjs_val_t h_in = mjs_get(mjs, element, "h", ~0);
|
||||
if(!mjs_is_number(w_in) || !mjs_is_number(h_in)) return false;
|
||||
*w = mjs_get_int32(mjs, w_in);
|
||||
*h = mjs_get_int32(mjs, h_in);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses alignment (V and H) from an element declaration object
|
||||
*/
|
||||
static bool
|
||||
element_get_alignment(struct mjs* mjs, mjs_val_t element, Align* align_v, Align* align_h) {
|
||||
mjs_val_t align_in = mjs_get(mjs, element, "align", ~0);
|
||||
const char* align = mjs_get_string(mjs, &align_in, NULL);
|
||||
if(!align) return false;
|
||||
if(strlen(align) != 2) return false;
|
||||
|
||||
if(align[0] == 't') {
|
||||
*align_v = AlignTop;
|
||||
} else if(align[0] == 'c') {
|
||||
*align_v = AlignCenter;
|
||||
} else if(align[0] == 'b') {
|
||||
*align_v = AlignBottom;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(align[1] == 'l') {
|
||||
*align_h = AlignLeft;
|
||||
} else if(align[1] == 'm') { // m = middle
|
||||
*align_h = AlignCenter;
|
||||
} else if(align[1] == 'r') {
|
||||
*align_h = AlignRight;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses font from an element declaration object
|
||||
*/
|
||||
static bool element_get_font(struct mjs* mjs, mjs_val_t element, Font* font) {
|
||||
mjs_val_t font_in = mjs_get(mjs, element, "font", ~0);
|
||||
const char* font_str = mjs_get_string(mjs, &font_in, NULL);
|
||||
if(!font_str) return false;
|
||||
|
||||
if(strcmp(font_str, "primary") == 0) {
|
||||
*font = FontPrimary;
|
||||
} else if(strcmp(font_str, "secondary") == 0) {
|
||||
*font = FontSecondary;
|
||||
} else if(strcmp(font_str, "keyboard") == 0) {
|
||||
*font = FontKeyboard;
|
||||
} else if(strcmp(font_str, "big_numbers") == 0) {
|
||||
*font = FontBigNumbers;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses text from an element declaration object
|
||||
*/
|
||||
static bool element_get_text(struct mjs* mjs, mjs_val_t element, mjs_val_t* text) {
|
||||
*text = mjs_get(mjs, element, "text", ~0);
|
||||
return mjs_is_string(*text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Widget button element callback
|
||||
*/
|
||||
static void js_widget_button_callback(GuiButtonType result, InputType type, JsWidgetCtx* context) {
|
||||
UNUSED(type);
|
||||
furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk);
|
||||
}
|
||||
|
||||
#define DESTRUCTURE_OR_RETURN(mjs, child_obj, part, ...) \
|
||||
if(!element_get_##part(mjs, child_obj, __VA_ARGS__)) \
|
||||
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element " #part);
|
||||
|
||||
static bool js_widget_add_child(
|
||||
struct mjs* mjs,
|
||||
Widget* widget,
|
||||
JsWidgetCtx* context,
|
||||
mjs_val_t child_obj) {
|
||||
UNUSED(context);
|
||||
if(!mjs_is_object(child_obj))
|
||||
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "child must be an object");
|
||||
|
||||
mjs_val_t element_type_term = mjs_get(mjs, child_obj, "element", ~0);
|
||||
const char* element_type = mjs_get_string(mjs, &element_type_term, NULL);
|
||||
if(!element_type)
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "child object must have `element` property");
|
||||
|
||||
if((strcmp(element_type, "string") == 0) || (strcmp(element_type, "string_multiline") == 0)) {
|
||||
int32_t x, y;
|
||||
Align align_v, align_h;
|
||||
Font font;
|
||||
mjs_val_t text;
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
|
||||
if(strcmp(element_type, "string") == 0) {
|
||||
widget_add_string_element(
|
||||
widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL));
|
||||
} else {
|
||||
widget_add_string_multiline_element(
|
||||
widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL));
|
||||
}
|
||||
|
||||
} else if(strcmp(element_type, "text_box") == 0) {
|
||||
int32_t x, y, w, h;
|
||||
Align align_v, align_h;
|
||||
Font font;
|
||||
mjs_val_t text;
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
|
||||
mjs_val_t strip_to_dots_in = mjs_get(mjs, child_obj, "stripToDots", ~0);
|
||||
if(!mjs_is_boolean(strip_to_dots_in))
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element stripToDots");
|
||||
bool strip_to_dots = mjs_get_bool(mjs, strip_to_dots_in);
|
||||
widget_add_text_box_element(
|
||||
widget, x, y, w, h, align_h, align_v, mjs_get_string(mjs, &text, NULL), strip_to_dots);
|
||||
|
||||
} else if(strcmp(element_type, "text_scroll") == 0) {
|
||||
int32_t x, y, w, h;
|
||||
mjs_val_t text;
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
|
||||
widget_add_text_scroll_element(widget, x, y, w, h, mjs_get_string(mjs, &text, NULL));
|
||||
|
||||
} else if(strcmp(element_type, "button") == 0) {
|
||||
mjs_val_t btn_in = mjs_get(mjs, child_obj, "button", ~0);
|
||||
const char* btn_name = mjs_get_string(mjs, &btn_in, NULL);
|
||||
if(!btn_name)
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element button");
|
||||
GuiButtonType btn_type;
|
||||
if(strcmp(btn_name, "left") == 0) {
|
||||
btn_type = GuiButtonTypeLeft;
|
||||
} else if(strcmp(btn_name, "center") == 0) {
|
||||
btn_type = GuiButtonTypeCenter;
|
||||
} else if(strcmp(btn_name, "right") == 0) {
|
||||
btn_type = GuiButtonTypeRight;
|
||||
} else {
|
||||
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "incorrect button type");
|
||||
}
|
||||
mjs_val_t text;
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
btn_type,
|
||||
mjs_get_string(mjs, &text, NULL),
|
||||
(ButtonCallback)js_widget_button_callback,
|
||||
context);
|
||||
|
||||
} else if(strcmp(element_type, "icon") == 0) {
|
||||
int32_t x, y;
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||
mjs_val_t icon_data_in = mjs_get(mjs, child_obj, "iconData", ~0);
|
||||
if(!mjs_is_foreign(icon_data_in))
|
||||
JS_ERROR_AND_RETURN_VAL(
|
||||
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element iconData");
|
||||
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) {
|
||||
int32_t x, y, w, h;
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
|
||||
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);
|
||||
widget_add_frame_element(widget, x, y, w, h, radius);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_widget_reset_children(Widget* widget, void* state) {
|
||||
UNUSED(state);
|
||||
widget_reset(widget);
|
||||
}
|
||||
|
||||
static mjs_val_t js_widget_button_event_transformer(
|
||||
struct mjs* mjs,
|
||||
FuriMessageQueue* queue,
|
||||
JsWidgetCtx* context) {
|
||||
UNUSED(context);
|
||||
GuiButtonType btn_type;
|
||||
furi_check(furi_message_queue_get(queue, &btn_type, 0) == FuriStatusOk);
|
||||
const char* btn_name;
|
||||
if(btn_type == GuiButtonTypeLeft) {
|
||||
btn_name = "left";
|
||||
} else if(btn_type == GuiButtonTypeCenter) {
|
||||
btn_name = "center";
|
||||
} else if(btn_type == GuiButtonTypeRight) {
|
||||
btn_name = "right";
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
return mjs_mk_string(mjs, btn_name, ~0, false);
|
||||
}
|
||||
|
||||
static void* js_widget_custom_make(struct mjs* mjs, Widget* widget, mjs_val_t view_obj) {
|
||||
UNUSED(widget);
|
||||
JsWidgetCtx* context = malloc(sizeof(JsWidgetCtx));
|
||||
context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(GuiButtonType));
|
||||
context->contract = (JsEventLoopContract){
|
||||
.magic = JsForeignMagic_JsEventLoopContract,
|
||||
.object_type = JsEventLoopObjectTypeQueue,
|
||||
.object = context->queue,
|
||||
.non_timer =
|
||||
{
|
||||
.event = FuriEventLoopEventIn,
|
||||
.transformer = (JsEventLoopTransformer)js_widget_button_event_transformer,
|
||||
},
|
||||
};
|
||||
mjs_set(mjs, view_obj, "button", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||
return context;
|
||||
}
|
||||
|
||||
static void js_widget_custom_destroy(Widget* widget, JsWidgetCtx* context, FuriEventLoop* loop) {
|
||||
UNUSED(widget);
|
||||
furi_event_loop_maybe_unsubscribe(loop, context->queue);
|
||||
furi_message_queue_free(context->queue);
|
||||
free(context);
|
||||
}
|
||||
|
||||
static const JsViewDescriptor view_descriptor = {
|
||||
.alloc = (JsViewAlloc)widget_alloc,
|
||||
.free = (JsViewFree)widget_free,
|
||||
.get_view = (JsViewGetView)widget_get_view,
|
||||
.custom_make = (JsViewCustomMake)js_widget_custom_make,
|
||||
.custom_destroy = (JsViewCustomDestroy)js_widget_custom_destroy,
|
||||
.add_child = (JsViewAddChild)js_widget_add_child,
|
||||
.reset_children = (JsViewResetChildren)js_widget_reset_children,
|
||||
.prop_cnt = 0,
|
||||
.props = {},
|
||||
};
|
||||
JS_GUI_VIEW_DEF(widget, &view_descriptor);
|
||||
@@ -1,922 +0,0 @@
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include <m-array.h>
|
||||
#include <m-list.h>
|
||||
#include <string.h>
|
||||
#include "../js_modules.h"
|
||||
|
||||
typedef struct WidgetComponent WidgetComponent;
|
||||
ARRAY_DEF(ComponentArray, WidgetComponent*, M_PTR_OPLIST);
|
||||
|
||||
typedef struct XbmImage XbmImage;
|
||||
LIST_DEF(XbmImageList, XbmImage*, M_POD_OPLIST);
|
||||
|
||||
struct WidgetComponent {
|
||||
void (*draw)(Canvas* canvas, void* model);
|
||||
void (*free)(WidgetComponent* component);
|
||||
void* model;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
struct XbmImage {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t w;
|
||||
uint8_t h;
|
||||
} BoxElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t r;
|
||||
} CircleElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t r;
|
||||
} DiscElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
} DotElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
const Icon* icon;
|
||||
} IconElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t w;
|
||||
uint8_t h;
|
||||
} FrameElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint16_t ch;
|
||||
} GlyphElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x1;
|
||||
uint8_t y1;
|
||||
uint8_t x2;
|
||||
uint8_t y2;
|
||||
} LineElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t w;
|
||||
uint8_t h;
|
||||
uint8_t r;
|
||||
} RboxElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t w;
|
||||
uint8_t h;
|
||||
uint8_t r;
|
||||
} RframeElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
Font font;
|
||||
FuriString* text;
|
||||
} TextElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint32_t index;
|
||||
View* view;
|
||||
} XbmElement;
|
||||
|
||||
typedef struct {
|
||||
ComponentArray_t component;
|
||||
XbmImageList_t image;
|
||||
uint32_t max_assigned_id;
|
||||
} WidgetModel;
|
||||
|
||||
typedef struct {
|
||||
View* view;
|
||||
ViewHolder* view_holder;
|
||||
bool is_shown;
|
||||
} JsWidgetInst;
|
||||
|
||||
static JsWidgetInst* get_this_ctx(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsWidgetInst* widget = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
static void ret_bad_args(struct mjs* mjs, const char* error) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static bool check_arg_count(struct mjs* mjs, size_t count) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args != count) {
|
||||
ret_bad_args(mjs, "Wrong argument count");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_widget_load_image_xbm(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_val_t path_arg = mjs_arg(mjs, 0);
|
||||
size_t path_len = 0;
|
||||
const char* path = mjs_get_string(mjs, &path_arg, &path_len);
|
||||
if(!path) {
|
||||
ret_bad_args(mjs, "Path must be a string");
|
||||
return;
|
||||
}
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
XbmImage* xbm = NULL;
|
||||
|
||||
do {
|
||||
if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
ret_bad_args(mjs, "Failed to open file");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t size = 0;
|
||||
if(storage_file_read(file, &size, sizeof(size)) != sizeof(size)) {
|
||||
ret_bad_args(mjs, "Failed to get file size");
|
||||
break;
|
||||
}
|
||||
|
||||
xbm = malloc(size);
|
||||
if(storage_file_read(file, xbm, size) != size) {
|
||||
ret_bad_args(mjs, "Failed to load entire file");
|
||||
free(xbm);
|
||||
xbm = NULL;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(xbm == NULL) {
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t count = 0;
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
count = XbmImageList_size(model->image);
|
||||
XbmImageList_push_back(model->image, xbm);
|
||||
},
|
||||
false);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, count));
|
||||
}
|
||||
|
||||
static void js_widget_remove(struct mjs* mjs) {
|
||||
bool removed = false;
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
uint32_t id = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
ComponentArray_it_t it;
|
||||
ComponentArray_it(it, model->component);
|
||||
while(!ComponentArray_end_p(it)) {
|
||||
WidgetComponent* component = *ComponentArray_ref(it);
|
||||
if(component->id == id) {
|
||||
if(component->free) {
|
||||
component->free(component);
|
||||
}
|
||||
ComponentArray_remove(model->component, it);
|
||||
removed = true;
|
||||
break;
|
||||
}
|
||||
ComponentArray_next(it);
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, removed));
|
||||
}
|
||||
|
||||
static void widget_box_draw(Canvas* canvas, void* model) {
|
||||
BoxElement* element = model;
|
||||
canvas_draw_box(canvas, element->x, element->y, element->w, element->h);
|
||||
}
|
||||
|
||||
static void widget_box_free(WidgetComponent* component) {
|
||||
BoxElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_box(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 4)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_box_draw;
|
||||
component->free = widget_box_free;
|
||||
component->model = malloc(sizeof(BoxElement));
|
||||
BoxElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->w = w;
|
||||
element->h = h;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_circle_draw(Canvas* canvas, void* model) {
|
||||
CircleElement* element = model;
|
||||
canvas_draw_circle(canvas, element->x, element->y, element->r);
|
||||
}
|
||||
|
||||
static void widget_circle_free(WidgetComponent* component) {
|
||||
CircleElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_circle(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 3)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_circle_draw;
|
||||
component->free = widget_circle_free;
|
||||
component->model = malloc(sizeof(CircleElement));
|
||||
CircleElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->r = r;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_disc_draw(Canvas* canvas, void* model) {
|
||||
DiscElement* element = model;
|
||||
canvas_draw_disc(canvas, element->x, element->y, element->r);
|
||||
}
|
||||
|
||||
static void widget_disc_free(WidgetComponent* component) {
|
||||
DiscElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_disc(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 3)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_disc_draw;
|
||||
component->free = widget_disc_free;
|
||||
component->model = malloc(sizeof(DiscElement));
|
||||
DiscElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->r = r;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_dot_draw(Canvas* canvas, void* model) {
|
||||
DotElement* element = model;
|
||||
canvas_draw_dot(canvas, element->x, element->y);
|
||||
}
|
||||
|
||||
static void widget_dot_free(WidgetComponent* component) {
|
||||
DotElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_dot(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 2)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_dot_draw;
|
||||
component->free = widget_dot_free;
|
||||
component->model = malloc(sizeof(DotElement));
|
||||
DotElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_frame_draw(Canvas* canvas, void* model) {
|
||||
FrameElement* element = model;
|
||||
canvas_draw_frame(canvas, element->x, element->y, element->w, element->h);
|
||||
}
|
||||
|
||||
static void widget_frame_free(WidgetComponent* component) {
|
||||
FrameElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_frame(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 4)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_frame_draw;
|
||||
component->free = widget_frame_free;
|
||||
component->model = malloc(sizeof(FrameElement));
|
||||
FrameElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->w = w;
|
||||
element->h = h;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_glyph_draw(Canvas* canvas, void* model) {
|
||||
GlyphElement* element = model;
|
||||
canvas_draw_glyph(canvas, element->x, element->y, element->ch);
|
||||
}
|
||||
|
||||
static void widget_glyph_free(WidgetComponent* component) {
|
||||
GlyphElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_glyph(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 3)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t ch = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_glyph_draw;
|
||||
component->free = widget_glyph_free;
|
||||
component->model = malloc(sizeof(GlyphElement));
|
||||
GlyphElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->ch = ch;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_line_draw(Canvas* canvas, void* model) {
|
||||
LineElement* element = model;
|
||||
canvas_draw_line(canvas, element->x1, element->y1, element->x2, element->y2);
|
||||
}
|
||||
|
||||
static void widget_line_free(WidgetComponent* component) {
|
||||
LineElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_line(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 4)) return;
|
||||
|
||||
int32_t x1 = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y1 = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t x2 = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
int32_t y2 = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_line_draw;
|
||||
component->free = widget_line_free;
|
||||
component->model = malloc(sizeof(LineElement));
|
||||
LineElement* element = component->model;
|
||||
element->x1 = x1;
|
||||
element->y1 = y1;
|
||||
element->x2 = x2;
|
||||
element->y2 = y2;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_rbox_draw(Canvas* canvas, void* model) {
|
||||
RboxElement* element = model;
|
||||
canvas_draw_rbox(canvas, element->x, element->y, element->w, element->h, element->r);
|
||||
}
|
||||
|
||||
static void widget_rbox_free(WidgetComponent* component) {
|
||||
BoxElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_rbox(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 5)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
||||
int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 4));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_rbox_draw;
|
||||
component->free = widget_rbox_free;
|
||||
component->model = malloc(sizeof(RboxElement));
|
||||
RboxElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->w = w;
|
||||
element->h = h;
|
||||
element->r = r;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_rframe_draw(Canvas* canvas, void* model) {
|
||||
RframeElement* element = model;
|
||||
canvas_draw_rframe(canvas, element->x, element->y, element->w, element->h, element->r);
|
||||
}
|
||||
|
||||
static void widget_rframe_free(WidgetComponent* component) {
|
||||
RframeElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_rframe(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 5)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
||||
int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 4));
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_rframe_draw;
|
||||
component->free = widget_rframe_free;
|
||||
component->model = malloc(sizeof(RframeElement));
|
||||
RframeElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->w = w;
|
||||
element->h = h;
|
||||
element->r = r;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_text_draw(Canvas* canvas, void* model) {
|
||||
TextElement* element = model;
|
||||
canvas_set_font(canvas, element->font);
|
||||
canvas_draw_str(canvas, element->x, element->y, furi_string_get_cstr(element->text));
|
||||
}
|
||||
|
||||
static void widget_text_free(WidgetComponent* component) {
|
||||
TextElement* element = component->model;
|
||||
furi_string_free(element->text);
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_text(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 4)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
|
||||
mjs_val_t font_arg = mjs_arg(mjs, 2);
|
||||
size_t font_name_len = 0;
|
||||
const char* font_name_text = mjs_get_string(mjs, &font_arg, &font_name_len);
|
||||
if(!font_name_text) {
|
||||
ret_bad_args(mjs, "Font name must be a string");
|
||||
return;
|
||||
}
|
||||
|
||||
Font font = FontTotalNumber;
|
||||
size_t cmp_str_len = strlen("Primary");
|
||||
if(font_name_len == cmp_str_len && strncmp(font_name_text, "Primary", cmp_str_len) == 0) {
|
||||
font = FontPrimary;
|
||||
} else {
|
||||
cmp_str_len = strlen("Secondary");
|
||||
if(font_name_len == cmp_str_len &&
|
||||
strncmp(font_name_text, "Secondary", cmp_str_len) == 0) {
|
||||
font = FontSecondary;
|
||||
}
|
||||
}
|
||||
if(font == FontTotalNumber) {
|
||||
ret_bad_args(mjs, "Unknown font name");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_val_t text_arg = mjs_arg(mjs, 3);
|
||||
size_t text_len = 0;
|
||||
const char* text = mjs_get_string(mjs, &text_arg, &text_len);
|
||||
if(!text) {
|
||||
ret_bad_args(mjs, "Text must be a string");
|
||||
return;
|
||||
}
|
||||
FuriString* text_str = furi_string_alloc_set(text);
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_text_draw;
|
||||
component->free = widget_text_free;
|
||||
component->model = malloc(sizeof(TextElement));
|
||||
TextElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->font = font;
|
||||
element->text = text_str;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void widget_xbm_draw(Canvas* canvas, void* model) {
|
||||
XbmElement* element = model;
|
||||
XbmImage* image = NULL;
|
||||
|
||||
with_view_model(
|
||||
element->view,
|
||||
WidgetModel * widget_model,
|
||||
{ image = *XbmImageList_get(widget_model->image, element->index); },
|
||||
false);
|
||||
|
||||
if(image) {
|
||||
canvas_draw_xbm(canvas, element->x, element->y, image->width, image->height, image->data);
|
||||
}
|
||||
}
|
||||
|
||||
static void widget_xbm_free(WidgetComponent* component) {
|
||||
XbmElement* element = component->model;
|
||||
free(element);
|
||||
free(component);
|
||||
}
|
||||
|
||||
static void js_widget_add_xbm(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 3)) return;
|
||||
|
||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
||||
int32_t index = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * widget_model,
|
||||
{
|
||||
size_t count = XbmImageList_size(widget_model->image);
|
||||
if(index < 0 || index >= (int32_t)count) {
|
||||
ret_bad_args(mjs, "Invalid image index");
|
||||
return;
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
||||
component->draw = widget_xbm_draw;
|
||||
component->free = widget_xbm_free;
|
||||
component->model = malloc(sizeof(XbmElement));
|
||||
XbmElement* element = component->model;
|
||||
element->x = x;
|
||||
element->y = y;
|
||||
element->index = index;
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
++model->max_assigned_id;
|
||||
component->id = model->max_assigned_id;
|
||||
element->view = widget->view;
|
||||
ComponentArray_push_back(model->component, component);
|
||||
},
|
||||
true);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
||||
}
|
||||
|
||||
static void js_widget_is_open(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, widget->is_shown));
|
||||
}
|
||||
|
||||
static void widget_callback(void* context, uint32_t arg) {
|
||||
UNUSED(arg);
|
||||
JsWidgetInst* widget = context;
|
||||
view_holder_set_view(widget->view_holder, NULL);
|
||||
widget->is_shown = false;
|
||||
}
|
||||
|
||||
static void widget_exit(void* context) {
|
||||
JsWidgetInst* widget = context;
|
||||
// Using timer to schedule view_holder stop, will not work under high CPU load
|
||||
furi_timer_pending_callback(widget_callback, widget, 0);
|
||||
}
|
||||
|
||||
static void js_widget_show(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
if(widget->is_shown) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Widget is already shown");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
view_holder_set_view(widget->view_holder, widget->view);
|
||||
widget->is_shown = true;
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_widget_close(struct mjs* mjs) {
|
||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
view_holder_set_view(widget->view_holder, NULL);
|
||||
widget->is_shown = false;
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void widget_draw_callback(Canvas* canvas, void* model) {
|
||||
WidgetModel* widget_model = model;
|
||||
canvas_clear(canvas);
|
||||
|
||||
ComponentArray_it_t it;
|
||||
ComponentArray_it(it, widget_model->component);
|
||||
while(!ComponentArray_end_p(it)) {
|
||||
WidgetComponent* component = *ComponentArray_ref(it);
|
||||
if(component->draw != NULL) {
|
||||
component->draw(canvas, component->model);
|
||||
}
|
||||
ComponentArray_next(it);
|
||||
}
|
||||
}
|
||||
|
||||
static void* js_widget_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||
UNUSED(modules);
|
||||
JsWidgetInst* widget = malloc(sizeof(JsWidgetInst));
|
||||
|
||||
mjs_val_t widget_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, widget_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, widget));
|
||||
// addBox(x: number, y: number, w: number, h: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addBox", ~0, MJS_MK_FN(js_widget_add_box));
|
||||
// addCircle(x: number, y: number, r: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addCircle", ~0, MJS_MK_FN(js_widget_add_circle));
|
||||
// addDisc(x: number, y: number, r: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addDisc", ~0, MJS_MK_FN(js_widget_add_disc));
|
||||
// addDot(x: number, y: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addDot", ~0, MJS_MK_FN(js_widget_add_dot));
|
||||
// addFrame(x: number, y: number, w: number, h: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addFrame", ~0, MJS_MK_FN(js_widget_add_frame));
|
||||
// addGlyph(x: number, y: number, ch: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addGlyph", ~0, MJS_MK_FN(js_widget_add_glyph));
|
||||
// addLine(x1: number, y1: number, x2: number, y2: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addLine", ~0, MJS_MK_FN(js_widget_add_line));
|
||||
// addRbox(x: number, y: number, w: number, h: number, r: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addRbox", ~0, MJS_MK_FN(js_widget_add_rbox));
|
||||
// addRframe(x: number, y: number, w: number, h: number, r: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addRframe", ~0, MJS_MK_FN(js_widget_add_rframe));
|
||||
// addText(x: number, y: number, font: string, text: string): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addText", ~0, MJS_MK_FN(js_widget_add_text));
|
||||
// addXbm(x: number, y: number, index: number): number (returns id of the added component)
|
||||
mjs_set(mjs, widget_obj, "addXbm", ~0, MJS_MK_FN(js_widget_add_xbm));
|
||||
// loadImageXbm(path: string): number (returns index of the loaded image)
|
||||
mjs_set(mjs, widget_obj, "loadImageXbm", ~0, MJS_MK_FN(js_widget_load_image_xbm));
|
||||
// remove(id: number): boolean (returns true if the component was removed)
|
||||
mjs_set(mjs, widget_obj, "remove", ~0, MJS_MK_FN(js_widget_remove));
|
||||
// isOpen(): boolean (returns true if the widget is open)
|
||||
mjs_set(mjs, widget_obj, "isOpen", ~0, MJS_MK_FN(js_widget_is_open));
|
||||
// show(): void (shows the widget)
|
||||
mjs_set(mjs, widget_obj, "show", ~0, MJS_MK_FN(js_widget_show));
|
||||
// close(): void (closes the widget)
|
||||
mjs_set(mjs, widget_obj, "close", ~0, MJS_MK_FN(js_widget_close));
|
||||
|
||||
widget->view = view_alloc();
|
||||
view_allocate_model(widget->view, ViewModelTypeLockFree, sizeof(WidgetModel));
|
||||
view_set_draw_callback(widget->view, widget_draw_callback);
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
ComponentArray_init(model->component);
|
||||
XbmImageList_init(model->image);
|
||||
model->max_assigned_id = 0;
|
||||
},
|
||||
true);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
widget->view_holder = view_holder_alloc();
|
||||
view_holder_attach_to_gui(widget->view_holder, gui);
|
||||
view_holder_set_back_callback(widget->view_holder, widget_exit, widget);
|
||||
|
||||
*object = widget_obj;
|
||||
return widget;
|
||||
}
|
||||
|
||||
static void js_widget_destroy(void* inst) {
|
||||
JsWidgetInst* widget = inst;
|
||||
|
||||
view_holder_set_view(widget->view_holder, NULL);
|
||||
view_holder_free(widget->view_holder);
|
||||
widget->view_holder = NULL;
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
with_view_model(
|
||||
widget->view,
|
||||
WidgetModel * model,
|
||||
{
|
||||
ComponentArray_it_t it;
|
||||
ComponentArray_it(it, model->component);
|
||||
while(!ComponentArray_end_p(it)) {
|
||||
WidgetComponent* component = *ComponentArray_ref(it);
|
||||
if(component && component->free) {
|
||||
component->free(component);
|
||||
}
|
||||
ComponentArray_next(it);
|
||||
}
|
||||
ComponentArray_reset(model->component);
|
||||
ComponentArray_clear(model->component);
|
||||
XbmImageList_clear(model->image);
|
||||
},
|
||||
false);
|
||||
view_free(widget->view);
|
||||
widget->view = NULL;
|
||||
|
||||
free(widget);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_widget_desc = {
|
||||
"widget",
|
||||
js_widget_create,
|
||||
js_widget_destroy,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor widget_plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_widget_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_widget_ep(void) {
|
||||
return &widget_plugin_descriptor;
|
||||
}
|
||||
Reference in New Issue
Block a user