From 21abcb56fdb96a4fea568a0e326d7641787cd5b6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 19 May 2024 03:54:21 +0300 Subject: [PATCH] merge ofw dev missing parts --- .../js_app/examples/apps/Scripts/math.js | 110 ++++--- .../js_app/examples/apps/Scripts/textbox.js | 6 +- applications/system/js_app/modules/js_math.c | 270 ++++++++++-------- .../system/js_app/modules/js_submenu.c | 34 +-- .../system/js_app/modules/js_textbox.c | 102 ++++--- 5 files changed, 292 insertions(+), 230 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js index 49212f904..c5a0bf18d 100644 --- a/applications/system/js_app/examples/apps/Scripts/math.js +++ b/applications/system/js_app/examples/apps/Scripts/math.js @@ -1,47 +1,69 @@ let math = require("math"); -let absResult = math.abs(-5); -let acosResult = math.acos(0.5); -let acoshResult = math.acosh(2); -let asinResult = math.asin(0.5); -let asinhResult = math.asinh(2); -let atanResult = math.atan(1); -let atan2Result = math.atan2(1, 1); -let atanhResult = math.atanh(0.5); -let cbrtResult = math.cbrt(27); -let ceilResult = math.ceil(5.3); -let clz32Result = math.clz32(1); -let cosResult = math.cos(math.PI); -let expResult = math.exp(1); -let floorResult = math.floor(5.7); -let maxResult = math.max(3, 5); -let minResult = math.min(3, 5); -let powResult = math.pow(2, 3); -let randomResult = math.random(); -let signResult = math.sign(-5); -let sinResult = math.sin(math.PI / 2); -let sqrtResult = math.sqrt(25); -let truncResult = math.trunc(5.7); +print("math.abs(-5):", math.abs(-5)); +print("math.acos(0.5):", math.acos(0.5)); +print("math.acosh(2):", math.acosh(2)); +print("math.asin(0.5):", math.asin(0.5)); +print("math.asinh(2):", math.asinh(2)); +print("math.atan(1):", math.atan(1)); +print("math.atan2(1, 1):", math.atan2(1, 1)); +print("math.atanh(0.5):", math.atanh(0.5)); +print("math.cbrt(27):", math.cbrt(27)); +print("math.ceil(5.3):", math.ceil(5.3)); +print("math.clz32(1):", math.clz32(1)); +print("math.cos(math.PI):", math.cos(math.PI)); +print("math.exp(1):", math.exp(1)); +print("math.floor(5.7):", math.floor(5.7)); +print("math.max(3, 5):", math.max(3, 5)); +print("math.min(3, 5):", math.min(3, 5)); +print("math.pow(2, 3):", math.pow(2, 3)); +print("math.random():", math.random()); +print("math.sign(-5):", math.sign(-5)); +print("math.sin(math.PI/2):", math.sin(math.PI / 2)); +print("math.sqrt(25):", math.sqrt(25)); +print("math.trunc(5.7):", math.trunc(5.7)); -print("math.abs(-5):", absResult); -print("math.acos(0.5):", acosResult); -print("math.acosh(2):", acoshResult); -print("math.asin(0.5):", asinResult); -print("math.asinh(2):", asinhResult); -print("math.atan(1):", atanResult); -print("math.atan2(1, 1):", atan2Result); -print("math.atanh(0.5):", atanhResult); -print("math.cbrt(27):", cbrtResult); -print("math.ceil(5.3):", ceilResult); -print("math.clz32(1):", clz32Result); -print("math.cos(math.PI):", cosResult); -print("math.exp(1):", expResult); -print("math.floor(5.7):", floorResult); -print("math.max(3, 5):", maxResult); -print("math.min(3, 5):", minResult); -print("math.pow(2, 3):", powResult); -print("math.random():", randomResult); -print("math.sign(-5):", signResult); -print("math.sin(math.PI/2):", sinResult); -print("math.sqrt(25):", sqrtResult); -print("math.trunc(5.7):", truncResult); \ No newline at end of file +// Unit tests. Please add more if you have time and knowledge. +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +let succeeded = 0; +let failed = 0; + +function test(text, result, expected, epsilon) { + let is_equal = math.is_equal(result, expected, epsilon); + if (is_equal) { + succeeded += 1; + } else { + failed += 1; + print(text, "expected", expected, "got", result); + } +} + +test("math.abs(5)", math.abs(-5), 5, math.EPSILON); +test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON); +test("math.abs(5)", math.abs(5), 5, math.EPSILON); +test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON); +test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON); +test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON); +test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON); +test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON); +test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON); +test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON); +test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON); +test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON); +test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON); +test("math.clz32(1)", math.clz32(1), 31, math.EPSILON); +test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON); +test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON); +test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON); +test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON); +test("math.sign(-5)", math.sign(-5), -1, math.EPSILON); +test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON); +test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON); +test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15 +test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16 +test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +if (failed > 0) { + print("!!!", failed, "Unit tests failed !!!"); +} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/textbox.js index bb6c4fc23..6caf37234 100644 --- a/applications/system/js_app/examples/apps/Scripts/textbox.js +++ b/applications/system/js_app/examples/apps/Scripts/textbox.js @@ -4,9 +4,9 @@ let textbox = require("textbox"); // Focus (start / end), Font (text / hex) textbox.setConfig("end", "text"); -// Can make sure it's empty before showing, in case of reusing in same script -// (Closing textbox already empties the text, but maybe you added more in a loop for example) -textbox.emptyText(); +// Can make sure it's cleared before showing, in case of reusing in same script +// (Closing textbox already clears the text, but maybe you added more in a loop for example) +textbox.clearText(); // Add default text textbox.addText("Example dynamic updating textbox\n"); diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index e7daae41b..b4c1cdca2 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -1,268 +1,313 @@ #include "../js_modules.h" #include "furi_hal_random.h" +#include -#define JS_MATH_PI (double)3.14159265358979323846 -#define JS_MATH_E (double)2.7182818284590452354 +#define JS_MATH_PI ((double)M_PI) +#define JS_MATH_E ((double)M_E) +#define JS_MATH_EPSILON ((double)DBL_EPSILON) + +#define TAG "JsMath" static void ret_bad_args(struct mjs* mjs, const char* error) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, mjs_mk_undefined()); + mjs_return(mjs, MJS_UNDEFINED); } -static bool check_arg_count(struct mjs* mjs, size_t count) { +static bool check_args(struct mjs* mjs, size_t count) { size_t num_args = mjs_nargs(mjs); if(num_args != count) { ret_bad_args(mjs, "Wrong argument count"); return false; } + for(size_t i = 0; i < count; i++) { + if(!mjs_is_number(mjs_arg(mjs, i))) { + ret_bad_args(mjs, "Wrong argument type"); + return false; + } + } return true; } -void js_math_abs(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); +void js_math_is_equal(struct mjs* mjs) { + if(!check_args(mjs, 3)) { + return; } + + double a = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double b = mjs_get_double(mjs, mjs_arg(mjs, 1)); + double e = mjs_get_double(mjs, mjs_arg(mjs, 2)); + double f = fabs(a - b); + + mjs_return(mjs, mjs_mk_boolean(mjs, (f <= e))); +} + +void js_math_abs(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, x < 0 ? mjs_mk_number(mjs, -x) : mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, fabs(x))); } void js_math_acos(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < -1 || x > 1) { - ret_bad_args(mjs, "Invalid input value for Math.acos"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acos"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, JS_MATH_PI / (double)2 - atan(x / sqrt(1 - x * x)))); + + mjs_return(mjs, mjs_mk_number(mjs, acos(x))); } void js_math_acosh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < 1) { - ret_bad_args(mjs, "Invalid input value for Math.acosh"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acosh"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - 1)))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - (double)1.)))); } void js_math_asin(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, atan(x / sqrt(1 - x * x)))); + + mjs_return(mjs, mjs_mk_number(mjs, asin(x))); } void js_math_asinh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + 1)))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + (double)1.)))); } void js_math_atan(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, atan(x))); } void js_math_atan2(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double y = mjs_get_double(mjs, mjs_arg(mjs, 0)); double x = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x))); } void js_math_atanh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x <= -1 || x >= 1) { - ret_bad_args(mjs, "Invalid input value for Math.atanh"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.atanh"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log((1 + x) / (1 - x)))); + + mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log(((double)1. + x) / ((double)1. - x)))); } void js_math_cbrt(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, pow(x, 1.0 / 3.0))); + + mjs_return(mjs, mjs_mk_number(mjs, cbrt(x))); } void js_math_ceil(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, (int)(x + (double)0.5))); + mjs_return(mjs, mjs_mk_number(mjs, ceil(x))); } void js_math_clz32(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0)); int count = 0; while(x) { x >>= 1; count++; } + mjs_return(mjs, mjs_mk_number(mjs, 32 - count)); } void js_math_cos(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, cos(x))); } void js_math_exp(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - double result = 1; - double term = 1; - for(int i = 1; i < 100; i++) { - term *= x / i; - result += term; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, exp(x))); } void js_math_floor(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, (int)x)); + + mjs_return(mjs, mjs_mk_number(mjs, floor(x))); } void js_math_log(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); if(x <= 0) { - ret_bad_args(mjs, "Invalid input value for Math.log"); - mjs_return(mjs, MJS_UNDEFINED); + ret_bad_args(mjs, "Invalid input value for math.log"); + return; } - double result = 0; - while(x >= JS_MATH_E) { - x /= JS_MATH_E; - result++; - } - mjs_return(mjs, mjs_mk_number(mjs, result + log(x))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x))); } void js_math_max(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y)); } void js_math_min(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y)); } void js_math_pow(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double base = mjs_get_double(mjs, mjs_arg(mjs, 0)); double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1)); - double result = 1; - for(int i = 0; i < exponent; i++) { - result *= base; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, pow(base, exponent))); } void js_math_random(struct mjs* mjs) { - if(!check_arg_count(mjs, 0)) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 0)) { + return; } + + // double clearly provides more bits for entropy then we pack + // 32bit should be enough for now, but fix it maybe const uint32_t random_val = furi_hal_random_get(); - double rnd = (double)random_val / FURI_HAL_RANDOM_MAX; + double rnd = (double)random_val / (double)FURI_HAL_RANDOM_MAX; + mjs_return(mjs, mjs_mk_number(mjs, rnd)); } void js_math_sign(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, x == 0 ? 0 : (x < 0 ? -1 : 1))); + + mjs_return( + mjs, + mjs_mk_number( + mjs, fabs(x) <= JS_MATH_EPSILON ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); } void js_math_sin(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - double result = x; - double term = x; - for(int i = 1; i < 10; i++) { - term *= -x * x / ((2 * i) * (2 * i + 1)); - result += term; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, sin(x))); } void js_math_sqrt(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < 0) { - ret_bad_args(mjs, "Invalid input value for Math.sqrt"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)0.) { + ret_bad_args(mjs, "Invalid input value for math.sqrt"); + return; } - double result = 1; - while(result * result < x) { - result += (double)0.001; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, sqrt(x))); } void js_math_trunc(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, x < 0 ? ceil(x) : floor(x))); + + mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); } static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t math_obj = mjs_mk_object(mjs); + mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos)); mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh)); @@ -288,6 +333,7 @@ static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc)); mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI)); mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E)); + mjs_set(mjs, math_obj, "EPSILON", ~0, mjs_mk_number(mjs, JS_MATH_EPSILON)); *object = math_obj; return (void*)1; } @@ -306,4 +352,4 @@ static const FlipperAppPluginDescriptor plugin_descriptor = { const FlipperAppPluginDescriptor* js_math_ep(void) { return &plugin_descriptor; -} \ No newline at end of file +} diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c index d66a7d24d..058b32fd0 100644 --- a/applications/system/js_app/modules/js_submenu.c +++ b/applications/system/js_app/modules/js_submenu.c @@ -1,11 +1,13 @@ #include -#include +#include #include +#include #include "../js_modules.h" typedef struct { Submenu* submenu; - ViewDispatcher* view_dispatcher; + ViewHolder* view_holder; + FuriApiLock lock; uint32_t result; bool accepted; } JsSubmenuInst; @@ -35,15 +37,14 @@ static void submenu_callback(void* context, uint32_t id) { JsSubmenuInst* submenu = context; submenu->result = id; submenu->accepted = true; - view_dispatcher_stop(submenu->view_dispatcher); + api_lock_unlock(submenu->lock); } -static bool submenu_exit(void* context) { +static void submenu_exit(void* context) { JsSubmenuInst* submenu = context; submenu->result = 0; submenu->accepted = false; - view_dispatcher_stop(submenu->view_dispatcher); - return true; + api_lock_unlock(submenu->lock); } static void js_submenu_add_item(struct mjs* mjs) { @@ -89,21 +90,20 @@ static void js_submenu_show(struct mjs* mjs) { JsSubmenuInst* submenu = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; + submenu->lock = api_lock_alloc_locked(); Gui* gui = furi_record_open(RECORD_GUI); - submenu->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(submenu->view_dispatcher); - view_dispatcher_add_view(submenu->view_dispatcher, 0, submenu_get_view(submenu->submenu)); - view_dispatcher_set_event_callback_context(submenu->view_dispatcher, submenu); - view_dispatcher_set_navigation_event_callback(submenu->view_dispatcher, submenu_exit); - view_dispatcher_attach_to_gui(submenu->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(submenu->view_dispatcher, 0); + submenu->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(submenu->view_holder, gui); + view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu); - view_dispatcher_run(submenu->view_dispatcher); + view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu)); + view_holder_start(submenu->view_holder); + api_lock_wait_unlock(submenu->lock); - view_dispatcher_remove_view(submenu->view_dispatcher, 0); - view_dispatcher_free(submenu->view_dispatcher); - submenu->view_dispatcher = NULL; + view_holder_stop(submenu->view_holder); + view_holder_free(submenu->view_holder); furi_record_close(RECORD_GUI); + api_lock_free(submenu->lock); submenu_reset(submenu->submenu); if(submenu->accepted) { diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c index cf4e8dbbf..d15cd2779 100644 --- a/applications/system/js_app/modules/js_textbox.c +++ b/applications/system/js_app/modules/js_textbox.c @@ -1,13 +1,12 @@ #include -#include -#include +#include #include "../js_modules.h" typedef struct { TextBox* text_box; - ViewDispatcher* view_dispatcher; - FuriThread* thread; + ViewHolder* view_holder; FuriString* text; + bool is_shown; } JsTextboxInst; static JsTextboxInst* get_this_ctx(struct mjs* mjs) { @@ -87,6 +86,9 @@ static void js_textbox_add_text(struct mjs* mjs) { return; } + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + size_t new_len = furi_string_size(textbox->text) + text_len; if(new_len >= 4096) { furi_string_right(textbox->text, new_len / 2); @@ -99,10 +101,13 @@ static void js_textbox_add_text(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void js_textbox_empty_text(struct mjs* mjs) { +static void js_textbox_clear_text(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + furi_string_reset(textbox->text); text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); @@ -114,58 +119,34 @@ static void js_textbox_is_open(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - mjs_return(mjs, mjs_mk_boolean(mjs, !!textbox->thread)); -} - -static void textbox_deinit(void* context) { - JsTextboxInst* textbox = context; - furi_thread_join(textbox->thread); - furi_thread_free(textbox->thread); - textbox->thread = NULL; - - view_dispatcher_remove_view(textbox->view_dispatcher, 0); - view_dispatcher_free(textbox->view_dispatcher); - textbox->view_dispatcher = NULL; - furi_record_close(RECORD_GUI); - - text_box_reset(textbox->text_box); - furi_string_reset(textbox->text); + mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); } static void textbox_callback(void* context, uint32_t arg) { UNUSED(arg); - textbox_deinit(context); -} - -static bool textbox_exit(void* context) { JsTextboxInst* textbox = context; - view_dispatcher_stop(textbox->view_dispatcher); - furi_timer_pending_callback(textbox_callback, textbox, 0); - return true; + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; } -static int32_t textbox_thread(void* context) { - ViewDispatcher* view_dispatcher = context; - view_dispatcher_run(view_dispatcher); - return 0; +static void textbox_exit(void* context) { + JsTextboxInst* textbox = context; + // Using timer to schedule view_holder stop, will not work under high CPU load + furi_timer_pending_callback(textbox_callback, textbox, 0); } static void js_textbox_show(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - Gui* gui = furi_record_open(RECORD_GUI); - textbox->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(textbox->view_dispatcher); - view_dispatcher_add_view(textbox->view_dispatcher, 0, text_box_get_view(textbox->text_box)); - view_dispatcher_set_event_callback_context(textbox->view_dispatcher, textbox); - view_dispatcher_set_navigation_event_callback(textbox->view_dispatcher, textbox_exit); - view_dispatcher_attach_to_gui(textbox->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(textbox->view_dispatcher, 0); + if(textbox->is_shown) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } - textbox->thread = - furi_thread_alloc_ex("JsTextbox", 1024, textbox_thread, textbox->view_dispatcher); - furi_thread_start(textbox->thread); + view_holder_start(textbox->view_holder); + textbox->is_shown = true; mjs_return(mjs, MJS_UNDEFINED); } @@ -174,36 +155,49 @@ static void js_textbox_close(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - if(textbox->thread) { - view_dispatcher_stop(textbox->view_dispatcher); - textbox_deinit(textbox); - } + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; mjs_return(mjs, MJS_UNDEFINED); } static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); + mjs_val_t textbox_obj = mjs_mk_object(mjs); mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); - mjs_set(mjs, textbox_obj, "emptyText", ~0, MJS_MK_FN(js_textbox_empty_text)); + mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); - textbox->text_box = text_box_alloc(); + textbox->text = furi_string_alloc(); + textbox->text_box = text_box_alloc(); + + Gui* gui = furi_record_open(RECORD_GUI); + textbox->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(textbox->view_holder, gui); + view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); + view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); + *object = textbox_obj; return textbox; } static void js_textbox_destroy(void* inst) { JsTextboxInst* textbox = inst; - if(textbox->thread) { - view_dispatcher_stop(textbox->view_dispatcher); - textbox_deinit(textbox); - } + + view_holder_stop(textbox->view_holder); + view_holder_free(textbox->view_holder); + textbox->view_holder = NULL; + + furi_record_close(RECORD_GUI); + + text_box_reset(textbox->text_box); + furi_string_reset(textbox->text); + text_box_free(textbox->text_box); furi_string_free(textbox->text); free(textbox); @@ -223,4 +217,4 @@ static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { const FlipperAppPluginDescriptor* js_textbox_ep(void) { return &textbox_plugin_descriptor; -} +} \ No newline at end of file