mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-29 04:09:58 -07:00
Merge branch 'feat/nfc-type-4-final' into mntm-dev
This commit is contained in:
169
applications/system/js_app/modules/js_gui/button_menu.c
Normal file
169
applications/system/js_app/modules/js_gui/button_menu.c
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "../../js_modules.h" // IWYU pragma: keep
|
||||
#include "js_gui.h"
|
||||
#include "../js_event_loop/js_event_loop.h"
|
||||
#include <gui/modules/button_menu.h>
|
||||
#include <toolbox/str_buffer.h>
|
||||
|
||||
typedef struct {
|
||||
int32_t next_index;
|
||||
StrBuffer str_buffer;
|
||||
|
||||
FuriMessageQueue* input_queue;
|
||||
JsEventLoopContract contract;
|
||||
} JsBtnMenuContext;
|
||||
|
||||
typedef struct {
|
||||
int32_t index;
|
||||
InputType input_type;
|
||||
} JsBtnMenuEvent;
|
||||
|
||||
static const char* js_input_type_to_str(InputType type) {
|
||||
switch(type) {
|
||||
case InputTypePress:
|
||||
return "press";
|
||||
case InputTypeRelease:
|
||||
return "release";
|
||||
case InputTypeShort:
|
||||
return "short";
|
||||
case InputTypeLong:
|
||||
return "long";
|
||||
case InputTypeRepeat:
|
||||
return "repeat";
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
static mjs_val_t
|
||||
input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsBtnMenuContext* context) {
|
||||
UNUSED(context);
|
||||
JsBtnMenuEvent event;
|
||||
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
|
||||
|
||||
mjs_val_t event_obj = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, event_obj) {
|
||||
JS_FIELD("index", mjs_mk_number(mjs, event.index));
|
||||
JS_FIELD("type", mjs_mk_string(mjs, js_input_type_to_str(event.input_type), ~0, false));
|
||||
}
|
||||
|
||||
return event_obj;
|
||||
}
|
||||
|
||||
static void input_callback(void* ctx, int32_t index, InputType type) {
|
||||
JsBtnMenuContext* context = ctx;
|
||||
JsBtnMenuEvent event = {
|
||||
.index = index,
|
||||
.input_type = type,
|
||||
};
|
||||
furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static bool matrix_header_assign(
|
||||
struct mjs* mjs,
|
||||
ButtonMenu* menu,
|
||||
JsViewPropValue value,
|
||||
JsBtnMenuContext* context) {
|
||||
UNUSED(mjs);
|
||||
button_menu_set_header(menu, str_buffer_make_owned_clone(&context->str_buffer, value.string));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool js_button_menu_add_child(
|
||||
struct mjs* mjs,
|
||||
ButtonMenu* menu,
|
||||
JsBtnMenuContext* context,
|
||||
mjs_val_t child_obj) {
|
||||
static const JsValueEnumVariant js_button_menu_item_type_variants[] = {
|
||||
{"common", ButtonMenuItemTypeCommon},
|
||||
{"control", ButtonMenuItemTypeControl},
|
||||
};
|
||||
static const JsValueDeclaration js_button_menu_item_type =
|
||||
JS_VALUE_ENUM(ButtonMenuItemType, js_button_menu_item_type_variants);
|
||||
|
||||
static const JsValueDeclaration js_button_menu_string = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueObjectField js_button_menu_child_fields[] = {
|
||||
{"type", &js_button_menu_item_type},
|
||||
{"label", &js_button_menu_string},
|
||||
};
|
||||
static const JsValueDeclaration js_button_menu_child =
|
||||
JS_VALUE_OBJECT(js_button_menu_child_fields);
|
||||
|
||||
ButtonMenuItemType item_type;
|
||||
const char* label;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_menu_child),
|
||||
JsValueParseFlagReturnOnError,
|
||||
&status,
|
||||
&child_obj,
|
||||
&item_type,
|
||||
&label);
|
||||
if(status != JsValueParseStatusOk) return false;
|
||||
|
||||
button_menu_add_item(
|
||||
menu,
|
||||
str_buffer_make_owned_clone(&context->str_buffer, label),
|
||||
context->next_index++,
|
||||
input_callback,
|
||||
item_type,
|
||||
context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_button_menu_reset_children(ButtonMenu* menu, JsBtnMenuContext* context) {
|
||||
context->next_index = 0;
|
||||
button_menu_reset(menu);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
}
|
||||
|
||||
static JsBtnMenuContext* ctx_make(struct mjs* mjs, ButtonMenu* menu, mjs_val_t view_obj) {
|
||||
UNUSED(menu);
|
||||
JsBtnMenuContext* context = malloc(sizeof(JsBtnMenuContext));
|
||||
*context = (JsBtnMenuContext){
|
||||
.next_index = 0,
|
||||
.str_buffer = {0},
|
||||
.input_queue = furi_message_queue_alloc(1, sizeof(JsBtnMenuEvent)),
|
||||
};
|
||||
context->contract = (JsEventLoopContract){
|
||||
.magic = JsForeignMagic_JsEventLoopContract,
|
||||
.object_type = JsEventLoopObjectTypeQueue,
|
||||
.object = context->input_queue,
|
||||
.non_timer =
|
||||
{
|
||||
.event = FuriEventLoopEventIn,
|
||||
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||
.transformer_context = context,
|
||||
},
|
||||
};
|
||||
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||
return context;
|
||||
}
|
||||
|
||||
static void ctx_destroy(ButtonMenu* input, JsBtnMenuContext* context, FuriEventLoop* loop) {
|
||||
UNUSED(input);
|
||||
furi_event_loop_maybe_unsubscribe(loop, context->input_queue);
|
||||
furi_message_queue_free(context->input_queue);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
free(context);
|
||||
}
|
||||
|
||||
static const JsViewDescriptor view_descriptor = {
|
||||
.alloc = (JsViewAlloc)button_menu_alloc,
|
||||
.free = (JsViewFree)button_menu_free,
|
||||
.get_view = (JsViewGetView)button_menu_get_view,
|
||||
.custom_make = (JsViewCustomMake)ctx_make,
|
||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||
.add_child = (JsViewAddChild)js_button_menu_add_child,
|
||||
.reset_children = (JsViewResetChildren)js_button_menu_reset_children,
|
||||
.prop_cnt = 1,
|
||||
.props = {
|
||||
(JsViewPropDescriptor){
|
||||
.name = "header",
|
||||
.type = JsViewPropTypeString,
|
||||
.assign = (JsViewPropAssign)matrix_header_assign},
|
||||
}};
|
||||
|
||||
JS_GUI_VIEW_DEF(button_menu, &view_descriptor);
|
||||
274
applications/system/js_app/modules/js_gui/button_panel.c
Normal file
274
applications/system/js_app/modules/js_gui/button_panel.c
Normal file
@@ -0,0 +1,274 @@
|
||||
#include "../../js_modules.h" // IWYU pragma: keep
|
||||
#include "js_gui.h"
|
||||
#include "../js_event_loop/js_event_loop.h"
|
||||
#include <gui/modules/button_panel.h>
|
||||
#include <toolbox/str_buffer.h>
|
||||
|
||||
typedef struct {
|
||||
size_t matrix_x, matrix_y;
|
||||
int32_t next_index;
|
||||
StrBuffer str_buffer;
|
||||
|
||||
FuriMessageQueue* input_queue;
|
||||
JsEventLoopContract contract;
|
||||
} JsBtnPanelContext;
|
||||
|
||||
typedef struct {
|
||||
int32_t index;
|
||||
InputType input_type;
|
||||
} JsBtnPanelEvent;
|
||||
|
||||
static const char* js_input_type_to_str(InputType type) {
|
||||
switch(type) {
|
||||
case InputTypePress:
|
||||
return "press";
|
||||
case InputTypeRelease:
|
||||
return "release";
|
||||
case InputTypeShort:
|
||||
return "short";
|
||||
case InputTypeLong:
|
||||
return "long";
|
||||
case InputTypeRepeat:
|
||||
return "repeat";
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
static mjs_val_t
|
||||
input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsBtnPanelContext* context) {
|
||||
UNUSED(context);
|
||||
JsBtnPanelEvent event;
|
||||
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
|
||||
|
||||
mjs_val_t event_obj = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, event_obj) {
|
||||
JS_FIELD("index", mjs_mk_number(mjs, event.index));
|
||||
JS_FIELD("type", mjs_mk_string(mjs, js_input_type_to_str(event.input_type), ~0, false));
|
||||
}
|
||||
|
||||
return event_obj;
|
||||
}
|
||||
|
||||
static void input_callback(void* ctx, int32_t index, InputType type) {
|
||||
JsBtnPanelContext* context = ctx;
|
||||
JsBtnPanelEvent event = {
|
||||
.index = index,
|
||||
.input_type = type,
|
||||
};
|
||||
furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static bool matrix_size_x_assign(
|
||||
struct mjs* mjs,
|
||||
ButtonPanel* panel,
|
||||
JsViewPropValue value,
|
||||
JsBtnPanelContext* context) {
|
||||
UNUSED(mjs);
|
||||
context->matrix_x = value.number;
|
||||
button_panel_reserve(panel, context->matrix_x, context->matrix_y);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool matrix_size_y_assign(
|
||||
struct mjs* mjs,
|
||||
ButtonPanel* panel,
|
||||
JsViewPropValue value,
|
||||
JsBtnPanelContext* context) {
|
||||
UNUSED(mjs);
|
||||
context->matrix_y = value.number;
|
||||
button_panel_reserve(panel, context->matrix_x, context->matrix_y);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool js_button_panel_add_child(
|
||||
struct mjs* mjs,
|
||||
ButtonPanel* panel,
|
||||
JsBtnPanelContext* context,
|
||||
mjs_val_t child_obj) {
|
||||
typedef enum {
|
||||
JsButtonPanelChildTypeButton,
|
||||
JsButtonPanelChildTypeLabel,
|
||||
JsButtonPanelChildTypeIcon,
|
||||
} JsButtonPanelChildType;
|
||||
static const JsValueEnumVariant js_button_panel_child_type_variants[] = {
|
||||
{"button", JsButtonPanelChildTypeButton},
|
||||
{"label", JsButtonPanelChildTypeLabel},
|
||||
{"icon", JsButtonPanelChildTypeIcon},
|
||||
};
|
||||
static const JsValueDeclaration js_button_panel_child_type =
|
||||
JS_VALUE_ENUM(JsButtonPanelChildType, js_button_panel_child_type_variants);
|
||||
|
||||
static const JsValueDeclaration js_button_panel_number = JS_VALUE_SIMPLE(JsValueTypeInt32);
|
||||
static const JsValueObjectField js_button_panel_common_fields[] = {
|
||||
{"type", &js_button_panel_child_type},
|
||||
{"x", &js_button_panel_number},
|
||||
{"y", &js_button_panel_number},
|
||||
};
|
||||
static const JsValueDeclaration js_button_panel_common =
|
||||
JS_VALUE_OBJECT(js_button_panel_common_fields);
|
||||
|
||||
static const JsValueDeclaration js_button_panel_pointer =
|
||||
JS_VALUE_SIMPLE(JsValueTypeRawPointer);
|
||||
static const JsValueObjectField js_button_panel_button_fields[] = {
|
||||
{"matrixX", &js_button_panel_number},
|
||||
{"matrixY", &js_button_panel_number},
|
||||
{"icon", &js_button_panel_pointer},
|
||||
{"iconSelected", &js_button_panel_pointer},
|
||||
};
|
||||
static const JsValueDeclaration js_button_panel_button =
|
||||
JS_VALUE_OBJECT(js_button_panel_button_fields);
|
||||
|
||||
static const JsValueDeclaration js_button_panel_string = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
static const JsValueObjectField js_button_panel_label_fields[] = {
|
||||
{"text", &js_button_panel_string},
|
||||
{"font", &js_gui_font_declaration},
|
||||
};
|
||||
static const JsValueDeclaration js_button_panel_label =
|
||||
JS_VALUE_OBJECT(js_button_panel_label_fields);
|
||||
|
||||
static const JsValueObjectField js_button_panel_icon_fields[] = {
|
||||
{"icon", &js_button_panel_pointer},
|
||||
};
|
||||
static const JsValueDeclaration js_button_panel_icon =
|
||||
JS_VALUE_OBJECT(js_button_panel_icon_fields);
|
||||
|
||||
JsButtonPanelChildType child_type;
|
||||
int32_t x, y;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_common),
|
||||
JsValueParseFlagReturnOnError,
|
||||
&status,
|
||||
&child_obj,
|
||||
&child_type,
|
||||
&x,
|
||||
&y);
|
||||
if(status != JsValueParseStatusOk) return false;
|
||||
|
||||
switch(child_type) {
|
||||
case JsButtonPanelChildTypeButton: {
|
||||
int32_t matrix_x, matrix_y;
|
||||
const Icon *icon, *icon_selected;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_button),
|
||||
JsValueParseFlagReturnOnError,
|
||||
&status,
|
||||
&child_obj,
|
||||
&matrix_x,
|
||||
&matrix_y,
|
||||
&icon,
|
||||
&icon_selected);
|
||||
if(status != JsValueParseStatusOk) return false;
|
||||
button_panel_add_item(
|
||||
panel,
|
||||
context->next_index++,
|
||||
matrix_x,
|
||||
matrix_y,
|
||||
x,
|
||||
y,
|
||||
icon,
|
||||
icon_selected,
|
||||
(ButtonItemCallback)input_callback,
|
||||
context);
|
||||
break;
|
||||
}
|
||||
|
||||
case JsButtonPanelChildTypeLabel: {
|
||||
const char* text;
|
||||
Font font;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_label),
|
||||
JsValueParseFlagReturnOnError,
|
||||
&status,
|
||||
&child_obj,
|
||||
&text,
|
||||
&font);
|
||||
if(status != JsValueParseStatusOk) return false;
|
||||
button_panel_add_label(
|
||||
panel, x, y, font, str_buffer_make_owned_clone(&context->str_buffer, text));
|
||||
break;
|
||||
}
|
||||
|
||||
case JsButtonPanelChildTypeIcon: {
|
||||
const Icon* icon;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_icon),
|
||||
JsValueParseFlagReturnOnError,
|
||||
&status,
|
||||
&child_obj,
|
||||
&icon);
|
||||
if(status != JsValueParseStatusOk) return false;
|
||||
button_panel_add_icon(panel, x, y, icon);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_button_panel_reset_children(ButtonPanel* panel, JsBtnPanelContext* context) {
|
||||
context->next_index = 0;
|
||||
button_panel_reset(panel);
|
||||
button_panel_reserve(panel, context->matrix_x, context->matrix_y);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
}
|
||||
|
||||
static JsBtnPanelContext* ctx_make(struct mjs* mjs, ButtonPanel* panel, mjs_val_t view_obj) {
|
||||
UNUSED(panel);
|
||||
JsBtnPanelContext* context = malloc(sizeof(JsBtnPanelContext));
|
||||
*context = (JsBtnPanelContext){
|
||||
.matrix_x = 1,
|
||||
.matrix_y = 1,
|
||||
.next_index = 0,
|
||||
.str_buffer = {0},
|
||||
.input_queue = furi_message_queue_alloc(1, sizeof(JsBtnPanelEvent)),
|
||||
};
|
||||
context->contract = (JsEventLoopContract){
|
||||
.magic = JsForeignMagic_JsEventLoopContract,
|
||||
.object_type = JsEventLoopObjectTypeQueue,
|
||||
.object = context->input_queue,
|
||||
.non_timer =
|
||||
{
|
||||
.event = FuriEventLoopEventIn,
|
||||
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||
.transformer_context = context,
|
||||
},
|
||||
};
|
||||
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||
return context;
|
||||
}
|
||||
|
||||
static void ctx_destroy(ButtonPanel* input, JsBtnPanelContext* context, FuriEventLoop* loop) {
|
||||
UNUSED(input);
|
||||
furi_event_loop_maybe_unsubscribe(loop, context->input_queue);
|
||||
furi_message_queue_free(context->input_queue);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
free(context);
|
||||
}
|
||||
|
||||
static const JsViewDescriptor view_descriptor = {
|
||||
.alloc = (JsViewAlloc)button_panel_alloc,
|
||||
.free = (JsViewFree)button_panel_free,
|
||||
.get_view = (JsViewGetView)button_panel_get_view,
|
||||
.custom_make = (JsViewCustomMake)ctx_make,
|
||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||
.add_child = (JsViewAddChild)js_button_panel_add_child,
|
||||
.reset_children = (JsViewResetChildren)js_button_panel_reset_children,
|
||||
.prop_cnt = 2,
|
||||
.props = {
|
||||
(JsViewPropDescriptor){
|
||||
.name = "matrixSizeX",
|
||||
.type = JsViewPropTypeNumber,
|
||||
.assign = (JsViewPropAssign)matrix_size_x_assign},
|
||||
(JsViewPropDescriptor){
|
||||
.name = "matrixSizeY",
|
||||
.type = JsViewPropTypeNumber,
|
||||
.assign = (JsViewPropAssign)matrix_size_y_assign},
|
||||
}};
|
||||
|
||||
JS_GUI_VIEW_DEF(button_panel, &view_descriptor);
|
||||
@@ -31,6 +31,14 @@ typedef struct {
|
||||
void* custom_data;
|
||||
} JsGuiViewData;
|
||||
|
||||
static const JsValueEnumVariant js_gui_font_variants[] = {
|
||||
{"primary", FontPrimary},
|
||||
{"secondary", FontSecondary},
|
||||
{"keyboard", FontKeyboard},
|
||||
{"bit_numbers", FontBigNumbers},
|
||||
};
|
||||
const JsValueDeclaration js_gui_font_declaration = JS_VALUE_ENUM(Font, js_gui_font_variants);
|
||||
|
||||
/**
|
||||
* @brief Transformer for custom events
|
||||
*/
|
||||
@@ -273,9 +281,12 @@ static bool
|
||||
/**
|
||||
* @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);
|
||||
static bool js_gui_view_internal_set_children(
|
||||
struct mjs* mjs,
|
||||
mjs_val_t children,
|
||||
JsGuiViewData* data,
|
||||
bool do_reset) {
|
||||
if(do_reset) 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);
|
||||
@@ -357,7 +368,7 @@ static void js_gui_view_set_children(struct mjs* 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");
|
||||
|
||||
js_gui_view_internal_set_children(mjs, children, data);
|
||||
js_gui_view_internal_set_children(mjs, children, data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,7 +461,7 @@ static void js_gui_vf_make_with(struct mjs* 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");
|
||||
|
||||
if(!js_gui_view_internal_set_children(mjs, children, data)) return;
|
||||
if(!js_gui_view_internal_set_children(mjs, children, data, false)) return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, view_obj);
|
||||
|
||||
@@ -20,6 +20,11 @@ typedef union {
|
||||
mjs_val_t term;
|
||||
} JsViewPropValue;
|
||||
|
||||
/**
|
||||
* JS-to-C font enum mapping
|
||||
*/
|
||||
extern const JsValueDeclaration js_gui_font_declaration;
|
||||
|
||||
/**
|
||||
* @brief Assigns a value to a view property
|
||||
*
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "js_gui.h"
|
||||
|
||||
static constexpr auto js_gui_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*))));
|
||||
API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)),
|
||||
API_VARIABLE(js_gui_font_declaration, const JsValueDeclaration)));
|
||||
|
||||
105
applications/system/js_app/modules/js_gui/menu.c
Normal file
105
applications/system/js_app/modules/js_gui/menu.c
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "../../js_modules.h" // IWYU pragma: keep
|
||||
#include "js_gui.h"
|
||||
#include "../js_event_loop/js_event_loop.h"
|
||||
#include <gui/modules/menu.h>
|
||||
#include <toolbox/str_buffer.h>
|
||||
|
||||
typedef struct {
|
||||
int32_t next_index;
|
||||
StrBuffer str_buffer;
|
||||
|
||||
FuriMessageQueue* queue;
|
||||
JsEventLoopContract contract;
|
||||
} JsMenuCtx;
|
||||
|
||||
static mjs_val_t choose_transformer(struct mjs* mjs, FuriMessageQueue* queue, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t index;
|
||||
furi_check(furi_message_queue_get(queue, &index, 0) == FuriStatusOk);
|
||||
return mjs_mk_number(mjs, (double)index);
|
||||
}
|
||||
|
||||
static void choose_callback(void* context, uint32_t index) {
|
||||
JsMenuCtx* ctx = context;
|
||||
furi_check(furi_message_queue_put(ctx->queue, &index, 0) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static bool
|
||||
js_menu_add_child(struct mjs* mjs, Menu* menu, JsMenuCtx* context, mjs_val_t child_obj) {
|
||||
static const JsValueDeclaration js_menu_string = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
static const JsValueDeclaration js_menu_pointer = JS_VALUE_SIMPLE(JsValueTypeRawPointer);
|
||||
|
||||
static const JsValueObjectField js_menu_child_fields[] = {
|
||||
{"icon", &js_menu_pointer},
|
||||
{"label", &js_menu_string},
|
||||
};
|
||||
static const JsValueDeclaration js_menu_child = JS_VALUE_OBJECT(js_menu_child_fields);
|
||||
|
||||
const Icon* icon;
|
||||
const char* label;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&js_menu_child),
|
||||
JsValueParseFlagReturnOnError,
|
||||
&status,
|
||||
&child_obj,
|
||||
&icon,
|
||||
&label);
|
||||
if(status != JsValueParseStatusOk) return false;
|
||||
|
||||
menu_add_item(
|
||||
menu,
|
||||
str_buffer_make_owned_clone(&context->str_buffer, label),
|
||||
icon,
|
||||
context->next_index++,
|
||||
choose_callback,
|
||||
context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_menu_reset_children(Menu* menu, JsMenuCtx* context) {
|
||||
context->next_index = 0;
|
||||
menu_reset(menu);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
}
|
||||
|
||||
static JsMenuCtx* ctx_make(struct mjs* mjs, Menu* input, mjs_val_t view_obj) {
|
||||
UNUSED(input);
|
||||
JsMenuCtx* context = malloc(sizeof(JsMenuCtx));
|
||||
context->queue = furi_message_queue_alloc(1, sizeof(uint32_t));
|
||||
context->contract = (JsEventLoopContract){
|
||||
.magic = JsForeignMagic_JsEventLoopContract,
|
||||
.object_type = JsEventLoopObjectTypeQueue,
|
||||
.object = context->queue,
|
||||
.non_timer =
|
||||
{
|
||||
.event = FuriEventLoopEventIn,
|
||||
.transformer = (JsEventLoopTransformer)choose_transformer,
|
||||
},
|
||||
};
|
||||
mjs_set(mjs, view_obj, "chosen", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||
return context;
|
||||
}
|
||||
|
||||
static void ctx_destroy(Menu* input, JsMenuCtx* context, FuriEventLoop* loop) {
|
||||
UNUSED(input);
|
||||
furi_event_loop_maybe_unsubscribe(loop, context->queue);
|
||||
furi_message_queue_free(context->queue);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
free(context);
|
||||
}
|
||||
|
||||
static const JsViewDescriptor view_descriptor = {
|
||||
.alloc = (JsViewAlloc)menu_alloc,
|
||||
.free = (JsViewFree)menu_free,
|
||||
.get_view = (JsViewGetView)menu_get_view,
|
||||
.custom_make = (JsViewCustomMake)ctx_make,
|
||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||
.add_child = (JsViewAddChild)js_menu_add_child,
|
||||
.reset_children = (JsViewResetChildren)js_menu_reset_children,
|
||||
.prop_cnt = 0,
|
||||
.props = {},
|
||||
};
|
||||
JS_GUI_VIEW_DEF(menu, &view_descriptor);
|
||||
130
applications/system/js_app/modules/js_gui/number_input.c
Normal file
130
applications/system/js_app/modules/js_gui/number_input.c
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "../../js_modules.h" // IWYU pragma: keep
|
||||
#include "js_gui.h"
|
||||
#include "../js_event_loop/js_event_loop.h"
|
||||
#include <gui/modules/number_input.h>
|
||||
|
||||
typedef struct {
|
||||
int32_t default_val, min_val, max_val;
|
||||
FuriMessageQueue* input_queue;
|
||||
JsEventLoopContract contract;
|
||||
} JsNumKbdContext;
|
||||
|
||||
static mjs_val_t
|
||||
input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsNumKbdContext* context) {
|
||||
UNUSED(context);
|
||||
int32_t number;
|
||||
furi_check(furi_message_queue_get(queue, &number, 0) == FuriStatusOk);
|
||||
return mjs_mk_number(mjs, number);
|
||||
}
|
||||
|
||||
static void input_callback(void* ctx, int32_t value) {
|
||||
JsNumKbdContext* context = ctx;
|
||||
furi_check(furi_message_queue_put(context->input_queue, &value, 0) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static bool header_assign(
|
||||
struct mjs* mjs,
|
||||
NumberInput* input,
|
||||
JsViewPropValue value,
|
||||
JsNumKbdContext* context) {
|
||||
UNUSED(mjs);
|
||||
UNUSED(context);
|
||||
number_input_set_header_text(input, value.string);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool min_val_assign(
|
||||
struct mjs* mjs,
|
||||
NumberInput* input,
|
||||
JsViewPropValue value,
|
||||
JsNumKbdContext* context) {
|
||||
UNUSED(mjs);
|
||||
context->min_val = value.number;
|
||||
number_input_set_result_callback(
|
||||
input, input_callback, context, context->default_val, context->min_val, context->max_val);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool max_val_assign(
|
||||
struct mjs* mjs,
|
||||
NumberInput* input,
|
||||
JsViewPropValue value,
|
||||
JsNumKbdContext* context) {
|
||||
UNUSED(mjs);
|
||||
context->max_val = value.number;
|
||||
number_input_set_result_callback(
|
||||
input, input_callback, context, context->default_val, context->min_val, context->max_val);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool default_val_assign(
|
||||
struct mjs* mjs,
|
||||
NumberInput* input,
|
||||
JsViewPropValue value,
|
||||
JsNumKbdContext* context) {
|
||||
UNUSED(mjs);
|
||||
context->default_val = value.number;
|
||||
number_input_set_result_callback(
|
||||
input, input_callback, context, context->default_val, context->min_val, context->max_val);
|
||||
return true;
|
||||
}
|
||||
|
||||
static JsNumKbdContext* ctx_make(struct mjs* mjs, NumberInput* input, mjs_val_t view_obj) {
|
||||
JsNumKbdContext* context = malloc(sizeof(JsNumKbdContext));
|
||||
*context = (JsNumKbdContext){
|
||||
.default_val = 0,
|
||||
.max_val = 100,
|
||||
.min_val = 0,
|
||||
.input_queue = furi_message_queue_alloc(1, sizeof(int32_t)),
|
||||
};
|
||||
context->contract = (JsEventLoopContract){
|
||||
.magic = JsForeignMagic_JsEventLoopContract,
|
||||
.object_type = JsEventLoopObjectTypeQueue,
|
||||
.object = context->input_queue,
|
||||
.non_timer =
|
||||
{
|
||||
.event = FuriEventLoopEventIn,
|
||||
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||
.transformer_context = context,
|
||||
},
|
||||
};
|
||||
number_input_set_result_callback(
|
||||
input, input_callback, context, context->default_val, context->min_val, context->max_val);
|
||||
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||
return context;
|
||||
}
|
||||
|
||||
static void ctx_destroy(NumberInput* input, JsNumKbdContext* context, FuriEventLoop* loop) {
|
||||
UNUSED(input);
|
||||
furi_event_loop_maybe_unsubscribe(loop, context->input_queue);
|
||||
furi_message_queue_free(context->input_queue);
|
||||
free(context);
|
||||
}
|
||||
|
||||
static const JsViewDescriptor view_descriptor = {
|
||||
.alloc = (JsViewAlloc)number_input_alloc,
|
||||
.free = (JsViewFree)number_input_free,
|
||||
.get_view = (JsViewGetView)number_input_get_view,
|
||||
.custom_make = (JsViewCustomMake)ctx_make,
|
||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||
.prop_cnt = 4,
|
||||
.props = {
|
||||
(JsViewPropDescriptor){
|
||||
.name = "header",
|
||||
.type = JsViewPropTypeString,
|
||||
.assign = (JsViewPropAssign)header_assign},
|
||||
(JsViewPropDescriptor){
|
||||
.name = "minValue",
|
||||
.type = JsViewPropTypeNumber,
|
||||
.assign = (JsViewPropAssign)min_val_assign},
|
||||
(JsViewPropDescriptor){
|
||||
.name = "maxValue",
|
||||
.type = JsViewPropTypeNumber,
|
||||
.assign = (JsViewPropAssign)max_val_assign},
|
||||
(JsViewPropDescriptor){
|
||||
.name = "defaultValue",
|
||||
.type = JsViewPropTypeNumber,
|
||||
.assign = (JsViewPropAssign)default_val_assign},
|
||||
}};
|
||||
|
||||
JS_GUI_VIEW_DEF(number_input, &view_descriptor);
|
||||
102
applications/system/js_app/modules/js_gui/popup.c
Normal file
102
applications/system/js_app/modules/js_gui/popup.c
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "../../js_modules.h" // IWYU pragma: keep
|
||||
#include "js_gui.h"
|
||||
#include "../js_event_loop/js_event_loop.h"
|
||||
#include <gui/modules/popup.h>
|
||||
#include <toolbox/str_buffer.h>
|
||||
|
||||
typedef struct {
|
||||
StrBuffer str_buffer;
|
||||
FuriSemaphore* semaphore;
|
||||
JsEventLoopContract contract;
|
||||
} JsPopupCtx;
|
||||
|
||||
static void timeout_callback(JsPopupCtx* context) {
|
||||
furi_check(furi_semaphore_release(context->semaphore) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static bool
|
||||
header_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) {
|
||||
UNUSED(mjs);
|
||||
UNUSED(context);
|
||||
popup_set_header(
|
||||
popup,
|
||||
str_buffer_make_owned_clone(&context->str_buffer, value.string),
|
||||
64,
|
||||
0,
|
||||
AlignCenter,
|
||||
AlignTop);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
text_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) {
|
||||
UNUSED(mjs);
|
||||
UNUSED(context);
|
||||
popup_set_text(
|
||||
popup,
|
||||
str_buffer_make_owned_clone(&context->str_buffer, value.string),
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
timeout_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) {
|
||||
UNUSED(mjs);
|
||||
UNUSED(context);
|
||||
popup_set_timeout(popup, value.number);
|
||||
popup_enable_timeout(popup);
|
||||
return true;
|
||||
}
|
||||
|
||||
static JsPopupCtx* ctx_make(struct mjs* mjs, Popup* popup, mjs_val_t view_obj) {
|
||||
JsPopupCtx* context = malloc(sizeof(JsPopupCtx));
|
||||
context->semaphore = furi_semaphore_alloc(1, 0);
|
||||
context->contract = (JsEventLoopContract){
|
||||
.magic = JsForeignMagic_JsEventLoopContract,
|
||||
.object_type = JsEventLoopObjectTypeSemaphore,
|
||||
.object = context->semaphore,
|
||||
.non_timer =
|
||||
{
|
||||
.event = FuriEventLoopEventIn,
|
||||
},
|
||||
};
|
||||
mjs_set(mjs, view_obj, "timeout", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||
popup_set_callback(popup, (PopupCallback)timeout_callback);
|
||||
popup_set_context(popup, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
static void ctx_destroy(Popup* popup, JsPopupCtx* context, FuriEventLoop* loop) {
|
||||
UNUSED(popup);
|
||||
furi_event_loop_maybe_unsubscribe(loop, context->semaphore);
|
||||
furi_semaphore_free(context->semaphore);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
free(context);
|
||||
}
|
||||
|
||||
static const JsViewDescriptor view_descriptor = {
|
||||
.alloc = (JsViewAlloc)popup_alloc,
|
||||
.free = (JsViewFree)popup_free,
|
||||
.get_view = (JsViewGetView)popup_get_view,
|
||||
.custom_make = (JsViewCustomMake)ctx_make,
|
||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||
.prop_cnt = 3,
|
||||
.props = {
|
||||
(JsViewPropDescriptor){
|
||||
.name = "header",
|
||||
.type = JsViewPropTypeString,
|
||||
.assign = (JsViewPropAssign)header_assign},
|
||||
(JsViewPropDescriptor){
|
||||
.name = "text",
|
||||
.type = JsViewPropTypeString,
|
||||
.assign = (JsViewPropAssign)text_assign},
|
||||
(JsViewPropDescriptor){
|
||||
.name = "timeout",
|
||||
.type = JsViewPropTypeNumber,
|
||||
.assign = (JsViewPropAssign)timeout_assign},
|
||||
}};
|
||||
|
||||
JS_GUI_VIEW_DEF(popup, &view_descriptor);
|
||||
@@ -6,6 +6,7 @@
|
||||
#define QUEUE_LEN 2
|
||||
|
||||
typedef struct {
|
||||
int32_t next_index;
|
||||
FuriMessageQueue* queue;
|
||||
JsEventLoopContract contract;
|
||||
} JsSubmenuCtx;
|
||||
@@ -30,18 +31,24 @@ static bool
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) {
|
||||
UNUSED(mjs);
|
||||
submenu_reset(submenu);
|
||||
size_t len = mjs_array_length(mjs, value.term);
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
mjs_val_t item = mjs_array_get(mjs, value.term, i);
|
||||
if(!mjs_is_string(item)) return false;
|
||||
submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context);
|
||||
}
|
||||
static bool js_submenu_add_child(
|
||||
struct mjs* mjs,
|
||||
Submenu* submenu,
|
||||
JsSubmenuCtx* context,
|
||||
mjs_val_t child_obj) {
|
||||
const char* str = mjs_get_string(mjs, &child_obj, NULL);
|
||||
if(!str) return false;
|
||||
|
||||
submenu_add_item(submenu, str, context->next_index++, choose_callback, context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_submenu_reset_children(Submenu* submenu, JsSubmenuCtx* context) {
|
||||
context->next_index = 0;
|
||||
submenu_reset(submenu);
|
||||
}
|
||||
|
||||
static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) {
|
||||
UNUSED(input);
|
||||
JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx));
|
||||
@@ -73,15 +80,13 @@ static const JsViewDescriptor view_descriptor = {
|
||||
.get_view = (JsViewGetView)submenu_get_view,
|
||||
.custom_make = (JsViewCustomMake)ctx_make,
|
||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||
.prop_cnt = 2,
|
||||
.add_child = (JsViewAddChild)js_submenu_add_child,
|
||||
.reset_children = (JsViewResetChildren)js_submenu_reset_children,
|
||||
.prop_cnt = 1,
|
||||
.props = {
|
||||
(JsViewPropDescriptor){
|
||||
.name = "header",
|
||||
.type = JsViewPropTypeString,
|
||||
.assign = (JsViewPropAssign)header_assign},
|
||||
(JsViewPropDescriptor){
|
||||
.name = "items",
|
||||
.type = JsViewPropTypeArr,
|
||||
.assign = (JsViewPropAssign)items_assign},
|
||||
}};
|
||||
JS_GUI_VIEW_DEF(submenu, &view_descriptor);
|
||||
|
||||
163
applications/system/js_app/modules/js_gui/vi_list.c
Normal file
163
applications/system/js_app/modules/js_gui/vi_list.c
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "../../js_modules.h" // IWYU pragma: keep
|
||||
#include "js_gui.h"
|
||||
#include "../js_event_loop/js_event_loop.h"
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <toolbox/str_buffer.h>
|
||||
|
||||
typedef struct {
|
||||
StrBuffer str_buffer;
|
||||
|
||||
// let mjs do the memory management heavy lifting, store children in a js array
|
||||
struct mjs* mjs;
|
||||
mjs_val_t children;
|
||||
VariableItemList* list;
|
||||
|
||||
FuriMessageQueue* input_queue;
|
||||
JsEventLoopContract contract;
|
||||
} JsViListContext;
|
||||
|
||||
typedef struct {
|
||||
int32_t item_index;
|
||||
int32_t value_index;
|
||||
} JsViListEvent;
|
||||
|
||||
static mjs_val_t
|
||||
input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsViListContext* context) {
|
||||
UNUSED(context);
|
||||
JsViListEvent event;
|
||||
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
|
||||
|
||||
mjs_val_t event_obj = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, event_obj) {
|
||||
JS_FIELD("itemIndex", mjs_mk_number(mjs, event.item_index));
|
||||
JS_FIELD("valueIndex", mjs_mk_number(mjs, event.value_index));
|
||||
}
|
||||
|
||||
return event_obj;
|
||||
}
|
||||
|
||||
static void js_vi_list_change_callback(VariableItem* item) {
|
||||
JsViListContext* context = variable_item_get_context(item);
|
||||
struct mjs* mjs = context->mjs;
|
||||
uint8_t item_index = variable_item_list_get_selected_item_index(context->list);
|
||||
uint8_t value_index = variable_item_get_current_value_index(item);
|
||||
|
||||
// type safety ensured in add_child
|
||||
mjs_val_t variants = mjs_array_get(mjs, context->children, item_index);
|
||||
mjs_val_t variant = mjs_array_get(mjs, variants, value_index);
|
||||
variable_item_set_current_value_text(item, mjs_get_string(mjs, &variant, NULL));
|
||||
|
||||
JsViListEvent event = {
|
||||
.item_index = item_index,
|
||||
.value_index = value_index,
|
||||
};
|
||||
furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static bool js_vi_list_add_child(
|
||||
struct mjs* mjs,
|
||||
VariableItemList* list,
|
||||
JsViListContext* context,
|
||||
mjs_val_t child_obj) {
|
||||
static const JsValueDeclaration js_vi_list_string = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
static const JsValueDeclaration js_vi_list_arr = JS_VALUE_SIMPLE(JsValueTypeAnyArray);
|
||||
static const JsValueDeclaration js_vi_list_int_default_0 =
|
||||
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 0);
|
||||
|
||||
static const JsValueObjectField js_vi_list_child_fields[] = {
|
||||
{"label", &js_vi_list_string},
|
||||
{"variants", &js_vi_list_arr},
|
||||
{"defaultSelected", &js_vi_list_int_default_0},
|
||||
};
|
||||
static const JsValueDeclaration js_vi_list_child =
|
||||
JS_VALUE_OBJECT_W_DEFAULTS(js_vi_list_child_fields);
|
||||
|
||||
JsValueParseStatus status;
|
||||
const char* label;
|
||||
mjs_val_t variants;
|
||||
int32_t default_selected;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&js_vi_list_child),
|
||||
JsValueParseFlagReturnOnError,
|
||||
&status,
|
||||
&child_obj,
|
||||
&label,
|
||||
&variants,
|
||||
&default_selected);
|
||||
if(status != JsValueParseStatusOk) return false;
|
||||
|
||||
size_t variants_cnt = mjs_array_length(mjs, variants);
|
||||
for(size_t i = 0; i < variants_cnt; i++)
|
||||
if(!mjs_is_string(mjs_array_get(mjs, variants, i))) return false;
|
||||
|
||||
VariableItem* item = variable_item_list_add(
|
||||
list,
|
||||
str_buffer_make_owned_clone(&context->str_buffer, label),
|
||||
variants_cnt,
|
||||
js_vi_list_change_callback,
|
||||
context);
|
||||
variable_item_set_current_value_index(item, default_selected);
|
||||
mjs_val_t default_variant = mjs_array_get(mjs, variants, default_selected);
|
||||
variable_item_set_current_value_text(item, mjs_get_string(mjs, &default_variant, NULL));
|
||||
|
||||
mjs_array_push(context->mjs, context->children, variants);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_vi_list_reset_children(VariableItemList* list, JsViListContext* context) {
|
||||
mjs_disown(context->mjs, &context->children);
|
||||
context->children = mjs_mk_array(context->mjs);
|
||||
mjs_own(context->mjs, &context->children);
|
||||
|
||||
variable_item_list_reset(list);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
}
|
||||
|
||||
static JsViListContext* ctx_make(struct mjs* mjs, VariableItemList* list, mjs_val_t view_obj) {
|
||||
JsViListContext* context = malloc(sizeof(JsViListContext));
|
||||
*context = (JsViListContext){
|
||||
.str_buffer = {0},
|
||||
.mjs = mjs,
|
||||
.children = mjs_mk_array(mjs),
|
||||
.list = list,
|
||||
.input_queue = furi_message_queue_alloc(1, sizeof(JsViListEvent)),
|
||||
};
|
||||
mjs_own(context->mjs, &context->children);
|
||||
context->contract = (JsEventLoopContract){
|
||||
.magic = JsForeignMagic_JsEventLoopContract,
|
||||
.object_type = JsEventLoopObjectTypeQueue,
|
||||
.object = context->input_queue,
|
||||
.non_timer =
|
||||
{
|
||||
.event = FuriEventLoopEventIn,
|
||||
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||
.transformer_context = context,
|
||||
},
|
||||
};
|
||||
mjs_set(mjs, view_obj, "valueUpdate", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||
return context;
|
||||
}
|
||||
|
||||
static void ctx_destroy(VariableItemList* input, JsViListContext* context, FuriEventLoop* loop) {
|
||||
UNUSED(input);
|
||||
furi_event_loop_maybe_unsubscribe(loop, context->input_queue);
|
||||
furi_message_queue_free(context->input_queue);
|
||||
str_buffer_clear_all_clones(&context->str_buffer);
|
||||
free(context);
|
||||
}
|
||||
|
||||
static const JsViewDescriptor view_descriptor = {
|
||||
.alloc = (JsViewAlloc)variable_item_list_alloc,
|
||||
.free = (JsViewFree)variable_item_list_free,
|
||||
.get_view = (JsViewGetView)variable_item_list_get_view,
|
||||
.custom_make = (JsViewCustomMake)ctx_make,
|
||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||
.add_child = (JsViewAddChild)js_vi_list_add_child,
|
||||
.reset_children = (JsViewResetChildren)js_vi_list_reset_children,
|
||||
.prop_cnt = 0,
|
||||
.props = {},
|
||||
};
|
||||
|
||||
JS_GUI_VIEW_DEF(vi_list, &view_descriptor);
|
||||
Reference in New Issue
Block a user