diff --git a/radio/src/gui/colorlcd/CMakeLists.txt b/radio/src/gui/colorlcd/CMakeLists.txt index 7fb1a4e74..5a2050217 100644 --- a/radio/src/gui/colorlcd/CMakeLists.txt +++ b/radio/src/gui/colorlcd/CMakeLists.txt @@ -134,6 +134,7 @@ if(LUA) lua/lua_widget.cpp lua/lua_widget_factory.cpp lua/widgets.cpp + lua/lua_lvgl_widget.cpp ) endif() diff --git a/radio/src/gui/colorlcd/layouts/layout_factory_impl.cpp b/radio/src/gui/colorlcd/layouts/layout_factory_impl.cpp index bad0cd0bc..e20549251 100644 --- a/radio/src/gui/colorlcd/layouts/layout_factory_impl.cpp +++ b/radio/src/gui/colorlcd/layouts/layout_factory_impl.cpp @@ -68,9 +68,6 @@ void Layout::adjustLayout() // Save settings decorationSettings = checkSettings; - - // Set visible decoration - show(); } void Layout::show(bool visible) diff --git a/radio/src/gui/colorlcd/model_select.cpp b/radio/src/gui/colorlcd/model_select.cpp index ab6fef7b8..a7714a118 100644 --- a/radio/src/gui/colorlcd/model_select.cpp +++ b/radio/src/gui/colorlcd/model_select.cpp @@ -142,7 +142,7 @@ class ModelButton : public Button if (modelLayouts[layout].hasImage) { if (modelCell->modelBitmap[0]) { GET_FILENAME(filename, BITMAPS_PATH, modelCell->modelBitmap, ""); - auto bitmap = new StaticImage(this, {PAD_TINY, PAD_TINY, w, h}, filename, true); + auto bitmap = new StaticImage(this, {PAD_TINY, PAD_TINY, w, h}, filename, false, true); lv_obj_move_background(bitmap->getLvObj()); bitmap->show(bitmap->hasImage()); if (bitmap->hasImage()) { @@ -626,7 +626,6 @@ void ModelLabelsWindow::newModel() snprintf(path + len, LEN_BUFFER - len, "/%s%s", name.c_str(), SCRIPT_EXT); if (f_stat(path, 0) == FR_OK) { luaExec(path); - StandaloneLuaWindow::instance()->attach(); } #endif } diff --git a/radio/src/gui/colorlcd/radio_sdmanager.cpp b/radio/src/gui/colorlcd/radio_sdmanager.cpp index 1cbbf5d3a..c9f631f4b 100644 --- a/radio/src/gui/colorlcd/radio_sdmanager.cpp +++ b/radio/src/gui/colorlcd/radio_sdmanager.cpp @@ -417,7 +417,6 @@ void RadioSdManagerPage::fileAction(const char* path, const char* name, else if (isExtensionMatching(ext, SCRIPTS_EXT)) { menu->addLine(STR_EXECUTE_FILE, [=]() { luaExec(fullpath); - StandaloneLuaWindow::instance()->attach(); }); } #endif diff --git a/radio/src/gui/colorlcd/radio_tools.cpp b/radio/src/gui/colorlcd/radio_tools.cpp index 5d60c83fc..aee9ffad5 100644 --- a/radio/src/gui/colorlcd/radio_tools.cpp +++ b/radio/src/gui/colorlcd/radio_tools.cpp @@ -102,8 +102,6 @@ static void run_lua_tool(Window* parent, const std::string& path) f_chdir(toolPath); luaExec(path.c_str()); - auto lua_win = StandaloneLuaWindow::instance(); - lua_win->attach(); } // LUA scripts in TOOLS diff --git a/radio/src/gui/colorlcd/standalone_lua.cpp b/radio/src/gui/colorlcd/standalone_lua.cpp index 5a594a390..d2d3f583a 100644 --- a/radio/src/gui/colorlcd/standalone_lua.cpp +++ b/radio/src/gui/colorlcd/standalone_lua.cpp @@ -85,8 +85,9 @@ void StandaloneLuaWindow::redraw_cb(lv_event_t* e) // singleton instance StandaloneLuaWindow* StandaloneLuaWindow::_instance; -StandaloneLuaWindow::StandaloneLuaWindow() : - Window(nullptr, {0, 0, LCD_W, LCD_H}), +StandaloneLuaWindow::StandaloneLuaWindow(bool useLvgl) : + Window(MainWindow::instance(), {0, 0, LCD_W, LCD_H}), + useLvgl(useLvgl), lcdBuffer(BMP_RGB565, LCD_W, LCD_H), popup({50, 73, LCD_W - 100, LCD_H - 146}) { @@ -94,24 +95,42 @@ StandaloneLuaWindow::StandaloneLuaWindow() : etx_solid_bg(lvobj); - lcdBuffer.clear(); - lcdBuffer.drawText(LCD_W / 2, LCD_H / 2 - 20, STR_LOADING, - FONT(L) | COLOR_THEME_PRIMARY2 | CENTERED); - lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE); + if (useLvglLayout()) { + padAll(PAD_ZERO); + etx_scrollbar(lvobj); + lv_obj_t* lbl = lv_label_create(lvobj); + lv_obj_set_pos(lbl, 0, 0); + lv_obj_set_size(lbl, LCD_W, LCD_H); + etx_solid_bg(lbl, COLOR_THEME_PRIMARY1_INDEX); + etx_txt_color(lbl, COLOR_THEME_PRIMARY2_INDEX); + 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.clear(); + lcdBuffer.drawText(LCD_W / 2, LCD_H / 2 - 20, STR_LOADING, + FONT(L) | COLOR_THEME_PRIMARY2 | CENTERED); + lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + lv_obj_add_event_cb(lvobj, StandaloneLuaWindow::redraw_cb, LV_EVENT_DRAW_MAIN, nullptr); + } // setup LUA event handler setupHandler(this); - lv_obj_add_event_cb(lvobj, StandaloneLuaWindow::redraw_cb, LV_EVENT_DRAW_MAIN, nullptr); + attach(); +} + +void StandaloneLuaWindow::setup(bool useLvgl) +{ + _instance = new StandaloneLuaWindow(useLvgl); } StandaloneLuaWindow* StandaloneLuaWindow::instance() { - if (!_instance) { - _instance = new StandaloneLuaWindow(); - } - return _instance; } @@ -121,13 +140,13 @@ void StandaloneLuaWindow::attach() // backup previous screen prevScreen = lv_scr_act(); - // and load new one - lv_scr_load(lvobj); - + Layer::back()->hide(); Layer::push(this); - lv_group_add_obj(lv_group_get_default(), lvobj); - lv_group_set_editing(lv_group_get_default(), true); + if (!useLvglLayout()) { + lv_group_add_obj(lv_group_get_default(), lvobj); + lv_group_set_editing(lv_group_get_default(), true); + } } } @@ -135,10 +154,12 @@ void StandaloneLuaWindow::deleteLater(bool detach, bool trash) { if (_deleted) return; + luaLvglManager = nullptr; + Layer::pop(this); + Layer::back()->show(); if (prevScreen) { - lv_scr_load(prevScreen); prevScreen = nullptr; } @@ -159,8 +180,20 @@ void StandaloneLuaWindow::checkEvents() if (luaState != INTERPRETER_RELOAD_PERMANENT_SCRIPTS) { // if LUA finished a full cycle, // invalidate to display the screen buffer + bool useLvgl = useLvglLayout(); if (luaTask(true)) { - invalidate(); + if (useLvgl && !hasError) { + PROTECT_LUA() { + if (!callRefs(lsScripts)) { + luaShowError(); + } + } else { + luaShowError(); + } + UNPROTECT_LUA(); + } else { + invalidate(); + } } } @@ -174,7 +207,7 @@ void StandaloneLuaWindow::checkEvents() luaLcdBuffer = nullptr; } -void StandaloneLuaWindow::onClicked() { LuaEventHandler::onClicked(); } +void StandaloneLuaWindow::onClicked() { Keyboard::hide(false); LuaEventHandler::onClicked(); } void StandaloneLuaWindow::onCancel() { LuaEventHandler::onCancel(); } @@ -210,3 +243,47 @@ bool StandaloneLuaWindow::displayPopup(event_t event, uint8_t type, return false; } + +void StandaloneLuaWindow::clear() +{ + clearRefs(lsScripts); + Window::clear(); +} + +void StandaloneLuaWindow::luaShowError() +{ + hasError = true; + extern void luaError(lua_State *L, uint8_t error); + luaError(lsScripts, SCRIPT_SYNTAX_ERROR); +} + +void StandaloneLuaWindow::showError(bool firstCall, const char* title, const char* msg) +{ + hasError = true; + if (!errorModal) { + lv_obj_set_scroll_dir(lvobj, LV_DIR_NONE); + errorModal = lv_obj_create(lvobj); + lv_obj_set_pos(errorModal, lv_obj_get_scroll_x(lvobj), lv_obj_get_scroll_y(lvobj)); + lv_obj_set_size(errorModal, LCD_W, LCD_H); + etx_bg_color(errorModal, COLOR_BLACK_INDEX); + etx_obj_add_style(errorModal, styles->bg_opacity_75, LV_PART_MAIN); + errorTitle = lv_label_create(errorModal); + lv_obj_set_pos(errorTitle, 50, 30); + lv_obj_set_size(errorTitle, LCD_W - 100, 32); + etx_txt_color(errorTitle, COLOR_THEME_PRIMARY2_INDEX); + etx_solid_bg(errorTitle, COLOR_THEME_SECONDARY1_INDEX); + etx_font(errorTitle, FONT_L_INDEX); + etx_obj_add_style(errorTitle, styles->text_align_center, LV_PART_MAIN); + errorMsg = lv_label_create(errorModal); + lv_obj_set_pos(errorMsg, 50, 62); + lv_obj_set_size(errorMsg, LCD_W - 100, LCD_H - 92); + lv_obj_set_style_pad_all(errorMsg, 4, LV_PART_MAIN); + etx_txt_color(errorMsg, COLOR_THEME_PRIMARY1_INDEX); + etx_solid_bg(errorMsg, COLOR_THEME_SECONDARY3_INDEX); + etx_font(errorMsg, FONT_STD_INDEX); + etx_obj_add_style(errorMsg, styles->text_align_center, LV_PART_MAIN); + } + + lv_label_set_text(errorTitle, title); + lv_label_set_text(errorMsg, msg); +} diff --git a/radio/src/gui/colorlcd/standalone_lua.h b/radio/src/gui/colorlcd/standalone_lua.h index f1b16be93..5ec992341 100644 --- a/radio/src/gui/colorlcd/standalone_lua.h +++ b/radio/src/gui/colorlcd/standalone_lua.h @@ -33,14 +33,15 @@ struct LuaPopup void paint(BitmapBuffer* dc, uint8_t type, const char* text, const char* info); }; -class StandaloneLuaWindow : public Window, public LuaEventHandler +class StandaloneLuaWindow : public Window, public LuaEventHandler, public LuaLvglManager { static StandaloneLuaWindow* _instance; - explicit StandaloneLuaWindow(); + explicit StandaloneLuaWindow(bool useLvgl); public: static StandaloneLuaWindow* instance(); + static void setup(bool useLvgl); void attach(); void deleteLater(bool detach = true, bool trash = true) override; @@ -52,8 +53,24 @@ public: bool displayPopup(event_t event, uint8_t type, const char* text, const char* info, bool& result); + Window* getCurrentParent() const override { return tempParent ? tempParent : (Window*)this; } + + void clear() override; + bool useLvglLayout() const override { return useLvgl; } + + void luaShowError() override; + + void showError(bool firstCall, const char* title, const char* msg); + + bool isWidget() override { return false; } + protected: lv_obj_t* prevScreen = nullptr; + lv_obj_t* errorModal = nullptr; + lv_obj_t* errorTitle = nullptr; + lv_obj_t* errorMsg = nullptr; + bool hasError = false; + bool useLvgl = false; // GFX BitmapBuffer lcdBuffer; diff --git a/radio/src/gui/colorlcd/view_main.h b/radio/src/gui/colorlcd/view_main.h index 3607badfd..481fe6dc4 100644 --- a/radio/src/gui/colorlcd/view_main.h +++ b/radio/src/gui/colorlcd/view_main.h @@ -75,6 +75,7 @@ class ViewMain : public NavWindow bool onLongPress() override; void show(bool visible = true) override; + bool viewIsVisible() const { return isVisible; } void showTopBarEdgeTxButton(); void hideTopBarEdgeTxButton(); diff --git a/radio/src/gui/colorlcd/widget.cpp b/radio/src/gui/colorlcd/widget.cpp index 71a4d0376..a82aa7e7e 100644 --- a/radio/src/gui/colorlcd/widget.cpp +++ b/radio/src/gui/colorlcd/widget.cpp @@ -115,16 +115,17 @@ void Widget::setFullscreen(bool enable) } // Enter Fullscreen Mode else { + ViewMain::instance()->enableWidgetSelect(false); + // ViewMain hidden - re-show this widget show(); // Set window opaque (inhibits redraw from windows below) setWindowFlag(OPAQUE); - updateZoneRect(parent->getRect()); + updateZoneRect(parent->getRect(), false); setRect(parent->getRect()); - ViewMain::instance()->enableWidgetSelect(false); bringToTop(); if (!lv_obj_get_group(lvobj)) { diff --git a/radio/src/gui/colorlcd/widget.h b/radio/src/gui/colorlcd/widget.h index 8b77c7270..059e6eae0 100644 --- a/radio/src/gui/colorlcd/widget.h +++ b/radio/src/gui/colorlcd/widget.h @@ -83,7 +83,7 @@ class Widget : public ButtonBase virtual void background() {} // Update widget 'zone' data (for Lua widgets) - virtual void updateZoneRect(rect_t rect) {} + virtual void updateZoneRect(rect_t rect, bool updateUI = true) {} void enableFocus(bool enable); diff --git a/radio/src/gui/colorlcd/widgets/timer.cpp b/radio/src/gui/colorlcd/widgets/timer.cpp index a6d5c88cf..78303930f 100644 --- a/radio/src/gui/colorlcd/widgets/timer.cpp +++ b/radio/src/gui/colorlcd/widgets/timer.cpp @@ -121,7 +121,7 @@ class TimerWidget : public Widget int val = lastValue; if (lastStartValue && timerData.showElapsed && - lastStartValue != lastValue) + (int)lastStartValue != lastValue) val = (int)lastStartValue - lastValue; if (isLarge) { diff --git a/radio/src/lua/CMakeLists.txt b/radio/src/lua/CMakeLists.txt index 2ac8fb509..e7fc20a59 100644 --- a/radio/src/lua/CMakeLists.txt +++ b/radio/src/lua/CMakeLists.txt @@ -43,7 +43,7 @@ add_custom_target(lua_mixsrc DEPENDS ${HW_DESC_JSON} lua_mixsrc.inc) add_custom_target(lua_keys DEPENDS ${HW_DESC_JSON} lua_keys.inc) if(GUI_DIR STREQUAL colorlcd) - set(SRC ${SRC} lua/api_colorlcd.cpp lua/widgets.cpp) + set(SRC ${SRC} lua/api_colorlcd.cpp lua/api_colorlcd_lvgl.cpp lua/widgets.cpp) else() set(SRC ${SRC} lua/api_stdlcd.cpp) endif() diff --git a/radio/src/lua/api_colorlcd.cpp b/radio/src/lua/api_colorlcd.cpp index facebb651..f40d137b8 100644 --- a/radio/src/lua/api_colorlcd.cpp +++ b/radio/src/lua/api_colorlcd.cpp @@ -30,17 +30,18 @@ #include "theme.h" #include "lua_api.h" +#include "lua_widget.h" #include "api_colorlcd.h" #define BITMAP_METATABLE "BITMAP*" constexpr coord_t INVERT_BOX_MARGIN = 2; -constexpr int8_t text_horizontal_offset[7] {-2,-1,-2,-2,-2,-2,-2}; -constexpr int8_t text_vertical_offset[7] {0,0,0,0,0,-1,7}; +constexpr int8_t text_horizontal_offset[7] = {-2,-1,-2,-2,-2,-2,-2}; +constexpr int8_t text_vertical_offset[7] = {0,0,0,0,0,-1,7}; BitmapBuffer* luaLcdBuffer = nullptr; -Widget* runningFS = nullptr; - +LuaWidget *runningFS = nullptr; + static int8_t getTextHorizontalOffset(LcdFlags flags) { // no need to adjust if not right aligned @@ -1413,7 +1414,7 @@ Exit full screen widget mode. static int luaLcdExitFullScreen(lua_State *L) { if (runningFS) { - Widget* rfs = runningFS; + auto rfs = runningFS; runningFS = nullptr; rfs->setFullscreen(false); } diff --git a/radio/src/lua/api_colorlcd.h b/radio/src/lua/api_colorlcd.h index d727ce0bb..8d499eddd 100644 --- a/radio/src/lua/api_colorlcd.h +++ b/radio/src/lua/api_colorlcd.h @@ -22,3 +22,4 @@ #include "definitions.h" EXTERN_C(LUALIB_API int luaopen_bitmap(lua_State * L)); +EXTERN_C(LUALIB_API int luaopen_lvgl(lua_State * L)); diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp new file mode 100644 index 000000000..b12c3c4f4 --- /dev/null +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define LUA_LIB + +#include "libopenui.h" +#include "lua_api.h" +#include "lua_widget.h" +#include "opentx.h" + +#include "api_colorlcd.h" + +LuaLvglManager *luaLvglManager = nullptr; + +static int luaLvglObj(lua_State *L, std::function create, bool standalone = false) +{ + if (luaLvglManager && (!standalone || !luaLvglManager->isWidget())) { + auto obj = create(); + obj->getParams(L, 1); + obj->build(L); + obj->push(L); + } else { + lua_pushnil(L); + } + + return 1; +} + +static int luaLvglLabel(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetLabel(); }); +} + +static int luaLvglRectangle(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetRectangle(); }); +} + +static int luaLvglCircle(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetCircle(); }); +} + +static int luaLvglArc(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetArc(); }); +} + +static int luaLvglImage(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetImage(); }); +} + +static int luaLvglMeter(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetMeter(); }); +} + +static int luaLvglButton(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetTextButton(); }, true); +} + +static int luaLvglToggle(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetToggleSwitch(); }, true); +} + +static int luaLvglTextEdit(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetTextEdit(); }, true); +} + +static int luaLvglNumberEdit(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetNumberEdit(); }, true); +} + +static int luaLvglChoice(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetChoice(); }, true); +} + +static int luaLvglSlider(lua_State *L) +{ + return luaLvglObj(L, [=]() { return new LvglWidgetSlider(); }, true); +} + +static int luaLvglConfirm(lua_State *L) +{ + auto obj = new LvglWidgetConfirmDialog(); + obj->getParams(L, 1); + obj->build(L); + return 0; +} + +LvglWidgetObject *LvglWidgetObject::checkLvgl(lua_State *L, int index) +{ + LvglWidgetObject **p = + (LvglWidgetObject **)luaL_checkudata(L, index, LVGL_METATABLE); + if (p) return *p; + return nullptr; +} + +static LvglWidgetObject *optLvgl(lua_State *L, int index) +{ + LvglWidgetObject **p = + (LvglWidgetObject **)luaL_testudata(L, index, LVGL_METATABLE); + if (p) return *p; + return nullptr; +} + +static int luaDestroyLvglWidget(lua_State *L) +{ + auto p = LvglWidgetObject::checkLvgl(L, 1); + if (p) delete p; + return 0; +} + +static int luaLvglSet(lua_State *L) +{ + auto p = LvglWidgetObject::checkLvgl(L, 1); + if (p) { + p->update(L); + } + return 0; +} + +static int luaLvglClear(lua_State *L) +{ + if (luaLvglManager) luaLvglManager->clear(); + + return 0; +} + +static int luaLvglSetParent(lua_State *L) +{ + if (luaLvglManager) { + auto p = optLvgl(L, 1); + luaLvglManager->setTempParent(p ? p->getWindow() : nullptr); + } + return 0; +} + +static int luaLvglShow(lua_State *L) +{ + auto p = LvglWidgetObject::checkLvgl(L, 1); + if (p) { + p->show(); + } + return 0; +} + +static int luaLvglHide(lua_State *L) +{ + auto p = LvglWidgetObject::checkLvgl(L, 1); + if (p) { + p->hide(); + } + return 0; +} + +class LvglWidgetParams +{ + public: + LvglWidgetParams(lua_State *L, int index = 1) + { + luaL_checktype(L, index, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + const char *key = lua_tostring(L, -2); + if (!strcmp(key, "type")) { + type = luaL_checkstring(L, -1); + } else if (!strcmp(key, "name")) { + name = luaL_checkstring(L, -1); + } else if (!strcmp(key, "children")) { + hasChildren = true; + } + } + } + + const char *type = nullptr; + const char *name = nullptr; + bool hasChildren = false; +}; + +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)) { + LvglWidgetParams p(L, -1); + LvglWidgetObject *lvobj = nullptr; + if (strcasecmp(p.type, "label") == 0) + lvobj = new LvglWidgetLabel(); + else if (strcasecmp(p.type, "rectangle") == 0) + lvobj = new LvglWidgetRectangle(); + else if (strcasecmp(p.type, "circle") == 0) + lvobj = new LvglWidgetCircle(); + else if (strcasecmp(p.type, "arc") == 0) + lvobj = new LvglWidgetArc(); + else if (strcasecmp(p.type, "image") == 0) + lvobj = new LvglWidgetImage(); + else if (strcasecmp(p.type, "meter") == 0) + lvobj = new LvglWidgetMeter(); + else if (!luaLvglManager->isWidget()) { + if (strcasecmp(p.type, "button") == 0) + lvobj = new LvglWidgetTextButton(); + else if (strcasecmp(p.type, "toggle") == 0) + lvobj = new LvglWidgetToggleSwitch(); + else if (strcasecmp(p.type, "textEdit") == 0) + lvobj = new LvglWidgetTextEdit(); + else if (strcasecmp(p.type, "numberEdit") == 0) + lvobj = new LvglWidgetNumberEdit(); + else if (strcasecmp(p.type, "choice") == 0) + lvobj = new LvglWidgetChoice(); + else if (strcasecmp(p.type, "slider") == 0) + lvobj = new LvglWidgetSlider(); + } + if (lvobj) { + lvobj->getParams(L, -1); + lvobj->build(L); + auto ref = lvobj->getRef(L); + if (p.name) { + lua_pushstring(L, p.name); + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_settable(L, refIndex - 4); + } + if (p.hasChildren) { + lua_getfield(L, -1, "children"); + auto prevParent = luaLvglManager->getTempParent(); + luaLvglManager->setTempParent(lvobj->getWindow()); + buildLvgl(L, -1, refIndex - 3); + lua_pop(L, 1); + luaLvglManager->setTempParent(prevParent); + } + } + } +} + +static int luaLvglBuild(lua_State *L) +{ + if (luaLvglManager) { + // Return array of lvgl object references + lua_newtable(L); + buildLvgl(L, -2, -1); + } else { + lua_pushnil(L); + } + return 1; +} + +LROT_BEGIN(lvgllib, NULL, LROT_MASK_GC_INDEX) +LROT_FUNCENTRY(__gc, luaDestroyLvglWidget) +LROT_TABENTRY(__index, lvgllib) +LROT_FUNCENTRY(clear, luaLvglClear) +LROT_FUNCENTRY(build, luaLvglBuild) +LROT_FUNCENTRY(setParent, luaLvglSetParent) +// Objects +LROT_FUNCENTRY(label, luaLvglLabel) +LROT_FUNCENTRY(rectangle, luaLvglRectangle) +LROT_FUNCENTRY(circle, luaLvglCircle) +LROT_FUNCENTRY(arc, luaLvglArc) +LROT_FUNCENTRY(image, luaLvglImage) +LROT_FUNCENTRY(meter, luaLvglMeter) +LROT_FUNCENTRY(button, luaLvglButton) +LROT_FUNCENTRY(toggle, luaLvglToggle) +LROT_FUNCENTRY(confirm, luaLvglConfirm) +LROT_FUNCENTRY(textEdit, luaLvglTextEdit) +LROT_FUNCENTRY(numberEdit, luaLvglNumberEdit) +LROT_FUNCENTRY(choice, luaLvglChoice) +LROT_FUNCENTRY(slider, luaLvglSlider) +// Manipulation functions +LROT_FUNCENTRY(set, luaLvglSet) +LROT_FUNCENTRY(show, luaLvglShow) +LROT_FUNCENTRY(hide, luaLvglHide) +LROT_END(lvgllib, NULL, LROT_MASK_GC_INDEX) + +extern "C" { +LUALIB_API int luaopen_lvgl(lua_State *L) +{ + luaL_rometatable(L, LVGL_METATABLE, LROT_TABLEREF(lvgllib)); + return 0; +} +} diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 2e8579863..ef16e4cc6 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -2165,7 +2165,15 @@ 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); + } else { + lua_pushinteger(L, instructionsPercent); + } +#else lua_pushinteger(L, instructionsPercent); +#endif return 1; } diff --git a/radio/src/lua/interface.cpp b/radio/src/lua/interface.cpp index 3bc53b1ea..f14a87530 100644 --- a/radio/src/lua/interface.cpp +++ b/radio/src/lua/interface.cpp @@ -35,6 +35,10 @@ #include "api_filesystem.h" #include "switches.h" +#if defined(COLORLCD) + #include "standalone_lua.h" +#endif + #if defined(LIBOPENUI) #include "libopenui.h" #else @@ -725,42 +729,11 @@ void displayLuaError(bool firstCall = false) } #if defined(COLORLCD) - static bool drewBackground = false; - - if (firstCall) - drewBackground = false; - - if (!luaLcdAllowed || luaLcdBuffer == nullptr) - return; - - coord_t w = 0.75 * LCD_W; - coord_t left = (LCD_W - w) / 2; -#if (!PORTRAIT_LCD) - coord_t hh = getFontHeight(FONT(XL)) + 4; -#else - coord_t hh = getFontHeight(FONT(L)) + 4; -#endif - coord_t h = 0.75 * LCD_H - hh; - coord_t top = (LCD_H - h + hh / 2) / 2; - - if (!drewBackground) { - drewBackground = true; - luaLcdBuffer->drawFilledRect(0, 0, LCD_W, LCD_H, SOLID, COLOR_BLACK, OPACITY(6)); - } - - luaLcdBuffer->drawSolidFilledRect(left, top - hh, w, hh, COLOR_THEME_SECONDARY1); - luaLcdBuffer->drawSolidFilledRect(left, top, w, h, COLOR_THEME_SECONDARY3); -#if (!PORTRAIT_LCD) - luaLcdBuffer->drawText(left + 10, top - hh + 2, title, FONT(XL) | COLOR_THEME_PRIMARY2); - luaLcdBuffer->drawTextLines(left + 10, top + 5, w - 20, h - 10, lua_warning_info, FONT(L) | COLOR_THEME_PRIMARY1); -#else - luaLcdBuffer->drawText(left + 10, top - hh + 2, title, FONT(L) | COLOR_THEME_PRIMARY2); - luaLcdBuffer->drawTextLines(left + 10, top + 5, w - 20, h - 10, lua_warning_info, FONT(STD) | COLOR_THEME_PRIMARY1); -#endif + StandaloneLuaWindow::instance()->showError(firstCall, title, lua_warning_info); #else if (!luaLcdAllowed) return; - + drawMessageBox(title); coord_t y = WARNING_LINE_Y + FH + 4; @@ -955,6 +928,13 @@ static void luaLoadScripts(bool init, const char * filename = nullptr) sid.run = luaRegisterFunction("run"); sid.background = luaRegisterFunction("background"); initFunction = luaRegisterFunction("init"); +#if defined(COLORLCD) + if (sid.reference == SCRIPT_STANDALONE) { + lua_getfield(lsScripts, -1, "useLvgl"); + sid.useLvgl = lua_toboolean(lsScripts, -1); + lua_pop(lsScripts, 1); + } +#endif if (sid.run == LUA_NOREF) { snprintf(lua_warning_info, LUA_WARNING_INFO_LEN, "luaLoadScripts(%.*s): No run function\n", LEN_SCRIPT_FILENAME, getScriptName(idx)); sid.state = SCRIPT_SYNTAX_ERROR; @@ -985,7 +965,12 @@ static void luaLoadScripts(bool init, const char * filename = nullptr) // If init(), push it on the stack if (initFunction != LUA_NOREF) { lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, initFunction); - if (ref == SCRIPT_STANDALONE) luaLcdAllowed = true; + if (ref == SCRIPT_STANDALONE) { + luaLcdAllowed = true; +#if defined(COLORLCD) + StandaloneLuaWindow::setup(sid.useLvgl); +#endif + } } } } diff --git a/radio/src/lua/lua_api.h b/radio/src/lua/lua_api.h index b4ebe60a3..a8f343a72 100644 --- a/radio/src/lua/lua_api.h +++ b/radio/src/lua/lua_api.h @@ -19,8 +19,7 @@ * GNU General Public License for more details. */ -#ifndef _LUA_API_H_ -#define _LUA_API_H_ +#pragma once #if defined(LUA) @@ -66,8 +65,11 @@ extern bool luaLcdAllowed; class BitmapBuffer; extern BitmapBuffer* luaLcdBuffer; -class Widget; -extern Widget* runningFS; +class LuaWidget; +extern LuaWidget* runningFS; + +class LuaLvglManager; +extern LuaLvglManager* luaLvglManager; LcdFlags flagsRGB(LcdFlags flags); @@ -149,6 +151,9 @@ struct ScriptInternalData { int run; int background; uint8_t instructions; +#if defined(COLORLCD) + bool useLvgl; +#endif }; struct ScriptInputsOutputs { @@ -252,5 +257,3 @@ void * tracer_alloc(void * ud, void * ptr, size_t osize, size_t nsize); #define LUA_LOAD_MODEL_SCRIPTS() #endif // defined(LUA) - -#endif // _LUA_API_H_ diff --git a/radio/src/lua/lua_lvgl_widget.cpp b/radio/src/lua/lua_lvgl_widget.cpp new file mode 100644 index 000000000..ede3bc096 --- /dev/null +++ b/radio/src/lua/lua_lvgl_widget.cpp @@ -0,0 +1,1200 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "lua_widget.h" +#include "opentx.h" +#include "slider.h" +#include "toggleswitch.h" + +//----------------------------------------------------------------------------- + +void LvglWidgetObjectBase::getParams(lua_State *L, int index) +{ + luaL_checktype(L, index, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + int top = lua_gettop(L); + parseParam(L, lua_tostring(L, -2)); + // Replace value on Lua stack if consumed by parseParam + if (top != lua_gettop(L)) lua_pushnil(L); + } +} + +bool LvglWidgetObjectBase::pcallFunc(lua_State *L, int getFuncRef, int nretval) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, getFuncRef); + return lua_pcall(L, 0, nretval, 0) == 0; +} + +void LvglWidgetObjectBase::pcallSimpleFunc(lua_State *L, int funcRef) +{ + if (funcRef) { + PROTECT_LUA() + { + if (!pcallFunc(L, funcRef, 1)) { + lvglManager->luaShowError(); + } + } + UNPROTECT_LUA(); + } +} + +bool LvglWidgetObjectBase::pcallUpdateBool(lua_State *L, int getFuncRef, + std::function update) +{ + if (getFuncRef) { + int t = lua_gettop(L); + if (pcallFunc(L, getFuncRef, 1)) { + bool val = lua_toboolean(L, -1); + update(val); + lua_settop(L, t); + } else { + return false; + } + } + return true; +} + +bool LvglWidgetObjectBase::pcallUpdate1Int(lua_State *L, int getFuncRef, + std::function update) +{ + if (getFuncRef) { + int t = lua_gettop(L); + if (pcallFunc(L, getFuncRef, 1)) { + int val = luaL_checkunsigned(L, -1); + update(val); + lua_settop(L, t); + } else { + return false; + } + } + return true; +} + +bool LvglWidgetObjectBase::pcallUpdate2Int(lua_State *L, int getFuncRef, + std::function update) +{ + if (getFuncRef) { + int t = lua_gettop(L); + if (pcallFunc(L, getFuncRef, 2)) { + int v1 = luaL_checkunsigned(L, -2); + int v2 = luaL_checkunsigned(L, -1); + update(v1, v2); + lua_settop(L, t); + } else { + return false; + } + } + return true; +} + +int LvglWidgetObjectBase::pcallGetIntVal(lua_State *L, int getFuncRef) +{ + int val = 0; + if (getFuncRef) { + int t = lua_gettop(L); + PROTECT_LUA() + { + if (pcallFunc(L, getFuncRef, 1)) { + val = luaL_checkinteger(L, -1); + } else { + lvglManager->luaShowError(); + } + } + else + { + lvglManager->luaShowError(); + } + UNPROTECT_LUA(); + lua_settop(L, t); + } + return val; +} + +void LvglWidgetObjectBase::pcallSetIntVal(lua_State *L, int setFuncRef, int val) +{ + if (setFuncRef) { + int t = lua_gettop(L); + PROTECT_LUA() + { + lua_rawgeti(L, LUA_REGISTRYINDEX, setFuncRef); + lua_pushinteger(L, val); + bool rv = lua_pcall(L, 1, 0, 0) == 0; + if (!rv) { + lvglManager->luaShowError(); + } + } + else + { + lvglManager->luaShowError(); + } + UNPROTECT_LUA(); + lua_settop(L, t); + } +} + +void LvglWidgetObjectBase::clearRef(lua_State *L, int ref) +{ + if (ref) luaL_unref(L, LUA_REGISTRYINDEX, ref); +} + +void LvglWidgetObjectBase::build(lua_State *L) {} + +//----------------------------------------------------------------------------- + +bool LvglWidgetObject::callRefs(lua_State *L) +{ + if (!pcallUpdate1Int(L, getColorFunction, + [=](int color) { setColor(color); })) + return false; + if (!pcallUpdateBool(L, getVisibleFunction, + [=](bool visible) { window->show(visible); })) + return false; + if (!pcallUpdate2Int(L, getSizeFunction, + [=](int w, int h) { setSize(w, h); })) + return false; + if (!pcallUpdate2Int(L, getPosFunction, [=](int x, int y) { setPos(x, y); })) + return false; + return true; +} + +void LvglWidgetObject::clearRefs(lua_State *L) +{ + clearRef(L, getColorFunction); + clearRef(L, getVisibleFunction); + clearRef(L, getSizeFunction); + clearRef(L, getPosFunction); +} + +void LvglWidgetObject::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "x")) { + x = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "y")) { + y = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "w")) { + w = luaL_checkinteger(L, -1); + if (w == 0) w = LV_SIZE_CONTENT; + } else if (!strcmp(key, "h")) { + h = luaL_checkinteger(L, -1); + if (h == 0) h = LV_SIZE_CONTENT; + } else if (!strcmp(key, "color")) { + if (lua_isfunction(L, -1)) { + getColorFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + color = luaL_checkunsigned(L, -1); + color = flagsRGB(color); + } + } else if (!strcmp(key, "visible")) { + getVisibleFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "size")) { + getSizeFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "pos")) { + getPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } +} + +int LvglWidgetObject::getRef(lua_State *L) +{ + LvglWidgetObject **p = + (LvglWidgetObject **)lua_newuserdata(L, sizeof(LvglWidgetObject *)); + *p = this; + luaL_getmetatable(L, LVGL_METATABLE); + lua_setmetatable(L, -2); + + // Save reference + auto ref = luaL_ref(L, LUA_REGISTRYINDEX); + lvglManager->saveLvglObjectRef(ref); + + return ref; +} + +void LvglWidgetObject::push(lua_State *L) +{ + // Save reference + auto ref = getRef(L); + // Push userdata back into Lua stack (return object) + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); +} + +void LvglWidgetObject::update(lua_State *L) +{ + getParams(L, 2); + refresh(); +} + +void LvglWidgetObject::refresh() +{ + setPos(x, y); + setSize(w, h); + setColor(color); +} + +void LvglWidgetObject::setPos(coord_t x, coord_t y) +{ + this->x = x; + this->y = y; + if (window) window->setPos(x, y); +} + +void LvglWidgetObject::setSize(coord_t w, coord_t h) +{ + this->w = w; + this->h = h; + if (window) window->setSize(w, h); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetBorderedObject::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "thickness")) { + thickness = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "filled")) { + filled = lua_toboolean(L, -1); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetBorderedObject::setColor(LcdFlags color) +{ + if (color != currentColor) { + currentColor = color; + if (filled) + lv_obj_set_style_bg_color(window->getLvObj(), + makeLvColor(flagsRGB(color)), LV_PART_MAIN); + else + lv_obj_set_style_border_color(window->getLvObj(), + makeLvColor(flagsRGB(color)), LV_PART_MAIN); + } +} + +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()) { + lv_obj_clear_flag(window->getLvObj(), LV_OBJ_FLAG_CLICKABLE); + } else { + etx_scrollbar(window->getLvObj()); + } + setColor(color); + if (filled) { + lv_obj_set_style_bg_opa(window->getLvObj(), LV_OPA_COVER, LV_PART_MAIN); + } else { + lv_obj_set_style_border_opa(window->getLvObj(), LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_width(window->getLvObj(), thickness, LV_PART_MAIN); + } +} + +//----------------------------------------------------------------------------- + +void LvglWidgetRoundObject::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "radius")) { + if (lua_isfunction(L, -1)) + getRadiusFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else + radius = luaL_checkunsigned(L, -1); + } else { + LvglWidgetBorderedObject::parseParam(L, key); + } +} + +bool LvglWidgetRoundObject::callRefs(lua_State *L) +{ + if (!pcallUpdate1Int(L, getRadiusFunction, [=](int val) { setRadius(val); })) + return false; + return LvglWidgetObject::callRefs(L); +} + +void LvglWidgetRoundObject::clearRefs(lua_State *L) +{ + clearRef(L, getRadiusFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetRoundObject::setPos(coord_t x, coord_t y) +{ + LvglWidgetObject::setPos(x - radius, y - radius); +} + +void LvglWidgetRoundObject::setRadius(coord_t r) +{ + // Set position to center + x += radius; + y += radius; + radius = r; + w = radius * 2; + h = radius * 2; + setPos(x, y); + setSize(w, h); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetLabel::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "text")) { + if (lua_isfunction(L, -1)) + getTextFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else + txt = luaL_checkstring(L, -1); + } else if (!strcmp(key, "font")) { + if (lua_isfunction(L, -1)) + getFontFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else + font = luaL_checkunsigned(L, -1); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +bool LvglWidgetLabel::callRefs(lua_State *L) +{ + int t = lua_gettop(L); + if (getTextFunction) { + if (pcallFunc(L, getTextFunction, 1)) { + const char *s = luaL_checkstring(L, -1); + setText(s); + lua_settop(L, t); + } else { + return false; + } + } + if (!pcallUpdate1Int(L, getFontFunction, [=](int val) { setFont(val); })) + return false; + return LvglWidgetObject::callRefs(L); +} + +void LvglWidgetLabel::clearRefs(lua_State *L) +{ + clearRef(L, getTextFunction); + clearRef(L, getFontFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetLabel::setText(const char *s) +{ + uint32_t h = hash(s, strlen(s)); + if (h != textHash) { + txt = s; + textHash = h; + lv_label_set_text(window->getLvObj(), s); + } +} + +void LvglWidgetLabel::setColor(LcdFlags color) +{ + if (color != currentColor) { + currentColor = color; + lv_obj_set_style_text_color(window->getLvObj(), + makeLvColor(flagsRGB(color)), LV_PART_MAIN); + } +} + +void LvglWidgetLabel::setFont(LcdFlags font) +{ + this->font = font; + lv_obj_set_style_text_align(window->getLvObj(), + (font & RIGHT) ? LV_TEXT_ALIGN_RIGHT + : (font & CENTERED) ? LV_TEXT_ALIGN_CENTER + : LV_TEXT_ALIGN_LEFT, + LV_PART_MAIN); + lv_obj_set_style_text_font(window->getLvObj(), getFont(font), LV_PART_MAIN); +} + +void LvglWidgetLabel::build(lua_State *L) +{ + window = new Window(lvglManager->getCurrentParent(), {x, y, w, h}, + lv_label_create); + setText(txt); + setColor(color); + setFont(font); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetRectangle::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "rounded")) { + rounded = luaL_checkunsigned(L, -1); + } else { + LvglWidgetBorderedObject::parseParam(L, key); + } +} + +void LvglWidgetRectangle::build(lua_State *L) +{ + LvglWidgetBorderedObject::build(L); + if (rounded) { + lv_obj_set_style_radius(window->getLvObj(), + (rounded >= thickness) ? rounded : thickness, + LV_PART_MAIN); + } +} + +//----------------------------------------------------------------------------- + +void LvglWidgetCircle::build(lua_State *L) +{ + // Reset position to corner + setPos(x, y); + // Set width & height + setRadius(radius); + LvglWidgetBorderedObject::build(L); + lv_obj_set_style_radius(window->getLvObj(), LV_RADIUS_CIRCLE, LV_PART_MAIN); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetArc::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "startAngle")) { + if (lua_isfunction(L, -1)) + getStartAngleFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else + startAngle = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "endAngle")) { + if (lua_isfunction(L, -1)) + getEndAngleFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else + endAngle = luaL_checkunsigned(L, -1); + } else { + LvglWidgetRoundObject::parseParam(L, key); + } +} + +void LvglWidgetArc::setColor(LcdFlags color) +{ + if (color != currentColor) { + currentColor = color; + lv_obj_set_style_arc_color(window->getLvObj(), makeLvColor(flagsRGB(color)), + LV_PART_INDICATOR); + } +} + +void LvglWidgetArc::setStartAngle(coord_t angle) +{ + lv_arc_set_start_angle(window->getLvObj(), angle); +} + +void LvglWidgetArc::setEndAngle(coord_t angle) +{ + lv_arc_set_end_angle(window->getLvObj(), angle); +} + +bool LvglWidgetArc::callRefs(lua_State *L) +{ + if (!pcallUpdate1Int(L, getStartAngleFunction, + [=](int val) { setStartAngle(val); })) + return false; + if (!pcallUpdate1Int(L, getEndAngleFunction, + [=](int val) { setEndAngle(val); })) + return false; + return LvglWidgetRoundObject::callRefs(L); +} + +void LvglWidgetArc::clearRefs(lua_State *L) +{ + clearRef(L, getStartAngleFunction); + clearRef(L, getEndAngleFunction); + LvglWidgetRoundObject::clearRefs(L); +} + +void LvglWidgetArc::build(lua_State *L) +{ + // Reset position to corner + setPos(x, y); + // Set width & height + setRadius(radius); + window = + new Window(lvglManager->getCurrentParent(), {x, y, w, h}, lv_arc_create); + lv_obj_add_flag(window->getLvObj(), LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_clear_flag(window->getLvObj(), LV_OBJ_FLAG_CLICKABLE); + setColor(color); + lv_arc_set_bg_angles(window->getLvObj(), 0, 360); + lv_arc_set_range(window->getLvObj(), 0, 360); + lv_arc_set_angles(window->getLvObj(), 0, 360); + setStartAngle(startAngle); + setEndAngle(endAngle); + lv_obj_remove_style(window->getLvObj(), NULL, LV_PART_KNOB); + 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_MAIN); + lv_obj_set_style_arc_opa(window->getLvObj(), LV_OPA_COVER, LV_PART_INDICATOR); + lv_obj_set_style_arc_width(window->getLvObj(), thickness, LV_PART_INDICATOR); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetImage::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "file")) { + filename = luaL_checkstring(L, -1); + } else if (!strcmp(key, "fill")) { + fillFrame = lua_toboolean(L, -1); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetImage::build(lua_State *L) +{ + window = new StaticImage(lvglManager->getCurrentParent(), {x, y, w, h}, + filename.c_str(), fillFrame); +} + +//----------------------------------------------------------------------------- + +class LvglWidgetScaleIndicator : public LvglWidgetObjectBase +{ + public: + LvglWidgetScaleIndicator() {} + + virtual void build(lv_obj_t *parent, lv_meter_scale_t *scale) = 0; + + protected: + lv_obj_t *meter = nullptr; + lv_meter_indicator_t *indic = nullptr; +}; + +class LvglWidgetScaleArc : public LvglWidgetScaleIndicator +{ + public: + LvglWidgetScaleArc(lua_State *L) { getParams(L, -1); } + + bool callRefs(lua_State *L) override + { + if (!pcallUpdate1Int(L, getStartPosFunction, [=](int val) { + lv_meter_set_indicator_start_value(meter, indic, val); + })) + return false; + if (!pcallUpdate1Int(L, getEndPosFunction, [=](int val) { + lv_meter_set_indicator_end_value(meter, indic, val); + })) + return false; + return true; + } + + void clearRefs(lua_State *L) override + { + clearRef(L, getStartPosFunction); + clearRef(L, getEndPosFunction); + } + + void build(lv_obj_t *parent, lv_meter_scale_t *scale) override + { + meter = parent; + + indic = + lv_meter_add_arc(meter, scale, w, makeLvColor(flagsRGB(color)), rmod); + if (hasStart) lv_meter_set_indicator_start_value(meter, indic, startPos); + if (hasEnd) lv_meter_set_indicator_end_value(meter, indic, endPos); + } + + protected: + uint8_t w = 0; + int8_t rmod = 0; + LcdFlags color = COLOR_THEME_PRIMARY1; + bool hasStart = false, hasEnd = false; + int16_t startPos = 0, endPos = 0; + + int getStartPosFunction = 0; + int getEndPosFunction = 0; + + void parseParam(lua_State *L, const char *key) override + { + if (!strcmp(key, "w")) { + w = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "rmod")) { + rmod = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "startPos")) { + if (lua_isfunction(L, -1)) + getStartPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else { + hasStart = true; + startPos = luaL_checkinteger(L, -1); + } + } else if (!strcmp(key, "endPos")) { + if (lua_isfunction(L, -1)) + getEndPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else { + hasEnd = true; + endPos = luaL_checkinteger(L, -1); + } + } else if (!strcmp(key, "color")) { + color = luaL_checkunsigned(L, -1); + } + } +}; + +class LvglWidgetScaleLines : public LvglWidgetScaleIndicator +{ + public: + LvglWidgetScaleLines(lua_State *L) { getParams(L, -1); } + + bool callRefs(lua_State *L) override + { + if (!pcallUpdate1Int(L, getStartPosFunction, [=](int val) { + lv_meter_set_indicator_start_value(meter, indic, val); + })) + return false; + if (!pcallUpdate1Int(L, getEndPosFunction, [=](int val) { + lv_meter_set_indicator_end_value(meter, indic, val); + })) + return false; + return true; + } + + void clearRefs(lua_State *L) override + { + clearRef(L, getStartPosFunction); + clearRef(L, getEndPosFunction); + } + + void build(lv_obj_t *parent, lv_meter_scale_t *scale) override + { + meter = parent; + + indic = lv_meter_add_scale_lines( + meter, scale, makeLvColor(flagsRGB(startColor)), + makeLvColor(flagsRGB(endColor)), localFade, wmod); + if (hasStart) lv_meter_set_indicator_start_value(meter, indic, startPos); + if (hasEnd) lv_meter_set_indicator_end_value(meter, indic, endPos); + } + + protected: + int8_t wmod = 0; + LcdFlags startColor = COLOR_THEME_PRIMARY1, endColor = COLOR_THEME_PRIMARY1; + bool hasStart = false, hasEnd = false; + int16_t startPos = 0, endPos = 0; + bool localFade = false; + + int getStartPosFunction = 0; + int getEndPosFunction = 0; + + void parseParam(lua_State *L, const char *key) override + { + if (!strcmp(key, "wmod")) { + wmod = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "startPos")) { + if (lua_isfunction(L, -1)) { + getStartPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + hasStart = true; + startPos = luaL_checkinteger(L, -1); + } + } else if (!strcmp(key, "endPos")) { + if (lua_isfunction(L, -1)) { + getEndPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + hasEnd = true; + endPos = luaL_checkinteger(L, -1); + } + } else if (!strcmp(key, "startColor")) { + startColor = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "endColor")) { + endColor = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "localFade")) { + localFade = lua_toboolean(L, -1); + } + } +}; + +class LvglWidgetScaleNeedle : public LvglWidgetScaleIndicator +{ + public: + LvglWidgetScaleNeedle(lua_State *L) { getParams(L, -1); } + + bool callRefs(lua_State *L) override + { + if (!pcallUpdate1Int(L, getPosFunction, [=](int val) { + lv_meter_set_indicator_value(meter, indic, val); + })) + return false; + return true; + } + + void clearRefs(lua_State *L) override { clearRef(L, getPosFunction); } + + void build(lv_obj_t *parent, lv_meter_scale_t *scale) override + { + meter = parent; + + indic = lv_meter_add_needle_line(meter, scale, w, + makeLvColor(flagsRGB(color)), rmod); + } + + protected: + uint8_t w = 0; + int8_t rmod = 0; + LcdFlags color = COLOR_THEME_PRIMARY1; + + int getPosFunction = 0; + + void parseParam(lua_State *L, const char *key) override + { + if (!strcmp(key, "w")) { + w = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "rmod")) { + rmod = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "color")) { + color = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "pos")) { + getPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } + } +}; + +class LvglWidgetMeterScale : public LvglWidgetObjectBase +{ + public: + LvglWidgetMeterScale(lua_State *L) { getParams(L, -1); } + + bool callRefs(lua_State *L) override + { + for (auto it = indicators.cbegin(); it != indicators.cend(); ++it) { + if (!(*it)->callRefs(L)) return false; + } + return true; + } + + void clearRefs(lua_State *L) override + { + for (auto it = indicators.cbegin(); it != indicators.cend(); ++it) { + (*it)->clearRefs(L); + } + } + + void build(lv_obj_t *parent) + { + meter = parent; + lv_meter_scale_t *scale = lv_meter_add_scale(meter); + + if (ticks) { + lv_meter_set_scale_ticks(meter, scale, ticks, tickWidth, tickLen, + makeLvColor(flagsRGB(tickColor))); + if (majorNth) + lv_meter_set_scale_major_ticks( + meter, scale, majorNth, majorWidth, majorLen, + makeLvColor(flagsRGB(majorColor)), labelGap); + } + + lv_meter_set_scale_range(meter, scale, scaleMin, scaleMax, scaleAngle, + scaleRotate); + + if (centerDotSize > 0) { + lv_obj_set_style_size(meter, centerDotSize, LV_PART_INDICATOR); + lv_obj_set_style_bg_color(meter, makeLvColor(flagsRGB(centerDotColor)), + LV_PART_INDICATOR); + lv_obj_set_style_bg_opa(meter, LV_OPA_COVER, LV_PART_INDICATOR); + lv_obj_set_style_radius(meter, LV_RADIUS_CIRCLE, LV_PART_INDICATOR); + } + + for (auto it = indicators.cbegin(); it != indicators.cend(); ++it) { + (*it)->build(meter, scale); + } + } + + protected: + lv_obj_t *meter; + std::vector indicators; + + int16_t scaleMin = 0, scaleMax = 100, scaleAngle = 360, scaleRotate = 0; + uint8_t ticks = 0, majorNth = 0; + uint8_t tickWidth = 0, majorWidth = 0; + uint8_t tickLen = 0, majorLen = 0; + LcdFlags tickColor = COLOR_THEME_PRIMARY1, majorColor = COLOR_THEME_PRIMARY1; + uint8_t labelGap = 0, centerDotSize = 0; + LcdFlags centerDotColor = COLOR_THEME_PRIMARY1; + + void parseParam(lua_State *L, const char *key) override + { + if (!strcmp(key, "ticks")) { + ticks = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "tickWidth")) { + tickWidth = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "tickLen")) { + tickLen = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "tickColor")) { + tickColor = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "majorNth")) { + majorNth = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "majorWidth")) { + majorWidth = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "majorLen")) { + majorLen = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "majorColor")) { + majorColor = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "labelGap")) { + labelGap = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "min")) { + scaleMin = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "max")) { + scaleMax = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "angle")) { + scaleAngle = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "rotate")) { + scaleRotate = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "dotSize")) { + centerDotSize = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "dotColor")) { + centerDotColor = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "indicators")) { + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + lua_getfield(L, -1, "type"); + const char *key = lua_tostring(L, -1); + lua_pop(L, 1); + if (!strcmp(key, "arc")) { + indicators.push_back(new LvglWidgetScaleArc(L)); + } else if (!strcmp(key, "lines")) { + indicators.push_back(new LvglWidgetScaleLines(L)); + } else if (!strcmp(key, "needle")) { + indicators.push_back(new LvglWidgetScaleNeedle(L)); + } + } + } + } +}; + +void LvglWidgetMeter::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "scales")) { + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + scales.push_back(new LvglWidgetMeterScale(L)); + } + } else { + LvglWidgetRoundObject::parseParam(L, key); + } +} + +bool LvglWidgetMeter::callRefs(lua_State *L) +{ + for (auto it = scales.cbegin(); it != scales.cend(); ++it) { + if (!(*it)->callRefs(L)) return false; + } + return LvglWidgetRoundObject::callRefs(L); +} + +void LvglWidgetMeter::clearRefs(lua_State *L) +{ + for (auto it = scales.cbegin(); it != scales.cend(); ++it) { + (*it)->clearRefs(L); + } + LvglWidgetRoundObject::clearRefs(L); +} + +void LvglWidgetMeter::build(lua_State *L) +{ + window = new Window(lvglManager->getCurrentParent(), + {x - radius, y - radius, radius * 2, radius * 2}, + lv_meter_create); + + lv_obj_t *meter = window->getLvObj(); + + lv_obj_add_flag(meter, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_clear_flag(meter, LV_OBJ_FLAG_CLICKABLE); + + for (auto it = scales.cbegin(); it != scales.cend(); ++it) { + (*it)->build(meter); + } +} + +//----------------------------------------------------------------------------- + +void LvglWidgetTextButton::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "text")) { + txt = luaL_checkstring(L, -1); + } else if (!strcmp(key, "press")) { + pressFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetTextButton::clearRefs(lua_State *L) +{ + clearRef(L, pressFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetTextButton::setText(const char *s) +{ + uint32_t h = hash(s, strlen(s)); + if (h != textHash) { + txt = s; + textHash = h; + ((TextButton *)window)->setText(s); + } +} + +void LvglWidgetTextButton::build(lua_State *L) +{ + window = + new TextButton(lvglManager->getCurrentParent(), {x, y, w, h}, txt, [=]() { + pcallSimpleFunc(L, pressFunction); + return 0; + }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetToggleSwitch::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "get")) { + getStateFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "set")) { + setStateFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetToggleSwitch::clearRefs(lua_State *L) +{ + clearRef(L, getStateFunction); + clearRef(L, setStateFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetToggleSwitch::build(lua_State *L) +{ + window = new ToggleSwitch( + lvglManager->getCurrentParent(), {x, y, 0, 0}, + [=]() { return pcallGetIntVal(L, getStateFunction); }, + [=](uint8_t val) { pcallSetIntVal(L, setStateFunction, val); }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetConfirmDialog::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "title")) { + title = luaL_checkstring(L, -1); + } else if (!strcmp(key, "message")) { + message = luaL_checkstring(L, -1); + } else if (!strcmp(key, "confirm")) { + confirmFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "cancel")) { + cancelFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetConfirmDialog::clearRefs(lua_State *L) +{ + clearRef(L, confirmFunction); + clearRef(L, cancelFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetConfirmDialog::build(lua_State *L) +{ + window = new ConfirmDialog( + MainWindow::instance(), title, message, + [=]() { pcallSimpleFunc(L, confirmFunction); }, + [=]() { pcallSimpleFunc(L, cancelFunction); }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetTextEdit::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "value")) { + txt = luaL_checkstring(L, -1); + } else if (!strcmp(key, "length")) { + maxLen = luaL_checkinteger(L, -1); + if (maxLen > 128) maxLen = 128; + if (maxLen <= 0) maxLen = 32; + } else if (!strcmp(key, "set")) { + setFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetTextEdit::clearRefs(lua_State *L) +{ + clearRef(L, setFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetTextEdit::build(lua_State *L) +{ + strcpy(value, txt); + if (h == LV_SIZE_CONTENT) h = 0; + window = new TextEdit(lvglManager->getCurrentParent(), {x, y, w, h}, value, + maxLen, [=]() { + if (setFunction) { + int t = lua_gettop(L); + PROTECT_LUA() + { + lua_rawgeti(L, LUA_REGISTRYINDEX, setFunction); + lua_pushstring(L, value); + bool rv = lua_pcall(L, 1, 0, 0) == 0; + if (!rv) { + lvglManager->luaShowError(); + } + } + else + { + lvglManager->luaShowError(); + } + UNPROTECT_LUA(); + lua_settop(L, t); + } + }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetNumberEdit::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "min")) { + min = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "max")) { + max = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "get")) { + getFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "set")) { + setFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "display")) { + dispFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetNumberEdit::clearRefs(lua_State *L) +{ + clearRef(L, getFunction); + clearRef(L, setFunction); + clearRef(L, dispFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetNumberEdit::build(lua_State *L) +{ + if (h == LV_SIZE_CONTENT) h = 0; + window = new NumberEdit( + lvglManager->getCurrentParent(), {x, y, w, h}, min, max, + [=]() { return pcallGetIntVal(L, getFunction); }, + [=](int val) { pcallSetIntVal(L, setFunction, val); }); + if (dispFunction) { + ((NumberEdit *)window)->setDisplayHandler([=](int val) { + const char *s = "???"; + int t = lua_gettop(L); + PROTECT_LUA() + { + lua_rawgeti(L, LUA_REGISTRYINDEX, dispFunction); + lua_pushinteger(L, val); + bool rv = lua_pcall(L, 1, 1, 0) == 0; + if (rv) { + s = luaL_checkstring(L, -1); + } else { + lvglManager->luaShowError(); + } + } + else + { + lvglManager->luaShowError(); + } + UNPROTECT_LUA(); + lua_settop(L, t); + return s; + }); + } +} + +//----------------------------------------------------------------------------- + +void LvglWidgetChoice::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "title")) { + title = luaL_checkstring(L, -1); + } else if (!strcmp(key, "values")) { + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + values.push_back(lua_tostring(L, -1)); + } + } else if (!strcmp(key, "get")) { + getFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "set")) { + setFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetChoice::clearRefs(lua_State *L) +{ + clearRef(L, getFunction); + clearRef(L, setFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetChoice::build(lua_State *L) +{ + window = new Choice( + lvglManager->getCurrentParent(), {x, y, w, h}, values, 0, + values.size() - 1, [=]() { return pcallGetIntVal(L, getFunction) - 1; }, + [=](int val) { pcallSetIntVal(L, setFunction, val + 1); }, title.c_str()); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetSlider::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "get")) { + getValueFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "set")) { + setValueFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "min")) { + vmin = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "max")) { + vmax = luaL_checkinteger(L, -1); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetSlider::clearRefs(lua_State *L) +{ + clearRef(L, getValueFunction); + clearRef(L, setValueFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetSlider::build(lua_State *L) +{ + window = new Slider( + lvglManager->getCurrentParent(), w, vmin, vmax, + [=]() { return pcallGetIntVal(L, getValueFunction); }, + [=](uint8_t val) { pcallSetIntVal(L, setValueFunction, val); }); + window->setPos(x, y); +} + +//----------------------------------------------------------------------------- diff --git a/radio/src/lua/lua_lvgl_widget.h b/radio/src/lua/lua_lvgl_widget.h new file mode 100644 index 000000000..b33087713 --- /dev/null +++ b/radio/src/lua/lua_lvgl_widget.h @@ -0,0 +1,405 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#define LVGL_METATABLE "LVGL*" + +//----------------------------------------------------------------------------- + +class LvglWidgetObjectBase +{ + public: + LvglWidgetObjectBase() { lvglManager = luaLvglManager; } + virtual ~LvglWidgetObjectBase() {} + + void getParams(lua_State *L, int index); + + virtual void build(lua_State *L); + virtual bool callRefs(lua_State *L) = 0; + virtual void clearRefs(lua_State *L) = 0; + + protected: + LuaLvglManager *lvglManager = nullptr; + + virtual void parseParam(lua_State *L, const char *key) = 0; + + void clearRef(lua_State *L, int ref); + + bool pcallFunc(lua_State *L, int getFuncRef, int nret); + void pcallSimpleFunc(lua_State *L, int funcRef); + bool pcallUpdateBool(lua_State *L, int getFuncRef, + std::function update); + bool pcallUpdate1Int(lua_State *L, int getFuncRef, + std::function update); + bool pcallUpdate2Int(lua_State *L, int getFuncRef, + std::function update); + int pcallGetIntVal(lua_State *L, int getFuncRef); + void pcallSetIntVal(lua_State *L, int setFuncRef, int val); +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetObject : public LvglWidgetObjectBase +{ + public: + LvglWidgetObject() : LvglWidgetObjectBase() {} + + int getRef(lua_State *L); + void push(lua_State *L); + + void update(lua_State *L); + + virtual void setColor(LcdFlags color) {} + virtual void setPos(coord_t x, coord_t y); + void setSize(coord_t w, coord_t h); + + void show() { window->show(); } + void hide() { window->hide(); } + + Window *getWindow() const { return window; } + + static LvglWidgetObject *checkLvgl(lua_State *L, int index); + + bool callRefs(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + Window *window = nullptr; + LcdFlags currentColor = -1; + + coord_t x = 0, y = 0, w = LV_SIZE_CONTENT, h = LV_SIZE_CONTENT; + LcdFlags color = COLOR_THEME_SECONDARY1; + int getColorFunction = 0; + int getVisibleFunction = 0; + int getSizeFunction = 0; + int getPosFunction = 0; + + void parseParam(lua_State *L, const char *key) override; + virtual void refresh(); +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetLabel : public LvglWidgetObject +{ + public: + LvglWidgetLabel() : LvglWidgetObject() {} + + void setText(const char *s); + void setColor(LcdFlags color) override; + void setFont(LcdFlags font); + + void build(lua_State *L) override; + bool callRefs(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + uint32_t textHash = -1; + + const char *txt = ""; + LcdFlags font = FONT(STD); + int getTextFunction = 0; + int getFontFunction = 0; + + void parseParam(lua_State *L, const char *key) override; + void refresh() override + { + setText(txt); + setFont(font); + LvglWidgetObject::refresh(); + } +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetBorderedObject : public LvglWidgetObject +{ + public: + LvglWidgetBorderedObject() : LvglWidgetObject() {} + + void setColor(LcdFlags color) override; + + void build(lua_State *L) override; + + protected: + coord_t thickness = 1; + bool filled = false; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetRoundObject : public LvglWidgetBorderedObject +{ + public: + LvglWidgetRoundObject() : LvglWidgetBorderedObject() {} + + void setPos(coord_t x, coord_t y) override; + void setRadius(coord_t r); + + bool callRefs(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + coord_t radius = 0; + + int getRadiusFunction = 0; + + void parseParam(lua_State *L, const char *key) override; + void refresh() override + { + setRadius(radius); + LvglWidgetObject::refresh(); + } +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetRectangle : public LvglWidgetBorderedObject +{ + public: + LvglWidgetRectangle() : LvglWidgetBorderedObject() {} + + void build(lua_State *L) override; + + protected: + coord_t rounded = 0; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetCircle : public LvglWidgetRoundObject +{ + public: + LvglWidgetCircle() : LvglWidgetRoundObject() {} + + void build(lua_State *L) override; + + protected: +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetArc : public LvglWidgetRoundObject +{ + public: + LvglWidgetArc() : LvglWidgetRoundObject() {} + + void setColor(LcdFlags color) override; + 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; + + protected: + coord_t startAngle = 0, endAngle = 0; + + int getStartAngleFunction = 0; + int getEndAngleFunction = 0; + + void parseParam(lua_State *L, const char *key) override; + void refresh() override + { + setStartAngle(startAngle); + setEndAngle(endAngle); + LvglWidgetRoundObject::refresh(); + } +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetImage : public LvglWidgetObject +{ + public: + LvglWidgetImage() : LvglWidgetObject() {} + + void build(lua_State *L) override; + + protected: + std::string filename; + bool fillFrame = false; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetMeterScale; + +class LvglWidgetMeter : public LvglWidgetRoundObject +{ + public: + LvglWidgetMeter() : LvglWidgetRoundObject() {} + + void build(lua_State *L) override; + bool callRefs(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + std::vector scales; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetTextButton : public LvglWidgetObject +{ + public: + LvglWidgetTextButton() : LvglWidgetObject() {} + + void setText(const char *s); + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + uint32_t textHash = -1; + + const char *txt = ""; + int pressFunction = 0; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetToggleSwitch : public LvglWidgetObject +{ + public: + LvglWidgetToggleSwitch() : LvglWidgetObject() {} + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + int getStateFunction = 0; + int setStateFunction = 0; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetConfirmDialog : public LvglWidgetObject +{ + public: + LvglWidgetConfirmDialog() : LvglWidgetObject() {} + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + const char *title = nullptr; + const char *message = nullptr; + + int confirmFunction = 0; + int cancelFunction = 0; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetTextEdit : public LvglWidgetObject +{ + public: + LvglWidgetTextEdit() : LvglWidgetObject() {} + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + const char *txt = ""; + char value[129]; + int maxLen = 32; + + int setFunction = 0; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetNumberEdit : public LvglWidgetObject +{ + public: + LvglWidgetNumberEdit() : LvglWidgetObject() {} + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + int min = -1024, max = 1024; + + int getFunction = 0; + int setFunction = 0; + int dispFunction = 0; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetChoice : public LvglWidgetObject +{ + public: + LvglWidgetChoice() : LvglWidgetObject() {} + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + std::string title; + std::vector values; + + int getFunction = 0; + int setFunction = 0; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetSlider : public LvglWidgetObject +{ + public: + LvglWidgetSlider() : LvglWidgetObject() {} + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + int32_t vmin = 0; + int32_t vmax = 100; + int getValueFunction = 0; + int setValueFunction = 0; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- diff --git a/radio/src/lua/lua_widget.cpp b/radio/src/lua/lua_widget.cpp index f1e625781..b1e01c51a 100644 --- a/radio/src/lua/lua_widget.cpp +++ b/radio/src/lua/lua_widget.cpp @@ -25,6 +25,7 @@ #include "lua_event.h" #include "lua_widget_factory.h" #include "touch.h" +#include "view_main.h" #define MAX_INSTRUCTIONS (20000 / 100) @@ -162,7 +163,9 @@ void LuaEventHandler::onClicked() luaPushEvent(EVT_KEY_BREAK(KEY_ENTER)); } -void LuaEventHandler::onCancel() { luaPushEvent(EVT_KEY_BREAK(KEY_EXIT)); } +void LuaEventHandler::onCancel() { + luaPushEvent(EVT_KEY_BREAK(KEY_EXIT)); +} void LuaEventHandler::onEvent(event_t event) { @@ -189,28 +192,35 @@ void LuaWidget::redraw_cb(lv_event_t* e) LuaWidget* widget = (LuaWidget*)lv_obj_get_user_data(target); if (widget) { - lv_draw_ctx_t* draw_ctx = lv_event_get_draw_ctx(e); + if (widget->useLvglLayout()) { + if (!widget->isCreated()) { + widget->update(); + widget->setCreated(); + } + } else { + lv_draw_ctx_t* draw_ctx = lv_event_get_draw_ctx(e); - lv_area_t a, clipping, obj_coords; - lv_area_copy(&a, draw_ctx->buf_area); - lv_area_copy(&clipping, draw_ctx->clip_area); - lv_obj_get_coords(target, &obj_coords); + lv_area_t a, clipping, obj_coords; + lv_area_copy(&a, draw_ctx->buf_area); + lv_area_copy(&clipping, draw_ctx->clip_area); + lv_obj_get_coords(target, &obj_coords); - auto w = a.x2 - a.x1 + 1; - auto h = a.y2 - a.y1 + 1; + auto w = a.x2 - a.x1 + 1; + auto h = a.y2 - a.y1 + 1; - TRACE_WINDOWS("Draw %s", widget->getWindowDebugString().c_str()); + TRACE_WINDOWS("Draw %s", widget->getWindowDebugString().c_str()); - BitmapBuffer buf = {BMP_RGB565, (uint16_t)w, (uint16_t)h, - (uint16_t*)draw_ctx->buf}; + BitmapBuffer buf = {BMP_RGB565, (uint16_t)w, (uint16_t)h, + (uint16_t*)draw_ctx->buf}; - buf.setDrawCtx(draw_ctx); + buf.setDrawCtx(draw_ctx); - buf.setOffset(obj_coords.x1 - a.x1, obj_coords.y1 - a.y1); - buf.setClippingRect(clipping.x1 - a.x1, clipping.x2 + 1 - a.x1, - clipping.y1 - a.y1, clipping.y2 + 1 - a.y1); + buf.setOffset(obj_coords.x1 - a.x1, obj_coords.y1 - a.y1); + buf.setClippingRect(clipping.x1 - a.x1, clipping.x2 + 1 - a.x1, + clipping.y1 - a.y1, clipping.y2 + 1 - a.y1); - widget->refresh(&buf); + widget->refresh(&buf); + } } } @@ -218,11 +228,12 @@ LuaWidget::LuaWidget(const WidgetFactory* factory, Window* parent, const rect_t& rect, WidgetPersistentData* persistentData, int luaWidgetDataRef, int zoneRectDataRef) : Widget(factory, parent, rect, persistentData), - luaWidgetDataRef(luaWidgetDataRef), zoneRectDataRef(zoneRectDataRef), errorMessage(nullptr) { - lv_obj_add_event_cb(lvobj, LuaWidget::redraw_cb, LV_EVENT_DRAW_MAIN, nullptr); + this->luaWidgetDataRef = luaWidgetDataRef; + lv_obj_add_event_cb(lvobj, LuaWidget::redraw_cb, LV_EVENT_DRAW_MAIN, + nullptr); } LuaWidget::~LuaWidget() @@ -258,12 +269,32 @@ void LuaWidget::checkEvents() // paint has not been called if (!refreshed) { - background(); + if (isCreated()) + background(); refreshed = true; } refreshed = false; - invalidate(); + if (useLvglLayout()) { + if (!lv_obj_has_flag(lvobj, LV_OBJ_FLAG_HIDDEN) && isCreated()) { + PROTECT_LUA() { + luaLvglManager = this; + refresh(nullptr); + if (!errorMessage) { + if (!callRefs(lsWidgets)) { + setErrorMessage("callRefs()"); + } + } + refreshInstructionsPercent = instructionsPercent; + } else { + // TODO: error handling + } + luaLvglManager = nullptr; + UNPROTECT_LUA(); + } + } else { + invalidate(); + } #if defined(DEBUG_WINDOWS) TRACE_WINDOWS("# refresh: %s", getWindowDebugString().c_str()); @@ -282,10 +313,9 @@ void LuaWidget::update() Widget::update(); if (lsWidgets == 0 || errorMessage) return; - LuaWidgetFactory* lua_factory = (LuaWidgetFactory*)factory; luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, lua_factory->updateFunction); + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaFactory()->updateFunction); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaWidgetDataRef); lua_newtable(lsWidgets); @@ -308,9 +338,12 @@ void LuaWidget::update() } } - if (lua_pcall(lsWidgets, 2, 0, 0) != 0) { + if (useLvglLayout()) luaLvglManager = this; + + if (lua_pcall(lsWidgets, 2, 0, 0) != 0) setErrorMessage("update()"); - } + + luaLvglManager = nullptr; } // Update table on top of Lua stack - set entry with name 'idx' to value 'val' @@ -338,7 +371,7 @@ bool LuaWidget::updateTable(const char* idx, int val) return update; } -void LuaWidget::updateZoneRect(rect_t rect) +void LuaWidget::updateZoneRect(rect_t rect, bool updateUI) { if (lsWidgets) { // Update widget zone with current size and position @@ -354,7 +387,8 @@ void LuaWidget::updateZoneRect(rect_t rect) lua_pop(lsWidgets, 1); - if (changed) update(); + if (changed && updateUI) + update(); } } @@ -391,15 +425,28 @@ void LuaWidget::refresh(BitmapBuffer* dc) if (lsWidgets == 0) return; if (errorMessage) { - dc->drawTextLines(0, 0, fullscreen ? LCD_W : rect.w, - fullscreen ? LCD_H : rect.h, errorMessage, - FONT(XS) | COLOR_THEME_WARNING); + if (dc) { + dc->drawTextLines(0, 0, fullscreen ? LCD_W : rect.w, + fullscreen ? LCD_H : rect.h, errorMessage, + FONT(XS) | COLOR_THEME_WARNING); + } else { + if (errorLabel == nullptr) { + errorLabel = lv_label_create(lvobj); + lv_obj_set_pos(errorLabel, 0, 0); + lv_obj_set_size(errorLabel, width(), height()); + lv_label_set_long_mode(errorLabel, LV_LABEL_LONG_WRAP); + etx_txt_color(errorLabel, COLOR_THEME_WARNING_INDEX); + etx_font(errorLabel, FONT_XS_INDEX); + etx_bg_color(errorLabel, COLOR_THEME_SECONDARY3_INDEX); + etx_obj_add_style(errorLabel, styles->bg_opacity_75, LV_PART_MAIN); + } + lv_label_set_text(errorLabel, errorMessage); + } return; } luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); - LuaWidgetFactory* factory = (LuaWidgetFactory*)this->factory; - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->refreshFunction); + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaFactory()->refreshFunction); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaWidgetDataRef); // Pass key event to fullscreen Lua widget @@ -445,9 +492,8 @@ void LuaWidget::background() // TRACE("LuaWidget::background()"); luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); - LuaWidgetFactory* factory = (LuaWidgetFactory*)this->factory; - if (factory->backgroundFunction) { - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->backgroundFunction); + if (luaFactory()->backgroundFunction) { + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaFactory()->backgroundFunction); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaWidgetDataRef); runningFS = this; if (lua_pcall(lsWidgets, 1, 0, 0) != 0) { @@ -464,3 +510,34 @@ void LuaWidget::onEvent(event_t event) } Widget::onEvent(event); } + +void LuaWidget::clear() +{ + clearRefs(lsWidgets); + Widget::clear(); +} + +bool LuaWidget::useLvglLayout() const { return luaFactory()->useLvglLayout(); } + +void LuaLvglManager::clearRefs(lua_State *L) +{ + for (size_t i = 0; i < lvglObjectRefs.size(); i += 1) { + lua_rawgeti(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); + auto lvobj = LvglWidgetObject::checkLvgl(L, -1); + lvobj->clearRefs(L); + + luaL_unref(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); + } + lvglObjectRefs.clear(); +} + +bool LuaLvglManager::callRefs(lua_State *L) +{ + bool rv = true; + for (size_t i = 0; i < lvglObjectRefs.size(); i += 1) { + lua_rawgeti(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); + auto lvobj = LvglWidgetObject::checkLvgl(L, -1); + lvobj->callRefs(L); + } + return rv; +} diff --git a/radio/src/lua/lua_widget.h b/radio/src/lua/lua_widget.h index 744582ab9..ecb0ec9e2 100644 --- a/radio/src/lua/lua_widget.h +++ b/radio/src/lua/lua_widget.h @@ -21,14 +21,47 @@ #pragma once +#include + #include "window.h" #include "widget.h" #include "lua_api.h" +#include "lua_lvgl_widget.h" #include "opentx_types.h" #define LUA_TAP_TIME 250 // 250 ms +class LuaWidgetFactory; + +class LuaLvglManager +{ + public: + LuaLvglManager() = default; + + std::vector getLvglObjectRefs() const { return lvglObjectRefs; } + void saveLvglObjectRef(int ref) { lvglObjectRefs.push_back(ref); } + void clearRefs(lua_State *L); + bool callRefs(lua_State *L); + + void setTempParent(Window *p) { tempParent = p; } + Window* getTempParent() const { return tempParent; } + + virtual Window* getCurrentParent() const = 0; + virtual void clear() = 0; + virtual bool useLvglLayout() const = 0; + + virtual void luaShowError() = 0; + + virtual bool isWidget() = 0; + + uint8_t refreshInstructionsPercent; + + protected: + std::vector lvglObjectRefs; + Window* tempParent = nullptr; +}; + class LuaEventHandler { #if defined(HARDWARE_TOUCH) @@ -55,11 +88,10 @@ public: void removeHandler(Window* w); }; -class LuaWidget : public Widget, public LuaEventHandler +class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager { friend class LuaWidgetFactory; - int luaWidgetDataRef; int zoneRectDataRef; char* errorMessage; bool refreshed = false; @@ -75,7 +107,7 @@ class LuaWidget : public Widget, public LuaEventHandler void setErrorMessage(const char* funcName); // Update 'zone' data - void updateZoneRect(rect_t rect) override; + void updateZoneRect(rect_t rect, bool updateUI = true) override; bool updateTable(const char* idx, int val); public: @@ -92,9 +124,26 @@ class LuaWidget : public Widget, public LuaEventHandler void update() override; void background() override; + void clear() override; + + LuaWidgetFactory* luaFactory() const { return (LuaWidgetFactory*)factory; } + bool isCreated() const { return created; } + void setCreated() { created = true; } + + Window* getCurrentParent() const override { return tempParent ? tempParent : (Window*)this; } + + bool useLvglLayout() const override; + + void luaShowError() override {} + + bool isWidget() override { return true; } + protected: + bool created = false; + lv_obj_t* errorLabel = nullptr; // Calls LUA widget 'refresh' method void refresh(BitmapBuffer* dc); + int luaWidgetDataRef = 0; // Window interface void onEvent(event_t event) override; diff --git a/radio/src/lua/lua_widget_factory.h b/radio/src/lua/lua_widget_factory.h index 6ea269da0..b37a26c98 100644 --- a/radio/src/lua/lua_widget_factory.h +++ b/radio/src/lua/lua_widget_factory.h @@ -39,6 +39,8 @@ class LuaWidgetFactory : public WidgetFactory bool isLuaWidgetFactory() const override { return true; } + bool useLvglLayout() const { return lvglLayout; } + protected: void translateOptions(ZoneOption * options); @@ -47,4 +49,5 @@ class LuaWidgetFactory : public WidgetFactory int refreshFunction; int backgroundFunction; int translateFunction; + bool lvglLayout; }; diff --git a/radio/src/lua/widgets.cpp b/radio/src/lua/widgets.cpp index 461bbe15a..d5aab912e 100644 --- a/radio/src/lua/widgets.cpp +++ b/radio/src/lua/widgets.cpp @@ -235,6 +235,7 @@ void luaLoadWidgetCallback() int widgetOptions = 0, createFunction = 0, updateFunction = 0, refreshFunction = 0, backgroundFunction = 0, translateFunction = 0; + bool lvglLayout = false; luaL_checktype(lsWidgets, -1, LUA_TTABLE); @@ -267,6 +268,9 @@ void luaLoadWidgetCallback() translateFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); lua_pushnil(lsWidgets); } + else if (!strcasecmp(key, "uselvgl")) { + lvglLayout = lua_toboolean(lsWidgets, -1); + } } if (name && createFunction) { @@ -276,6 +280,7 @@ void luaLoadWidgetCallback() factory->updateFunction = updateFunction; factory->refreshFunction = refreshFunction; factory->backgroundFunction = backgroundFunction; // NOSONAR + factory->lvglLayout = lvglLayout; factory->translateFunction = translateFunction; factory->translateOptions(options); TRACE("Loaded Lua widget %s", name); diff --git a/radio/src/lv_conf.h b/radio/src/lv_conf.h index f86e2a43d..478aaab9c 100644 --- a/radio/src/lv_conf.h +++ b/radio/src/lv_conf.h @@ -601,7 +601,7 @@ #define LV_USE_MENU 0 -#define LV_USE_METER 0 +#define LV_USE_METER 1 #define LV_USE_MSGBOX 0 diff --git a/radio/src/main.cpp b/radio/src/main.cpp index c891ebe30..cea043867 100644 --- a/radio/src/main.cpp +++ b/radio/src/main.cpp @@ -375,6 +375,8 @@ void guiMain(event_t evt) maxLuaInterval = interval; } + luaDoGc(lsWidgets, true); + DEBUG_TIMER_START(debugTimerLua); luaTask(false); DEBUG_TIMER_STOP(debugTimerLua); diff --git a/radio/src/thirdparty/Lua/src/linit.c b/radio/src/thirdparty/Lua/src/linit.c index 3dac78a03..16e758765 100644 --- a/radio/src/thirdparty/Lua/src/linit.c +++ b/radio/src/thirdparty/Lua/src/linit.c @@ -43,6 +43,7 @@ extern LROT_TABLE(modellib); extern LROT_TABLE(bitmaplib); #if defined(COLORLCD) +extern LROT_TABLE(lvgllib); extern LROT_TABLE(tablib); #endif @@ -99,6 +100,7 @@ LROT_BEGIN(rotables, LROT_TABLEREF(rotables_meta), 0) LROT_TABENTRY( bitmap, bitmaplib ) LROT_TABENTRY( Bitmap, bitmaplib ) /* TODO: obsolete after 2.9 */ #if defined(COLORLCD) + LROT_TABENTRY( lvgl, lvgllib ) LROT_TABENTRY( table, tablib ) #endif LROT_TABENTRY( ROM, rotables ) @@ -110,6 +112,7 @@ LROT_BEGIN(lua_libs, NULL, 0) LROT_FUNCENTRY( dir, luaopen_etxdir ) LROT_FUNCENTRY( bitmap_mt, luaopen_bitmap ) #if defined(COLORLCD) + LROT_FUNCENTRY( lvgl_mt, luaopen_lvgl ) LROT_FUNCENTRY( package, luaopen_package ) #endif #if defined(LUA_ENABLE_STRLIB_MT) diff --git a/radio/src/thirdparty/libopenui/src/static.cpp b/radio/src/thirdparty/libopenui/src/static.cpp index 1981586da..10640d79d 100644 --- a/radio/src/thirdparty/libopenui/src/static.cpp +++ b/radio/src/thirdparty/libopenui/src/static.cpp @@ -183,8 +183,8 @@ void StaticIcon::center(coord_t w, coord_t h) // Display image from file system using LVGL with LVGL scaling // - LVGL scaling is slow so don't use this if there are many images StaticImage::StaticImage(Window* parent, const rect_t& rect, - const char* filename, bool dontEnlarge) : - Window(parent, rect), dontEnlarge(dontEnlarge) + const char* filename, bool fillFrame, bool dontEnlarge) : + Window(parent, rect), fillFrame(fillFrame), dontEnlarge(dontEnlarge) { setWindowFlag(NO_FOCUS); @@ -234,7 +234,7 @@ void StaticImage::setZoom() if (img && img->w && img->h) { uint16_t zw = (width() * 256) / img->w; uint16_t zh = (height() * 256) / img->h; - uint16_t z = min(zw, zh); + uint16_t z = fillFrame ? max(zw, zh) : min(zw, zh); if (dontEnlarge) z = min(z, (uint16_t)256); lv_img_set_zoom(image, z); } diff --git a/radio/src/thirdparty/libopenui/src/static.h b/radio/src/thirdparty/libopenui/src/static.h index afbf9604a..4a59ec3e8 100644 --- a/radio/src/thirdparty/libopenui/src/static.h +++ b/radio/src/thirdparty/libopenui/src/static.h @@ -142,7 +142,7 @@ class StaticImage : public Window { public: StaticImage(Window *parent, const rect_t &rect, - const char *filename = nullptr, bool dontEnlarge = false); + const char *filename = nullptr, bool fillFrame = false, bool dontEnlarge = false); #if defined(DEBUG_WINDOWS) std::string getName() const override { return "StaticImage"; } @@ -154,6 +154,7 @@ class StaticImage : public Window bool hasImage() const; protected: + bool fillFrame; bool dontEnlarge = false; lv_obj_t *image = nullptr; }; diff --git a/radio/src/thirdparty/libopenui/src/window.h b/radio/src/thirdparty/libopenui/src/window.h index 739abec65..a08cef774 100644 --- a/radio/src/thirdparty/libopenui/src/window.h +++ b/radio/src/thirdparty/libopenui/src/window.h @@ -74,7 +74,7 @@ class Window typedef std::function FocusHandler; void setFocusHandler(FocusHandler h) { focusHandler = std::move(h); } - void clear(); + virtual void clear(); virtual void deleteLater(bool detach = true, bool trash = true); bool hasFocus() const;