mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
[FL-3925] JS views finished (#4155)
* js: value destructuring and tests * js: temporary fix to see size impact * js_val: reduce code size 1 * i may be stupid. * test: js_value args * Revert "js: temporary fix to see size impact" This reverts commit f51d726dbafc4300d3552020de1c3b8f9ecd3ac1. * pvs: silence warnings * style: formatting * pvs: silence warnings? * pvs: silence warnings?? * js_value: redesign declaration types for less code * js: temporary fix to see size impact * style: formatting * pvs: fix helpful warnings * js_value: reduce .rodata size * pvs: fix helpful warning * js_value: reduce code size 1 * fix build error * style: format * Revert "js: temporary fix to see size impact" This reverts commit d6a46f01794132e882e03fd273dec24386a4f8ba. * style: format * js: move to new arg parser * style: format * feat: all js views done * js, toolbox: generalize string owning * toolbox: silence pvs warning --------- Co-authored-by: hedger <hedger@nanode.su> Co-authored-by: hedger <hedger@users.noreply.github.com>
This commit is contained in:
@@ -93,13 +93,12 @@ static bool popup_view_input_callback(InputEvent* event, void* context) {
|
|||||||
void popup_start_timer(void* context) {
|
void popup_start_timer(void* context) {
|
||||||
Popup* popup = context;
|
Popup* popup = context;
|
||||||
if(popup->timer_enabled) {
|
if(popup->timer_enabled) {
|
||||||
uint32_t timer_period =
|
uint32_t timer_period = furi_ms_to_ticks(popup->timer_period_in_ms);
|
||||||
popup->timer_period_in_ms / (1000.0f / furi_kernel_get_tick_frequency());
|
|
||||||
if(timer_period == 0) timer_period = 1;
|
if(timer_period == 0) timer_period = 1;
|
||||||
|
|
||||||
if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) {
|
if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) {
|
||||||
furi_crash();
|
furi_crash();
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ void popup_free(Popup* popup);
|
|||||||
*/
|
*/
|
||||||
View* popup_get_view(Popup* popup);
|
View* popup_get_view(Popup* popup);
|
||||||
|
|
||||||
/** Set popup header text
|
/** Set popup timeout callback
|
||||||
*
|
*
|
||||||
* @param popup Popup instance
|
* @param popup Popup instance
|
||||||
* @param callback PopupCallback
|
* @param callback PopupCallback
|
||||||
|
|||||||
@@ -78,6 +78,54 @@ App(
|
|||||||
sources=["modules/js_gui/text_input.c"],
|
sources=["modules/js_gui/text_input.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__number_input",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_number_input_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/number_input.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__button_panel",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_button_panel_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/button_panel.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__popup",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_popup_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/popup.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__button_menu",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_button_menu_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/button_menu.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__menu",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_menu_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/menu.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__vi_list",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_vi_list_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/vi_list.c"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="js_gui__byte_input",
|
appid="js_gui__byte_input",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ let byteInputView = require("gui/byte_input");
|
|||||||
let textBoxView = require("gui/text_box");
|
let textBoxView = require("gui/text_box");
|
||||||
let dialogView = require("gui/dialog");
|
let dialogView = require("gui/dialog");
|
||||||
let filePicker = require("gui/file_picker");
|
let filePicker = require("gui/file_picker");
|
||||||
|
let buttonMenuView = require("gui/button_menu");
|
||||||
|
let buttonPanelView = require("gui/button_panel");
|
||||||
|
let menuView = require("gui/menu");
|
||||||
|
let numberInputView = require("gui/number_input");
|
||||||
|
let popupView = require("gui/popup");
|
||||||
|
let viListView = require("gui/vi_list");
|
||||||
let widget = require("gui/widget");
|
let widget = require("gui/widget");
|
||||||
let icon = require("gui/icon");
|
let icon = require("gui/icon");
|
||||||
let flipper = require("flipper");
|
let flipper = require("flipper");
|
||||||
@@ -27,6 +33,11 @@ let stopwatchWidgetElements = [
|
|||||||
{ element: "button", button: "right", text: "Back" },
|
{ element: "button", button: "right", text: "Back" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// icons for the button panel
|
||||||
|
let offIcons = [icon.getBuiltin("off_19x20"), icon.getBuiltin("off_hover_19x20")];
|
||||||
|
let powerIcons = [icon.getBuiltin("power_19x20"), icon.getBuiltin("power_hover_19x20")];
|
||||||
|
let settingsIcon = icon.getBuiltin("Settings_14");
|
||||||
|
|
||||||
// declare view instances
|
// declare view instances
|
||||||
let views = {
|
let views = {
|
||||||
loading: loadingView.make(),
|
loading: loadingView.make(),
|
||||||
@@ -48,19 +59,57 @@ let views = {
|
|||||||
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
|
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
|
||||||
}),
|
}),
|
||||||
stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements),
|
stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements),
|
||||||
|
buttonMenu: buttonMenuView.makeWith({
|
||||||
|
header: "Header"
|
||||||
|
}, [
|
||||||
|
{ type: "common", label: "Test" },
|
||||||
|
{ type: "control", label: "Test2" },
|
||||||
|
]),
|
||||||
|
buttonPanel: buttonPanelView.makeWith({
|
||||||
|
matrixSizeX: 2,
|
||||||
|
matrixSizeY: 2,
|
||||||
|
}, [
|
||||||
|
{ type: "button", x: 0, y: 0, matrixX: 0, matrixY: 0, icon: offIcons[0], iconSelected: offIcons[1] },
|
||||||
|
{ type: "button", x: 30, y: 30, matrixX: 1, matrixY: 1, icon: powerIcons[0], iconSelected: powerIcons[1] },
|
||||||
|
{ type: "label", x: 0, y: 50, text: "Label", font: "primary" },
|
||||||
|
]),
|
||||||
|
menu: menuView.makeWith({}, [
|
||||||
|
{ label: "One", icon: settingsIcon },
|
||||||
|
{ label: "Two", icon: settingsIcon },
|
||||||
|
{ label: "three", icon: settingsIcon },
|
||||||
|
]),
|
||||||
|
numberKbd: numberInputView.makeWith({
|
||||||
|
header: "Number input",
|
||||||
|
defaultValue: 100,
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: 200,
|
||||||
|
}),
|
||||||
|
popup: popupView.makeWith({
|
||||||
|
header: "Hello",
|
||||||
|
text: "I'm going to be gone\nin 2 seconds",
|
||||||
|
}),
|
||||||
|
viList: viListView.makeWith({}, [
|
||||||
|
{ label: "One", variants: ["1", "1.0"] },
|
||||||
|
{ label: "Two", variants: ["2", "2.0"] },
|
||||||
|
]),
|
||||||
demos: submenuView.makeWith({
|
demos: submenuView.makeWith({
|
||||||
header: "Choose a demo",
|
header: "Choose a demo",
|
||||||
items: [
|
}, [
|
||||||
"Hourglass screen",
|
"Hourglass screen",
|
||||||
"Empty screen",
|
"Empty screen",
|
||||||
"Text input & Dialog",
|
"Text input & Dialog",
|
||||||
"Byte input",
|
"Byte input",
|
||||||
"Text box",
|
"Text box",
|
||||||
"File picker",
|
"File picker",
|
||||||
"Widget",
|
"Widget",
|
||||||
"Exit app",
|
"Button menu",
|
||||||
],
|
"Button panel",
|
||||||
}),
|
"Menu",
|
||||||
|
"Number input",
|
||||||
|
"Popup",
|
||||||
|
"Var. item list",
|
||||||
|
"Exit app",
|
||||||
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
// demo selector
|
// demo selector
|
||||||
@@ -92,6 +141,19 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v
|
|||||||
} else if (index === 6) {
|
} else if (index === 6) {
|
||||||
gui.viewDispatcher.switchTo(views.stopwatchWidget);
|
gui.viewDispatcher.switchTo(views.stopwatchWidget);
|
||||||
} else if (index === 7) {
|
} else if (index === 7) {
|
||||||
|
gui.viewDispatcher.switchTo(views.buttonMenu);
|
||||||
|
} else if (index === 8) {
|
||||||
|
gui.viewDispatcher.switchTo(views.buttonPanel);
|
||||||
|
} else if (index === 9) {
|
||||||
|
gui.viewDispatcher.switchTo(views.menu);
|
||||||
|
} else if (index === 10) {
|
||||||
|
gui.viewDispatcher.switchTo(views.numberKbd);
|
||||||
|
} else if (index === 11) {
|
||||||
|
views.popup.set("timeout", 2000);
|
||||||
|
gui.viewDispatcher.switchTo(views.popup);
|
||||||
|
} else if (index === 12) {
|
||||||
|
gui.viewDispatcher.switchTo(views.viList);
|
||||||
|
} else if (index === 13) {
|
||||||
eventLoop.stop();
|
eventLoop.stop();
|
||||||
}
|
}
|
||||||
}, gui, eventLoop, views);
|
}, gui, eventLoop, views);
|
||||||
@@ -156,6 +218,42 @@ eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, vie
|
|||||||
return [views, stopwatchWidgetElements, halfSeconds];
|
return [views, stopwatchWidgetElements, halfSeconds];
|
||||||
}, views, stopwatchWidgetElements, 0);
|
}, views, stopwatchWidgetElements, 0);
|
||||||
|
|
||||||
|
// go back after popup times out
|
||||||
|
eventLoop.subscribe(views.popup.timeout, function (_sub, _item, gui, views) {
|
||||||
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// button menu callback
|
||||||
|
eventLoop.subscribe(views.buttonMenu.input, function (_sub, input, gui, views) {
|
||||||
|
views.helloDialog.set("text", "You selected #" + input.index.toString());
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// button panel callback
|
||||||
|
eventLoop.subscribe(views.buttonPanel.input, function (_sub, input, gui, views) {
|
||||||
|
views.helloDialog.set("text", "You selected #" + input.index.toString());
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// menu callback
|
||||||
|
eventLoop.subscribe(views.menu.chosen, function (_sub, index, gui, views) {
|
||||||
|
views.helloDialog.set("text", "You selected #" + index.toString());
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// menu callback
|
||||||
|
eventLoop.subscribe(views.numberKbd.input, function (_sub, number, gui, views) {
|
||||||
|
views.helloDialog.set("text", "You typed " + number.toString());
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// ignore VI list
|
||||||
|
eventLoop.subscribe(views.viList.valueUpdate, function (_sub, _item) {});
|
||||||
|
|
||||||
// run UI
|
// run UI
|
||||||
gui.viewDispatcher.switchTo(views.demos);
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
eventLoop.run();
|
eventLoop.run();
|
||||||
|
|||||||
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);
|
||||||
@@ -14,9 +14,19 @@ typedef struct {
|
|||||||
.name = #icon, .data = &I_##icon \
|
.name = #icon, .data = &I_##icon \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define ANIM_ICON_DEF(icon) \
|
||||||
|
(IconDefinition) { \
|
||||||
|
.name = #icon, .data = &A_##icon \
|
||||||
|
}
|
||||||
|
|
||||||
static const IconDefinition builtin_icons[] = {
|
static const IconDefinition builtin_icons[] = {
|
||||||
ICON_DEF(DolphinWait_59x54),
|
ICON_DEF(DolphinWait_59x54),
|
||||||
ICON_DEF(js_script_10px),
|
ICON_DEF(js_script_10px),
|
||||||
|
ICON_DEF(off_19x20),
|
||||||
|
ICON_DEF(off_hover_19x20),
|
||||||
|
ICON_DEF(power_19x20),
|
||||||
|
ICON_DEF(power_hover_19x20),
|
||||||
|
ANIM_ICON_DEF(Settings_14),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Firmware's Icon struct needs a frames array, and uses a small CompressHeader
|
// Firmware's Icon struct needs a frames array, and uses a small CompressHeader
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ typedef struct {
|
|||||||
void* custom_data;
|
void* custom_data;
|
||||||
} JsGuiViewData;
|
} 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
|
* @brief Transformer for custom events
|
||||||
*/
|
*/
|
||||||
@@ -273,9 +281,12 @@ static bool
|
|||||||
/**
|
/**
|
||||||
* @brief Sets the list of children. Not available from JS.
|
* @brief Sets the list of children. Not available from JS.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool js_gui_view_internal_set_children(
|
||||||
js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) {
|
struct mjs* mjs,
|
||||||
data->descriptor->reset_children(data->specific_view, data->custom_data);
|
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++) {
|
for(size_t i = 0; i < mjs_array_length(mjs, children); i++) {
|
||||||
mjs_val_t child = mjs_array_get(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)
|
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_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)
|
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_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);
|
mjs_return(mjs, view_obj);
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ typedef union {
|
|||||||
mjs_val_t term;
|
mjs_val_t term;
|
||||||
} JsViewPropValue;
|
} JsViewPropValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JS-to-C font enum mapping
|
||||||
|
*/
|
||||||
|
extern const JsValueDeclaration js_gui_font_declaration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Assigns a value to a view property
|
* @brief Assigns a value to a view property
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "js_gui.h"
|
#include "js_gui.h"
|
||||||
|
|
||||||
static constexpr auto js_gui_api_table = sort(create_array_t<sym_entry>(
|
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
|
#define QUEUE_LEN 2
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
int32_t next_index;
|
||||||
FuriMessageQueue* queue;
|
FuriMessageQueue* queue;
|
||||||
JsEventLoopContract contract;
|
JsEventLoopContract contract;
|
||||||
} JsSubmenuCtx;
|
} JsSubmenuCtx;
|
||||||
@@ -30,18 +31,24 @@ static bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) {
|
static bool js_submenu_add_child(
|
||||||
UNUSED(mjs);
|
struct mjs* mjs,
|
||||||
submenu_reset(submenu);
|
Submenu* submenu,
|
||||||
size_t len = mjs_array_length(mjs, value.term);
|
JsSubmenuCtx* context,
|
||||||
for(size_t i = 0; i < len; i++) {
|
mjs_val_t child_obj) {
|
||||||
mjs_val_t item = mjs_array_get(mjs, value.term, i);
|
const char* str = mjs_get_string(mjs, &child_obj, NULL);
|
||||||
if(!mjs_is_string(item)) return false;
|
if(!str) return false;
|
||||||
submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context);
|
|
||||||
}
|
submenu_add_item(submenu, str, context->next_index++, choose_callback, context);
|
||||||
|
|
||||||
return true;
|
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) {
|
static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) {
|
||||||
UNUSED(input);
|
UNUSED(input);
|
||||||
JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx));
|
JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx));
|
||||||
@@ -73,15 +80,13 @@ static const JsViewDescriptor view_descriptor = {
|
|||||||
.get_view = (JsViewGetView)submenu_get_view,
|
.get_view = (JsViewGetView)submenu_get_view,
|
||||||
.custom_make = (JsViewCustomMake)ctx_make,
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
.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 = {
|
.props = {
|
||||||
(JsViewPropDescriptor){
|
(JsViewPropDescriptor){
|
||||||
.name = "header",
|
.name = "header",
|
||||||
.type = JsViewPropTypeString,
|
.type = JsViewPropTypeString,
|
||||||
.assign = (JsViewPropAssign)header_assign},
|
.assign = (JsViewPropAssign)header_assign},
|
||||||
(JsViewPropDescriptor){
|
|
||||||
.name = "items",
|
|
||||||
.type = JsViewPropTypeArr,
|
|
||||||
.assign = (JsViewPropAssign)items_assign},
|
|
||||||
}};
|
}};
|
||||||
JS_GUI_VIEW_DEF(submenu, &view_descriptor);
|
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);
|
||||||
40
applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts
vendored
Normal file
40
applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Displays a list of buttons.
|
||||||
|
*
|
||||||
|
* <img src="../images/button_menu.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let buttonMenuView = require("gui/button_menu");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This module depends on the `gui` module, which in turn depends on the
|
||||||
|
* `event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
* recommended to conceptualize these modules first before using this one.
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
* For an example refer to the `gui.js` example script.
|
||||||
|
*
|
||||||
|
* # View props
|
||||||
|
* - `header`: Textual header above the buttons
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory, InputType } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Child = { type: "common" | "control", label: string };
|
||||||
|
|
||||||
|
declare class ButtonMenu extends View<Props, Child> {
|
||||||
|
input: Contract<{ index: number, type: InputType }>;
|
||||||
|
}
|
||||||
|
declare class ButtonMenuFactory extends ViewFactory<Props, Child, ButtonMenu> { }
|
||||||
|
declare const factory: ButtonMenuFactory;
|
||||||
|
export = factory;
|
||||||
49
applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts
vendored
Normal file
49
applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Displays a button matrix.
|
||||||
|
*
|
||||||
|
* <img src="../images/button_panel.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let buttonPanelView = require("gui/button_panel");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This module depends on the `gui` module, which in turn depends on the
|
||||||
|
* `event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
* recommended to conceptualize these modules first before using this one.
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
* For an example refer to the `gui.js` example script.
|
||||||
|
*
|
||||||
|
* # View props
|
||||||
|
* - `matrixSizeX`: Width of imaginary grid used for navigation
|
||||||
|
* - `matrixSizeY`: Height of imaginary grid used for navigation
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory, Font, InputType } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
import { IconData } from "./icon";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
matrixSizeX: number,
|
||||||
|
matrixSizeY: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Position = { x: number, y: number };
|
||||||
|
|
||||||
|
type ButtonChild = { type: "button", matrixX: number, matrixY: number, icon: IconData, iconSelected: IconData } & Position;
|
||||||
|
type LabelChild = { type: "label", font: Font, text: string } & Position;
|
||||||
|
type IconChild = { type: "icon", icon: IconData };
|
||||||
|
|
||||||
|
type Child = ButtonChild | LabelChild | IconChild;
|
||||||
|
|
||||||
|
declare class ButtonPanel extends View<Props, Child> {
|
||||||
|
input: Contract<{ index: number, type: InputType }>;
|
||||||
|
}
|
||||||
|
declare class ButtonPanelFactory extends ViewFactory<Props, Child, ButtonPanel> { }
|
||||||
|
declare const factory: ButtonPanelFactory;
|
||||||
|
export = factory;
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px";
|
export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px"
|
||||||
|
| "off_19x20" | "off_hover_19x20"
|
||||||
|
| "power_19x20" | "power_hover_19x20"
|
||||||
|
| "Settings_14";
|
||||||
|
|
||||||
export type IconData = symbol & { "__tag__": "icon" };
|
export type IconData = symbol & { "__tag__": "icon" };
|
||||||
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
|
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
|
||||||
|
|||||||
@@ -24,24 +24,23 @@
|
|||||||
* ### View
|
* ### View
|
||||||
* In Flipper's terminology, a "View" is a fullscreen design element that
|
* In Flipper's terminology, a "View" is a fullscreen design element that
|
||||||
* assumes control over the entire viewport and all input events. Different
|
* assumes control over the entire viewport and all input events. Different
|
||||||
* types of views are available (not all of which are unfortunately currently
|
* types of views are available:
|
||||||
* implemented in JS):
|
|
||||||
* | View | Has JS adapter? |
|
* | View | Has JS adapter? |
|
||||||
* |----------------------|-----------------------|
|
* |----------------------|-----------------------|
|
||||||
* | `button_menu` | ❌ |
|
* | `button_menu` | ✅ |
|
||||||
* | `button_panel` | ❌ |
|
* | `button_panel` | ✅ |
|
||||||
* | `byte_input` | ✅ |
|
* | `byte_input` | ✅ |
|
||||||
* | `dialog_ex` | ✅ (as `dialog`) |
|
* | `dialog_ex` | ✅ (as `dialog`) |
|
||||||
* | `empty_screen` | ✅ |
|
* | `empty_screen` | ✅ |
|
||||||
* | `file_browser` | ✅ (as `file_picker`) |
|
* | `file_browser` | ✅ (as `file_picker`) |
|
||||||
* | `loading` | ✅ |
|
* | `loading` | ✅ |
|
||||||
* | `menu` | ❌ |
|
* | `menu` | ✅ |
|
||||||
* | `number_input` | ❌ |
|
* | `number_input` | ✅ |
|
||||||
* | `popup` | ❌ |
|
* | `popup` | ✅ |
|
||||||
* | `submenu` | ✅ |
|
* | `submenu` | ✅ |
|
||||||
* | `text_box` | ✅ |
|
* | `text_box` | ✅ |
|
||||||
* | `text_input` | ✅ |
|
* | `text_input` | ✅ |
|
||||||
* | `variable_item_list` | ❌ |
|
* | `variable_item_list` | ✅ (as `vi_list`) |
|
||||||
* | `widget` | ✅ |
|
* | `widget` | ✅ |
|
||||||
*
|
*
|
||||||
* In JS, each view has its own set of properties (or just "props"). The
|
* In JS, each view has its own set of properties (or just "props"). The
|
||||||
@@ -119,6 +118,9 @@
|
|||||||
|
|
||||||
import type { Contract } from "../event_loop";
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
export type Font = "primary" | "secondary" | "keyboard" | "big_numbers";
|
||||||
|
export type InputType = "press" | "release" | "short" | "long" | "repeat";
|
||||||
|
|
||||||
type Properties = { [K: string]: any };
|
type Properties = { [K: string]: any };
|
||||||
|
|
||||||
export declare class View<Props extends Properties, Child> {
|
export declare class View<Props extends Properties, Child> {
|
||||||
|
|||||||
38
applications/system/js_app/packages/fz-sdk/gui/menu.d.ts
vendored
Normal file
38
applications/system/js_app/packages/fz-sdk/gui/menu.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* A list of selectable entries consisting of an icon and a label.
|
||||||
|
*
|
||||||
|
* <img src="../images/menu.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let submenuView = require("gui/menu");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This module depends on the `gui` module, which in turn depends on the
|
||||||
|
* `event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
* recommended to conceptualize these modules first before using this one.
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
* For an example refer to the GUI example.
|
||||||
|
*
|
||||||
|
* # View props
|
||||||
|
* This view doesn't have any props.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @version API changed in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
import type { IconData } from "./icon";
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
type Child = { icon: IconData, label: string };
|
||||||
|
declare class Submenu extends View<Props, Child> {
|
||||||
|
chosen: Contract<number>;
|
||||||
|
}
|
||||||
|
declare class SubmenuFactory extends ViewFactory<Props, Child, Submenu> { }
|
||||||
|
declare const factory: SubmenuFactory;
|
||||||
|
export = factory;
|
||||||
44
applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts
vendored
Normal file
44
applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Displays a number input keyboard.
|
||||||
|
*
|
||||||
|
* <img src="../images/number_input.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let numberInputView = require("gui/number_input");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This module depends on the `gui` module, which in turn depends on the
|
||||||
|
* `event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
* recommended to conceptualize these modules first before using this one.
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
* For an example refer to the `gui.js` example script.
|
||||||
|
*
|
||||||
|
* # View props
|
||||||
|
* - `header`: Text displayed at the top of the screen
|
||||||
|
* - `minValue`: Minimum allowed numeric value
|
||||||
|
* - `maxValue`: Maximum allowed numeric value
|
||||||
|
* - `defaultValue`: Default numeric value
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string,
|
||||||
|
minValue: number,
|
||||||
|
maxValue: number,
|
||||||
|
defaultValue: number,
|
||||||
|
}
|
||||||
|
type Child = never;
|
||||||
|
declare class NumberInput extends View<Props, Child> {
|
||||||
|
input: Contract<number>;
|
||||||
|
}
|
||||||
|
declare class NumberInputFactory extends ViewFactory<Props, Child, NumberInput> { }
|
||||||
|
declare const factory: NumberInputFactory;
|
||||||
|
export = factory;
|
||||||
43
applications/system/js_app/packages/fz-sdk/gui/popup.d.ts
vendored
Normal file
43
applications/system/js_app/packages/fz-sdk/gui/popup.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Like a Dialog, but with a built-in timer.
|
||||||
|
*
|
||||||
|
* <img src="../images/popup.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let popupView = require("gui/popup");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This module depends on the `gui` module, which in turn depends on the
|
||||||
|
* `event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
* recommended to conceptualize these modules first before using this one.
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
* For an example refer to the `gui.js` example script.
|
||||||
|
*
|
||||||
|
* # View props
|
||||||
|
* - `header`: Text displayed in bold at the top of the screen
|
||||||
|
* - `text`: Text displayed in the middle of the string
|
||||||
|
* - `timeout`: Timeout, in milliseconds, after which the event will fire. The
|
||||||
|
* timer starts counting down when this property is assigned.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string,
|
||||||
|
text: string,
|
||||||
|
timeout: number,
|
||||||
|
}
|
||||||
|
type Child = never;
|
||||||
|
declare class Popup extends View<Props, Child> {
|
||||||
|
timeout: Contract;
|
||||||
|
}
|
||||||
|
declare class PopupFactory extends ViewFactory<Props, Child, Popup> { }
|
||||||
|
declare const factory: PopupFactory;
|
||||||
|
export = factory;
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
*
|
*
|
||||||
* # View props
|
* # View props
|
||||||
* - `header`: Text displayed at the top of the screen in bold
|
* - `header`: Text displayed at the top of the screen in bold
|
||||||
* - `items`: Array of selectable textual items
|
|
||||||
*
|
*
|
||||||
* @version Added in JS SDK 0.1
|
* @version Added in JS SDK 0.1
|
||||||
|
* @version API changed in JS SDK 0.4
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -29,9 +29,8 @@ import type { Contract } from "../event_loop";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
header: string,
|
header: string,
|
||||||
items: string[],
|
|
||||||
};
|
};
|
||||||
type Child = never;
|
type Child = string;
|
||||||
declare class Submenu extends View<Props, Child> {
|
declare class Submenu extends View<Props, Child> {
|
||||||
chosen: Contract<number>;
|
chosen: Contract<number>;
|
||||||
}
|
}
|
||||||
|
|||||||
38
applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts
vendored
Normal file
38
applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Displays a list of settings-like variable items.
|
||||||
|
*
|
||||||
|
* <img src="../images/vi_list.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let viListView = require("gui/vi_list");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This module depends on the `gui` module, which in turn depends on the
|
||||||
|
* `event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
* recommended to conceptualize these modules first before using this one.
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
* For an example refer to the `gui.js` example script.
|
||||||
|
*
|
||||||
|
* # View props
|
||||||
|
* This view doesn't have any props
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
type Child = { label: string, variants: string[] };
|
||||||
|
|
||||||
|
declare class ViList extends View<Props, Child> {
|
||||||
|
valueUpdate: Contract<{ itemIndex: number, valueIndex: number }>;
|
||||||
|
}
|
||||||
|
declare class ViListFactory extends ViewFactory<Props, Child, ViList> { }
|
||||||
|
declare const factory: ViListFactory;
|
||||||
|
export = factory;
|
||||||
@@ -24,4 +24,4 @@
|
|||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
"serialport": "^12.0.0"
|
"serialport": "^12.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ env.Append(
|
|||||||
File("float_tools.h"),
|
File("float_tools.h"),
|
||||||
File("value_index.h"),
|
File("value_index.h"),
|
||||||
File("tar/tar_archive.h"),
|
File("tar/tar_archive.h"),
|
||||||
|
File("str_buffer.h"),
|
||||||
File("stream/stream.h"),
|
File("stream/stream.h"),
|
||||||
File("stream/file_stream.h"),
|
File("stream/file_stream.h"),
|
||||||
File("stream/string_stream.h"),
|
File("stream/string_stream.h"),
|
||||||
|
|||||||
18
lib/toolbox/str_buffer.c
Normal file
18
lib/toolbox/str_buffer.c
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include "str_buffer.h"
|
||||||
|
|
||||||
|
const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str) {
|
||||||
|
char* owned = strdup(str);
|
||||||
|
buffer->n_owned_strings++;
|
||||||
|
buffer->owned_strings =
|
||||||
|
realloc(buffer->owned_strings, buffer->n_owned_strings * sizeof(const char*)); // -V701
|
||||||
|
buffer->owned_strings[buffer->n_owned_strings - 1] = owned;
|
||||||
|
return owned;
|
||||||
|
}
|
||||||
|
|
||||||
|
void str_buffer_clear_all_clones(StrBuffer* buffer) {
|
||||||
|
for(size_t i = 0; i < buffer->n_owned_strings; i++) {
|
||||||
|
free(buffer->owned_strings[i]);
|
||||||
|
}
|
||||||
|
free(buffer->owned_strings);
|
||||||
|
buffer->owned_strings = NULL;
|
||||||
|
}
|
||||||
47
lib/toolbox/str_buffer.h
Normal file
47
lib/toolbox/str_buffer.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* @file str_buffer.h
|
||||||
|
*
|
||||||
|
* Allows you to create an owned clone of however many strings that you need,
|
||||||
|
* then free all of them at once. Essentially the simplest possible append-only
|
||||||
|
* unindexable array of owned C-style strings.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief StrBuffer instance
|
||||||
|
*
|
||||||
|
* Place this struct directly wherever you want, it doesn't have to be `alloc`ed
|
||||||
|
* and `free`d.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char** owned_strings;
|
||||||
|
size_t n_owned_strings;
|
||||||
|
} StrBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Makes a owned duplicate of the provided string
|
||||||
|
*
|
||||||
|
* @param[in] buffer StrBuffer instance
|
||||||
|
* @param[in] str Input C-style string
|
||||||
|
*
|
||||||
|
* @returns C-style string that contains to be valid event after `str` becomes
|
||||||
|
* invalid
|
||||||
|
*/
|
||||||
|
const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clears all owned duplicates
|
||||||
|
*
|
||||||
|
* @param[in] buffer StrBuffer instance
|
||||||
|
*/
|
||||||
|
void str_buffer_clear_all_clones(StrBuffer* buffer);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -173,6 +173,7 @@ Header,+,lib/toolbox/protocols/protocol_dict.h,,
|
|||||||
Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,,
|
Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,,
|
||||||
Header,+,lib/toolbox/saved_struct.h,,
|
Header,+,lib/toolbox/saved_struct.h,,
|
||||||
Header,+,lib/toolbox/simple_array.h,,
|
Header,+,lib/toolbox/simple_array.h,,
|
||||||
|
Header,+,lib/toolbox/str_buffer.h,,
|
||||||
Header,+,lib/toolbox/stream/buffered_file_stream.h,,
|
Header,+,lib/toolbox/stream/buffered_file_stream.h,,
|
||||||
Header,+,lib/toolbox/stream/file_stream.h,,
|
Header,+,lib/toolbox/stream/file_stream.h,,
|
||||||
Header,+,lib/toolbox/stream/stream.h,,
|
Header,+,lib/toolbox/stream/stream.h,,
|
||||||
@@ -2632,6 +2633,8 @@ Function,+,storage_simply_remove,_Bool,"Storage*, const char*"
|
|||||||
Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*"
|
Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*"
|
||||||
Function,-,stpcpy,char*,"char*, const char*"
|
Function,-,stpcpy,char*,"char*, const char*"
|
||||||
Function,-,stpncpy,char*,"char*, const char*, size_t"
|
Function,-,stpncpy,char*,"char*, const char*, size_t"
|
||||||
|
Function,+,str_buffer_clear_all_clones,void,StrBuffer*
|
||||||
|
Function,+,str_buffer_make_owned_clone,const char*,"StrBuffer*, const char*"
|
||||||
Function,+,strcasecmp,int,"const char*, const char*"
|
Function,+,strcasecmp,int,"const char*, const char*"
|
||||||
Function,-,strcasecmp_l,int,"const char*, const char*, locale_t"
|
Function,-,strcasecmp_l,int,"const char*, const char*, locale_t"
|
||||||
Function,+,strcasestr,char*,"const char*, const char*"
|
Function,+,strcasestr,char*,"const char*, const char*"
|
||||||
|
|||||||
|
@@ -245,6 +245,7 @@ Header,+,lib/toolbox/protocols/protocol_dict.h,,
|
|||||||
Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,,
|
Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,,
|
||||||
Header,+,lib/toolbox/saved_struct.h,,
|
Header,+,lib/toolbox/saved_struct.h,,
|
||||||
Header,+,lib/toolbox/simple_array.h,,
|
Header,+,lib/toolbox/simple_array.h,,
|
||||||
|
Header,+,lib/toolbox/str_buffer.h,,
|
||||||
Header,+,lib/toolbox/stream/buffered_file_stream.h,,
|
Header,+,lib/toolbox/stream/buffered_file_stream.h,,
|
||||||
Header,+,lib/toolbox/stream/file_stream.h,,
|
Header,+,lib/toolbox/stream/file_stream.h,,
|
||||||
Header,+,lib/toolbox/stream/stream.h,,
|
Header,+,lib/toolbox/stream/stream.h,,
|
||||||
@@ -3322,6 +3323,8 @@ Function,+,storage_simply_remove,_Bool,"Storage*, const char*"
|
|||||||
Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*"
|
Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*"
|
||||||
Function,-,stpcpy,char*,"char*, const char*"
|
Function,-,stpcpy,char*,"char*, const char*"
|
||||||
Function,-,stpncpy,char*,"char*, const char*, size_t"
|
Function,-,stpncpy,char*,"char*, const char*, size_t"
|
||||||
|
Function,+,str_buffer_clear_all_clones,void,StrBuffer*
|
||||||
|
Function,+,str_buffer_make_owned_clone,const char*,"StrBuffer*, const char*"
|
||||||
Function,+,strcasecmp,int,"const char*, const char*"
|
Function,+,strcasecmp,int,"const char*, const char*"
|
||||||
Function,-,strcasecmp_l,int,"const char*, const char*, locale_t"
|
Function,-,strcasecmp_l,int,"const char*, const char*, locale_t"
|
||||||
Function,+,strcasestr,char*,"const char*, const char*"
|
Function,+,strcasestr,char*,"const char*, const char*"
|
||||||
|
|||||||
|
Reference in New Issue
Block a user