From 113711d414c358e442467e0f42c7f96226e889b1 Mon Sep 17 00:00:00 2001 From: philmoz Date: Tue, 18 Feb 2025 16:10:51 +1100 Subject: [PATCH 01/11] Make current Lua script owner visible to API functions. Send path name of widget folder to script 'create()' function. Enhancements for widgets option default values, getSwitchIndex() and getSourceIndex() Lua function. --- .../gui/colorlcd/libui/list_line_button.cpp | 2 +- radio/src/gui/colorlcd/mainview/layout.h | 2 +- .../colorlcd/mainview/layout_factory_impl.h | 2 +- .../gui/colorlcd/mainview/screen_setup.cpp | 2 +- radio/src/gui/colorlcd/mainview/widget.cpp | 9 +- radio/src/gui/colorlcd/mainview/widget.h | 6 +- .../gui/colorlcd/mainview/widget_settings.cpp | 3 +- .../gui/colorlcd/mainview/widgets_setup.cpp | 4 +- radio/src/gui/colorlcd/standalone_lua.cpp | 12 +- radio/src/gui/colorlcd/standalone_lua.h | 2 +- radio/src/lua/api_colorlcd.cpp | 5 +- radio/src/lua/api_colorlcd_lvgl.cpp | 48 ++-- radio/src/lua/api_general.cpp | 47 +--- radio/src/lua/lua_api.h | 6 - radio/src/lua/lua_lvgl_widget.cpp | 38 +-- radio/src/lua/lua_lvgl_widget.h | 4 +- radio/src/lua/lua_widget.cpp | 39 ++-- radio/src/lua/lua_widget.h | 60 ++--- radio/src/lua/lua_widget_factory.cpp | 216 +++++++++++++++++- radio/src/lua/lua_widget_factory.h | 13 +- radio/src/lua/widgets.cpp | 146 ++---------- radio/src/strhelpers.cpp | 130 ++++++++--- radio/src/strhelpers.h | 30 +-- 23 files changed, 479 insertions(+), 347 deletions(-) diff --git a/radio/src/gui/colorlcd/libui/list_line_button.cpp b/radio/src/gui/colorlcd/libui/list_line_button.cpp index fcfe7b96482..b32369cd3d4 100644 --- a/radio/src/gui/colorlcd/libui/list_line_button.cpp +++ b/radio/src/gui/colorlcd/libui/list_line_button.cpp @@ -109,7 +109,7 @@ void InputMixButtonBase::setSource(mixsrc_t idx) else lv_obj_clear_state(source, LV_STATE_USER_1); - lv_label_set_text(source, s); + lv_label_set_text(source, s); } void InputMixButtonBase::setOpts(const char* s) diff --git a/radio/src/gui/colorlcd/mainview/layout.h b/radio/src/gui/colorlcd/mainview/layout.h index ec3856b241b..e2561d815b7 100644 --- a/radio/src/gui/colorlcd/mainview/layout.h +++ b/radio/src/gui/colorlcd/mainview/layout.h @@ -49,7 +49,7 @@ class LayoutFactory virtual const uint8_t* getBitmap() const = 0; - virtual const ZoneOption* getOptions() const = 0; + virtual const ZoneOption* getLayoutOptions() const = 0; virtual WidgetsContainer* create( Window* parent, LayoutPersistentData* persistentData) const = 0; diff --git a/radio/src/gui/colorlcd/mainview/layout_factory_impl.h b/radio/src/gui/colorlcd/mainview/layout_factory_impl.h index c16ef321db3..7f44857231a 100644 --- a/radio/src/gui/colorlcd/mainview/layout_factory_impl.h +++ b/radio/src/gui/colorlcd/mainview/layout_factory_impl.h @@ -182,7 +182,7 @@ class BaseLayoutFactory: public LayoutFactory const uint8_t* getBitmap() const override { return bitmap; } - const ZoneOption * getOptions() const override + const ZoneOption * getLayoutOptions() const override { return options; } diff --git a/radio/src/gui/colorlcd/mainview/screen_setup.cpp b/radio/src/gui/colorlcd/mainview/screen_setup.cpp index dc0a912c610..1aea14711c9 100644 --- a/radio/src/gui/colorlcd/mainview/screen_setup.cpp +++ b/radio/src/gui/colorlcd/mainview/screen_setup.cpp @@ -327,7 +327,7 @@ void ScreenSetupPage::buildLayoutOptions() if (!factory) return; int index = 0; - for (auto* option = factory->getOptions(); option->name; option++, index++) { + for (auto* option = factory->getLayoutOptions(); option->name; option++, index++) { auto layoutData = &g_model.screenData[customScreenIndex].layoutData; ZoneOptionValue* value = &layoutData->options[index].value; diff --git a/radio/src/gui/colorlcd/mainview/widget.cpp b/radio/src/gui/colorlcd/mainview/widget.cpp index dd8f57f9519..22a4d9129e3 100644 --- a/radio/src/gui/colorlcd/mainview/widget.cpp +++ b/radio/src/gui/colorlcd/mainview/widget.cpp @@ -57,13 +57,13 @@ void Widget::openMenu() return; } - if (getOptions() || fsAllowed) { + if (hasOptions() || fsAllowed) { Menu* menu = new Menu(); menu->setTitle(getFactory()->getDisplayName()); if (fsAllowed) { menu->addLine(STR_WIDGET_FULLSCREEN, [&]() { setFullscreen(true); }); } - if (getOptions() && getOptions()->name) { + if (hasOptions()) { menu->addLine(STR_WIDGET_SETTINGS, [=]() { new WidgetSettings(this); }); } @@ -146,9 +146,9 @@ bool Widget::onLongPress() return true; } -const ZoneOption* Widget::getOptions() const +const ZoneOption* Widget::getOptionDefinitions() const { - return getFactory()->getOptions(); + return getFactory()->getDefaultOptions(); } void Widget::enableFocus(bool enable) @@ -253,6 +253,7 @@ void WidgetFactory::initPersistentData(Widget::PersistentData* persistentData, { if (setDefault) { memset(persistentData, 0, sizeof(Widget::PersistentData)); + parseOptionDefaults(); } if (options) { int i = 0; diff --git a/radio/src/gui/colorlcd/mainview/widget.h b/radio/src/gui/colorlcd/mainview/widget.h index 67738955082..039c678d5d3 100644 --- a/radio/src/gui/colorlcd/mainview/widget.h +++ b/radio/src/gui/colorlcd/mainview/widget.h @@ -42,7 +42,8 @@ class Widget : public ButtonBase const WidgetFactory* getFactory() const { return factory; } - const ZoneOption* getOptions() const; + const ZoneOption* getOptionDefinitions() const; + bool hasOptions() const { return getOptionDefinitions() && getOptionDefinitions()->name; } virtual const char* getErrorMessage() const { return nullptr; } @@ -117,7 +118,8 @@ class WidgetFactory const char* getName() const { return name; } - const ZoneOption* getOptions() const { return options; } + const ZoneOption* getDefaultOptions() const { return options; } + virtual const void parseOptionDefaults() const {} const char* getDisplayName() const { diff --git a/radio/src/gui/colorlcd/mainview/widget_settings.cpp b/radio/src/gui/colorlcd/mainview/widget_settings.cpp index 927cd073e92..d1f2aa5b171 100644 --- a/radio/src/gui/colorlcd/mainview/widget_settings.cpp +++ b/radio/src/gui/colorlcd/mainview/widget_settings.cpp @@ -49,7 +49,8 @@ WidgetSettings::WidgetSettings(Widget* w) : FlexGridLayout grid(line_col_dsc, line_row_dsc, PAD_TINY); uint8_t optIdx = 0; - auto opt = widget->getOptions(); + auto opt = widget->getOptionDefinitions(); + while (opt && opt->name != nullptr) { auto line = form->newLine(grid); diff --git a/radio/src/gui/colorlcd/mainview/widgets_setup.cpp b/radio/src/gui/colorlcd/mainview/widgets_setup.cpp index 9acef26110b..ed0e2ea4516 100644 --- a/radio/src/gui/colorlcd/mainview/widgets_setup.cpp +++ b/radio/src/gui/colorlcd/mainview/widgets_setup.cpp @@ -41,7 +41,7 @@ SetupWidgetsPageSlot::SetupWidgetsPageSlot(Window* parent, const rect_t& rect, menu->addLine(STR_SELECT_WIDGET, [=]() { addNewWidget(container, slotIndex); }); auto widget = container->getWidget(slotIndex); - if (widget->getOptions() && widget->getOptions()->name) + if (widget->hasOptions()) menu->addLine(STR_WIDGET_SETTINGS, [=]() { new WidgetSettings(widget); }); menu->addLine(STR_REMOVE_WIDGET, @@ -103,7 +103,7 @@ void SetupWidgetsPageSlot::addNewWidget(WidgetsContainer* container, menu->addLine(factory->getDisplayName(), [=]() { container->createWidget(slotIndex, factory); auto widget = container->getWidget(slotIndex); - if (widget->getOptions() && widget->getOptions()->name) + if (widget->hasOptions()) new WidgetSettings(widget); }); if (cur && strcmp(cur, factory->getDisplayName()) == 0) diff --git a/radio/src/gui/colorlcd/standalone_lua.cpp b/radio/src/gui/colorlcd/standalone_lua.cpp index 3c649ee9f8c..036007a6bee 100644 --- a/radio/src/gui/colorlcd/standalone_lua.cpp +++ b/radio/src/gui/colorlcd/standalone_lua.cpp @@ -133,6 +133,8 @@ StandaloneLuaWindow::StandaloneLuaWindow(bool useLvgl, int initFn, int runFn) : etx_solid_bg(lvobj); + luaScriptManager = this; + if (useLvglLayout()) { padAll(PAD_ZERO); etx_scrollbar(lvobj); @@ -146,8 +148,6 @@ StandaloneLuaWindow::StandaloneLuaWindow(bool useLvgl, int initFn, int runFn) : lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); lv_obj_set_style_pad_top(lbl, (LCD_H - EdgeTxStyles::PAGE_LINE_HEIGHT) / 2, LV_PART_MAIN); lv_label_set_text(lbl, STR_LOADING); - - luaLvglManager = this; } else { lcdBuffer = new BitmapBuffer(BMP_RGB565, LCD_W, LCD_H); @@ -218,7 +218,7 @@ void StandaloneLuaWindow::deleteLater(bool detach, bool trash) if (lcdBuffer) delete lcdBuffer; lcdBuffer = nullptr; - luaLvglManager = nullptr; + luaScriptManager = nullptr; Layer::pop(this); Layer::back()->show(); @@ -320,13 +320,13 @@ void StandaloneLuaWindow::checkEvents() luaLcdAllowed = false; } -void StandaloneLuaWindow::onClicked() { Keyboard::hide(false); LuaEventHandler::onClicked(); } +void StandaloneLuaWindow::onClicked() { Keyboard::hide(false); LuaScriptManager::onClickedEvent(); } -void StandaloneLuaWindow::onCancel() { LuaEventHandler::onCancel(); } +void StandaloneLuaWindow::onCancel() { LuaScriptManager::onCancelEvent(); } void StandaloneLuaWindow::onEvent(event_t evt) { - LuaEventHandler::onLuaEvent(evt); + LuaScriptManager::onLuaEvent(evt); } void StandaloneLuaWindow::popupPaint(BitmapBuffer* dc, coord_t x, coord_t y, coord_t w, coord_t h, diff --git a/radio/src/gui/colorlcd/standalone_lua.h b/radio/src/gui/colorlcd/standalone_lua.h index a4ac6f32e33..dee8f0ba479 100644 --- a/radio/src/gui/colorlcd/standalone_lua.h +++ b/radio/src/gui/colorlcd/standalone_lua.h @@ -28,7 +28,7 @@ extern void luaExecStandalone(const char * filename); -class StandaloneLuaWindow : public Window, public LuaEventHandler, public LuaLvglManager +class StandaloneLuaWindow : public Window, public LuaScriptManager { static StandaloneLuaWindow* _instance; diff --git a/radio/src/lua/api_colorlcd.cpp b/radio/src/lua/api_colorlcd.cpp index d0392e1aee5..f4f6ca2dfb1 100644 --- a/radio/src/lua/api_colorlcd.cpp +++ b/radio/src/lua/api_colorlcd.cpp @@ -36,7 +36,6 @@ #define BITMAP_METATABLE "BITMAP*" BitmapBuffer* luaLcdBuffer = nullptr; -LuaWidget *runningFS = nullptr; /*luadoc @function lcd.refresh() @@ -1386,8 +1385,8 @@ Exit full screen widget mode. */ static int luaLcdExitFullScreen(lua_State *L) { - if (runningFS) - runningFS->closeFullscreen(); + if (luaScriptManager) + luaScriptManager->exitFullscreen(); return 0; } diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index 5e910cd0062..ae973ac7689 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -28,11 +28,9 @@ #include "api_colorlcd.h" -LuaLvglManager *luaLvglManager = nullptr; - static int luaLvglObj(lua_State *L, std::function create, bool fullscreenOnly = false) { - if (luaLvglManager && (!fullscreenOnly || luaLvglManager->isFullscreen())) { + if (luaScriptManager && (!fullscreenOnly || luaScriptManager->isFullscreen())) { auto obj = create(); obj->getParams(L, 1); obj->build(L); @@ -46,14 +44,14 @@ static int luaLvglObj(lua_State *L, std::function create, b static int luaLvglObjEx(lua_State *L, std::function create, bool fullscreenOnly = false) { - if (luaLvglManager && (!fullscreenOnly || luaLvglManager->isFullscreen())) { + if (luaScriptManager && (!fullscreenOnly || luaScriptManager->isFullscreen())) { LvglWidgetObjectBase* p = nullptr; LvglWidgetObjectBase* prevParent = nullptr; if (lua_gettop(L) == 2) { p = LvglWidgetObjectBase::checkLvgl(L, 1); if (p) { - prevParent = luaLvglManager->getTempParent(); - luaLvglManager->setTempParent(p); + prevParent = luaScriptManager->getTempParent(); + luaScriptManager->setTempParent(p); } } @@ -63,7 +61,7 @@ static int luaLvglObjEx(lua_State *L, std::function cre obj->push(L); if (p) - luaLvglManager->setTempParent((prevParent)); + luaScriptManager->setTempParent((prevParent)); } else { lua_pushnil(L); } @@ -100,14 +98,14 @@ static int luaLvglSet(lua_State *L) static int luaLvglClear(lua_State *L) { - if (luaLvglManager) { + if (luaScriptManager) { if (lua_gettop(L) == 1) { auto p = LvglWidgetObjectBase::checkLvgl(L, 1); if (p) { p->clear(); } } else { - luaLvglManager->clear(); + luaScriptManager->clear(); } } @@ -201,7 +199,7 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) obj = new LvglWidgetQRCode(); else if (strcasecmp(p.type, "box") == 0) obj = new LvglWidgetBox(); - else if (luaLvglManager->isFullscreen()) { + else if (luaScriptManager->isFullscreen()) { if (strcasecmp(p.type, "button") == 0) obj = new LvglWidgetTextButton(); if (strcasecmp(p.type, "momentaryButton") == 0) @@ -246,11 +244,11 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) } if (p.hasChildren && obj->getWindow()) { lua_getfield(L, -1, "children"); - auto prevParent = luaLvglManager->getTempParent(); - luaLvglManager->setTempParent(obj); + auto prevParent = luaScriptManager->getTempParent(); + luaScriptManager->setTempParent(obj); buildLvgl(L, -1, refIndex - 3); lua_pop(L, 1); - luaLvglManager->setTempParent(prevParent); + luaScriptManager->setTempParent(prevParent); } } } @@ -258,14 +256,14 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) static int luaLvglBuild(lua_State *L) { - if (luaLvglManager) { + if (luaScriptManager) { LvglWidgetObjectBase* p = nullptr; LvglWidgetObjectBase* prevParent = nullptr; if (lua_gettop(L) == 2) { p = LvglWidgetObjectBase::checkLvgl(L, 1); if (p) { - prevParent = luaLvglManager->getTempParent(); - luaLvglManager->setTempParent(p); + prevParent = luaScriptManager->getTempParent(); + luaScriptManager->setTempParent(p); } } @@ -274,7 +272,7 @@ static int luaLvglBuild(lua_State *L) buildLvgl(L, -2, -1); if (p) - luaLvglManager->setTempParent((prevParent)); + luaScriptManager->setTempParent((prevParent)); } else { lua_pushnil(L); } @@ -283,8 +281,8 @@ static int luaLvglBuild(lua_State *L) static int luaLvglIsAppMode(lua_State *L) { - if (luaLvglManager) { - lua_pushboolean(L, luaLvglManager->isAppMode()); + if (luaScriptManager) { + lua_pushboolean(L, luaScriptManager->isAppMode()); } else { lua_pushboolean(L, false); } @@ -293,8 +291,8 @@ static int luaLvglIsAppMode(lua_State *L) static int luaLvglIsFullscreen(lua_State *L) { - if (luaLvglManager) { - lua_pushboolean(L, luaLvglManager->isFullscreen()); + if (luaScriptManager) { + lua_pushboolean(L, luaScriptManager->isFullscreen()); } else { lua_pushboolean(L, false); } @@ -303,16 +301,16 @@ static int luaLvglIsFullscreen(lua_State *L) static int luaLvglExitFullscreen(lua_State *L) { - if (luaLvglManager) - luaLvglManager->exitFullscreen(); + if (luaScriptManager) + luaScriptManager->exitFullscreen(); return 0; } static int luaLvglGetContext(lua_State *L) { - if (luaLvglManager && luaLvglManager->getContext() != LUA_REFNIL) { + if (luaScriptManager && luaScriptManager->getContext() != LUA_REFNIL) { // Push context tanle onto Lua stack (return object) - lua_rawgeti(L, LUA_REGISTRYINDEX, luaLvglManager->getContext()); + lua_rawgeti(L, LUA_REGISTRYINDEX, luaScriptManager->getContext()); } else { lua_pushnil(L); } diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 161801362a6..39a1d074543 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -2193,8 +2193,8 @@ Get percent of already used Lua instructions in current script execution cycle. static int luaGetUsage(lua_State * L) { #if defined(COLORLCD) - if (luaLvglManager && luaLvglManager->useLvglLayout()) { - lua_pushinteger(L, luaLvglManager->refreshInstructionsPercent); + if (luaScriptManager && luaScriptManager->useLvglLayout()) { + lua_pushinteger(L, luaScriptManager->refreshInstructionsPercent); } else { lua_pushinteger(L, instructionsPercent); } @@ -2592,30 +2592,10 @@ to the fields in the table returned by `model.getLogicalSwitch(switch)` identify static int luaGetSwitchIndex(lua_State * L) { const char * name = luaL_checkstring(L, 1); - bool negate = false; - bool found = false; - swsrc_t idx; + swsrc_t idx = getSwitchIndex(name, true); - if (name[0] == '!') { - name++; - negate = true; - } - - for (idx = SWSRC_NONE; idx < SWSRC_COUNT; idx++) { - if (isSwitchAvailable(idx, ModelCustomFunctionsContext)) { - char* s = getSwitchPositionName(idx); - if (!strncasecmp(s, name, 31)) { - found = true; - break; - } - } - } - - if (found) { - if (negate) - idx = -idx; + if (idx != SWSRC_INVERT) lua_pushinteger(L, idx); - } else lua_pushnil(L); @@ -2715,8 +2695,7 @@ static int luaSwitches(lua_State * L) } else last = SWSRC_LAST; - lua_pushcfunction(L, luaNextSwitch); - lua_pushinteger(L, last); + lua_pushcfunction(L, luaNextSwitch); lua_pushinteger(L, last); lua_pushinteger(L, first); return 3; } @@ -2737,21 +2716,9 @@ This function is rather time consuming, and should not be used repeatedly in a s static int luaGetSourceIndex(lua_State* const L) { const char* const name = luaL_checkstring(L, 1); - bool found = false; - mixsrc_t idx; - - for (idx = MIXSRC_NONE; idx <= MIXSRC_LAST_TELEM; idx++) { - if (isSourceAvailable(idx)) { - char srcName[maxSourceNameLength]; - getSourceString(srcName, idx); - if (!strncasecmp(srcName, name)) { - found = true; - break; - } - } - } + mixsrc_t idx = getSourceIndex(name, true); - if (found) + if (idx >= 0) lua_pushinteger(L, idx); else lua_pushnil(L); diff --git a/radio/src/lua/lua_api.h b/radio/src/lua/lua_api.h index 4d408f746da..f3c922c0f2e 100644 --- a/radio/src/lua/lua_api.h +++ b/radio/src/lua/lua_api.h @@ -61,12 +61,6 @@ extern bool luaLcdAllowed; class BitmapBuffer; extern BitmapBuffer* luaLcdBuffer; -class LuaWidget; -extern LuaWidget* runningFS; - -class LuaLvglManager; -extern LuaLvglManager* luaLvglManager; - extern uint32_t luaExtraMemoryUsage; void luaInitThemesAndWidgets(); #endif diff --git a/radio/src/lua/lua_lvgl_widget.cpp b/radio/src/lua/lua_lvgl_widget.cpp index 4fadaff7fea..e371af1c2fb 100644 --- a/radio/src/lua/lua_lvgl_widget.cpp +++ b/radio/src/lua/lua_lvgl_widget.cpp @@ -83,7 +83,7 @@ LvglWidgetObjectBase *LvglWidgetObjectBase::checkLvgl(lua_State *L, int index) LvglWidgetObjectBase::LvglWidgetObjectBase(const char* meta) : metatable(meta), - lvglManager(luaLvglManager) + lvglManager(luaScriptManager) { } @@ -126,12 +126,12 @@ void LvglWidgetObjectBase::pcallSimpleFunc(lua_State *L, int funcRef) if (funcRef != LUA_REFNIL) { PROTECT_LUA() { - auto save = luaLvglManager; - luaLvglManager = lvglManager; + auto save = luaScriptManager; + luaScriptManager = lvglManager; if (!pcallFunc(L, funcRef, 0)) { lvglManager->luaShowError(); } - luaLvglManager = save; + luaScriptManager = save; } UNPROTECT_LUA(); } @@ -142,8 +142,8 @@ bool LvglWidgetObjectBase::pcallUpdateBool(lua_State *L, int getFuncRef, { bool res = true; if (getFuncRef != LUA_REFNIL) { - auto save = luaLvglManager; - luaLvglManager = lvglManager; + auto save = luaScriptManager; + luaScriptManager = lvglManager; int t = lua_gettop(L); if (pcallFunc(L, getFuncRef, 1)) { bool val = false; @@ -166,8 +166,8 @@ bool LvglWidgetObjectBase::pcallUpdate1Int(lua_State *L, int getFuncRef, { bool res = true; if (getFuncRef != LUA_REFNIL) { - auto save = luaLvglManager; - luaLvglManager = lvglManager; + auto save = luaScriptManager; + luaScriptManager = lvglManager; int t = lua_gettop(L); if (pcallFunc(L, getFuncRef, 1)) { int val = luaL_checkunsigned(L, -1); @@ -186,8 +186,8 @@ bool LvglWidgetObjectBase::pcallUpdate2Int(lua_State *L, int getFuncRef, { bool res = true; if (getFuncRef != LUA_REFNIL) { - auto save = luaLvglManager; - luaLvglManager = lvglManager; + auto save = luaScriptManager; + luaScriptManager = lvglManager; int t = lua_gettop(L); if (pcallFunc(L, getFuncRef, 2)) { int v1 = luaL_checkunsigned(L, -2); @@ -206,8 +206,8 @@ int LvglWidgetObjectBase::pcallGetIntVal(lua_State *L, int getFuncRef) { int val = 0; if (getFuncRef != LUA_REFNIL) { - auto save = luaLvglManager; - luaLvglManager = lvglManager; + auto save = luaScriptManager; + luaScriptManager = lvglManager; int t = lua_gettop(L); PROTECT_LUA() { @@ -235,8 +235,8 @@ int LvglWidgetObjectBase::pcallGetOptIntVal(lua_State *L, int getFuncRef, int de { int val = 0; if (getFuncRef != LUA_REFNIL) { - auto save = luaLvglManager; - luaLvglManager = lvglManager; + auto save = luaScriptManager; + luaScriptManager = lvglManager; int t = lua_gettop(L); PROTECT_LUA() { @@ -263,8 +263,8 @@ int LvglWidgetObjectBase::pcallGetOptIntVal(lua_State *L, int getFuncRef, int de void LvglWidgetObjectBase::pcallSetIntVal(lua_State *L, int setFuncRef, int val) { if (setFuncRef != LUA_REFNIL) { - auto save = luaLvglManager; - luaLvglManager = lvglManager; + auto save = luaScriptManager; + luaScriptManager = lvglManager; int t = lua_gettop(L); PROTECT_LUA() { @@ -1051,7 +1051,7 @@ void LvglWidgetBox::build(lua_State* L) window = new Window(lvglManager->getCurrentParent(), {x, y, w, h}, lv_obj_create); lv_obj_add_flag(window->getLvObj(), LV_OBJ_FLAG_EVENT_BUBBLE); - if (luaLvglManager->isWidget()) { + if (luaScriptManager->isWidget()) { lv_obj_clear_flag(window->getLvObj(), LV_OBJ_FLAG_CLICKABLE); } else { etx_scrollbar(window->getLvObj()); @@ -1129,7 +1129,7 @@ void LvglWidgetBorderedObject::build(lua_State *L) window = new Window(lvglManager->getCurrentParent(), {x, y, w, h}, lv_obj_create); lv_obj_add_flag(window->getLvObj(), LV_OBJ_FLAG_EVENT_BUBBLE); - if (luaLvglManager->isWidget()) { + if (luaScriptManager->isWidget()) { lv_obj_clear_flag(window->getLvObj(), LV_OBJ_FLAG_CLICKABLE); } else { etx_scrollbar(window->getLvObj()); @@ -1729,7 +1729,7 @@ class WidgetPage : public NavWindow, public LuaEventHandler bool bubbleEvents() override { return true; } - void onClicked() override { Keyboard::hide(false); LuaEventHandler::onClicked(); } + void onClicked() override { Keyboard::hide(false); LuaEventHandler::onClickedEvent(); } void onCancel() override { backAction(); } diff --git a/radio/src/lua/lua_lvgl_widget.h b/radio/src/lua/lua_lvgl_widget.h index fbed7f93a8d..dfd451cc2f3 100644 --- a/radio/src/lua/lua_lvgl_widget.h +++ b/radio/src/lua/lua_lvgl_widget.h @@ -24,6 +24,8 @@ #define LVGL_METATABLE "LVGL*" #define LVGL_SIMPLEMETATABLE "LVGLSIMPLE*" +class LuaScriptManager; + //----------------------------------------------------------------------------- class LvglWidgetObjectBase @@ -66,7 +68,7 @@ class LvglWidgetObjectBase std::vector lvglObjectRefs; const char* metatable = nullptr; bool clearRequest = false; - LuaLvglManager *lvglManager = nullptr; + LuaScriptManager *lvglManager = nullptr; coord_t x = 0, y = 0, w = LV_SIZE_CONTENT, h = LV_SIZE_CONTENT; LcdFlags color = COLOR2FLAGS(COLOR_THEME_SECONDARY1_INDEX); LcdFlags currentColor = -1; diff --git a/radio/src/lua/lua_widget.cpp b/radio/src/lua/lua_widget.cpp index 790560c1dfa..b46fb576a17 100644 --- a/radio/src/lua/lua_widget.cpp +++ b/radio/src/lua/lua_widget.cpp @@ -31,6 +31,8 @@ #define MAX_INSTRUCTIONS (20000 / 100) +LuaScriptManager *luaScriptManager = nullptr; + #if defined(HARDWARE_TOUCH) uint32_t LuaEventHandler::downTime = 0; uint32_t LuaEventHandler::tapTime = 0; @@ -140,7 +142,7 @@ void LuaEventHandler::event_cb(lv_event_t* e) #endif } -void LuaEventHandler::onClicked() +void LuaEventHandler::onClickedEvent() { #if defined(HARDWARE_TOUCH) auto click_source = lv_indev_get_act(); @@ -167,7 +169,7 @@ void LuaEventHandler::onClicked() luaPushEvent(EVT_KEY_BREAK(KEY_ENTER)); } -void LuaEventHandler::onCancel() { +void LuaEventHandler::onCancelEvent() { luaPushEvent(EVT_KEY_BREAK(KEY_EXIT)); } @@ -217,7 +219,10 @@ void LuaWidget::redraw_cb(lv_event_t* e) buf.setClippingRect(clipping.x1 - a.x1, clipping.x2 + 1 - a.x1, clipping.y1 - a.y1, clipping.y2 + 1 - a.y1); + auto save = luaScriptManager; + luaScriptManager = widget; widget->refresh(&buf); + luaScriptManager = save; } } @@ -252,7 +257,7 @@ void LuaWidget::onClicked() return; } - LuaEventHandler::onClicked(); + LuaScriptManager::onClickedEvent(); } void LuaWidget::onCancel() @@ -262,7 +267,7 @@ void LuaWidget::onCancel() return; } - LuaEventHandler::onCancel(); + LuaScriptManager::onCancelEvent(); } void LuaWidget::checkEvents() @@ -286,8 +291,9 @@ void LuaWidget::checkEvents() lv_obj_get_coords(lvobj, &a); // Check widget is at least partially visible if (a.x2 >= 0 && a.x1 < LCD_W) { + auto save = luaScriptManager; PROTECT_LUA() { - luaLvglManager = this; + luaScriptManager = this; refresh(nullptr); if (!errorMessage) { if (!callRefs(lsWidgets)) { @@ -298,7 +304,7 @@ void LuaWidget::checkEvents() } else { // TODO: error handling } - luaLvglManager = nullptr; + luaScriptManager = save; UNPROTECT_LUA(); } } @@ -325,7 +331,7 @@ void LuaWidget::update() // Get options table and update values lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, optionsDataRef); int i = 0; - for (const ZoneOption* option = getOptions(); option->name; option++, i++) { + for (const ZoneOption* option = getOptionDefinitions(); option->name; option++, i++) { auto optVal = getOptionValue(i); switch (option->type) { case ZoneOption::String: @@ -347,7 +353,7 @@ void LuaWidget::update() lua_setfield(lsWidgets, -2, option->name); } - if (useLvglLayout()) luaLvglManager = this; + luaScriptManager = this; if (lua_pcall(lsWidgets, 2, 0, 0) != 0) setErrorMessage("update()"); @@ -370,7 +376,7 @@ void LuaWidget::update() } } - luaLvglManager = nullptr; + luaScriptManager = nullptr; } // Update table on top of Lua stack - set entry with name 'idx' to value 'val' @@ -499,12 +505,10 @@ void LuaWidget::refresh(BitmapBuffer* dc) // scripts bool lla = luaLcdAllowed; luaLcdAllowed = true; - runningFS = this; if (lua_pcall(lsWidgets, 3, 0, 0) != 0) { setErrorMessage("refresh()"); } - runningFS = nullptr; // Remove LCD luaLcdAllowed = lla; luaLcdBuffer = nullptr; @@ -521,18 +525,19 @@ void LuaWidget::background() luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaFactory()->backgroundFunction); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaScriptContextRef); - runningFS = this; + auto save = luaScriptManager; + luaScriptManager = this; if (lua_pcall(lsWidgets, 1, 0, 0) != 0) { setErrorMessage("background()"); } - runningFS = nullptr; + luaScriptManager = save; } } void LuaWidget::onEvent(event_t event) { if (fullscreen) { - LuaEventHandler::onLuaEvent(event); + LuaScriptManager::onLuaEvent(event); } Widget::onEvent(event); } @@ -550,7 +555,7 @@ bool LuaWidget::isAppMode() const return fullscreen && ViewMain::instance()->isAppMode(); } -void LuaLvglManager::saveLvglObjectRef(int ref) +void LuaScriptManager::saveLvglObjectRef(int ref) { if (tempParent) tempParent->saveLvglObjectRef(ref); @@ -558,7 +563,7 @@ void LuaLvglManager::saveLvglObjectRef(int ref) lvglObjectRefs.push_back(ref); } -void LuaLvglManager::clearRefs(lua_State *L) +void LuaScriptManager::clearRefs(lua_State *L) { for (size_t i = 0; i < lvglObjectRefs.size(); i += 1) { lua_rawgeti(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); @@ -569,7 +574,7 @@ void LuaLvglManager::clearRefs(lua_State *L) lvglObjectRefs.clear(); } -bool LuaLvglManager::callRefs(lua_State *L) +bool LuaScriptManager::callRefs(lua_State *L) { for (size_t i = 0; i < lvglObjectRefs.size(); i += 1) { lua_rawgeti(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); diff --git a/radio/src/lua/lua_widget.h b/radio/src/lua/lua_widget.h index c450a6b3482..2f93210ba76 100644 --- a/radio/src/lua/lua_widget.h +++ b/radio/src/lua/lua_widget.h @@ -35,10 +35,36 @@ class LuaWidgetFactory; -class LuaLvglManager +class LuaEventHandler +{ +public: + LuaEventHandler() = default; + void setupHandler(Window* w); + void removeHandler(Window* w); + +protected: +#if defined(HARDWARE_TOUCH) + // "tap" handling + static uint32_t downTime; + static uint32_t tapTime; + static uint32_t tapCount; + // "swipe" / "slide" handling + static tmr10ms_t swipeTimeOut; + static bool _sliding; + static coord_t _startX; + static coord_t _startY; +#endif + static void event_cb(lv_event_t* e); + + void onClickedEvent(); + void onCancelEvent(); + void onLuaEvent(event_t event); +}; + +class LuaScriptManager : public LuaEventHandler { public: - LuaLvglManager() = default; + LuaScriptManager() = default; std::vector getLvglObjectRefs() const { return lvglObjectRefs; } void saveLvglObjectRef(int ref); @@ -68,33 +94,7 @@ class LuaLvglManager LvglWidgetObjectBase* tempParent = nullptr; }; -class LuaEventHandler -{ -public: - LuaEventHandler() = default; - void setupHandler(Window* w); - void removeHandler(Window* w); - -protected: -#if defined(HARDWARE_TOUCH) - // "tap" handling - static uint32_t downTime; - static uint32_t tapTime; - static uint32_t tapCount; - // "swipe" / "slide" handling - static tmr10ms_t swipeTimeOut; - static bool _sliding; - static coord_t _startX; - static coord_t _startY; -#endif - static void event_cb(lv_event_t* e); - - void onClicked(); - void onCancel(); - void onLuaEvent(event_t event); -}; - -class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager +class LuaWidget : public Widget, public LuaScriptManager { friend class LuaWidgetFactory; @@ -160,3 +160,5 @@ class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager static void redraw_cb(lv_event_t *e); }; + +extern LuaScriptManager* luaScriptManager; diff --git a/radio/src/lua/lua_widget_factory.cpp b/radio/src/lua/lua_widget_factory.cpp index 20601d01e0b..b08160335c3 100644 --- a/radio/src/lua/lua_widget_factory.cpp +++ b/radio/src/lua/lua_widget_factory.cpp @@ -24,18 +24,26 @@ #include "lua_api.h" #include "lua_states.h" +#include "strhelpers.h" #define MAX_INSTRUCTIONS (20000/100) -LuaWidgetFactory::LuaWidgetFactory(const char* name, ZoneOption* widgetOptions, - int createFunction) : +LuaWidgetFactory::LuaWidgetFactory(const char* name, ZoneOption* widgetOptions, int optionDefinitionsReference, + int createFunction, int updateFunction, int refreshFunction, + int backgroundFunction, int translateFunction, bool lvglLayout, + const char* filename) : WidgetFactory(name, widgetOptions), + optionDefinitionsReference(optionDefinitionsReference), createFunction(createFunction), - updateFunction(0), - refreshFunction(0), - backgroundFunction(0), - translateFunction(0) + updateFunction(updateFunction), + refreshFunction(refreshFunction), + backgroundFunction(backgroundFunction), + translateFunction(translateFunction), + lvglLayout(lvglLayout), + path(filename) { + path = path.substr(0, path.rfind("/") + 1); + translateOptions(widgetOptions); } LuaWidgetFactory::~LuaWidgetFactory() { @@ -45,7 +53,7 @@ LuaWidgetFactory::~LuaWidgetFactory() { delete displayName; } - auto option = getOptions(); + auto option = getDefaultOptions(); while (option && option->name != nullptr) { if (option->displayName) { delete option->displayName; @@ -102,7 +110,9 @@ Widget* LuaWidgetFactory::create(Window* parent, const rect_t& rect, // Push stored options for 'create' call lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, optionsDataRef); - bool err = lua_pcall(lsWidgets, 2, 1, 0); + lua_pushstring(lsWidgets, path.c_str()); + + bool err = lua_pcall(lsWidgets, 3, 1, 0); int widgetData = err ? LUA_NOREF : luaL_ref(lsWidgets, LUA_REGISTRYINDEX); LuaWidget* lw = new LuaWidget(this, parent, rect, persistentData, widgetData, zoneRectDataRef, optionsDataRef); if (err) lw->setErrorMessage("create()"); @@ -144,3 +154,193 @@ void LuaWidgetFactory::translateOptions(ZoneOption * options) } lua_pop(lsWidgets, 1); } + +static int switchValue() +{ + int v; + if (lua_type(lsWidgets, -1) == LUA_TSTRING) { + v = getSwitchIndex(lua_tostring(lsWidgets, -1), true); + if (v == SWSRC_INVERT) v = SWSRC_NONE; + } else { + v = luaL_checkinteger(lsWidgets, -1); + } + return v; +} + +static int sourceValue() +{ + int v; + if (lua_type(lsWidgets, -1) == LUA_TSTRING) { + v = getSourceIndex(lua_tostring(lsWidgets, -1), true); + if (v == -1) v = MIXSRC_NONE; + } else { + v = luaL_checkunsigned(lsWidgets, -1); + } + return v; +} + +// Parse the options table to get the default, min and max values +// Called when the widget settings dialog is opened to get values +// for current loaded model. +const void LuaWidgetFactory::parseOptionDefaults() const +{ + if (optionDefinitionsReference == LUA_REFNIL) { + // TRACE("parseOptionDefaults() no options"); + return; + } + + PROTECT_LUA() + { + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, optionDefinitionsReference); + ZoneOption *option = (ZoneOption*)options; + for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2), option->name; + lua_pop(lsWidgets, 1)) { + // TRACE("parsing option %d", count); + luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number + luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is table + uint8_t field = 0; + for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2) && field < 5; + lua_pop(lsWidgets, 1), field++) { + luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number + switch (field) { + case 2: + if (option->type == ZoneOption::Switch) { + option->deflt.signedValue = switchValue(); + } else if (option->type == ZoneOption::Source) { + option->deflt.unsignedValue = sourceValue(); + } else if (option->type == ZoneOption::Integer) { + option->deflt.signedValue = luaL_checkinteger(lsWidgets, -1); + } else if (option->type == ZoneOption::Bool) { + option->deflt.boolValue = (luaL_checkunsigned(lsWidgets, -1) != 0); + } else if (option->type == ZoneOption::String || option->type == ZoneOption::File) { + strncpy(option->deflt.stringValue, luaL_checkstring(lsWidgets, -1), + LEN_ZONE_OPTION_STRING); + } else { + option->deflt.unsignedValue = luaL_checkunsigned(lsWidgets, -1); + } + break; + case 3: + if (option->type == ZoneOption::Switch) { + option->min.signedValue = switchValue(); + } else if (option->type == ZoneOption::Source) { + option->min.unsignedValue = sourceValue(); + } else if (option->type == ZoneOption::Integer || option->type == ZoneOption::Slider) { + option->min.signedValue = luaL_checkinteger(lsWidgets, -1); + } else if (option->type == ZoneOption::Choice) { + luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is a table + for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) { + option->choiceValues.push_back(luaL_checkstring(lsWidgets, -1)); + } + } else if (option->type == ZoneOption::File) { + option->fileSelectPath = luaL_checkstring(lsWidgets, -1); + } + break; + case 4: + if (option->type == ZoneOption::Switch) { + option->max.signedValue = switchValue(); + } else if (option->type == ZoneOption::Source) { + option->max.unsignedValue = sourceValue(); + } else if (option->type == ZoneOption::Integer || option->type == ZoneOption::Slider) { + option->max.signedValue = luaL_checkinteger(lsWidgets, -1); + } + break; + default: + break; + } + } + option++; + } + } + else + { + TRACE("error in theme/widget options"); + } + UNPROTECT_LUA(); + return; +} + +// Parse options table to get name and type values. +// Called on radio startup to build base data for all widgtes. +ZoneOption* LuaWidgetFactory::parseOptionDefinitions(int reference) +{ + if (reference == LUA_REFNIL) { + // TRACE("parseOptionDefinitions() no options"); + return NULL; + } + + int count = 0; + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference); + for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) { + count++; + } + + // TRACE("we have %d options", count); + if (count > MAX_WIDGET_OPTIONS) { + count = MAX_WIDGET_OPTIONS; + // TRACE("limited to %d options", count); + } + + ZoneOption *options = new ZoneOption[count + 1]; + if (!options) { + return NULL; + } + + PROTECT_LUA() + { + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference); + ZoneOption *option = options; + for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2), count-- > 0; + lua_pop(lsWidgets, 1)) { + // TRACE("parsing option %d", count); + luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number + luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is table + uint8_t field = 0; + for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2) && field < 5; + lua_pop(lsWidgets, 1), field++) { + luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number + switch (field) { + case 0: + option->name = luaL_checkstring(lsWidgets, -1); + option->displayName = nullptr; + // TRACE("name = %s", option->name); + break; + case 1: + option->type = (ZoneOption::Type)luaL_checkinteger(lsWidgets, -1); + option->deflt.unsignedValue = 0; + // set some sensible defaults + if (option->type == ZoneOption::Integer) { + option->min.signedValue = -100; + option->max.signedValue = 100; + } else if (option->type == ZoneOption::Switch) { + option->min.signedValue = SWSRC_FIRST; + option->max.signedValue = SWSRC_LAST; + } else if (option->type == ZoneOption::Timer) { + option->min.unsignedValue = 0; + option->max.unsignedValue = MAX_TIMERS - 1; + } else if (option->type == ZoneOption::TextSize) { + option->min.unsignedValue = FONT_STD_INDEX; + option->max.unsignedValue = FONTS_COUNT - 1; + } else if (option->type == ZoneOption::String || option->type == ZoneOption::File) { + option->deflt.stringValue[0] = 0; + } else if (option->type == ZoneOption::Slider) { + option->min.unsignedValue = 0; + option->max.unsignedValue = 9; + } + break; + default: + break; + } + } + option++; + } + option->name = NULL; // sentinel + } + else + { + TRACE("error in theme/widget options"); + delete[] options; + return NULL; + } + UNPROTECT_LUA(); + return options; +} diff --git a/radio/src/lua/lua_widget_factory.h b/radio/src/lua/lua_widget_factory.h index 1b4a3b11f3a..77cd70b71f9 100644 --- a/radio/src/lua/lua_widget_factory.h +++ b/radio/src/lua/lua_widget_factory.h @@ -25,12 +25,13 @@ class LuaWidgetFactory : public WidgetFactory { - friend void luaLoadWidgetCallback(); friend class LuaWidget; public: - LuaWidgetFactory(const char* name, ZoneOption* widgetOptions, - int createFunction); + LuaWidgetFactory(const char* name, ZoneOption* widgetOptions, int optionDefinitionsReference, + int createFunction, int updateFunction, int refreshFunction, + int backgroundFunction, int translateFunction, bool lvgllayout, + const char* filename); ~LuaWidgetFactory(); Widget* create(Window* parent, const rect_t& rect, @@ -41,14 +42,18 @@ class LuaWidgetFactory : public WidgetFactory bool useLvglLayout() const { return lvglLayout; } + static ZoneOption* parseOptionDefinitions(int reference); + const void parseOptionDefaults() const override; + protected: void translateOptions(ZoneOption * options); + int optionDefinitionsReference; int createFunction; int updateFunction; int refreshFunction; int backgroundFunction; int translateFunction; - int settingsFunction; bool lvglLayout; + std::string path; }; diff --git a/radio/src/lua/widgets.cpp b/radio/src/lua/widgets.cpp index c7608ff50c5..2dd509af861 100644 --- a/radio/src/lua/widgets.cpp +++ b/radio/src/lua/widgets.cpp @@ -98,123 +98,12 @@ void luaSetInstructionsLimit(lua_State * L, int count) #endif } -ZoneOption *createOptionsArray(int reference, uint8_t maxOptions) -{ - if (reference == 0) { - // TRACE("createOptionsArray() no options"); - return NULL; - } - - int count = 0; - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference); - for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) { - count++; - } - - // TRACE("we have %d options", count); - if (count > maxOptions) { - count = maxOptions; - // TRACE("limited to %d options", count); - } - - ZoneOption *options = new ZoneOption[count + 1]; - if (!options) { - return NULL; - } - - PROTECT_LUA() - { - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference); - ZoneOption *option = options; - for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2), count-- > 0; - lua_pop(lsWidgets, 1)) { - // TRACE("parsing option %d", count); - luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number - luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is table - uint8_t field = 0; - for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2) && field < 5; - lua_pop(lsWidgets, 1), field++) { - luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number - switch (field) { - case 0: - option->name = luaL_checkstring(lsWidgets, -1); - option->displayName = nullptr; - // TRACE("name = %s", option->name); - break; - case 1: - option->type = (ZoneOption::Type)luaL_checkinteger(lsWidgets, -1); - option->deflt.unsignedValue = 0; - // set some sensible defaults - if (option->type == ZoneOption::Integer) { - option->min.signedValue = -100; - option->max.signedValue = 100; - } else if (option->type == ZoneOption::Switch) { - option->min.signedValue = SWSRC_FIRST; - option->max.signedValue = SWSRC_LAST; - } else if (option->type == ZoneOption::Timer) { - option->min.unsignedValue = 0; - option->max.unsignedValue = MAX_TIMERS - 1; - } else if (option->type == ZoneOption::TextSize) { - option->min.unsignedValue = FONT_STD_INDEX; - option->max.unsignedValue = FONTS_COUNT - 1; - } else if (option->type == ZoneOption::String || option->type == ZoneOption::File) { - option->deflt.stringValue[0] = 0; - } else if (option->type == ZoneOption::Slider) { - option->min.unsignedValue = 0; - option->max.unsignedValue = 9; - } - break; - case 2: - if (option->type == ZoneOption::Integer || option->type == ZoneOption::Switch) { - option->deflt.signedValue = luaL_checkinteger(lsWidgets, -1); - } else if (option->type == ZoneOption::Bool) { - option->deflt.boolValue = (luaL_checkunsigned(lsWidgets, -1) != 0); - } else if (option->type == ZoneOption::String || option->type == ZoneOption::File) { - strncpy(option->deflt.stringValue, luaL_checkstring(lsWidgets, -1), - LEN_ZONE_OPTION_STRING); - } else { - option->deflt.unsignedValue = luaL_checkunsigned(lsWidgets, -1); - } - break; - case 3: - if (option->type == ZoneOption::Integer || option->type == ZoneOption::Switch || option->type == ZoneOption::Slider) { - option->min.signedValue = luaL_checkinteger(lsWidgets, -1); - } else if (option->type == ZoneOption::Choice) { - luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is a table - for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) { - option->choiceValues.push_back(luaL_checkstring(lsWidgets, -1)); - } - } else if (option->type == ZoneOption::File) { - option->fileSelectPath = luaL_checkstring(lsWidgets, -1); - } - break; - case 4: - if (option->type == ZoneOption::Integer || option->type == ZoneOption::Switch || option->type == ZoneOption::Slider) { - option->max.signedValue = luaL_checkinteger(lsWidgets, -1); - } - break; - } - } - option++; - } - option->name = NULL; // sentinel - } - else - { - TRACE("error in theme/widget options"); - delete[] options; - return NULL; - } - UNPROTECT_LUA(); - return options; -} - -void luaLoadWidgetCallback() +void luaLoadWidgetCallback(const char* filename) { TRACE("luaLoadWidgetCallback()"); const char * name=NULL; - int widgetOptions = 0, createFunction = 0, updateFunction = 0, settingsFunction = 0, + int optionDefinitionsReference = LUA_REFNIL, createFunction = 0, updateFunction = 0, refreshFunction = 0, backgroundFunction = 0, translateFunction = 0; bool lvglLayout = false; @@ -226,7 +115,7 @@ void luaLoadWidgetCallback() name = luaL_checkstring(lsWidgets, -1); } else if (!strcmp(key, "options")) { - widgetOptions = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); + optionDefinitionsReference = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); lua_pushnil(lsWidgets); } else if (!strcmp(key, "create")) { @@ -249,34 +138,25 @@ void luaLoadWidgetCallback() translateFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); lua_pushnil(lsWidgets); } - else if (!strcmp(key, "settings")) { - settingsFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); - lua_pushnil(lsWidgets); - } else if (!strcasecmp(key, "useLvgl")) { lvglLayout = lua_toboolean(lsWidgets, -1); } } if (name && createFunction) { - ZoneOption * options = createOptionsArray(widgetOptions, MAX_WIDGET_OPTIONS); + ZoneOption * options = LuaWidgetFactory::parseOptionDefinitions(optionDefinitionsReference); if (options) { - LuaWidgetFactory * factory = new LuaWidgetFactory(name, options, createFunction); - factory->updateFunction = updateFunction; - factory->refreshFunction = refreshFunction; - factory->backgroundFunction = backgroundFunction; - factory->translateFunction = translateFunction; - factory->settingsFunction = settingsFunction; - factory->translateOptions(options); - factory->lvglLayout = lvglLayout; + new LuaWidgetFactory(name, options, optionDefinitionsReference, + createFunction, updateFunction, refreshFunction, backgroundFunction, + translateFunction, lvglLayout, filename); TRACE("Loaded Lua widget %s", name); } } } -void luaLoadFile(const char * filename, void (*callback)()) +static void luaLoadFile(const char * filename, std::function callback) { - if (lsWidgets == NULL || callback == NULL) + if (lsWidgets == NULL) return; TRACE("luaLoadFile(%s)", filename); @@ -286,7 +166,7 @@ void luaLoadFile(const char * filename, void (*callback)()) PROTECT_LUA() { if (luaLoadScriptFileToState(lsWidgets, filename, LUA_SCRIPT_LOAD_MODE) == SCRIPT_OK) { if (lua_pcall(lsWidgets, 0, 1, 0) == LUA_OK && lua_istable(lsWidgets, -1)) { - (*callback)(); + callback(); } else { TRACE("luaLoadFile(%s): Error parsing script: %s", filename, lua_tostring(lsWidgets, -1)); @@ -301,7 +181,7 @@ void luaLoadFile(const char * filename, void (*callback)()) UNPROTECT_LUA(); } -void luaLoadFiles(const char * directory, void (*callback)()) +static void luaLoadFiles(const char * directory) { char path[LUA_FULLPATH_MAXLEN+1]; FILINFO fno; @@ -324,7 +204,7 @@ void luaLoadFiles(const char * directory, void (*callback)()) strcpy(&path[pathlen], fno.fname); strcat(&path[pathlen], LUA_WIDGET_FILENAME); if (isFileAvailable(path)) { - luaLoadFile(path, callback); + luaLoadFile(path, [=]() { luaLoadWidgetCallback(path); }); } } } @@ -374,7 +254,7 @@ void luaInitThemesAndWidgets() } UNPROTECT_LUA(); TRACE("lsWidgets %p", lsWidgets); - luaLoadFiles(WIDGETS_PATH, luaLoadWidgetCallback); + luaLoadFiles(WIDGETS_PATH); luaDoGc(lsWidgets, true); } } diff --git a/radio/src/strhelpers.cpp b/radio/src/strhelpers.cpp index dfb3243f184..9a756ae7dc3 100644 --- a/radio/src/strhelpers.cpp +++ b/radio/src/strhelpers.cpp @@ -427,9 +427,9 @@ char *getCustomSwitchesGroupName(char *dest, uint8_t idx) return dest; } -char *getSwitchName(char *dest, uint8_t idx) +char *getSwitchName(char *dest, uint8_t idx, bool defaultOnly) { - if (switchHasCustomName(idx)) { + if (!defaultOnly && switchHasCustomName(idx)) { dest = strAppend(dest, switchGetCustomName(idx), LEN_SWITCH_NAME); } else { dest = strAppend(dest, switchGetName(idx), LEN_SWITCH_NAME); @@ -464,7 +464,7 @@ const char *getSwitchPositionSymbol(uint8_t pos) return _switch_state_str[pos + 1]; } -char *getSwitchPositionName(char *dest, swsrc_t idx) +char *getSwitchPositionName(char *dest, swsrc_t idx, bool defaultOnly) { if (idx == SWSRC_NONE) { return strcpy(dest, STR_EMPTY); @@ -480,7 +480,7 @@ char *getSwitchPositionName(char *dest, swsrc_t idx) if (idx <= SWSRC_LAST_SWITCH) { div_t swinfo = switchInfo(idx); - s = getSwitchName(s, swinfo.quot); + s = getSwitchName(s, swinfo.quot, defaultOnly); s = strAppend(s, getSwitchPositionSymbol(swinfo.rem), 2); *s = '\0'; } else if (idx <= SWSRC_LAST_MULTIPOS_SWITCH) { @@ -522,9 +522,42 @@ char *getSwitchPositionName(char *dest, swsrc_t idx) return dest; } -const char *getAnalogLabel(uint8_t type, uint8_t idx) +bool switchCanHaveCustomName(swsrc_t idx) { - if (analogHasCustomLabel(type, idx)) return analogGetCustomLabel(type, idx); + return (idx >= SWSRC_FIRST_SWITCH && idx <= SWSRC_LAST_SWITCH); +} + +int getSwitchIndex(const char* name, bool all) +{ + bool negate = false; + + if (name[0] == '!') { + name++; + negate = true; + } + + for (swsrc_t idx = SWSRC_NONE; idx < SWSRC_COUNT; idx++) { + if (all || isSwitchAvailable(idx, ModelCustomFunctionsContext)) { + char* s; + if (switchCanHaveCustomName(idx)) { + // Check default name + s = getSwitchPositionName(idx, true); + if (!strcasecmp(s, name)) + return negate ? -idx : idx; + } + s = getSwitchPositionName(idx); + if (!strcasecmp(s, name)) + return negate ? -idx : idx; + } + } + + return SWSRC_INVERT; // Not found +} + +const char *getAnalogLabel(uint8_t type, uint8_t idx, bool defaultOnly) +{ + if (!defaultOnly && analogHasCustomLabel(type, idx)) + return analogGetCustomLabel(type, idx); if (type == ADC_INPUT_MAIN) { // main controls: translated label is stored in "short label" @@ -566,15 +599,15 @@ const char *getAnalogShortLabel(uint8_t idx) return ""; } -const char *getMainControlLabel(uint8_t idx) +const char *getMainControlLabel(uint8_t idx, bool defaultOnly) { - return getAnalogLabel(ADC_INPUT_MAIN, idx); + return getAnalogLabel(ADC_INPUT_MAIN, idx, defaultOnly); } -const char *getTrimLabel(uint8_t idx) +const char *getTrimLabel(uint8_t idx, bool defaultOnly) { if (idx < adcGetMaxInputs(ADC_INPUT_MAIN)) { - return getMainControlLabel(idx); + return getMainControlLabel(idx, defaultOnly); } // TODO: replace with string from HW def @@ -595,15 +628,15 @@ const char *getTrimSourceLabel(uint16_t src_raw, int8_t trim_src) } } -const char *getPotLabel(uint8_t idx) +const char *getPotLabel(uint8_t idx, bool defaultOnly) { - return getAnalogLabel(ADC_INPUT_FLEX, idx); + return getAnalogLabel(ADC_INPUT_FLEX, idx, defaultOnly); } // this should be declared in header, but it used so much foreign symbols that // we declare it in cpp-file and pre-instantiate it for the uses template -char *getSourceString(char (&destRef)[L], mixsrc_t idx) +char *getSourceString(char (&destRef)[L], mixsrc_t idx, bool defaultOnly) { size_t dest_len = L; char* dest = destRef; @@ -622,7 +655,7 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx) static_assert(L > sizeof(STR_CHAR_INPUT) - 1, "dest string too small"); dest_len -= sizeof(STR_CHAR_INPUT) - 1; char *pos = strAppend(dest, STR_CHAR_INPUT, sizeof(STR_CHAR_INPUT) - 1); - if (g_model.inputNames[idx][0] != '\0' && + if (!defaultOnly && g_model.inputNames[idx][0] != '\0' && (dest_len > sizeof(g_model.inputNames[idx]))) { memset(pos, 0, sizeof(g_model.inputNames[idx]) + 1); size_t input_len = @@ -669,7 +702,7 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx) if (idx < MAX_STICKS) { pos = strAppend(pos, STR_CHAR_STICK, sizeof(STR_CHAR_STICK) - 1); dest_len -= sizeof(STR_CHAR_STICK) - 1; - name = getMainControlLabel(idx); + name = getMainControlLabel(idx, defaultOnly); } else { idx -= MAX_STICKS; if (IS_SLIDER(idx)) { @@ -680,7 +713,7 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx) dest_len -= sizeof(STR_CHAR_POT) - 1; } // TODO: AXIS / SWITCH ??? - name = getPotLabel(idx); + name = getPotLabel(idx, defaultOnly); } strncpy(pos, name, dest_len - 1); pos[dest_len - 1] = '\0'; @@ -707,11 +740,11 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx) } else if (idx <= MIXSRC_LAST_TRIM) { idx -= MIXSRC_FIRST_TRIM; char *pos = strAppend(dest, STR_CHAR_TRIM, sizeof(STR_CHAR_TRIM) - 1); - strAppend(pos, getTrimLabel(idx)); + strAppend(pos, getTrimLabel(idx, defaultOnly)); } else if (idx <= MIXSRC_LAST_SWITCH) { idx -= MIXSRC_FIRST_SWITCH; char *pos = strAppend(dest, STR_CHAR_SWITCH, sizeof(STR_CHAR_SWITCH) - 1); - getSwitchName(pos, idx); + getSwitchName(pos, idx, defaultOnly); #if defined(FUNCTION_SWITCHES) } else if (idx <= MIXSRC_LAST_CUSTOMSWITCH_GROUP) { idx -= MIXSRC_FIRST_CUSTOMSWITCH_GROUP; @@ -719,15 +752,14 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx) getCustomSwitchesGroupName(pos, idx); #endif } else if (idx <= MIXSRC_LAST_LOGICAL_SWITCH) { - // TODO: unnecessary, use the direct way instead idx -= MIXSRC_FIRST_LOGICAL_SWITCH; - getSwitchPositionName(dest, idx + SWSRC_FIRST_LOGICAL_SWITCH); + getSwitchPositionName(dest, idx + SWSRC_FIRST_LOGICAL_SWITCH, defaultOnly); } else if (idx <= MIXSRC_LAST_TRAINER) { idx -= MIXSRC_FIRST_TRAINER; strAppendStringWithIndex(dest, STR_PPM_TRAINER, idx + 1); } else if (idx <= MIXSRC_LAST_CH) { auto ch = idx - MIXSRC_FIRST_CH; - if (g_model.limitData[ch].name[0] != '\0') { + if (!defaultOnly && g_model.limitData[ch].name[0] != '\0') { strAppend(dest, g_model.limitData[ch].name, LEN_CHANNEL_NAME); } else { strAppendStringWithIndex(dest, STR_CH, ch + 1); @@ -736,7 +768,7 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx) idx -= MIXSRC_FIRST_GVAR; #if defined(LIBOPENUI) char *s = strAppendStringWithIndex(dest, STR_GV, idx + 1); - if (g_model.gvars[idx].name[0]) { + if (!defaultOnly && g_model.gvars[idx].name[0]) { s = strAppend(s, ":"); getGVarString(s, idx); } @@ -763,7 +795,7 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx) strncpy(dest, src_str, dest_len - 1); } else if (idx <= MIXSRC_LAST_TIMER) { idx -= MIXSRC_FIRST_TIMER; - if (g_model.timers[idx].name[0] != '\0') { + if (!defaultOnly && g_model.timers[idx].name[0] != '\0') { strAppend(dest, g_model.timers[idx].name, LEN_TIMER_NAME); } else { strAppendStringWithIndex(dest, STR_SRC_TIMER, idx + 1); @@ -781,13 +813,53 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx) return destRef; } +bool sourceCanHaveCustomName(mixsrc_t idx) +{ + return (idx >= MIXSRC_FIRST_INPUT && idx <= MIXSRC_LAST_INPUT) || + (idx >= MIXSRC_FIRST_STICK && idx <= MIXSRC_LAST_STICK) || + (idx >= MIXSRC_FIRST_POT && idx <= MIXSRC_LAST_POT) || + (idx >= MIXSRC_FIRST_TRIM && idx <= MIXSRC_LAST_TRIM) || + (idx >= MIXSRC_FIRST_SWITCH && idx <= MIXSRC_LAST_SWITCH) || + (idx >= MIXSRC_FIRST_CH && idx <= MIXSRC_LAST_CH) || + (idx >= MIXSRC_FIRST_GVAR && idx <= MIXSRC_LAST_GVAR) || + (idx >= MIXSRC_FIRST_TIMER && idx <= MIXSRC_LAST_TIMER); +} + +bool matchSource(const char* name, mixsrc_t idx, bool defaultOnly) +{ + char *s = getSourceString(idx, defaultOnly); + if (strcasecmp(s, name) == 0) + return true; + // Check for names starting with CHAR_xxx symbol strings + if (s[0] == '\302' && (strcasecmp(s + 2, name) == 0)) + return true; + return false; +} + +int getSourceIndex(const char* name, bool all) +{ + for (mixsrc_t idx = MIXSRC_NONE; idx <= MIXSRC_LAST_TELEM; idx++) { + if (all || isSourceAvailable(idx)) { + if (sourceCanHaveCustomName(idx)) { + // Check default name + if (matchSource(name, idx, true)) + return idx; + } + if (matchSource(name, idx, false)) + return idx; + } + } + + return -1; +} + // pre-instantiate for use from external // all other instantiations are done from this file -template char *getSourceString<16>(char (&dest)[16], mixsrc_t idx); +template char *getSourceString<16>(char (&dest)[16], mixsrc_t idx, bool defaultOnly); -char *getSourceString(mixsrc_t idx) +char *getSourceString(mixsrc_t idx, bool defaultOnly) { - return getSourceString(_static_str_buffer, idx); + return getSourceString(_static_str_buffer, idx, defaultOnly); } char *getCurveString(int idx) @@ -805,9 +877,9 @@ char *getTimerString(char *dest, int32_t tme, TimerOptions timerOptions) return getFormattedTimerString(dest, tme, timerOptions); } -char *getSwitchPositionName(swsrc_t idx) +char *getSwitchPositionName(swsrc_t idx, bool defaultOnly) { - return getSwitchPositionName(_static_str_buffer, idx); + return getSwitchPositionName(_static_str_buffer, idx, defaultOnly); } char *getGVarString(int idx) { return getGVarString(_static_str_buffer, idx); } @@ -1102,6 +1174,8 @@ char *strAppendSigned(char *dest, int32_t value, uint8_t digits, uint8_t radix) char *strAppend(char *dest, const char *source, int len) { + if (source == nullptr) { *dest = '\0'; return dest; } + while ((*dest++ = *source++)) { if (--len == 0) { *dest = '\0'; diff --git a/radio/src/strhelpers.h b/radio/src/strhelpers.h index 436f83e55f6..838713c7f1b 100644 --- a/radio/src/strhelpers.h +++ b/radio/src/strhelpers.h @@ -110,9 +110,11 @@ char *strAppendStringWithIndex(char *dest, const char *s, int idx); #define LEN_TIMER_STRING 10 // "-00:00:00" char *getTimerString(char *dest, int32_t tme, TimerOptions timerOptions = {.options = 0}); +char *getTimerString(int32_t tme, TimerOptions timerOptions = {.options = 0}); char *getFormattedTimerString(char *dest, int32_t tme, TimerOptions timerOptions); char *getCurveString(char *dest, int idx); +char *getCurveString(int idx); char *getGVarString(char *dest, int idx); char *getGVarString(int idx); char *getValueOrGVarString(char *dest, size_t len, gvar_t value, gvar_t vmin, @@ -123,33 +125,33 @@ char *getValueOrSrcVarString(char *dest, size_t len, gvar_t value, gvar_t vmin, const char *suffix = nullptr, gvar_t offset = 0, bool usePPMUnit = false); const char *getSwitchWarnSymbol(uint8_t pos); const char *getSwitchPositionSymbol(uint8_t pos); -char *getSwitchPositionName(char *dest, swsrc_t idx); -char *getSwitchName(char *dest, uint8_t idx); +char *getSwitchPositionName(char *dest, swsrc_t idx, bool defaultOnly = false); +char *getSwitchPositionName(swsrc_t idx, bool defaultOnly = false); +char *getSwitchName(char *dest, uint8_t idx, bool defaultOnly = false); +int getSwitchIndex(const char* name, bool all); +int getSourceIndex(const char* name, bool all); -const char *getAnalogLabel(uint8_t type, uint8_t idx); +const char *getAnalogLabel(uint8_t type, uint8_t idx, bool defaultOnly = false); const char *getAnalogShortLabel(uint8_t idx); -const char *getMainControlLabel(uint8_t idx); -const char *getTrimLabel(uint8_t idx); +const char *getMainControlLabel(uint8_t idx, bool defaultOnly = false); +const char *getTrimLabel(uint8_t idx, bool defaultOnly = false); const char *getTrimSourceLabel(uint16_t src_raw, int8_t trim_src); -const char *getPotLabel(uint8_t idx); +const char *getPotLabel(uint8_t idx, bool defaultOnly = false); char *getCustomSwitchesGroupName(char *dest, uint8_t idx); template -char *getSourceString(char (&dest)[L], mixsrc_t idx); +char *getSourceString(char (&dest)[L], mixsrc_t idx, bool defaultOnly = false); +char *getSourceString(mixsrc_t idx, bool defaultOnly = false); template char *getSourceCustomValueString(char (&dest)[L], mixsrc_t source, int32_t val, LcdFlags flags); - -#endif +char *getSourceCustomValueString(mixsrc_t source, int32_t val, LcdFlags flags); char *getFlightModeString(char *dest, int8_t idx); -char *getSourceString(mixsrc_t idx); -char *getSourceCustomValueString(mixsrc_t source, int32_t val, LcdFlags flags); -char *getSwitchPositionName(swsrc_t idx); -char *getCurveString(int idx); -char *getTimerString(int32_t tme, TimerOptions timerOptions = {.options = 0}); +#endif + void splitTimer(char *s0, char *s1, char *s2, char *s3, int tme, bool bLowercase = true); From 6407af917af713b37196b6c0f0c01158482de5d2 Mon Sep 17 00:00:00 2001 From: philmoz Date: Tue, 18 Feb 2025 20:12:53 +1100 Subject: [PATCH 02/11] Add telemetry queue per widget. --- radio/src/lua/api_general.cpp | 115 +++++++++++++++------------- radio/src/lua/lua_widget.cpp | 17 ++++ radio/src/lua/lua_widget.h | 8 ++ radio/src/telemetry/crossfire.cpp | 8 +- radio/src/telemetry/frsky_sport.cpp | 34 +++----- radio/src/telemetry/ghost.cpp | 6 +- radio/src/telemetry/telemetry.cpp | 33 +++++++- radio/src/telemetry/telemetry.h | 6 +- radio/src/tests/crossfire.cpp | 3 +- 9 files changed, 140 insertions(+), 90 deletions(-) diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 39a1d074543..313a93ff2aa 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -898,6 +898,24 @@ static int luaGetRotEncMode(lua_State * L) return 1; } +static TelemetryQueue* getTelemetryQueue() +{ +#if defined(COLORLCD) + if (luaScriptManager) { + luaScriptManager->createTelemetryQueue(); + return luaScriptManager->telemetryQueue(); + } else { + if (!luaInputTelemetryFifo) + luaInputTelemetryFifo = new TelemetryQueue(); + return luaInputTelemetryFifo; + } +#else + if (!luaInputTelemetryFifo) + luaInputTelemetryFifo = new TelemetryQueue(); + return luaInputTelemetryFifo; +#endif +} + /*luadoc @function sportTelemetryPop() @@ -917,23 +935,20 @@ the LUA telemetry receive queue. */ static int luaSportTelemetryPop(lua_State * L) { - if (!luaInputTelemetryFifo) { - luaInputTelemetryFifo = new Fifo(); - if (!luaInputTelemetryFifo) { - return 0; - } - } + auto queue = getTelemetryQueue(); - if (luaInputTelemetryFifo->size() >= sizeof(SportTelemetryPacket)) { - SportTelemetryPacket packet; - for (uint8_t i=0; ipop(packet.raw[i]); + if (queue) { + if (queue->size() >= sizeof(SportTelemetryPacket)) { + SportTelemetryPacket packet; + for (uint8_t i=0; ipop(packet.raw[i]); + } + lua_pushinteger(L, packet.physicalId); + lua_pushinteger(L, packet.primId); + lua_pushinteger(L, packet.dataId); + lua_pushinteger(L, packet.value); + return 4; } - lua_pushinteger(L, packet.physicalId); - lua_pushinteger(L, packet.primId); - lua_pushinteger(L, packet.dataId); - lua_pushinteger(L, packet.value); - return 4; } return 0; @@ -1144,27 +1159,24 @@ Pops a received Crossfire Telemetry packet from the queue. */ static int luaCrossfireTelemetryPop(lua_State * L) { - if (!luaInputTelemetryFifo) { - luaInputTelemetryFifo = new Fifo(); - if (!luaInputTelemetryFifo) { - return 0; - } - } + auto queue = getTelemetryQueue(); - uint8_t length = 0, data = 0; - if (luaInputTelemetryFifo->probe(length) && luaInputTelemetryFifo->size() >= uint32_t(length)) { - // length value includes the length field - luaInputTelemetryFifo->pop(length); - luaInputTelemetryFifo->pop(data); // command - lua_pushinteger(L, data); - lua_newtable(L); - for (uint8_t i=1; ipop(data); - lua_pushinteger(L, i); + if (queue) { + uint8_t length = 0, data = 0; + if (queue->probe(length) && queue->size() >= uint32_t(length)) { + // length value includes the length field + queue->pop(length); + queue->pop(data); // command lua_pushinteger(L, data); - lua_settable(L, -3); + lua_newtable(L); + for (uint8_t i=1; ipop(data); + lua_pushinteger(L, i); + lua_pushinteger(L, data); + lua_settable(L, -3); + } + return 2; } - return 2; } return 0; @@ -1268,27 +1280,24 @@ Pops a received Ghost Telemetry packet from the queue. */ static int luaGhostTelemetryPop(lua_State * L) { - if (!luaInputTelemetryFifo) { - luaInputTelemetryFifo = new Fifo(); - if (!luaInputTelemetryFifo) { - return 0; - } - } - - uint8_t length = 0, data = 0; - if (luaInputTelemetryFifo->probe(length) && luaInputTelemetryFifo->size() >= uint32_t(length)) { - // length value includes type(1B), payload, crc(1B) - luaInputTelemetryFifo->pop(length); - luaInputTelemetryFifo->pop(data); // type - lua_pushinteger(L, data); // return type - lua_newtable(L); - for (uint8_t i=0; ipop(data); - lua_pushinteger(L, i + 1); - lua_pushinteger(L, data); - lua_settable(L, -3); + auto queue = getTelemetryQueue(); + + if (queue) { + uint8_t length = 0, data = 0; + if (queue->probe(length) && queue->size() >= uint32_t(length)) { + // length value includes type(1B), payload, crc(1B) + queue->pop(length); + queue->pop(data); // type + lua_pushinteger(L, data); // return type + lua_newtable(L); + for (uint8_t i=0; ipop(data); + lua_pushinteger(L, i + 1); + lua_pushinteger(L, data); + lua_settable(L, -3); + } + return 2; } - return 2; } return 0; diff --git a/radio/src/lua/lua_widget.cpp b/radio/src/lua/lua_widget.cpp index b46fb576a17..368ea7369cc 100644 --- a/radio/src/lua/lua_widget.cpp +++ b/radio/src/lua/lua_widget.cpp @@ -584,3 +584,20 @@ bool LuaScriptManager::callRefs(lua_State *L) } return true; } + +void LuaScriptManager::createTelemetryQueue() +{ + if (luaInputTelemetryFifo == nullptr) { + luaInputTelemetryFifo = new TelemetryQueue(); + registerTelemetryQueue(luaInputTelemetryFifo); + } +} + +LuaScriptManager::~LuaScriptManager() +{ + if (luaInputTelemetryFifo == nullptr) { + deregisterTelemetryQueue(luaInputTelemetryFifo); + delete luaInputTelemetryFifo; + luaInputTelemetryFifo = nullptr; + } +} diff --git a/radio/src/lua/lua_widget.h b/radio/src/lua/lua_widget.h index 2f93210ba76..14746f3bab9 100644 --- a/radio/src/lua/lua_widget.h +++ b/radio/src/lua/lua_widget.h @@ -28,6 +28,7 @@ #include "lua_states.h" #include "lua_api.h" #include "lua_lvgl_widget.h" +#include "telemetry/telemetry.h" #include "edgetx_types.h" @@ -65,6 +66,7 @@ class LuaScriptManager : public LuaEventHandler { public: LuaScriptManager() = default; + ~LuaScriptManager(); std::vector getLvglObjectRefs() const { return lvglObjectRefs; } void saveLvglObjectRef(int ref); @@ -88,10 +90,16 @@ class LuaScriptManager : public LuaEventHandler uint8_t refreshInstructionsPercent; + void createTelemetryQueue(); + TelemetryQueue* telemetryQueue() { return luaInputTelemetryFifo; } + protected: int luaScriptContextRef = 0; std::vector lvglObjectRefs; LvglWidgetObjectBase* tempParent = nullptr; +#if defined(LUA) + TelemetryQueue* luaInputTelemetryFifo = nullptr; +#endif }; class LuaWidget : public Widget, public LuaScriptManager diff --git a/radio/src/telemetry/crossfire.cpp b/radio/src/telemetry/crossfire.cpp index b54f8bcd863..cb3207fafa0 100644 --- a/radio/src/telemetry/crossfire.cpp +++ b/radio/src/telemetry/crossfire.cpp @@ -311,12 +311,8 @@ void processCrossfireTelemetryFrame(uint8_t module, uint8_t* rxBuffer, crossfireModuleStatus[module].queryCompleted = true; } - if (luaInputTelemetryFifo && luaInputTelemetryFifo->hasSpace(rxBufferCount - 2)) { - for (uint8_t i = 1; i < rxBufferCount - 1; i++) { - // destination address and CRC are skipped - luaInputTelemetryFifo->push(rxBuffer[i]); - } - } + // destination address and CRC are skipped + pushTelemetryDataToQueues(rxBuffer + 1, rxBufferCount - 2); break; #endif } diff --git a/radio/src/telemetry/frsky_sport.cpp b/radio/src/telemetry/frsky_sport.cpp index 2a81d189279..e8452a8ac83 100644 --- a/radio/src/telemetry/frsky_sport.cpp +++ b/radio/src/telemetry/frsky_sport.cpp @@ -391,18 +391,12 @@ void sportProcessTelemetryPacketWithoutCrc(uint8_t module, uint8_t origin, const } else if (dataId >= DIY_STREAM_FIRST_ID && dataId <= DIY_STREAM_LAST_ID) { #if defined(LUA) - if (luaInputTelemetryFifo && - luaInputTelemetryFifo->hasSpace(sizeof(SportTelemetryPacket))) { - - SportTelemetryPacket luaPacket; - luaPacket.physicalId = physicalId; - luaPacket.primId = primId; - luaPacket.dataId = dataId; - luaPacket.value = data; - for (uint8_t i=0; ipush(luaPacket.raw[i]); - } - } + SportTelemetryPacket luaPacket; + luaPacket.physicalId = physicalId; + luaPacket.primId = primId; + luaPacket.dataId = dataId; + luaPacket.value = data; + pushTelemetryDataToQueues(luaPacket.raw, sizeof(SportTelemetryPacket)); #endif } else if (dataId >= RB3040_CH1_2_FIRST_ID && dataId <= RB3040_CH7_8_LAST_ID) { @@ -433,16 +427,12 @@ void sportProcessTelemetryPacketWithoutCrc(uint8_t module, uint8_t origin, const } #if defined(LUA) else if (primId == 0x32) { - if (luaInputTelemetryFifo && luaInputTelemetryFifo->hasSpace(sizeof(SportTelemetryPacket))) { - SportTelemetryPacket luaPacket; - luaPacket.physicalId = physicalId; - luaPacket.primId = primId; - luaPacket.dataId = dataId; - luaPacket.value = data; - for (uint8_t i=0; ipush(luaPacket.raw[i]); - } - } + SportTelemetryPacket luaPacket; + luaPacket.physicalId = physicalId; + luaPacket.primId = primId; + luaPacket.dataId = dataId; + luaPacket.value = data; + pushTelemetryDataToQueues(luaPacket.raw, sizeof(SportTelemetryPacket)); } #endif } diff --git a/radio/src/telemetry/ghost.cpp b/radio/src/telemetry/ghost.cpp index 988c7a55543..efb4adec2ce 100644 --- a/radio/src/telemetry/ghost.cpp +++ b/radio/src/telemetry/ghost.cpp @@ -315,11 +315,7 @@ void processGhostTelemetryFrame(uint8_t module, uint8_t* buffer, uint32_t length #if defined(LUA) default: // destination address and CRC are skipped - if (luaInputTelemetryFifo && luaInputTelemetryFifo->hasSpace(length - 2) ) { - for (uint8_t i = 1; i < length - 1; i++) { - luaInputTelemetryFifo->push(buffer[i]); - } - } + pushTelemetryDataToQueues(buffer + 1, length - 2); break; #endif } diff --git a/radio/src/telemetry/telemetry.cpp b/radio/src/telemetry/telemetry.cpp index 8ffbb6421a7..c7c1f11b45e 100644 --- a/radio/src/telemetry/telemetry.cpp +++ b/radio/src/telemetry/telemetry.cpp @@ -468,7 +468,38 @@ void logTelemetryWriteByte(uint8_t data) OutputTelemetryBuffer outputTelemetryBuffer __DMA_NO_CACHE; #if defined(LUA) -Fifo * luaInputTelemetryFifo = NULL; +TelemetryQueue* luaInputTelemetryFifo = nullptr; +#if defined(COLORLCD) +std::list telemetryQueues; + +void registerTelemetryQueue(TelemetryQueue* queue) +{ + telemetryQueues.emplace_back(queue); +} + +void deregisterTelemetryQueue(TelemetryQueue* queue) +{ + telemetryQueues.remove(queue); +} +#endif + +static void pushDataToQueue(TelemetryQueue* queue, uint8_t* data, int length) +{ + if (queue && queue->hasSpace(length)) { + for (uint8_t i = 0; i < length; i += 1) { + queue->push(data[i]); + } + } +} + +void pushTelemetryDataToQueues(uint8_t* data, int length) +{ +#if defined(COLORLCD) + for (auto it = telemetryQueues.cbegin(); it != telemetryQueues.cend(); ++it) + pushDataToQueue(*it, data, length); +#endif + pushDataToQueue(luaInputTelemetryFifo, data, length); +} #endif #if defined(HARDWARE_INTERNAL_MODULE) diff --git a/radio/src/telemetry/telemetry.h b/radio/src/telemetry/telemetry.h index c6cb61d3533..7ae2ec7bb18 100644 --- a/radio/src/telemetry/telemetry.h +++ b/radio/src/telemetry/telemetry.h @@ -236,7 +236,11 @@ extern OutputTelemetryBuffer outputTelemetryBuffer __DMA_NO_CACHE; #if defined(LUA) #include "fifo.h" #define LUA_TELEMETRY_INPUT_FIFO_SIZE 256 -extern Fifo * luaInputTelemetryFifo; +typedef Fifo TelemetryQueue; +extern TelemetryQueue* luaInputTelemetryFifo; +void registerTelemetryQueue(TelemetryQueue*); +void deregisterTelemetryQueue(TelemetryQueue*); +void pushTelemetryDataToQueues(uint8_t* data, int length); #endif void processPXX2Frame(uint8_t idx, const uint8_t* frame, diff --git a/radio/src/tests/crossfire.cpp b/radio/src/tests/crossfire.cpp index 1ed3caa0b55..18350b7f21d 100644 --- a/radio/src/tests/crossfire.cpp +++ b/radio/src/tests/crossfire.cpp @@ -61,8 +61,7 @@ struct crsf_frame_test { { ctx = CrossfireDriver.init(EXTERNAL_MODULE); if (!luaInputTelemetryFifo) { - luaInputTelemetryFifo = - new Fifo(); + luaInputTelemetryFifo = new TelemetryQueue(); assert(luaInputTelemetryFifo != nullptr); } else { luaInputTelemetryFifo->clear(); From 19912a35e31fc9858a61d6468c5a7b5e68994c1b Mon Sep 17 00:00:00 2001 From: philmoz Date: Thu, 20 Feb 2025 10:36:42 +1100 Subject: [PATCH 03/11] Add table for default value to select first available. --- radio/src/lua/lua_widget_factory.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/radio/src/lua/lua_widget_factory.cpp b/radio/src/lua/lua_widget_factory.cpp index b08160335c3..52bda1be76c 100644 --- a/radio/src/lua/lua_widget_factory.cpp +++ b/radio/src/lua/lua_widget_factory.cpp @@ -157,25 +157,39 @@ void LuaWidgetFactory::translateOptions(ZoneOption * options) static int switchValue() { - int v; - if (lua_type(lsWidgets, -1) == LUA_TSTRING) { + int v = SWSRC_INVERT; + if (lua_istable(lsWidgets, -1)) { + // Find first available + int t = lua_gettop(lsWidgets); + for (lua_pushnil(lsWidgets); v == SWSRC_INVERT && lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) { + v = getSwitchIndex(luaL_checkstring(lsWidgets, -1), false); + } + lua_settop(lsWidgets, t); + } else if (lua_type(lsWidgets, -1) == LUA_TSTRING) { v = getSwitchIndex(lua_tostring(lsWidgets, -1), true); - if (v == SWSRC_INVERT) v = SWSRC_NONE; } else { v = luaL_checkinteger(lsWidgets, -1); } + if (v == SWSRC_INVERT) v = SWSRC_NONE; return v; } static int sourceValue() { - int v; - if (lua_type(lsWidgets, -1) == LUA_TSTRING) { + int v = -1; + if (lua_istable(lsWidgets, -1)) { + // Find first available + int t = lua_gettop(lsWidgets); + for (lua_pushnil(lsWidgets); v < 0 && lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) { + v = getSourceIndex(luaL_checkstring(lsWidgets, -1), false); + } + lua_settop(lsWidgets, t); + } else if (lua_type(lsWidgets, -1) == LUA_TSTRING) { v = getSourceIndex(lua_tostring(lsWidgets, -1), true); - if (v == -1) v = MIXSRC_NONE; } else { v = luaL_checkunsigned(lsWidgets, -1); } + if (v == -1) v = MIXSRC_NONE; return v; } From 5e461b6d816d73c9f1f248994d470f8e7bbed15b Mon Sep 17 00:00:00 2001 From: philmoz Date: Sun, 23 Feb 2025 12:38:14 +1100 Subject: [PATCH 04/11] Allow 'pts' property tp be set to a function. --- radio/src/lua/api_colorlcd_lvgl.cpp | 12 +-- radio/src/lua/lua_lvgl_widget.cpp | 128 +++++++++++++++++++--------- radio/src/lua/lua_lvgl_widget.h | 83 +++++++++--------- 3 files changed, 132 insertions(+), 91 deletions(-) diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index ae973ac7689..da4672f214b 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -32,8 +32,7 @@ static int luaLvglObj(lua_State *L, std::function create, b { if (luaScriptManager && (!fullscreenOnly || luaScriptManager->isFullscreen())) { auto obj = create(); - obj->getParams(L, 1); - obj->build(L); + obj->create(L, 1); obj->push(L); } else { lua_pushnil(L); @@ -56,8 +55,7 @@ static int luaLvglObjEx(lua_State *L, std::function cre } auto obj = create(); - obj->getParams(L, -1); - obj->build(L); + obj->create(L, -1); obj->push(L); if (p) @@ -72,8 +70,7 @@ static int luaLvglObjEx(lua_State *L, std::function cre static int luaLvglPopup(lua_State *L, std::function create) { auto obj = create(); - obj->getParams(L, 1); - obj->build(L); + obj->create(L, 1); return 0; } @@ -234,8 +231,7 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) obj = new LvglWidgetSetting(); } if (obj) { - obj->getParams(L, -1); - obj->build(L); + obj->create(L, -1); auto ref = obj->getRef(L); if (p.name) { lua_pushstring(L, p.name); diff --git a/radio/src/lua/lua_lvgl_widget.cpp b/radio/src/lua/lua_lvgl_widget.cpp index e371af1c2fb..2a7d0f39861 100644 --- a/radio/src/lua/lua_lvgl_widget.cpp +++ b/radio/src/lua/lua_lvgl_widget.cpp @@ -396,6 +396,13 @@ void LvglWidgetObjectBase::refresh() setOpacity(opacity); } +void LvglWidgetObjectBase::create(lua_State *L, int index) +{ + getParams(L, index); + build(L); + callRefs(L); +} + void LvglWidgetObjectBase::update(lua_State *L) { getParams(L, 2); @@ -547,7 +554,6 @@ void LvglWidgetLabel::build(lua_State *L) setOpacity(opacity); setFont(font); setAlign(align); - callRefs(L); } //----------------------------------------------------------------------------- @@ -600,7 +606,7 @@ void LvglWidgetLineBase::setSize(coord_t w, coord_t h) setLine(); } -void LvglWidgetLineBase::build(lua_State* L) +void LvglWidgetLineBase::build(lua_State *L) { lvobj = lv_line_create(lvglManager->getCurrentParent()->getLvObj()); refresh(); @@ -736,7 +742,6 @@ void LvglWidgetLine::build(lua_State *L) { lvobj = lv_line_create(lvglManager->getCurrentParent()->getLvObj()); refresh(); - callRefs(L); } void LvglWidgetLine::refresh() @@ -748,6 +753,11 @@ void LvglWidgetLine::refresh() //----------------------------------------------------------------------------- +LvglWidgetTriangle::LvglWidgetTriangle() : LvglSimpleWidgetObject() +{ + parent = lvglManager->getCurrentParent()->getLvObj(); +} + LvglWidgetTriangle::~LvglWidgetTriangle() { if (mask) @@ -760,25 +770,56 @@ void LvglWidgetTriangle::getPt(lua_State* L, int n) lua_rawgeti(L, -1, n + 1); luaL_checktype(L, -1, LUA_TTABLE); lua_rawgeti(L, -1, 1); - px[n] = luaL_checkunsigned(L, -1); + pts[n].x = luaL_checkunsigned(L, -1); lua_pop(L, 1); lua_rawgeti(L, -1, 2); - py[n] = luaL_checkunsigned(L, -1); + pts[n].y = luaL_checkunsigned(L, -1); lua_pop(L, 2); } void LvglWidgetTriangle::parseParam(lua_State *L, const char *key) { if (!strcmp(key, "pts")) { - luaL_checktype(L, -1, LUA_TTABLE); - getPt(L, 0); - getPt(L, 1); - getPt(L, 2); + if (lua_isfunction(L, -1)) { + getPointsFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + luaL_checktype(L, -1, LUA_TTABLE); + getPt(L, 0); + getPt(L, 1); + getPt(L, 2); + } } else { LvglSimpleWidgetObject::parseParam(L, key); } } +bool LvglWidgetTriangle::callRefs(lua_State *L) +{ + int t = lua_gettop(L); + if (getPointsFunction != LUA_REFNIL) { + if (pcallFunc(L, getPointsFunction, 1)) { + luaL_checktype(L, -1, LUA_TTABLE); + getPt(L, 0); + getPt(L, 1); + getPt(L, 2); + lua_settop(L, t); + if (memcmp(&last, &pts, sizeof(pts)) != 0) { + memcpy(&last, &pts, sizeof(pts)); + refresh(); + } + } else { + return false; + } + } + return LvglSimpleWidgetObject::callRefs(L); +} + +void LvglWidgetTriangle::clearRefs(lua_State *L) +{ + clearRef(L, getPointsFunction); + LvglSimpleWidgetObject::clearRefs(L); +} + void LvglWidgetTriangle::setColor(LcdFlags newColor) { if (lvobj && colorChanged(newColor)) { @@ -810,9 +851,12 @@ void LvglWidgetTriangle::fillTriangle() { if (!mask) return; - coord_t x1 = px[0], y1 = py[0], x2 = px[1], y2 = py[1], x3 = px[2], y3 = py[2]; + // Convert to relative coords + coord_t x1 = pts[0].x - x, y1 = pts[0].y - y; + coord_t x2 = pts[1].x - x, y2 = pts[1].y - y; + coord_t x3 = pts[2].x - x, y3 = pts[2].y - y; - coord_t t1x, t2x, y, minx, maxx, t1xp, t2xp; + coord_t t1x, t2x, ty, minx, maxx, t1xp, t2xp; bool changed1 = false; bool changed2 = false; coord_t signx1, signx2, dx1, dy1, dx2, dy2; @@ -823,7 +867,7 @@ void LvglWidgetTriangle::fillTriangle() if (y1 > y3) { SWAP(y1, y3); SWAP(x1, x3); } if (y2 > y3) { SWAP(y2, y3); SWAP(x2, x3); } - t1x = t2x = x1; y = y1; // Starting points + t1x = t2x = x1; ty = y1; // Starting points dx1 = (coord_t)(x2 - x1); if(dx1 < 0) { dx1 = -dx1; signx1 = -1; } else signx1 = 1; dy1 = (coord_t)(y2 - y1); @@ -849,7 +893,7 @@ void LvglWidgetTriangle::fillTriangle() t1xp = 0; t2xp = 0; if (t1x < t2x) { minx = t1x; maxx = t2x; } else { minx = t2x; maxx = t1x; } - // process first line until y value is about to change + // process first line until ty value is about to change while (i < dx1) { i++; e1 += dy1; @@ -863,7 +907,7 @@ void LvglWidgetTriangle::fillTriangle() } // Move line next1: - // process second line until y value is about to change + // process second line until ty value is about to change while (1) { e2 += dy2; while (e2 >= dx2) { @@ -879,14 +923,14 @@ void LvglWidgetTriangle::fillTriangle() if (minx > t2x) minx = t2x; if (maxx < t1x) maxx = t1x; if (maxx < t2x) maxx = t2x; - fillLine(minx, maxx, y); // Draw line from min to max points found on the y + fillLine(minx, maxx, ty); // Draw line from min to max points found on the y // Now increase y if (!changed1) t1x += signx1; t1x += t1xp; if (!changed2) t2x += signx2; t2x += t2xp; - y += 1; - if (y == y2) break; + ty += 1; + if (ty == y2) break; } next: // Second half @@ -905,7 +949,7 @@ void LvglWidgetTriangle::fillTriangle() t1xp = 0; t2xp = 0; if (t1x < t2x) { minx = t1x; maxx = t2x; } else { minx = t2x; maxx = t1x; } - // process first line until y value is about to change + // process first line until ty value is about to change while (i < dx1) { e1 += dy1; while (e1 >= dx1) { @@ -918,7 +962,7 @@ void LvglWidgetTriangle::fillTriangle() if (i < dx1) i++; } next3: - // process second line until y value is about to change + // process second line until ty value is about to change while (t2x != x3) { e2 += dy2; while (e2 >= dx2) { @@ -934,29 +978,25 @@ void LvglWidgetTriangle::fillTriangle() if (minx > t2x) minx = t2x; if (maxx < t1x) maxx = t1x; if (maxx < t2x) maxx = t2x; - fillLine(minx, maxx, y); // Draw line from min to max points found on the y + fillLine(minx, maxx, ty); // Draw line from min to max points found on the y // Now increase y if (!changed1) t1x += signx1; t1x += t1xp; if (!changed2) t2x += signx2; t2x += t2xp; - y += 1; - if (y > y3) return; + ty += 1; + if (ty > y3) return; } } void LvglWidgetTriangle::build(lua_State *L) { // Bounds - x = min(min(px[0], px[1]), px[2]); - y = min(min(py[0], py[1]), py[2]); - w = max(max(px[0], px[1]), px[2]) - x + 1; - h = max(max(py[0], py[1]), py[2]) - y + 1; - - // Convert to relative coords - px[0] -= x; px[1] -= x; px[2] -= x; - py[0] -= y; py[1] -= y; py[2] -= y; - + x = min(min(pts[0].x, pts[1].x), pts[2].x); + y = min(min(pts[0].y, pts[1].y), pts[2].y); + w = max(max(pts[0].x, pts[1].x), pts[2].x) - x + 1; + h = max(max(pts[0].y, pts[1].y), pts[2].y) - y + 1; + // Allocate mask size_t size = w * h; mask = (MaskBitmap*)malloc(size + 4); @@ -970,22 +1010,31 @@ void LvglWidgetTriangle::build(lua_State *L) // Create canvas from mask buffer if (lvobj == nullptr) - lvobj = lv_canvas_create(lvglManager->getCurrentParent()->getLvObj()); + lvobj = lv_canvas_create(parent); lv_canvas_set_buffer(lvobj, (void*)mask->data, mask->width, mask->height, LV_IMG_CF_ALPHA_8BIT); - // Set position, size and color + // Set position and, size setPos(x, y); LvglSimpleWidgetObject::setSize(w,h); + + // Set color setColor(color); } - if (L) callRefs(L); } void LvglWidgetTriangle::refresh() { - if (mask) free(mask); - mask = nullptr; + if (mask) { + free(mask); + mask = nullptr; + } + if (lvobj) { + // May render incorrectly when trying to reuse previous canvas + lv_obj_del(lvobj); + lvobj = nullptr; + } + currentColor = -1; build(nullptr); } @@ -1046,7 +1095,7 @@ void LvglWidgetObject::clearRefs(lua_State *L) //----------------------------------------------------------------------------- -void LvglWidgetBox::build(lua_State* L) +void LvglWidgetBox::build(lua_State *L) { window = new Window(lvglManager->getCurrentParent(), {x, y, w, h}, lv_obj_create); @@ -1071,7 +1120,7 @@ void LvglWidgetSetting::parseParam(lua_State *L, const char *key) } } -void LvglWidgetSetting::build(lua_State* L) +void LvglWidgetSetting::build(lua_State *L) { window = new Window(lvglManager->getCurrentParent(), {x, y, w, h}, lv_obj_create); @@ -1201,7 +1250,6 @@ void LvglWidgetRectangle::build(lua_State *L) (rounded >= thickness) ? rounded : thickness, LV_PART_MAIN); } - callRefs(L); } //----------------------------------------------------------------------------- @@ -1214,7 +1262,6 @@ void LvglWidgetCircle::build(lua_State *L) setRadius(radius); LvglWidgetRoundObject::build(L); lv_obj_set_style_radius(window->getLvObj(), LV_RADIUS_CIRCLE, LV_PART_MAIN); - callRefs(L); } //----------------------------------------------------------------------------- @@ -1303,7 +1350,6 @@ void LvglWidgetArc::build(lua_State *L) lv_obj_set_style_arc_opa(window->getLvObj(), LV_OPA_TRANSP, LV_PART_MAIN); lv_obj_set_style_arc_width(window->getLvObj(), thickness, LV_PART_INDICATOR); setOpacity(opacity); - callRefs(L); } //----------------------------------------------------------------------------- diff --git a/radio/src/lua/lua_lvgl_widget.h b/radio/src/lua/lua_lvgl_widget.h index dfd451cc2f3..30341c6e35c 100644 --- a/radio/src/lua/lua_lvgl_widget.h +++ b/radio/src/lua/lua_lvgl_widget.h @@ -38,9 +38,6 @@ class LvglWidgetObjectBase void push(lua_State *L); void saveLvglObjectRef(int ref); - void getParams(lua_State *L, int index); - - virtual void build(lua_State *L); virtual bool callRefs(lua_State *L); virtual void clearRefs(lua_State *L); void clearChildRefs(lua_State *L); @@ -55,6 +52,7 @@ class LvglWidgetObjectBase virtual void setPos(coord_t x, coord_t y) {} virtual void setSize(coord_t w, coord_t h) {} + void create(lua_State *L, int index); void update(lua_State *L); virtual Window *getWindow() const = 0; @@ -79,10 +77,13 @@ class LvglWidgetObjectBase int getSizeFunction = LUA_REFNIL; int getPosFunction = LUA_REFNIL; + virtual void build(lua_State *L); virtual void refresh(); virtual void parseParam(lua_State *L, const char *key); + void getParams(lua_State *L, int index); + bool colorChanged(LcdFlags newColor); void clearRef(lua_State *L, int& ref); @@ -130,7 +131,6 @@ class LvglWidgetLabel : public LvglSimpleWidgetObject void setFont(LcdFlags font); void setAlign(LcdFlags align); - void build(lua_State *L) override; bool callRefs(lua_State *L) override; void clearRefs(lua_State *L) override; @@ -144,6 +144,7 @@ class LvglWidgetLabel : public LvglSimpleWidgetObject int getFontFunction = LUA_REFNIL; int getAlignFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; void refresh() override { @@ -166,8 +167,6 @@ class LvglWidgetLineBase : public LvglSimpleWidgetObject void setPos(coord_t x, coord_t y) override; void setSize(coord_t w, coord_t h) override; - void build(lua_State *L) override; - protected: bool rounded = false; int dashGap = 0; @@ -176,6 +175,7 @@ class LvglWidgetLineBase : public LvglSimpleWidgetObject virtual void setLine() = 0; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; void refresh() override; }; @@ -214,8 +214,6 @@ class LvglWidgetLine : public LvglSimpleWidgetObject void setPos(coord_t x, coord_t y) override; void setSize(coord_t w, coord_t h) override; - void build(lua_State *L) override; - protected: coord_t thickness = 1; bool rounded = false; @@ -225,6 +223,7 @@ class LvglWidgetLine : public LvglSimpleWidgetObject void setLine(); void getPt(lua_State* L, int n); + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; void refresh() override; }; @@ -234,22 +233,27 @@ class LvglWidgetLine : public LvglSimpleWidgetObject class LvglWidgetTriangle : public LvglSimpleWidgetObject { public: - LvglWidgetTriangle() : LvglSimpleWidgetObject() {} + LvglWidgetTriangle(); ~LvglWidgetTriangle(); void setColor(LcdFlags newColor) override; void setSize(coord_t w, coord_t h) override; - void build(lua_State *L) override; + bool callRefs(lua_State *L) override; + void clearRefs(lua_State *L) override; protected: - coord_t px[3] = {0}, py[3] = {0}; + lv_point_t pts[3] = {0}; + lv_point_t last[3] = {-1}; MaskBitmap* mask = nullptr; + lv_obj_t* parent = nullptr; + int getPointsFunction = LUA_REFNIL; void fillTriangle(); void fillLine(coord_t x1, coord_t x2, coord_t y); void getPt(lua_State* L, int n); + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; void refresh() override; }; @@ -292,9 +296,8 @@ class LvglWidgetBox : public LvglWidgetObject public: LvglWidgetBox() : LvglWidgetObject() {} - void build(lua_State *L) override; - protected: + void build(lua_State *L) override; }; //----------------------------------------------------------------------------- @@ -304,11 +307,10 @@ class LvglWidgetSetting : public LvglWidgetObject public: LvglWidgetSetting() : LvglWidgetObject() {} - void build(lua_State *L) override; - protected: const char *txt = ""; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -322,12 +324,11 @@ class LvglWidgetBorderedObject : public LvglWidgetObject void setColor(LcdFlags newColor) override; void setOpacity(uint8_t newOpa) override; - void build(lua_State *L) override; - protected: coord_t thickness = 1; bool filled = false; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -364,11 +365,10 @@ class LvglWidgetRectangle : public LvglWidgetBorderedObject public: LvglWidgetRectangle() : LvglWidgetBorderedObject() {} - void build(lua_State *L) override; - protected: coord_t rounded = 0; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -379,9 +379,8 @@ class LvglWidgetCircle : public LvglWidgetRoundObject public: LvglWidgetCircle() : LvglWidgetRoundObject() {} - void build(lua_State *L) override; - protected: + void build(lua_State *L) override; }; //----------------------------------------------------------------------------- @@ -396,7 +395,6 @@ class LvglWidgetArc : public LvglWidgetRoundObject void setStartAngle(coord_t angle); void setEndAngle(coord_t angle); - void build(lua_State *L) override; bool callRefs(lua_State *L) override; void clearRefs(lua_State *L) override; @@ -406,6 +404,7 @@ class LvglWidgetArc : public LvglWidgetRoundObject int getStartAngleFunction = LUA_REFNIL; int getEndAngleFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; void refresh() override { @@ -422,12 +421,11 @@ class LvglWidgetImage : public LvglWidgetObject public: LvglWidgetImage() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} - void build(lua_State *L) override; - protected: std::string filename; bool fillFrame = false; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -438,12 +436,11 @@ class LvglWidgetQRCode : public LvglWidgetObject public: LvglWidgetQRCode() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} - void build(lua_State *L) override; - protected: std::string data; LcdFlags bgColor = COLOR2FLAGS(COLOR_THEME_SECONDARY3_INDEX); + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -487,13 +484,13 @@ class LvglWidgetTextButton : public LvglWidgetTextButtonBase void setFont(LcdFlags font); void setChecked(bool check); - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: bool checked = false; int longPressFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; void refresh() override { @@ -511,12 +508,12 @@ class LvglWidgetMomentaryButton : public LvglWidgetTextButtonBase void setFont(LcdFlags font); - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: int releaseFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -527,13 +524,13 @@ class LvglWidgetToggleSwitch : public LvglWidgetObject public: LvglWidgetToggleSwitch() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: int getStateFunction = LUA_REFNIL; int setStateFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -544,7 +541,6 @@ class LvglWidgetTextEdit : public LvglWidgetObject public: LvglWidgetTextEdit() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: @@ -554,6 +550,7 @@ class LvglWidgetTextEdit : public LvglWidgetObject int setFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -564,7 +561,6 @@ class LvglWidgetNumberEdit : public LvglWidgetObject public: LvglWidgetNumberEdit() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: @@ -574,6 +570,7 @@ class LvglWidgetNumberEdit : public LvglWidgetObject int setFunction = LUA_REFNIL; int dispFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -607,6 +604,7 @@ class LvglWidgetSlider : public LvglWidgetSliderBase public: LvglWidgetSlider() : LvglWidgetSliderBase() {} + protected: void build(lua_State *L) override; }; @@ -617,6 +615,7 @@ class LvglWidgetVerticalSlider : public LvglWidgetSliderBase public: LvglWidgetVerticalSlider() : LvglWidgetSliderBase() {} + protected: void build(lua_State *L) override; }; @@ -627,7 +626,6 @@ class LvglWidgetPage : public LvglWidgetObject public: LvglWidgetPage() : LvglWidgetObject() {} - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: @@ -637,6 +635,7 @@ class LvglWidgetPage : public LvglWidgetObject int backActionFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -647,7 +646,6 @@ class LvglWidgetDialog : public LvglWidgetObject public: LvglWidgetDialog() : LvglWidgetObject() {} - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: @@ -655,6 +653,7 @@ class LvglWidgetDialog : public LvglWidgetObject int closeFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -665,7 +664,6 @@ class LvglWidgetConfirmDialog : public LvglWidgetObject public: LvglWidgetConfirmDialog() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: @@ -675,6 +673,7 @@ class LvglWidgetConfirmDialog : public LvglWidgetObject int confirmFunction = LUA_REFNIL; int cancelFunction = LUA_REFNIL; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -685,13 +684,12 @@ class LvglWidgetMessageDialog : public LvglWidgetObject public: LvglWidgetMessageDialog() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} - void build(lua_State *L) override; - protected: const char *title = nullptr; const char *message = nullptr; const char *details = nullptr; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -718,12 +716,11 @@ class LvglWidgetChoice : public LvglWidgetPicker public: LvglWidgetChoice() : LvglWidgetPicker() {} - void build(lua_State *L) override; - protected: std::string title; std::vector values; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -734,6 +731,7 @@ class LvglWidgetFontPicker : public LvglWidgetPicker public: LvglWidgetFontPicker() : LvglWidgetPicker() {} + protected: void build(lua_State *L) override; }; @@ -744,6 +742,7 @@ class LvglWidgetAlignPicker : public LvglWidgetPicker public: LvglWidgetAlignPicker() : LvglWidgetPicker() {} + protected: void build(lua_State *L) override; }; @@ -754,6 +753,7 @@ class LvglWidgetColorPicker : public LvglWidgetPicker public: LvglWidgetColorPicker() : LvglWidgetPicker() {} + protected: void build(lua_State *L) override; }; @@ -764,6 +764,7 @@ class LvglWidgetTimerPicker : public LvglWidgetPicker public: LvglWidgetTimerPicker() : LvglWidgetPicker() {} + protected: void build(lua_State *L) override; }; @@ -774,11 +775,10 @@ class LvglWidgetSwitchPicker : public LvglWidgetPicker public: LvglWidgetSwitchPicker() : LvglWidgetPicker() {} - void build(lua_State *L) override; - protected: uint32_t filter = 0xFFFFFFFF; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; @@ -789,11 +789,10 @@ class LvglWidgetSourcePicker : public LvglWidgetPicker public: LvglWidgetSourcePicker() : LvglWidgetPicker() {} - void build(lua_State *L) override; - protected: uint32_t filter = 0xFFFFFFFF; + void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; }; From 13031e2d32216997d04e18ad804ddb2fdbed3983 Mon Sep 17 00:00:00 2001 From: philmoz Date: Sun, 23 Feb 2025 13:17:20 +1100 Subject: [PATCH 05/11] Allow 'line.pts' property to be set to a function. Use hash to determine if line or triangle points have changed. --- radio/src/lua/lua_lvgl_widget.cpp | 90 ++++++++++++++++++++++++++----- radio/src/lua/lua_lvgl_widget.h | 11 +++- 2 files changed, 85 insertions(+), 16 deletions(-) diff --git a/radio/src/lua/lua_lvgl_widget.cpp b/radio/src/lua/lua_lvgl_widget.cpp index 2a7d0f39861..e8c35a24681 100644 --- a/radio/src/lua/lua_lvgl_widget.cpp +++ b/radio/src/lua/lua_lvgl_widget.cpp @@ -654,6 +654,18 @@ void LvglWidgetVLine::setLine() //----------------------------------------------------------------------------- +LvglWidgetLine::LvglWidgetLine() : LvglSimpleWidgetObject() +{ + parent = lvglManager->getCurrentParent()->getLvObj(); +} + +LvglWidgetLine::~LvglWidgetLine() +{ + if (pts) + delete pts; + pts = nullptr; +} + void LvglWidgetLine::getPt(lua_State* L, int n) { lua_rawgeti(L, -1, n + 1); @@ -673,19 +685,61 @@ void LvglWidgetLine::parseParam(lua_State *L, const char *key) } else if (!strcmp(key, "rounded")) { rounded = lua_toboolean(L, -1); } else if (!strcmp(key, "pts")) { - luaL_checktype(L, -1, LUA_TTABLE); - ptCnt = lua_rawlen(L, -1); - if (pts) delete pts; - if (ptCnt > 1) { - pts = new lv_point_t[ptCnt]; - for (size_t i = 0; i < ptCnt; i += 1) - getPt(L, i); + if (lua_isfunction(L, -1)) { + getPointsFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + luaL_checktype(L, -1, LUA_TTABLE); + ptCnt = lua_rawlen(L, -1); + if (pts) delete pts; + if (ptCnt > 1) { + pts = new lv_point_t[ptCnt]; + for (size_t i = 0; i < ptCnt; i += 1) + getPt(L, i); + } else { + pts = nullptr; + ptCnt = 0; + } } } else { LvglSimpleWidgetObject::parseParam(L, key); } } +bool LvglWidgetLine::callRefs(lua_State *L) +{ + int t = lua_gettop(L); + if (getPointsFunction != LUA_REFNIL) { + if (pcallFunc(L, getPointsFunction, 1)) { + luaL_checktype(L, -1, LUA_TTABLE); + ptCnt = lua_rawlen(L, -1); + if (pts) delete pts; + if (ptCnt > 1) { + pts = new lv_point_t[ptCnt]; + for (size_t i = 0; i < ptCnt; i += 1) + getPt(L, i); + uint32_t h = hash(pts, sizeof(ptCnt * sizeof(lv_point_t))); + if (h != ptsHash) { + ptsHash = h; + refresh(); + } + } else { + pts = nullptr; + ptCnt = 0; + } + lua_settop(L, t); + } else { + return false; + } + } + return LvglSimpleWidgetObject::callRefs(L); +} + +void LvglWidgetLine::clearRefs(lua_State *L) +{ + clearRef(L, getPointsFunction); + LvglSimpleWidgetObject::clearRefs(L); +} + void LvglWidgetLine::setColor(LcdFlags newColor) { if (lvobj && colorChanged(newColor)) { @@ -740,15 +794,22 @@ void LvglWidgetLine::setLine() void LvglWidgetLine::build(lua_State *L) { - lvobj = lv_line_create(lvglManager->getCurrentParent()->getLvObj()); - refresh(); + if (pts) { + lvobj = lv_line_create(parent); + setColor(color); + setOpacity(opacity); + setLine(); + } } void LvglWidgetLine::refresh() { - setColor(color); - setOpacity(opacity); - setLine(); + if (lvobj) { + lv_obj_del(lvobj); + lvobj = nullptr; + } + currentColor = -1; + build(nullptr); } //----------------------------------------------------------------------------- @@ -803,8 +864,9 @@ bool LvglWidgetTriangle::callRefs(lua_State *L) getPt(L, 1); getPt(L, 2); lua_settop(L, t); - if (memcmp(&last, &pts, sizeof(pts)) != 0) { - memcpy(&last, &pts, sizeof(pts)); + uint32_t h = hash(&pts, sizeof(pts)); + if (h != ptsHash) { + ptsHash = h; refresh(); } } else { diff --git a/radio/src/lua/lua_lvgl_widget.h b/radio/src/lua/lua_lvgl_widget.h index 30341c6e35c..5f2ea14e9fd 100644 --- a/radio/src/lua/lua_lvgl_widget.h +++ b/radio/src/lua/lua_lvgl_widget.h @@ -207,18 +207,25 @@ class LvglWidgetVLine : public LvglWidgetLineBase class LvglWidgetLine : public LvglSimpleWidgetObject { public: - LvglWidgetLine() : LvglSimpleWidgetObject() {} + LvglWidgetLine(); + ~LvglWidgetLine(); void setColor(LcdFlags newColor) override; void setOpacity(uint8_t newOpa) override; void setPos(coord_t x, coord_t y) override; void setSize(coord_t w, coord_t h) override; + bool callRefs(lua_State *L) override; + void clearRefs(lua_State *L) override; + protected: coord_t thickness = 1; bool rounded = false; size_t ptCnt = 0; lv_point_t* pts = nullptr; + uint32_t ptsHash = -1; + lv_obj_t* parent = nullptr; + int getPointsFunction = LUA_REFNIL; void setLine(); @@ -244,7 +251,7 @@ class LvglWidgetTriangle : public LvglSimpleWidgetObject protected: lv_point_t pts[3] = {0}; - lv_point_t last[3] = {-1}; + uint32_t ptsHash = -1; MaskBitmap* mask = nullptr; lv_obj_t* parent = nullptr; int getPointsFunction = LUA_REFNIL; From c987ffca56422fe1ce6f59efd98bb3e134b419e5 Mon Sep 17 00:00:00 2001 From: philmoz Date: Mon, 3 Mar 2025 20:09:22 +1100 Subject: [PATCH 06/11] Prevent crashes if there are errors when building a layout - should show error message. --- radio/src/lua/api_colorlcd_lvgl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index da4672f214b..38281754905 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -172,6 +172,7 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) { luaL_checktype(L, srcIndex, LUA_TTABLE); for (lua_pushnil(L); lua_next(L, srcIndex - 1); lua_pop(L, 1)) { + auto t = lua_gettop(L); LvglWidgetParams p(L, -1); LvglWidgetObjectBase *obj = nullptr; if (strcasecmp(p.type, "label") == 0) @@ -247,6 +248,7 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) luaScriptManager->setTempParent(prevParent); } } + lua_settop(L, t); // In case of errors in build functions } } From 7ea1e2a81d2e4fab7e4634a72a4fd0a3271028fc Mon Sep 17 00:00:00 2001 From: philmoz Date: Wed, 5 Mar 2025 16:12:29 +1100 Subject: [PATCH 07/11] Change 'isAppMode()' to always return true when widget installed in App Mode layout. --- radio/src/lua/lua_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio/src/lua/lua_widget.cpp b/radio/src/lua/lua_widget.cpp index 368ea7369cc..a5762718ed5 100644 --- a/radio/src/lua/lua_widget.cpp +++ b/radio/src/lua/lua_widget.cpp @@ -552,7 +552,7 @@ bool LuaWidget::useLvglLayout() const { return luaFactory()->useLvglLayout(); } bool LuaWidget::isAppMode() const { - return fullscreen && ViewMain::instance()->isAppMode(); + return ViewMain::instance()->isAppMode(); } void LuaScriptManager::saveLvglObjectRef(int ref) From 6c4487240de480f223e7b4f427d20886238a8feb Mon Sep 17 00:00:00 2001 From: philmoz Date: Fri, 7 Mar 2025 09:54:55 +1100 Subject: [PATCH 08/11] Add missing metatable entries. --- radio/src/lua/api_colorlcd_lvgl.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index 38281754905..03c1dedc67e 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -351,7 +351,7 @@ LROT_BEGIN(lvgllib, NULL, 0) LROT_FUNCENTRY(source, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSourcePicker(); }, true); }) // Containers LROT_FUNCENTRY(box, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetBox(); }, true); }) - LROT_FUNCENTRY(setting, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetSetting(); }, true); }) + LROT_FUNCENTRY(setting, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSetting(); }, true); }) LROT_FUNCENTRY(page, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetPage(); }, true); }) LROT_FUNCENTRY(dialog, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetDialog(); }, true); }) // Dialogs @@ -436,6 +436,9 @@ LROT_BEGIN(lvgl_mt, NULL, LROT_MASK_GC_INDEX) LROT_FUNCENTRY(timer, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTimerPicker(); }, true); }) LROT_FUNCENTRY(switch, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSwitchPicker(); }, true); }) LROT_FUNCENTRY(source, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSourcePicker(); }, true); }) + // Containers + LROT_FUNCENTRY(box, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetBox(); }, true); }) + LROT_FUNCENTRY(setting, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSetting(); }, true); }) // Object manipulation functions LROT_FUNCENTRY(set, luaLvglSet) LROT_FUNCENTRY(show, luaLvglShow) From 7234068cecb894886b415ac765bc77356fc09163 Mon Sep 17 00:00:00 2001 From: philmoz Date: Fri, 7 Mar 2025 17:06:16 +1100 Subject: [PATCH 09/11] Add 'close()' function for 'dialog' control. --- radio/src/lua/api_colorlcd_lvgl.cpp | 11 +++++++++++ radio/src/lua/lua_lvgl_widget.cpp | 13 ++++++++++--- radio/src/lua/lua_lvgl_widget.h | 4 ++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index 03c1dedc67e..f0ed1ed7674 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -145,6 +145,15 @@ static int luaLvglDisable(lua_State *L) return 0; } +static int luaLvglClose(lua_State *L) +{ + auto p = LvglWidgetObjectBase::checkLvgl(L, 1); + if (p) { + p->close(); + } + return 0; +} + class LvglWidgetParams { public: @@ -363,6 +372,7 @@ LROT_BEGIN(lvgllib, NULL, 0) LROT_FUNCENTRY(hide, luaLvglHide) LROT_FUNCENTRY(enable, luaLvglEnable) LROT_FUNCENTRY(disable, luaLvglDisable) + LROT_FUNCENTRY(close, luaLvglClose) LROT_NUMENTRY(FLOW_ROW, LV_FLEX_FLOW_ROW) LROT_NUMENTRY(FLOW_COLUMN, LV_FLEX_FLOW_COLUMN) LROT_NUMENTRY(PAD_TINY, PAD_TINY) @@ -445,6 +455,7 @@ LROT_BEGIN(lvgl_mt, NULL, LROT_MASK_GC_INDEX) LROT_FUNCENTRY(hide, luaLvglHide) LROT_FUNCENTRY(enable, luaLvglEnable) LROT_FUNCENTRY(disable, luaLvglDisable) + LROT_FUNCENTRY(close, luaLvglClose) LROT_END(lvgl_mt, NULL, LROT_MASK_GC_INDEX) extern "C" { diff --git a/radio/src/lua/lua_lvgl_widget.cpp b/radio/src/lua/lua_lvgl_widget.cpp index e8c35a24681..fdd51dddf40 100644 --- a/radio/src/lua/lua_lvgl_widget.cpp +++ b/radio/src/lua/lua_lvgl_widget.cpp @@ -1896,14 +1896,14 @@ class LvglDialog : public BaseDialog Window *getBody() { return form; } - protected: - std::function onClose; - void onCancel() override { onClose(); BaseDialog::onCancel(); } + + protected: + std::function onClose; }; void LvglWidgetDialog::parseParam(lua_State *L, const char *key) @@ -1929,11 +1929,18 @@ void LvglWidgetDialog::build(lua_State *L) if (h == LV_SIZE_CONTENT) h = DIALOG_DEFAULT_HEIGHT; auto dlg = new LvglDialog(title, w, h, [=]() { pcallSimpleFunc(L, closeFunction); }); + dialog = dlg; window = dlg->getBody(); window->setWidth(w); setFlex(); } +void LvglWidgetDialog::close() +{ + dialog->onCancel(); + clear(); +} + //----------------------------------------------------------------------------- void LvglWidgetConfirmDialog::parseParam(lua_State *L, const char *key) diff --git a/radio/src/lua/lua_lvgl_widget.h b/radio/src/lua/lua_lvgl_widget.h index 5f2ea14e9fd..c988a6f0057 100644 --- a/radio/src/lua/lua_lvgl_widget.h +++ b/radio/src/lua/lua_lvgl_widget.h @@ -25,6 +25,7 @@ #define LVGL_SIMPLEMETATABLE "LVGLSIMPLE*" class LuaScriptManager; +class LvglDialog; //----------------------------------------------------------------------------- @@ -46,6 +47,7 @@ class LvglWidgetObjectBase virtual void hide() = 0; virtual void enable() {}; virtual void disable() {}; + virtual void close() {}; virtual void setColor(LcdFlags newColor) {} virtual void setOpacity(uint8_t newOpa) {} @@ -654,9 +656,11 @@ class LvglWidgetDialog : public LvglWidgetObject LvglWidgetDialog() : LvglWidgetObject() {} void clearRefs(lua_State *L) override; + void close() override; protected: const char *title = nullptr; + LvglDialog* dialog = nullptr; int closeFunction = LUA_REFNIL; From 70ac9ac432bc20a9a3ef0c72f9b725e62eb429e1 Mon Sep 17 00:00:00 2001 From: philmoz Date: Mon, 10 Mar 2025 08:31:53 +1100 Subject: [PATCH 10/11] Fix 'box' object when used in widgets. --- radio/src/lua/api_colorlcd_lvgl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index f0ed1ed7674..ba365c24870 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -359,7 +359,7 @@ LROT_BEGIN(lvgllib, NULL, 0) LROT_FUNCENTRY(switch, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSwitchPicker(); }, true); }) LROT_FUNCENTRY(source, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSourcePicker(); }, true); }) // Containers - LROT_FUNCENTRY(box, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetBox(); }, true); }) + LROT_FUNCENTRY(box, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetBox(); }); }) LROT_FUNCENTRY(setting, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSetting(); }, true); }) LROT_FUNCENTRY(page, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetPage(); }, true); }) LROT_FUNCENTRY(dialog, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetDialog(); }, true); }) @@ -447,7 +447,7 @@ LROT_BEGIN(lvgl_mt, NULL, LROT_MASK_GC_INDEX) LROT_FUNCENTRY(switch, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSwitchPicker(); }, true); }) LROT_FUNCENTRY(source, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSourcePicker(); }, true); }) // Containers - LROT_FUNCENTRY(box, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetBox(); }, true); }) + LROT_FUNCENTRY(box, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetBox(); }); }) LROT_FUNCENTRY(setting, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSetting(); }, true); }) // Object manipulation functions LROT_FUNCENTRY(set, luaLvglSet) From 1340bda4c47edc956c3be663f8d9d5219a98c86f Mon Sep 17 00:00:00 2001 From: philmoz Date: Mon, 10 Mar 2025 15:51:54 +1100 Subject: [PATCH 11/11] Fix isAppMode() not working on startup. --- .../colorlcd/mainview/layout_factory_impl.h | 2 -- .../gui/colorlcd/mainview/widgets_container.h | 1 + radio/src/lua/lua_widget.cpp | 26 ++++++++++++++++--- radio/src/lua/lua_widget.h | 4 +-- radio/src/lua/lua_widget_factory.cpp | 13 +--------- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/radio/src/gui/colorlcd/mainview/layout_factory_impl.h b/radio/src/gui/colorlcd/mainview/layout_factory_impl.h index 7f44857231a..54670cf2d3e 100644 --- a/radio/src/gui/colorlcd/mainview/layout_factory_impl.h +++ b/radio/src/gui/colorlcd/mainview/layout_factory_impl.h @@ -98,8 +98,6 @@ class Layout: public LayoutBase return getOptionValue(LAYOUT_OPTION_MIRRORED)->boolValue; } - virtual bool isAppMode() const { return false; } - // Set decoration visibility void setTrimsVisible(bool visible); void setSlidersVisible(bool visible); diff --git a/radio/src/gui/colorlcd/mainview/widgets_container.h b/radio/src/gui/colorlcd/mainview/widgets_container.h index 8c40aed8665..4e48faf8c9d 100644 --- a/radio/src/gui/colorlcd/mainview/widgets_container.h +++ b/radio/src/gui/colorlcd/mainview/widgets_container.h @@ -85,5 +85,6 @@ class WidgetsContainer: public Window virtual void runBackground() = 0; virtual bool isLayout() { return false; } + virtual bool isAppMode() const { return false; } bool isWidgetsContainer() override { return true; } }; diff --git a/radio/src/lua/lua_widget.cpp b/radio/src/lua/lua_widget.cpp index a5762718ed5..87df7575777 100644 --- a/radio/src/lua/lua_widget.cpp +++ b/radio/src/lua/lua_widget.cpp @@ -228,12 +228,32 @@ void LuaWidget::redraw_cb(lv_event_t* e) LuaWidget::LuaWidget(const WidgetFactory* factory, Window* parent, const rect_t& rect, WidgetPersistentData* persistentData, - int luaScriptContextRef, int zoneRectDataRef, int optionsDataRef) : + int zoneRectDataRef, int optionsDataRef, + int createFunctionRef, std::string path) : Widget(factory, parent, rect, persistentData), zoneRectDataRef(zoneRectDataRef), optionsDataRef(optionsDataRef), errorMessage(nullptr) { - this->luaScriptContextRef = luaScriptContextRef; + // Push create function + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, createFunctionRef); + // Push stored zone for 'create' call + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, zoneRectDataRef); + // Push stored options for 'create' call + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, optionsDataRef); + // Push widget folder path + lua_pushstring(lsWidgets, path.c_str()); + + auto save = luaScriptManager; + luaScriptManager = this; + + if (lua_pcall(lsWidgets, 3, 1, 0) == LUA_OK) { + luaScriptContextRef = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); + } else { + luaScriptContextRef = LUA_NOREF; + setErrorMessage("create()"); + } + + luaScriptManager = save; if (useLvglLayout()) { update(); @@ -552,7 +572,7 @@ bool LuaWidget::useLvglLayout() const { return luaFactory()->useLvglLayout(); } bool LuaWidget::isAppMode() const { - return ViewMain::instance()->isAppMode(); + return ((WidgetsContainer*)parent)->isAppMode(); } void LuaScriptManager::saveLvglObjectRef(int ref) diff --git a/radio/src/lua/lua_widget.h b/radio/src/lua/lua_widget.h index 14746f3bab9..1432a9e61b1 100644 --- a/radio/src/lua/lua_widget.h +++ b/radio/src/lua/lua_widget.h @@ -108,8 +108,8 @@ class LuaWidget : public Widget, public LuaScriptManager public: LuaWidget(const WidgetFactory* factory, Window* parent, const rect_t& rect, - WidgetPersistentData* persistentData, int luaScriptContextRef, int zoneRectDataRef, - int optionsDataRef); + WidgetPersistentData* persistentData, int zoneRectDataRef, + int optionsDataRef, int createFunctionRef, std::string path); ~LuaWidget() override; #if defined(DEBUG_WINDOWS) diff --git a/radio/src/lua/lua_widget_factory.cpp b/radio/src/lua/lua_widget_factory.cpp index 52bda1be76c..7d395d45c29 100644 --- a/radio/src/lua/lua_widget_factory.cpp +++ b/radio/src/lua/lua_widget_factory.cpp @@ -70,7 +70,6 @@ Widget* LuaWidgetFactory::create(Window* parent, const rect_t& rect, initPersistentData(persistentData, init); luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, createFunction); // Make 'zone' table for 'create' call lua_newtable(lsWidgets); @@ -83,8 +82,6 @@ Widget* LuaWidgetFactory::create(Window* parent, const rect_t& rect, // Store the zone data in registry for later updates int zoneRectDataRef = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); - // Push stored zone for 'create' call - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, zoneRectDataRef); // Create options table lua_newtable(lsWidgets); @@ -107,16 +104,8 @@ Widget* LuaWidgetFactory::create(Window* parent, const rect_t& rect, // Store the options data in registry for later updates int optionsDataRef = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); - // Push stored options for 'create' call - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, optionsDataRef); - lua_pushstring(lsWidgets, path.c_str()); - - bool err = lua_pcall(lsWidgets, 3, 1, 0); - int widgetData = err ? LUA_NOREF : luaL_ref(lsWidgets, LUA_REGISTRYINDEX); - LuaWidget* lw = new LuaWidget(this, parent, rect, persistentData, widgetData, zoneRectDataRef, optionsDataRef); - if (err) lw->setErrorMessage("create()"); - return lw; + return new LuaWidget(this, parent, rect, persistentData, zoneRectDataRef, optionsDataRef, createFunction, path); } void LuaWidgetFactory::translateOptions(ZoneOption * options)