diff --git a/radio/src/gui/colorlcd/libui/etx_lv_theme.h b/radio/src/gui/colorlcd/libui/etx_lv_theme.h index c1546ee01..f559aca53 100644 --- a/radio/src/gui/colorlcd/libui/etx_lv_theme.h +++ b/radio/src/gui/colorlcd/libui/etx_lv_theme.h @@ -58,13 +58,16 @@ class Window; #if LANDSCAPE #if LCD_W == 320 #define LAYOUT_SCALE(x) (((x) * 8 + 5) / 10) + #define LUA_LCD_SCALE 0.8 #elif LCD_W == 800 #define LAYOUT_SCALE(x) (((x) * 11 + 4) / 8) + #define LUA_LCD_SCALE 1.375 #endif #endif #if !defined(LAYOUT_SCALE) #define LAYOUT_SCALE(x) (x) + #define LUA_LCD_SCALE 1.0 #endif // Macros for setting up layout values diff --git a/radio/src/gui/colorlcd/libui/libopenui_defines.h b/radio/src/gui/colorlcd/libui/libopenui_defines.h index 320761303..fd58f169a 100644 --- a/radio/src/gui/colorlcd/libui/libopenui_defines.h +++ b/radio/src/gui/colorlcd/libui/libopenui_defines.h @@ -26,6 +26,8 @@ #define INVERS 0x01u #define BLINK 0x1000u #define TIMEHOUR 0x2000u +#define VTOP 0x00u +#define VBOTTOM 0x200u /* drawText flags */ #define LEFT 0x00u /* align left */ diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index 474c889f0..a9f6b0e56 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -27,44 +27,69 @@ #include "api_colorlcd.h" -static int luaLvglObj(lua_State *L, std::function create, bool fullscreenOnly = false) +class LvglWidgetParams { - if (luaScriptManager && (!fullscreenOnly || luaScriptManager->isFullscreen())) { - auto obj = create(); - obj->create(L, 1); - obj->push(L); - } else { - lua_pushnil(L); - } - - return 1; -} - -static int luaLvglObjEx(lua_State *L, std::function create, bool fullscreenOnly = false) -{ - if (luaScriptManager && (!fullscreenOnly || luaScriptManager->isFullscreen())) { - LvglWidgetObjectBase* p = nullptr; - LvglWidgetObjectBase* prevParent = nullptr; - if (lua_gettop(L) == 2) { - p = LvglWidgetObjectBase::checkLvgl(L, 1, true); - if (p) { - prevParent = luaScriptManager->getTempParent(); - luaScriptManager->setTempParent(p); + 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")) { + if (lua_isinteger(L, -1)) { + int n = lua_tointeger(L, -1); + if (n > ETX_UNDEF && n < ETX_LAST) + type = (LuaLvglType)n; + else + type = ETX_UNDEF; + } else { + type = getType(luaL_checkstring(L, -1)); + } + } else if (!strcmp(key, "name")) { + name = luaL_checkstring(L, -1); + } else if (!strcmp(key, "children")) { + hasChildren = true; } } - - auto obj = create(); - obj->create(L, -1); - obj->push(L); - - if (p) - luaScriptManager->setTempParent((prevParent)); - } else { - lua_pushnil(L); } - return 1; -} + LuaLvglType getType(const char* s) + { + if (strcasecmp(s, "label") == 0) return ETX_LABEL; + if (strcasecmp(s, "rectangle") == 0) return ETX_RECTANGLE; + if (strcasecmp(s, "circle") == 0) return ETX_CIRCLE; + if (strcasecmp(s, "arc") == 0) return ETX_ARC; + if (strcasecmp(s, "hline") == 0) return ETX_HLINE; + if (strcasecmp(s, "vline") == 0) return ETX_VLINE; + if (strcasecmp(s, "line") == 0) return ETX_LINE; + if (strcasecmp(s, "triangle") == 0) return ETX_TRIANGLE; + if (strcasecmp(s, "image") == 0) return ETX_IMAGE; + if (strcasecmp(s, "qrcode") == 0) return ETX_QRCODE; + if (strcasecmp(s, "box") == 0) return ETX_BOX; + if (strcasecmp(s, "button") == 0) return ETX_BUTTON; + if (strcasecmp(s, "momentaryButton") == 0) return ETX_MOMENTARY_BUTTON; + if (strcasecmp(s, "toggle") == 0) return ETX_TOGGLE; + if (strcasecmp(s, "textEdit") == 0) return ETX_TEXTEDIT; + if (strcasecmp(s, "numberEdit") == 0) return ETX_NUMBEREDIT; + if (strcasecmp(s, "choice") == 0) return ETX_CHOICE; + if (strcasecmp(s, "slider") == 0) return ETX_SLIDER; + if (strcasecmp(s, "verticalSlider") == 0) return ETX_VERTICAL_SLIDER; + if (strcasecmp(s, "page") == 0) return ETX_PAGE; + if (strcasecmp(s, "font") == 0) return ETX_FONT; + if (strcasecmp(s, "align") == 0) return ETX_ALIGN; + if (strcasecmp(s, "color") == 0) return ETX_COLOR; + if (strcasecmp(s, "timer") == 0) return ETX_TIMER; + if (strcasecmp(s, "switch") == 0) return ETX_SWITCH; + if (strcasecmp(s, "source") == 0) return ETX_SOURCE; + if (strcasecmp(s, "file") == 0) return ETX_FILE; + if (strcasecmp(s, "setting") == 0) return ETX_SETTING; + return ETX_UNDEF; + } + + LuaLvglType type = ETX_UNDEF; + const char *name = nullptr; + bool hasChildren = false; +}; static int luaLvglPopup(lua_State *L, std::function create) { @@ -153,98 +178,107 @@ static int luaLvglClose(lua_State *L) 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)) { auto t = lua_gettop(L); LvglWidgetParams p(L, -1); + if (p.type >= ETX_FIRST_CONTROL && !luaScriptManager->isFullscreen()) + continue; LvglWidgetObjectBase *obj = nullptr; - if (strcasecmp(p.type, "label") == 0) - obj = new LvglWidgetLabel(); - else if (strcasecmp(p.type, "rectangle") == 0) - obj = new LvglWidgetRectangle(); - else if (strcasecmp(p.type, "circle") == 0) - obj = new LvglWidgetCircle(); - else if (strcasecmp(p.type, "arc") == 0) - obj = new LvglWidgetArc(); - else if (strcasecmp(p.type, "hline") == 0) - obj = new LvglWidgetHLine(); - else if (strcasecmp(p.type, "vline") == 0) - obj = new LvglWidgetVLine(); - else if (strcasecmp(p.type, "line") == 0) - obj = new LvglWidgetLine(); - else if (strcasecmp(p.type, "triangle") == 0) - obj = new LvglWidgetTriangle(); - else if (strcasecmp(p.type, "image") == 0) - obj = new LvglWidgetImage(); - else if (strcasecmp(p.type, "qrcode") == 0) - obj = new LvglWidgetQRCode(); - else if (strcasecmp(p.type, "box") == 0) - obj = new LvglWidgetBox(); - else if (luaScriptManager->isFullscreen()) { - if (strcasecmp(p.type, "button") == 0) + switch (p.type) { + case ETX_LABEL: + obj = new LvglWidgetLabel(); + break; + case ETX_RECTANGLE: + obj = new LvglWidgetRectangle(); + break; + case ETX_CIRCLE: + obj = new LvglWidgetCircle(); + break; + case ETX_ARC: + obj = new LvglWidgetArc(); + break; + case ETX_HLINE: + obj = new LvglWidgetHLine(); + break; + case ETX_VLINE: + obj = new LvglWidgetVLine(); + break; + case ETX_LINE: + obj = new LvglWidgetLine(); + break; + case ETX_TRIANGLE: + obj = new LvglWidgetTriangle(); + break; + case ETX_IMAGE: + obj = new LvglWidgetImage(); + break; + case ETX_QRCODE: + obj = new LvglWidgetQRCode(); + break; + case ETX_BOX: + obj = new LvglWidgetBox(); + break; + case ETX_BUTTON: obj = new LvglWidgetTextButton(); - if (strcasecmp(p.type, "momentaryButton") == 0) + break; + case ETX_MOMENTARY_BUTTON: obj = new LvglWidgetMomentaryButton(); - else if (strcasecmp(p.type, "toggle") == 0) + break; + case ETX_TOGGLE: obj = new LvglWidgetToggleSwitch(); - else if (strcasecmp(p.type, "textEdit") == 0) + break; + case ETX_TEXTEDIT: obj = new LvglWidgetTextEdit(); - else if (strcasecmp(p.type, "numberEdit") == 0) + break; + case ETX_NUMBEREDIT: obj = new LvglWidgetNumberEdit(); - else if (strcasecmp(p.type, "choice") == 0) + break; + case ETX_CHOICE: obj = new LvglWidgetChoice(); - else if (strcasecmp(p.type, "slider") == 0) + break; + case ETX_SLIDER: obj = new LvglWidgetSlider(); - else if (strcasecmp(p.type, "verticalSlider") == 0) + break; + case ETX_VERTICAL_SLIDER: obj = new LvglWidgetVerticalSlider(); - else if (strcasecmp(p.type, "page") == 0) + break; + case ETX_PAGE: obj = new LvglWidgetPage(); - else if (strcasecmp(p.type, "font") == 0) + break; + case ETX_FONT: obj = new LvglWidgetFontPicker(); - else if (strcasecmp(p.type, "align") == 0) + break; + case ETX_ALIGN: obj = new LvglWidgetAlignPicker(); - else if (strcasecmp(p.type, "color") == 0) + break; + case ETX_COLOR: obj = new LvglWidgetColorPicker(); - else if (strcasecmp(p.type, "timer") == 0) + break; + case ETX_TIMER: obj = new LvglWidgetTimerPicker(); - else if (strcasecmp(p.type, "switch") == 0) + break; + case ETX_SWITCH: obj = new LvglWidgetSwitchPicker(); - else if (strcasecmp(p.type, "source") == 0) + break; + case ETX_SOURCE: obj = new LvglWidgetSourcePicker(); - else if (strcasecmp(p.type, "file") == 0) + break; + case ETX_FILE: obj = new LvglWidgetFilePicker(); - else if (strcasecmp(p.type, "setting") == 0) + break; + case ETX_SETTING: obj = new LvglWidgetSetting(); + break; + default: + continue; } if (obj) { obj->create(L, -1); auto ref = obj->getRef(L); - if (p.name) { + if (p.name && refIndex != LUA_REFNIL) { lua_pushstring(L, p.name); lua_rawgeti(L, LUA_REGISTRYINDEX, ref); lua_settable(L, refIndex - 4); @@ -253,7 +287,7 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) lua_getfield(L, -1, "children"); auto prevParent = luaScriptManager->getTempParent(); luaScriptManager->setTempParent(obj); - buildLvgl(L, -1, refIndex - 3); + buildLvgl(L, -1, (refIndex != LUA_REFNIL) ? refIndex - 3 : LUA_REFNIL); lua_pop(L, 1); luaScriptManager->setTempParent(prevParent); } @@ -262,6 +296,67 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) } } +static void addChildren(lua_State *L, LvglWidgetObjectBase* obj) +{ + if (obj->getWindow()) { + lua_getfield(L, -1, "children"); + auto prevParent = luaScriptManager->getTempParent(); + luaScriptManager->setTempParent(obj); + buildLvgl(L, -1, LUA_REFNIL); + lua_pop(L, 1); + luaScriptManager->setTempParent(prevParent); + } +} + +static int luaLvglObj(lua_State *L, std::function create, bool fullscreenOnly = false) +{ + if (luaScriptManager && (!fullscreenOnly || luaScriptManager->isFullscreen())) { + LvglWidgetParams params(L, 1); + + auto obj = create(); + obj->create(L, 1); + + if (params.hasChildren) addChildren(L, obj); + + obj->push(L); + } else { + lua_pushnil(L); + } + + return 1; +} + +static int luaLvglObjEx(lua_State *L, std::function create, bool fullscreenOnly = false) +{ + if (luaScriptManager && (!fullscreenOnly || luaScriptManager->isFullscreen())) { + LvglWidgetObjectBase* p = nullptr; + LvglWidgetObjectBase* prevParent = nullptr; + if (lua_gettop(L) == 2) { + p = LvglWidgetObjectBase::checkLvgl(L, 1, true); + if (p) { + prevParent = luaScriptManager->getTempParent(); + luaScriptManager->setTempParent(p); + } + } + + LvglWidgetParams params(L, -1); + + auto obj = create(); + obj->create(L, -1); + + if (params.hasChildren) addChildren(L, obj); + + obj->push(L); + + if (p) + luaScriptManager->setTempParent((prevParent)); + } else { + lua_pushnil(L); + } + + return 1; +} + static int luaLvglBuild(lua_State *L) { if (luaScriptManager) { @@ -396,6 +491,7 @@ LROT_BEGIN(lvgllib, NULL, 0) LROT_NUMENTRY(PAD_MEDIUM, PAD_MEDIUM) LROT_NUMENTRY(PAD_LARGE, PAD_LARGE) LROT_NUMENTRY(PAD_OUTLINE, PAD_OUTLINE) + LROT_NUMENTRY(PAD_BORDER, PAD_BORDER) LROT_NUMENTRY(SRC_ALL, 0xFFFFFFFF) LROT_NUMENTRY(SRC_INPUT, SRC_INPUT) LROT_NUMENTRY(SRC_LUA, SRC_LUA) @@ -423,6 +519,38 @@ LROT_BEGIN(lvgllib, NULL, 0) LROT_NUMENTRY(SCROLL_HOR, LV_DIR_HOR) LROT_NUMENTRY(SCROLL_VER, LV_DIR_VER) LROT_NUMENTRY(SCROLL_ALL, LV_DIR_ALL) + LROT_NUMENTRY(PERCENT_SIZE, LV_PCT(0)) + LROT_NUMENTRY(PAGE_BODY_HEIGHT, LCD_H - EdgeTxStyles::MENU_HEADER_HEIGHT) + LROT_NUMENTRY(UI_ELEMENT_HEIGHT, EdgeTxStyles::UI_ELEMENT_HEIGHT) + LROT_FLOATENTRY(LCD_SCALE, LUA_LCD_SCALE) + LROT_NUMENTRY(LABEL, ETX_LABEL) + LROT_NUMENTRY(RECTANGLE, ETX_RECTANGLE) + LROT_NUMENTRY(CIRCLE, ETX_CIRCLE) + LROT_NUMENTRY(ARC, ETX_ARC) + LROT_NUMENTRY(HLINE, ETX_HLINE) + LROT_NUMENTRY(VLINE, ETX_VLINE) + LROT_NUMENTRY(LINE, ETX_LINE) + LROT_NUMENTRY(TRIANGLE, ETX_TRIANGLE) + LROT_NUMENTRY(IMAGE, ETX_IMAGE) + LROT_NUMENTRY(QRCODE, ETX_QRCODE) + LROT_NUMENTRY(BOX, ETX_BOX) + LROT_NUMENTRY(BUTTON, ETX_BUTTON) + LROT_NUMENTRY(MOMENTARY_BUTTON, ETX_MOMENTARY_BUTTON) + LROT_NUMENTRY(TOGGLE, ETX_TOGGLE) + LROT_NUMENTRY(TEXT_EDIT, ETX_TEXTEDIT) + LROT_NUMENTRY(NUMBER_EDIT, ETX_NUMBEREDIT) + LROT_NUMENTRY(CHOIDE, ETX_CHOICE) + LROT_NUMENTRY(SLIDER, ETX_SLIDER) + LROT_NUMENTRY(VERTICAL_SLIDER, ETX_VERTICAL_SLIDER) + LROT_NUMENTRY(PAGE, ETX_PAGE) + LROT_NUMENTRY(FONT, ETX_FONT) + LROT_NUMENTRY(ALIGN, ETX_ALIGN) + LROT_NUMENTRY(COLOR, ETX_COLOR) + LROT_NUMENTRY(TIMER, ETX_TIMER) + LROT_NUMENTRY(SWITCH, ETX_SWITCH) + LROT_NUMENTRY(SOURCE, ETX_SOURCE) + LROT_NUMENTRY(FILE, ETX_FILE) + LROT_NUMENTRY(SETTING, ETX_SETTING) LROT_END(lvgllib, NULL, 0) // Metatable for simple objects (line, arc, label) diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 4d9bef519..0e56a5d12 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -3165,6 +3165,8 @@ LROT_BEGIN(etxcst, NULL, 0) LROT_NUMENTRY( BLINK, BLINK ) LROT_NUMENTRY( INVERS, INVERS ) LROT_NUMENTRY( VCENTER, VCENTERED ) + LROT_NUMENTRY( VTOP, VTOP ) + LROT_NUMENTRY( VBOTTOM, VBOTTOM ) #else LROT_NUMENTRY( XXLSIZE, XXLSIZE ) LROT_NUMENTRY( DBLSIZE, DBLSIZE ) diff --git a/radio/src/lua/lua_lvgl_widget.cpp b/radio/src/lua/lua_lvgl_widget.cpp index e91c16d96..01d61c161 100644 --- a/radio/src/lua/lua_lvgl_widget.cpp +++ b/radio/src/lua/lua_lvgl_widget.cpp @@ -30,6 +30,8 @@ #include "toggleswitch.h" #include "filechoice.h" #include "keyboard_base.h" +#include "theme_manager.h" +#include "pagegroup.h" //----------------------------------------------------------------------------- @@ -40,6 +42,13 @@ static void clearRef(lua_State *L, int& ref) ref = LUA_REFNIL; } +static bool getLuaBool(lua_State *L) +{ + if (lua_isboolean(L, -1)) + return lua_toboolean(L, -1); + return luaL_checkunsigned(L, -1); +} + //----------------------------------------------------------------------------- void LvglParamFuncOrValue::parse(lua_State *L) @@ -164,6 +173,11 @@ bool LvglTitleParam::parseTitleParam(lua_State *L, const char *key) return false; } +void LvglTitleParam::clearTitleRefs(lua_State *L) +{ + title.clearRef(L); +} + bool LvglMessageParam::parseMessageParam(lua_State *L, const char *key) { if (!strcmp(key, "message")) { @@ -176,12 +190,26 @@ bool LvglMessageParam::parseMessageParam(lua_State *L, const char *key) bool LvglRoundedParam::parseRoundedParam(lua_State *L, const char *key) { if (!strcmp(key, "rounded")) { - rounded = lua_toboolean(L, -1); + rounded = getLuaBool(L); return true; } return false; } +bool LvglAlignParam::parseAlignParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "align")) { + align.parse(L); + return true; + } + return false; +} + +void LvglAlignParam::clearAlignRefs(lua_State *L) +{ + align.clearRef(L); +} + bool LvglThicknessParam::parseThicknessParam(lua_State *L, const char *key) { if (!strcmp(key, "thickness")) { @@ -206,7 +234,7 @@ bool LvglValuesParam::parseValuesParam(lua_State *L, const char *key) bool LvglScrollableParams::parseScrollableParam(lua_State *L, const char *key) { if (!strcmp(key, "scrollBar")) { - showScrollBar = lua_toboolean(L, -1); + showScrollBar = getLuaBool(L); return true; } if (!strcmp(key, "scrollDir")) { @@ -487,7 +515,7 @@ int LvglWidgetObjectBase::pcallGetIntVal(lua_State *L, int getFuncRef) int LvglWidgetObjectBase::pcallGetOptIntVal(lua_State *L, int getFuncRef, int defVal) { - int val = 0; + int val = defVal; if (getFuncRef != LUA_REFNIL) { auto save = luaScriptManager; luaScriptManager = lvglManager; @@ -740,11 +768,9 @@ bool LvglSimpleWidgetObject::isVisible() void LvglWidgetLabel::parseParam(lua_State *L, const char *key) { - if (!strcmp(key, "align")) { - align.parse(L); - } else if (!parseTextParam(L, key)) { - LvglSimpleWidgetObject::parseParam(L, key); - } + if (parseAlignParam(L, key)) return; + if (parseTextParam(L, key)) return; + LvglSimpleWidgetObject::parseParam(L, key); } bool LvglWidgetLabel::callRefs(lua_State *L) @@ -765,7 +791,7 @@ bool LvglWidgetLabel::callRefs(lua_State *L) void LvglWidgetLabel::clearRefs(lua_State *L) { - align.clearRef(L); + clearAlignRefs(L); clearTextRefs(L); LvglSimpleWidgetObject::clearRefs(L); } @@ -1067,6 +1093,11 @@ void LvglWidgetLine::refresh() build(nullptr); } +bool LvglWidgetLine::isVisible() +{ + return !lvobj || !lv_obj_has_flag(lvobj, LV_OBJ_FLAG_HIDDEN); +} + //----------------------------------------------------------------------------- LvglWidgetTriangle::LvglWidgetTriangle() : LvglSimpleWidgetObject() @@ -1424,6 +1455,7 @@ void LvglWidgetObject::clearRefs(lua_State *L) void LvglWidgetBox::parseParam(lua_State *L, const char *key) { + if (parseAlignParam(L, key)) return; if (parseScrollableParam(L, key)) return; LvglWidgetObject::parseParam(L, key); } @@ -1454,6 +1486,7 @@ bool LvglWidgetBox::callRefs(lua_State *L) void LvglWidgetBox::clearRefs(lua_State *L) { + clearAlignRefs(L); clearRef(L, scrollToFunction); LvglWidgetObject::clearRefs(L); } @@ -1472,8 +1505,16 @@ void LvglWidgetBox::build(lua_State *L) if (showScrollBar) etx_scrollbar(window->getLvObj()); } - if (setFlex()) - lv_obj_set_flex_align(window->getLvObj(), LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_AROUND); + if (setFlex()) { + lv_flex_align_t align1 = (align.flags & RIGHT) ? LV_FLEX_ALIGN_END : (align.flags & CENTERED) ? LV_FLEX_ALIGN_CENTER : LV_FLEX_ALIGN_START; + lv_flex_align_t align2 = (align.flags & VCENTERED) ? LV_FLEX_ALIGN_CENTER : (align.flags & VBOTTOM) ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START; + if (flexFlow & LV_FLEX_FLOW_COLUMN) + lv_obj_set_flex_align(window->getLvObj(), align2, align2, align1); + else + lv_obj_set_flex_align(window->getLvObj(), align1, align1, align2); + } + setColor(color.flags); + setOpacity(opacity.value); } //----------------------------------------------------------------------------- @@ -1484,6 +1525,12 @@ void LvglWidgetSetting::parseParam(lua_State *L, const char *key) LvglWidgetObject::parseParam(L, key); } +void LvglWidgetSetting::clearRefs(lua_State *L) +{ + clearTitleRefs(L); + LvglWidgetObjectBase::clearRefs(L); +} + void LvglWidgetSetting::build(lua_State *L) { window = @@ -1801,7 +1848,7 @@ void LvglWidgetImage::parseParam(lua_State *L, const char *key) if (!strcmp(key, "file")) { filename.parse(L); } else if (!strcmp(key, "fill")) { - fillFrame = lua_toboolean(L, -1); + fillFrame = getLuaBool(L); } else { LvglWidgetObject::parseParam(L, key); } @@ -1931,7 +1978,7 @@ void LvglWidgetTextButtonBase::setRounded() void LvglWidgetTextButton::parseParam(lua_State *L, const char *key) { if (!strcmp(key, "checked")) { - checked = lua_toboolean(L, -1); + checked = getLuaBool(L); } else if (!strcmp(key, "longpress")) { longPressFunction = luaL_ref(L, LUA_REGISTRYINDEX); } else { @@ -2203,10 +2250,21 @@ void LvglWidgetVerticalSlider::build(lua_State *L) class WidgetPage : public NavWindow, public LuaEventHandler { public: - WidgetPage(Window *parent, std::function backAction, + WidgetPage(Window *parent, + std::function backAction, + std::function menuAction, + std::function prevAction, + std::function nextAction, + std::function prevActive, + std::function nextActive, std::string title, std::string subtitle, std::string iconFile, - lv_dir_t scrollDir, bool showScrollBar) : - NavWindow(parent, {0, 0, LCD_W, LCD_H}), backAction(std::move(backAction)) + lv_dir_t scrollDir, bool showScrollBar, + bool showBackBtn, bool showPrevBtn, bool showNextBtn) : + NavWindow(parent, {0, 0, LCD_W, LCD_H}), + backAction(std::move(backAction)), menuAction(menuAction), + prevAction(std::move(prevAction)), nextAction(std::move(nextAction)), + prevActive(std::move(prevActive)), nextActive(std::move(nextActive)), + showPrev(showPrevBtn), showNext(showNextBtn) { if (iconFile.empty()) header = new PageHeader(this, ICON_EDGETX); @@ -2214,9 +2272,15 @@ class WidgetPage : public NavWindow, public LuaEventHandler header = new PageHeader(this, iconFile.c_str()); #if defined(HARDWARE_TOUCH) - addCustomButton(0, 0, [=]() { onCancel(); }); + if (showBackBtn) { + new HeaderBackIcon(header); + addCustomButton(0, 0, this->menuAction); + addCustomButton(LCD_W - EdgeTxStyles::MENU_HEADER_HEIGHT, 0, this->backAction); + } else { + addCustomButton(0, 0, this->backAction); + } #endif - + body = new Window( this, {0, EdgeTxStyles::MENU_HEADER_HEIGHT, LCD_W, LCD_H - EdgeTxStyles::MENU_HEADER_HEIGHT}); body->setWindowFlag(NO_FOCUS); @@ -2230,6 +2294,26 @@ class WidgetPage : public NavWindow, public LuaEventHandler lv_obj_set_scroll_dir(body->getLvObj(), scrollDir); if (showScrollBar) etx_scrollbar(body->getLvObj()); + + delayLoad(); + } + + void delayedInit() override + { +#if defined(HARDWARE_TOUCH) + if (showPrev) { + prevBtn = new IconButton(this, ICON_BTN_PREV, LCD_W - PageGroup::PAGE_GROUP_BACK_BTN_W * 3, PAD_MEDIUM, [=]() { + prevAction(); + return 0; + }); + } + if (showNext) { + nextBtn = new IconButton(this, ICON_BTN_NEXT, LCD_W - PageGroup::PAGE_GROUP_BACK_BTN_W * 2, PAD_MEDIUM, [=]() { + nextAction(); + return 0; + }); + } +#endif } Window *getBody() { return body; } @@ -2239,8 +2323,17 @@ class WidgetPage : public NavWindow, public LuaEventHandler protected: std::function backAction; + std::function menuAction; + std::function prevAction; + std::function nextAction; + std::function prevActive; + std::function nextActive; + bool showPrev = false; + bool showNext = false; PageHeader *header = nullptr; Window *body = nullptr; + IconButton* prevBtn = nullptr; + IconButton* nextBtn = nullptr; void onClicked() override { Keyboard::hide(false); LuaEventHandler::onClickedEvent(); } @@ -2251,14 +2344,49 @@ class WidgetPage : public NavWindow, public LuaEventHandler LuaEventHandler::onLuaEvent(evt); parent->onEvent(evt); } + + void checkEvents() override + { + if (prevBtn) prevBtn->enable(prevActive()); + if (nextBtn) nextBtn->enable(nextActive()); + } }; void LvglWidgetPage::parseParam(lua_State *L, const char *key) { + if (parseAlignParam(L, key)) return; if (parseTitleParam(L, key)) return; if (parseScrollableParam(L, key)) return; if (!strcmp(key, "back")) { backActionFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "menu")) { + menuActionFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "prevButton")) { + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + const char *key = lua_tostring(L, -2); + if (!strcmp(key, "press")) { + prevActionFunction = luaL_ref(L, LUA_REGISTRYINDEX); + lua_pushnil(L); + } else if (!strcmp(key, "active")) { + prevActiveFunction = luaL_ref(L, LUA_REGISTRYINDEX); + lua_pushnil(L); + } + } + } else if (!strcmp(key, "nextButton")) { + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + const char *key = lua_tostring(L, -2); + if (!strcmp(key, "press")) { + nextActionFunction = luaL_ref(L, LUA_REGISTRYINDEX); + lua_pushnil(L); + } else if (!strcmp(key, "active")) { + nextActiveFunction = luaL_ref(L, LUA_REGISTRYINDEX); + lua_pushnil(L); + } + } + } else if (!strcmp(key, "backButton")) { + showBackButton = getLuaBool(L); } else if (!strcmp(key, "subtitle")) { subtitle.parse(L); } else if (!strcmp(key, "icon")) { @@ -2310,23 +2438,44 @@ bool LvglWidgetPage::callRefs(lua_State *L) void LvglWidgetPage::clearRefs(lua_State *L) { - title.clearRef(L); + clearAlignRefs(L); + clearTitleRefs(L); subtitle.clearRef(L); clearRef(L, backActionFunction); + clearRef(L, menuActionFunction); + clearRef(L, prevActionFunction); + clearRef(L, nextActionFunction); + clearRef(L, prevActiveFunction); + clearRef(L, nextActiveFunction); LvglWidgetObject::clearRefs(L); } void LvglWidgetPage::build(lua_State *L) { + w = LCD_W; + h = LCD_H; page = new WidgetPage( lvglManager->getCurrentParent(), - [=]() { pcallSimpleFunc(L, backActionFunction); }, title.txt, subtitle.txt, iconFile, scrollDir, showScrollBar); + [=]() { pcallSimpleFunc(L, backActionFunction); }, + [=]() { pcallSimpleFunc(L, menuActionFunction); }, + [=]() { pcallSimpleFunc(L, prevActionFunction); }, + [=]() { pcallSimpleFunc(L, nextActionFunction); }, + [=]() { return pcallGetOptIntVal(L, prevActiveFunction, true); }, + [=]() { return pcallGetOptIntVal(L, nextActiveFunction, true); }, + title.txt, subtitle.txt, iconFile, scrollDir, showScrollBar, + showBackButton, prevActionFunction != LUA_REFNIL, nextActionFunction != LUA_REFNIL); window = page->getBody(); window->disableForcedScroll(); window->setScrollHandler([=](coord_t x, coord_t y) { pcallFuncWith2Int(L, scrolledFunction, 0, x, y); }); - if (setFlex()) - lv_obj_set_flex_align(window->getLvObj(), LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_AROUND); + if (setFlex()) { + lv_flex_align_t align1 = (align.flags & RIGHT) ? LV_FLEX_ALIGN_END : (align.flags & CENTERED) ? LV_FLEX_ALIGN_CENTER : LV_FLEX_ALIGN_START; + lv_flex_align_t align2 = (align.flags & VCENTERED) ? LV_FLEX_ALIGN_CENTER : (align.flags & VBOTTOM) ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START; + if (flexFlow & LV_FLEX_FLOW_COLUMN) + lv_obj_set_flex_align(window->getLvObj(), align2, align2, align1); + else + lv_obj_set_flex_align(window->getLvObj(), align1, align1, align2); + } } //----------------------------------------------------------------------------- @@ -2365,7 +2514,7 @@ void LvglWidgetDialog::parseParam(lua_State *L, const char *key) void LvglWidgetDialog::clearRefs(lua_State *L) { - title.clearRef(L); + clearTitleRefs(L); clearRef(L, closeFunction); LvglWidgetObject::clearRefs(L); } @@ -2405,7 +2554,7 @@ void LvglWidgetConfirmDialog::parseParam(lua_State *L, const char *key) void LvglWidgetConfirmDialog::clearRefs(lua_State *L) { - title.clearRef(L); + clearTitleRefs(L); clearRef(L, confirmFunction); clearRef(L, cancelFunction); LvglWidgetObject::clearRefs(L); @@ -2433,7 +2582,7 @@ void LvglWidgetMessageDialog::parseParam(lua_State *L, const char *key) void LvglWidgetMessageDialog::clearRefs(lua_State *L) { - title.clearRef(L); + clearTitleRefs(L); LvglWidgetObject::clearRefs(L); } @@ -2474,7 +2623,7 @@ void LvglWidgetChoice::parseParam(lua_State *L, const char *key) void LvglWidgetChoice::clearRefs(lua_State *L) { - title.clearRef(L); + clearTitleRefs(L); clearRef(L, filterFunction); LvglWidgetPicker::clearRefs(L); } @@ -2496,10 +2645,7 @@ void LvglWidgetChoice::build(lua_State *L) PROTECT_LUA() { if (pcallFuncWithInt(L, filterFunction, 1, n + 1)) { - if (lua_isboolean(L, -1)) - rv = lua_toboolean(L, -1); - else - rv = luaL_checkinteger(L, -1) != 0; + rv = getLuaBool(L); } else { lvglManager->luaShowError(); } @@ -2527,7 +2673,7 @@ void LvglWidgetMenu::parseParam(lua_State *L, const char *key) void LvglWidgetMenu::clearRefs(lua_State *L) { - title.clearRef(L); + clearTitleRefs(L); LvglWidgetPicker::clearRefs(L); } @@ -2669,7 +2815,7 @@ void LvglWidgetFilePicker::parseParam(lua_State *L, const char *key) } else if (!strcmp(key, "maxLen")) { maxLen = luaL_checkunsigned(L, -1); } else if (!strcmp(key, "hideExtension")) { - hideExtension = lua_toboolean(L, -1); + hideExtension = getLuaBool(L); } else { LvglWidgetPicker::parseParam(L, key); } @@ -2677,7 +2823,7 @@ void LvglWidgetFilePicker::parseParam(lua_State *L, const char *key) void LvglWidgetFilePicker::clearRefs(lua_State *L) { - title.clearRef(L); + clearTitleRefs(L); LvglWidgetPicker::clearRefs(L); } diff --git a/radio/src/lua/lua_lvgl_widget.h b/radio/src/lua/lua_lvgl_widget.h index 6c93af637..aa23875b2 100644 --- a/radio/src/lua/lua_lvgl_widget.h +++ b/radio/src/lua/lua_lvgl_widget.h @@ -29,6 +29,52 @@ class LvglDialog; //----------------------------------------------------------------------------- +enum LuaLvglType +{ + ETX_UNDEF, + + // Drawing primitives + ETX_LABEL, + ETX_RECTANGLE, + ETX_CIRCLE, + ETX_ARC, + ETX_HLINE, + ETX_VLINE, + ETX_LINE, + ETX_TRIANGLE, + ETX_IMAGE, + ETX_QRCODE, + + // Contianers + ETX_BOX, + + // Controls - tools / fullscreen widgets only + ETX_FIRST_CONTROL, + ETX_BUTTON = ETX_FIRST_CONTROL, + ETX_MOMENTARY_BUTTON, + ETX_TOGGLE, + ETX_TEXTEDIT, + ETX_NUMBEREDIT, + ETX_CHOICE, + ETX_SLIDER, + ETX_VERTICAL_SLIDER, + // Containers + ETX_PAGE, + // Value selectors + ETX_FONT, + ETX_ALIGN, + ETX_COLOR, + ETX_TIMER, + ETX_SWITCH, + ETX_SOURCE, + ETX_FILE, + ETX_SETTING, + + ETX_LAST +}; + +//----------------------------------------------------------------------------- + struct LvglParamFuncOrValue { public: @@ -114,6 +160,7 @@ class LvglTitleParam LvglParamFuncOrString title = { .function = LUA_REFNIL, .txt = ""}; bool parseTitleParam(lua_State *L, const char *key); + void clearTitleRefs(lua_State *L); }; class LvglMessageParam @@ -138,6 +185,18 @@ class LvglRoundedParam bool parseRoundedParam(lua_State *L, const char *key); }; +class LvglAlignParam +{ + public: + LvglAlignParam() {} + + protected: + LvglParamFuncOrValue align = { .function = LUA_REFNIL, .flags = LEFT}; + + bool parseAlignParam(lua_State *L, const char *key); + void clearAlignRefs(lua_State *L); +}; + class LvglThicknessParam { public: @@ -271,7 +330,7 @@ class LvglSimpleWidgetObject : public LvglWidgetObjectBase //----------------------------------------------------------------------------- -class LvglWidgetLabel : public LvglSimpleWidgetObject, public LvglTextParams +class LvglWidgetLabel : public LvglSimpleWidgetObject, public LvglTextParams, public LvglAlignParam { public: LvglWidgetLabel() : LvglSimpleWidgetObject() {} @@ -285,8 +344,6 @@ class LvglWidgetLabel : public LvglSimpleWidgetObject, public LvglTextParams void clearRefs(lua_State *L) override; protected: - LvglParamFuncOrValue align = { .function = LUA_REFNIL, .flags = LEFT}; - void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override; void refresh() override @@ -360,6 +417,8 @@ class LvglWidgetLine : public LvglSimpleWidgetObject, public LvglRoundedParam, p bool callRefs(lua_State *L) override; void clearRefs(lua_State *L) override; + bool isVisible() override; + protected: size_t ptCnt = 0; size_t ptAlloc = 0; @@ -441,10 +500,13 @@ class LvglWidgetObject : public LvglWidgetObjectBase //----------------------------------------------------------------------------- -class LvglWidgetBox : public LvglWidgetObject, public LvglScrollableParams +class LvglWidgetBox : public LvglWidgetObject, public LvglScrollableParams, public LvglAlignParam { public: - LvglWidgetBox() : LvglWidgetObject() {} + LvglWidgetBox() : LvglWidgetObject(), LvglScrollableParams(), LvglAlignParam() + { + align.flags = CENTERED; + } coord_t getScrollX() override; coord_t getScrollY() override; @@ -464,6 +526,8 @@ class LvglWidgetSetting : public LvglWidgetObject, public LvglTitleParam public: LvglWidgetSetting() : LvglWidgetObject() {} + void clearRefs(lua_State *L) override; + protected: void build(lua_State *L) override; @@ -787,10 +851,13 @@ class LvglWidgetVerticalSlider : public LvglWidgetSliderBase class WidgetPage; -class LvglWidgetPage : public LvglWidgetObject, public LvglTitleParam, public LvglScrollableParams +class LvglWidgetPage : public LvglWidgetObject, public LvglTitleParam, public LvglScrollableParams, public LvglAlignParam { public: - LvglWidgetPage() : LvglWidgetObject() {} + LvglWidgetPage() : LvglWidgetObject() + { + align.flags = CENTERED; + } bool callRefs(lua_State *L) override; void clearRefs(lua_State *L) override; @@ -805,8 +872,15 @@ class LvglWidgetPage : public LvglWidgetObject, public LvglTitleParam, public Lv LvglParamFuncOrString subtitle = { .function = LUA_REFNIL, .txt = ""}; std::string iconFile; WidgetPage* page = nullptr; + bool showBackButton = false; + bool showNavButtons = false; int backActionFunction = LUA_REFNIL; + int menuActionFunction = LUA_REFNIL; + int prevActionFunction = LUA_REFNIL; + int nextActionFunction = LUA_REFNIL; + int prevActiveFunction = LUA_REFNIL; + int nextActiveFunction = LUA_REFNIL; void build(lua_State *L) override; void parseParam(lua_State *L, const char *key) override;