diff --git a/radio/src/dataconstants.h b/radio/src/dataconstants.h index 267a18b09..5ed0e87d2 100644 --- a/radio/src/dataconstants.h +++ b/radio/src/dataconstants.h @@ -476,6 +476,16 @@ enum SwitchSources { SWSRC_INVERT SKIP = SWSRC_COUNT+1, }; +enum SwitchTypes { + SW_SWITCH = 1 << 0, + SW_TRIM = 1 << 1, + SW_LOGICAL_SWITCH = 1 << 2, + SW_FLIGHT_MODE = 1 << 3, + SW_TELEM = 1 << 4, + SW_OTHER = 1 << 5, + SW_NONE = 1 << 20, +}; + enum MixSources { MIXSRC_NONE, @@ -563,6 +573,31 @@ enum MixSources { #define MIXSRC_FIRST_FS_SWITCH (MIXSRC_LAST_REGULAR_SWITCH + 1) #endif +enum SrcTypes { + SRC_INPUT = 1 << 0, + SRC_LUA = 1 << 1, + SRC_STICK = 1 << 2, + SRC_POT = 1 << 3, + SRC_TILT = 1 << 4, + SRC_SPACEMOUSE = 1 << 5, + SRC_MINMAX = 1 << 6, + SRC_HELI = 1 << 7, + SRC_TRIM = 1 << 8, + SRC_SWITCH = 1 << 9, + SRC_FUNC_SWITCH = 1 << 10, + SRC_LOGICAL_SWITCH = 1 << 11, + SRC_TRAINER = 1 << 12, + SRC_CHANNEL = 1 << 13, + SRC_CHANNEL_ALL = 1 << 14, + SRC_GVAR = 1 << 15, + SRC_TX = 1 << 16, + SRC_TIMER = 1 << 17, + SRC_TELEM = 1 << 18, + SRC_TELEM_COMP = 1 << 19, + SRC_NONE = 1 << 20, + SRC_INVERT = 1 << 21, +}; + enum BacklightMode { e_backlight_mode_off = 0, e_backlight_mode_keys = 1, diff --git a/radio/src/gui/colorlcd/controls/sourcechoice.cpp b/radio/src/gui/colorlcd/controls/sourcechoice.cpp index 962cb1088..6e82e4525 100644 --- a/radio/src/gui/colorlcd/controls/sourcechoice.cpp +++ b/radio/src/gui/colorlcd/controls/sourcechoice.cpp @@ -54,10 +54,6 @@ class SourceChoiceMenuToolbar : public MenuToolbar [=](int16_t index) { if (index >= MIXSRC_FIRST_POT && index <= MIXSRC_LAST_POT) return false; -#if MAX_AXIS > 0 - if (index >= MIXSRC_FIRST_AXIS && index <= MIXSRC_LAST_AXIS) - return false; -#endif return index >= MIXSRC_FIRST_STICK && index <= lastSource; }, STR_MENU_STICKS); diff --git a/radio/src/gui/colorlcd/libui/button.cpp b/radio/src/gui/colorlcd/libui/button.cpp index 6ef224404..6549c0d56 100644 --- a/radio/src/gui/colorlcd/libui/button.cpp +++ b/radio/src/gui/colorlcd/libui/button.cpp @@ -21,6 +21,8 @@ #include "static.h" #include "etx_lv_theme.h" +//----------------------------------------------------------------------------- + static void button_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) { etx_btn_style(obj, LV_PART_MAIN); @@ -51,6 +53,8 @@ Button::Button(Window* parent, const rect_t& rect, { } +//----------------------------------------------------------------------------- + ButtonBase::ButtonBase(Window* parent, const rect_t& rect, std::function pressHandler, LvglCreate objConstruct) : @@ -97,6 +101,8 @@ void ButtonBase::checkEvents() if (checkHandler) checkHandler(); } +//----------------------------------------------------------------------------- + TextButton::TextButton(Window* parent, const rect_t& rect, std::string text, std::function pressHandler) : ButtonBase(parent, rect, pressHandler, button_create), @@ -107,6 +113,8 @@ TextButton::TextButton(Window* parent, const rect_t& rect, std::string text, lv_obj_center(label); } +//----------------------------------------------------------------------------- + IconButton::IconButton(Window* parent, EdgeTxIcon icon, coord_t x, coord_t y, std::function pressHandler) : ButtonBase(parent, {x, y, EdgeTxStyles::UI_ELEMENT_HEIGHT, EdgeTxStyles::UI_ELEMENT_HEIGHT}, pressHandler, button_create) @@ -117,3 +125,33 @@ IconButton::IconButton(Window* parent, EdgeTxIcon icon, coord_t x, coord_t y, } void IconButton::setIcon(EdgeTxIcon icon) { iconImage->setIcon(icon); } + +//----------------------------------------------------------------------------- + +MomentaryButton::MomentaryButton(Window* parent, const rect_t& rect, std::string text, + std::function pressHandler, + std::function releaseHandler) : + FormField(parent, rect, button_create), + pressHandler(std::move(pressHandler)), + releaseHandler(std::move(releaseHandler)), + text(std::move(text)) +{ + label = lv_label_create(lvobj); + lv_label_set_text(label, this->text.c_str()); + lv_obj_center(label); +} + +void MomentaryButton::onPressed() +{ + if (pressHandler) + pressHandler(); + lv_obj_add_state(lvobj, LV_STATE_CHECKED); + lv_obj_clear_state(lvobj, LV_STATE_PRESSED); +} + +void MomentaryButton::onReleased() +{ + if (releaseHandler) + releaseHandler(); + lv_obj_clear_state(lvobj, LV_STATE_CHECKED); +} diff --git a/radio/src/gui/colorlcd/libui/button.h b/radio/src/gui/colorlcd/libui/button.h index 82e18958d..3d7b53be0 100644 --- a/radio/src/gui/colorlcd/libui/button.h +++ b/radio/src/gui/colorlcd/libui/button.h @@ -94,6 +94,11 @@ class TextButton : public ButtonBase } } + void setFont(FontIndex font) + { + etx_font(label, font); + } + void setWrap() { lv_obj_set_width(label, lv_pct(100)); @@ -102,9 +107,8 @@ class TextButton : public ButtonBase } protected: - lv_obj_t* label = nullptr; - std::string text; + lv_obj_t* label = nullptr; }; class IconButton : public ButtonBase @@ -118,3 +122,40 @@ class IconButton : public ButtonBase protected: StaticIcon* iconImage = nullptr; }; + +// Button class that is in checked state while pressed and +// returns to normal state when released. +// Used by Lua scripts. +class MomentaryButton : public FormField +{ + public: + MomentaryButton(Window* parent, const rect_t& rect, std::string text, + std::function pressHandler = nullptr, + std::function releaseHandler = nullptr); + +#if defined(DEBUG_WINDOWS) + std::string getName() const override { return "MomentaryButton"; } +#endif + + void onPressed() override; + void onReleased() override; + + void setText(std::string value) + { + if (value != text) { + text = std::move(value); + lv_label_set_text(label, text.c_str()); + } + } + + void setFont(FontIndex font) + { + etx_font(label, font); + } + + protected: + std::function pressHandler; + std::function releaseHandler; + std::string text; + lv_obj_t* label = nullptr; +}; diff --git a/radio/src/gui/colorlcd/libui/slider.cpp b/radio/src/gui/colorlcd/libui/slider.cpp index f45a5f50e..7e80c6d44 100644 --- a/radio/src/gui/colorlcd/libui/slider.cpp +++ b/radio/src/gui/colorlcd/libui/slider.cpp @@ -21,11 +21,94 @@ #include "form.h" #include "etx_lv_theme.h" +//----------------------------------------------------------------------------- + +void SliderBase::slider_changed_cb(lv_event_t* e) +{ + if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) { + SliderBase* sl = (SliderBase*)lv_event_get_user_data(e); + if (sl != nullptr) { + lv_obj_t* target = lv_event_get_target(e); + sl->setValue(lv_slider_get_value(target)); + } + } +} + +SliderBase::SliderBase(Window* parent, coord_t width, coord_t height, int32_t vmin, int32_t vmax, + std::function getValue, + std::function setValue) : + Window(parent, {0, 0, width, height}, window_create), + vmin(vmin), + vmax(vmax), + _getValue(std::move(getValue)), + _setValue(std::move(setValue)) +{ +} + +void SliderBase::update() +{ + if (_getValue != nullptr) { + // Fix for lv_slider_set_value not working when using the rotary encoder to + // update value + auto bar = (lv_bar_t*)slider; + bar->cur_value_anim.anim_state = -1; + bar->cur_value_anim.anim_end = _getValue(); + + lv_slider_set_value(slider, _getValue(), LV_ANIM_OFF); + } +} + +void SliderBase::deleteLater(bool detach, bool trash) +{ + if (!deleted()) { + if (tickPts) delete tickPts; + Window::deleteLater(detach, trash); + } +} + +void SliderBase::setValue(int value) +{ + if (_setValue != nullptr) + _setValue(limit(vmin, value, vmax)); +} + +void SliderBase::checkEvents() +{ + Window::checkEvents(); + if (_getValue != nullptr) { + int v = _getValue(); + if (v != lv_slider_get_value(slider)) + update(); + } +} + +void SliderBase::enable(bool enabled) +{ + if (!_deleted && slider) { + if (lv_obj_has_state(slider, LV_STATE_DISABLED) == enabled) { + if (enabled) + lv_obj_clear_state(slider, LV_STATE_DISABLED); + else + lv_obj_add_state(slider, LV_STATE_DISABLED); + } + } +} + +void SliderBase::setColor(LcdFlags color) +{ + etx_bg_color_from_flags(slider, color, LV_PART_KNOB); + if (tickPts) + for (int i = 0; i < (vmax - vmin - 1); i += 1) + etx_bg_color_from_flags(tickPts[i], color); +} + +//----------------------------------------------------------------------------- + // Slider const lv_style_const_prop_t slider_knob_props[] = { - LV_STYLE_CONST_PAD_TOP(9), LV_STYLE_CONST_PAD_BOTTOM(9), - LV_STYLE_CONST_PAD_LEFT(4), LV_STYLE_CONST_PAD_RIGHT(4), - LV_STYLE_CONST_RADIUS(4), LV_STYLE_CONST_BORDER_WIDTH(2), + LV_STYLE_CONST_PAD_TOP(PAD_LARGE + 1), LV_STYLE_CONST_PAD_BOTTOM(PAD_LARGE + 1), + LV_STYLE_CONST_PAD_LEFT(PAD_SMALL), LV_STYLE_CONST_PAD_RIGHT(PAD_SMALL), + LV_STYLE_CONST_RADIUS(PAD_SMALL), LV_STYLE_CONST_BORDER_WIDTH(PAD_TINY), LV_STYLE_PROP_INV, }; LV_STYLE_CONST_MULTI_INIT(slider_knob, slider_knob_props); @@ -43,11 +126,15 @@ static void slider_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) etx_bg_color(obj, COLOR_THEME_EDIT_INDEX, LV_PART_INDICATOR | LV_STATE_FOCUSED | LV_STATE_EDITED); - etx_obj_add_style(obj, styles->bg_opacity_cover, LV_PART_KNOB); + etx_solid_bg(obj, COLOR_THEME_PRIMARY2_INDEX, LV_PART_KNOB); etx_obj_add_style(obj, slider_knob, LV_PART_KNOB); etx_obj_add_style(obj, styles->border_color[COLOR_THEME_SECONDARY1_INDEX], LV_PART_MAIN); etx_obj_add_style(obj, styles->border_color[COLOR_THEME_FOCUS_INDEX], LV_PART_KNOB | LV_STATE_FOCUSED); + + etx_obj_add_style(obj, styles->disabled, LV_PART_MAIN | LV_STATE_DISABLED); + etx_obj_add_style(obj, styles->disabled, LV_PART_KNOB | LV_STATE_DISABLED); + etx_obj_add_style(obj, styles->disabled, LV_PART_INDICATOR | LV_STATE_DISABLED); } static const lv_obj_class_t slider_class = { @@ -57,7 +144,7 @@ static const lv_obj_class_t slider_class = { .user_data = nullptr, .event_cb = nullptr, .width_def = 0, - .height_def = 8, + .height_def = PAD_LARGE, .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT, .instance_size = sizeof(lv_slider_t), @@ -68,34 +155,19 @@ static lv_obj_t* slider_create(lv_obj_t* parent) return etx_create(&slider_class, parent); } -void Slider::slider_changed_cb(lv_event_t* e) -{ - if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) { - Slider* sl = (Slider*)lv_event_get_user_data(e); - if (sl != nullptr) { - lv_obj_t* target = lv_event_get_target(e); - sl->setValue(lv_slider_get_value(target)); - } - } -} - Slider::Slider(Window* parent, coord_t width, int32_t vmin, int32_t vmax, std::function getValue, std::function setValue) : - Window(parent, {0, 0, width, 32}, window_create), - vmin(vmin), - vmax(vmax), - _getValue(std::move(getValue)), - _setValue(std::move(setValue)) + SliderBase(parent, width, EdgeTxStyles::UI_ELEMENT_HEIGHT, vmin, vmax, getValue, setValue) { - padTop(9); - padLeft(8); - padRight(8); + padTop(PAD_LARGE + 1); + padLeft(PAD_LARGE); + padRight(PAD_LARGE); slider = (new FormField(this, rect_t{}, slider_create))->getLvObj(); lv_obj_set_width(slider, lv_pct(100)); - lv_obj_add_event_cb(slider, Slider::slider_changed_cb, LV_EVENT_VALUE_CHANGED, this); + lv_obj_add_event_cb(slider, SliderBase::slider_changed_cb, LV_EVENT_VALUE_CHANGED, this); lv_slider_set_range(slider, vmin, vmax); lv_obj_add_event_cb(lvobj, Slider::on_draw, LV_EVENT_DRAW_MAIN_BEGIN, @@ -106,7 +178,7 @@ Slider::Slider(Window* parent, coord_t width, int32_t vmin, int32_t vmax, tickPts = new lv_obj_t*[range - 1]; for (int n = 1; n < range; n += 1) { lv_obj_t* p = lv_obj_create(lvobj); - lv_obj_set_size(p, 2, 6); + lv_obj_set_size(p, PAD_TINY, PAD_MEDIUM); etx_solid_bg(p, COLOR_THEME_PRIMARY2_INDEX); tickPts[n - 1] = p; } @@ -126,7 +198,7 @@ void Slider::delayedInit() if (!loaded) { loaded = true; - coord_t w = lv_obj_get_width(lvobj) - 16; + coord_t w = lv_obj_get_width(lvobj) - PAD_LARGE * 2; coord_t x = -1; int range = vmax - vmin; if (range < 10) { @@ -137,23 +209,109 @@ void Slider::delayedInit() } } -void Slider::update() -{ - if (_getValue != nullptr) { - // Fix for lv_slider_set_value not working when using the rotary encoder to - // update value - auto bar = (lv_bar_t*)slider; - bar->cur_value_anim.anim_state = -1; - bar->cur_value_anim.anim_end = _getValue(); +//----------------------------------------------------------------------------- - lv_slider_set_value(slider, _getValue(), LV_ANIM_OFF); - } +// Vertical Slider +const lv_style_const_prop_t vslider_knob_props[] = { + LV_STYLE_CONST_PAD_TOP(PAD_SMALL), LV_STYLE_CONST_PAD_BOTTOM(PAD_SMALL), + LV_STYLE_CONST_PAD_LEFT(PAD_LARGE + 1), LV_STYLE_CONST_PAD_RIGHT(PAD_LARGE + 1), + LV_STYLE_CONST_RADIUS(PAD_SMALL), LV_STYLE_CONST_BORDER_WIDTH(PAD_TINY), + LV_STYLE_PROP_INV, +}; +LV_STYLE_CONST_MULTI_INIT(vslider_knob, vslider_knob_props); + +static void vslider_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) +{ + etx_solid_bg(obj, COLOR_THEME_SECONDARY1_INDEX); + etx_obj_add_style(obj, styles->rounded, LV_PART_MAIN); + etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, LV_PART_MAIN | LV_STATE_FOCUSED); + etx_bg_color(obj, COLOR_THEME_EDIT_INDEX, + LV_PART_MAIN | LV_STATE_FOCUSED | LV_STATE_EDITED); + + etx_bg_color(obj, COLOR_THEME_FOCUS_INDEX, + LV_PART_INDICATOR | LV_STATE_FOCUSED); + etx_bg_color(obj, COLOR_THEME_EDIT_INDEX, + LV_PART_INDICATOR | LV_STATE_FOCUSED | LV_STATE_EDITED); + + etx_obj_add_style(obj, styles->bg_opacity_cover, LV_PART_KNOB); + etx_obj_add_style(obj, vslider_knob, LV_PART_KNOB); + etx_obj_add_style(obj, styles->border_color[COLOR_THEME_SECONDARY1_INDEX], LV_PART_MAIN); + etx_obj_add_style(obj, styles->border_color[COLOR_THEME_FOCUS_INDEX], + LV_PART_KNOB | LV_STATE_FOCUSED); + + etx_obj_add_style(obj, styles->disabled, LV_PART_MAIN | LV_STATE_DISABLED); + etx_obj_add_style(obj, styles->disabled, LV_PART_KNOB | LV_STATE_DISABLED); + etx_obj_add_style(obj, styles->disabled, LV_PART_INDICATOR | LV_STATE_DISABLED); } -void Slider::deleteLater(bool detach, bool trash) +static const lv_obj_class_t vslider_class = { + .base_class = &lv_slider_class, + .constructor_cb = vslider_constructor, + .destructor_cb = nullptr, + .user_data = nullptr, + .event_cb = nullptr, + .width_def = PAD_LARGE, + .height_def = 0, + .editable = LV_OBJ_CLASS_EDITABLE_INHERIT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT, + .instance_size = sizeof(lv_slider_t), +}; + +static lv_obj_t* vslider_create(lv_obj_t* parent) { - if (!deleted()) { - if (tickPts) delete tickPts; - Window::deleteLater(detach, trash); + return etx_create(&vslider_class, parent); +} + +VerticalSlider::VerticalSlider(Window* parent, coord_t height, int32_t vmin, int32_t vmax, + std::function getValue, + std::function setValue) : + SliderBase(parent, EdgeTxStyles::UI_ELEMENT_HEIGHT, height, vmin,vmax, getValue, setValue) +{ + padLeft(PAD_LARGE + 1); + padTop(PAD_LARGE); + padBottom(PAD_LARGE); + + slider = (new FormField(this, rect_t{}, vslider_create))->getLvObj(); + lv_obj_set_height(slider, lv_pct(100)); + + lv_obj_add_event_cb(slider, SliderBase::slider_changed_cb, LV_EVENT_VALUE_CHANGED, this); + lv_slider_set_range(slider, vmin, vmax); + + lv_obj_add_event_cb(lvobj, VerticalSlider::on_draw, LV_EVENT_DRAW_MAIN_BEGIN, + nullptr); + + int range = vmax - vmin; + if (range < 10) { + tickPts = new lv_obj_t*[range - 1]; + for (int n = 1; n < range; n += 1) { + lv_obj_t* p = lv_obj_create(lvobj); + lv_obj_set_size(p, PAD_MEDIUM, PAD_TINY); + etx_solid_bg(p, COLOR_THEME_PRIMARY2_INDEX); + tickPts[n - 1] = p; + } } -} \ No newline at end of file + + update(); +} + +void VerticalSlider::on_draw(lv_event_t* e) +{ + auto dlg = (VerticalSlider*)lv_obj_get_user_data(lv_event_get_target(e)); + if (dlg) dlg->delayedInit(); +} + +void VerticalSlider::delayedInit() +{ + if (!loaded) { + loaded = true; + + coord_t h = lv_obj_get_height(lvobj) - PAD_LARGE * 2; + coord_t y = -1; + int range = vmax - vmin; + if (range < 10) { + for (int n = 1; n < range; n += 1) { + lv_obj_set_pos(tickPts[n - 1], 1, y + (h * n) / range); + } + } + } +} diff --git a/radio/src/gui/colorlcd/libui/slider.h b/radio/src/gui/colorlcd/libui/slider.h index f7e0d29e3..3f429d1a6 100644 --- a/radio/src/gui/colorlcd/libui/slider.h +++ b/radio/src/gui/colorlcd/libui/slider.h @@ -20,23 +20,19 @@ #include "window.h" -class Slider : public Window +class SliderBase : public Window { public: - Slider(Window* parent, coord_t width, int32_t vmin, int32_t vmax, + SliderBase(Window* parent, coord_t width, coord_t height, int32_t vmin, int32_t vmax, std::function getValue, std::function setValue); -#if defined(DEBUG_WINDOWS) - std::string getName() const override { return "Slider"; } -#endif - - void setValue(int value) - { - if (_setValue != nullptr) _setValue(limit(vmin, value, vmax)); - } + void setValue(int value); + void setColor(LcdFlags color); void update(); + void enable(bool enabled = true) override; + protected: int vmin; int vmax; @@ -47,9 +43,38 @@ class Slider : public Window std::function _getValue; std::function _setValue; + void deleteLater(bool detach, bool trash) override; + void checkEvents() override; + static void slider_changed_cb(lv_event_t* e); +}; + +class Slider : public SliderBase +{ + public: + Slider(Window* parent, coord_t width, int32_t vmin, int32_t vmax, + std::function getValue, std::function setValue); + +#if defined(DEBUG_WINDOWS) + std::string getName() const override { return "Slider"; } +#endif + + protected: + static void on_draw(lv_event_t* e); + void delayedInit(); +}; + +class VerticalSlider : public SliderBase +{ + public: + VerticalSlider(Window* parent, coord_t height, int32_t vmin, int32_t vmax, + std::function getValue, std::function setValue); + +#if defined(DEBUG_WINDOWS) + std::string getName() const override { return "VerticalSlider"; } +#endif + + protected: static void on_draw(lv_event_t* e); void delayedInit(); - - void deleteLater(bool detach, bool trash) override; }; diff --git a/radio/src/gui/colorlcd/libui/toggleswitch.cpp b/radio/src/gui/colorlcd/libui/toggleswitch.cpp index e7a920e1c..d9ad34dbf 100644 --- a/radio/src/gui/colorlcd/libui/toggleswitch.cpp +++ b/radio/src/gui/colorlcd/libui/toggleswitch.cpp @@ -100,3 +100,14 @@ void ToggleSwitch::onClicked() { // prevent FormField::onClicked() } + +void ToggleSwitch::checkEvents() +{ + Window::checkEvents(); + if (_getValue != nullptr) { + bool v = _getValue() != 0; + bool s = (lv_obj_get_state(lvobj) & LV_STATE_CHECKED) == LV_STATE_CHECKED; + if (v != s) + update(); + } +} diff --git a/radio/src/gui/colorlcd/libui/toggleswitch.h b/radio/src/gui/colorlcd/libui/toggleswitch.h index a5d613da5..cb20a2326 100644 --- a/radio/src/gui/colorlcd/libui/toggleswitch.h +++ b/radio/src/gui/colorlcd/libui/toggleswitch.h @@ -54,5 +54,7 @@ class ToggleSwitch : public FormField std::function _getValue; std::function _setValue; + void checkEvents() override; + static void toggleswitch_event_handler(lv_event_t* e); }; diff --git a/radio/src/gui/colorlcd/libui/window.cpp b/radio/src/gui/colorlcd/libui/window.cpp index 6520d8d06..197b4246a 100644 --- a/radio/src/gui/colorlcd/libui/window.cpp +++ b/radio/src/gui/colorlcd/libui/window.cpp @@ -96,6 +96,12 @@ void Window::eventHandler(lv_event_t *e) TRACE("LONG PRESS[%p]", this); _longPressed = onLongPress(); break; + case LV_EVENT_PRESSED: + onPressed(); + break; + case LV_EVENT_RELEASED: + onReleased(); + break; default: break; } diff --git a/radio/src/gui/colorlcd/libui/window.h b/radio/src/gui/colorlcd/libui/window.h index abafde865..697f8392a 100644 --- a/radio/src/gui/colorlcd/libui/window.h +++ b/radio/src/gui/colorlcd/libui/window.h @@ -146,6 +146,8 @@ class Window virtual void onClicked(); virtual void onCancel(); virtual bool onLongPress(); + virtual void onPressed() {} + virtual void onReleased() {} void invalidate(); @@ -177,7 +179,7 @@ class Window virtual void show(bool visible = true); void hide() { show(false); } - void enable(bool enabled = true); + virtual void enable(bool enabled = true); void disable() { enable(false); } protected: diff --git a/radio/src/gui/colorlcd/mainview/view_main.cpp b/radio/src/gui/colorlcd/mainview/view_main.cpp index b139deb46..9a01f895a 100644 --- a/radio/src/gui/colorlcd/mainview/view_main.cpp +++ b/radio/src/gui/colorlcd/mainview/view_main.cpp @@ -361,6 +361,7 @@ bool ViewMain::onLongPress() if (isAppMode()) { int view = getCurrentMainView(); customScreens[view]->getWidget(0)->setFullscreen(true); + killEvents(KEY_ENTER); } else { enableWidgetSelect(true); } diff --git a/radio/src/gui/colorlcd/mainview/widget.cpp b/radio/src/gui/colorlcd/mainview/widget.cpp index b7b7c3ff4..dd8f57f95 100644 --- a/radio/src/gui/colorlcd/mainview/widget.cpp +++ b/radio/src/gui/colorlcd/mainview/widget.cpp @@ -120,9 +120,8 @@ void Widget::setFullscreen(bool enable) bringToTop(); - if (!lv_obj_get_group(lvobj)) { + if (!lv_obj_get_group(lvobj)) lv_group_add_obj(lv_group_get_default(), lvobj); - } // disable scroll chaining (sliding main view) lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_SCROLL_CHAIN_HOR); @@ -130,7 +129,8 @@ void Widget::setFullscreen(bool enable) } // set group in editing mode (keys LEFT / RIGHT) - lv_group_set_editing(lv_group_get_default(), enable); + if (enableFullScreenRE()) + lv_group_set_editing(lv_group_get_default(), enable); onFullscreen(enable); diff --git a/radio/src/gui/colorlcd/mainview/widget.h b/radio/src/gui/colorlcd/mainview/widget.h index c75a71d01..677389550 100644 --- a/radio/src/gui/colorlcd/mainview/widget.h +++ b/radio/src/gui/colorlcd/mainview/widget.h @@ -67,6 +67,10 @@ class Widget : public ButtonBase // Set/unset fullscreen mode void setFullscreen(bool enable); void closeFullscreen() { closeFS = true; } + bool isFullscreen() const { return fullscreen; } + + // Should rotary encoder events be enabled when full screen + virtual bool enableFullScreenRE() const { return true; } // Called when the widget options have changed virtual void update(); diff --git a/radio/src/gui/colorlcd/standalone_lua.cpp b/radio/src/gui/colorlcd/standalone_lua.cpp index b35457384..3c649ee9f 100644 --- a/radio/src/gui/colorlcd/standalone_lua.cpp +++ b/radio/src/gui/colorlcd/standalone_lua.cpp @@ -237,6 +237,8 @@ void StandaloneLuaWindow::deleteLater(bool detach, bool trash) luaState = prevLuaState; + luaEmptyEventBuffer(); + Window::deleteLater(detach, trash); } @@ -324,7 +326,7 @@ void StandaloneLuaWindow::onCancel() { LuaEventHandler::onCancel(); } void StandaloneLuaWindow::onEvent(event_t evt) { - LuaEventHandler::onEvent(evt); + LuaEventHandler::onLuaEvent(evt); } void StandaloneLuaWindow::popupPaint(BitmapBuffer* dc, coord_t x, coord_t y, coord_t w, coord_t h, diff --git a/radio/src/gui/colorlcd/standalone_lua.h b/radio/src/gui/colorlcd/standalone_lua.h index 398c4961d..a4ac6f32e 100644 --- a/radio/src/gui/colorlcd/standalone_lua.h +++ b/radio/src/gui/colorlcd/standalone_lua.h @@ -53,13 +53,13 @@ public: void clear() override; bool useLvglLayout() const override { return useLvgl; } bool isAppMode() const override { return false; } + bool isWidget() override { return false; } + bool isFullscreen() override { return true; } void luaShowError() override; void showError(bool firstCall, const char* title, const char* msg); - bool isWidget() override { return false; } - static LAYOUT_VAL(POPUP_HEADER_HEIGHT, 30, 30); static LAYOUT_VAL(POPUP_X, 50, 40); static LAYOUT_VAL(POPUP_Y, 70, 110); diff --git a/radio/src/gui/gui_common.cpp b/radio/src/gui/gui_common.cpp index 8d0b5b501..0764c8efc 100644 --- a/radio/src/gui/gui_common.cpp +++ b/radio/src/gui/gui_common.cpp @@ -253,29 +253,6 @@ static bool isSourceTelemCompAvailable(int source) { return isTelemetryFieldComparisonAvailable(qr.quot); } -enum SrcTypes { - SRC_INPUT = 1 << 0, - SRC_LUA = 1 << 1, - SRC_STICK = 1 << 2, - SRC_POT = 1 << 3, - SRC_TILT = 1 << 4, - SRC_SPACEMOUSE = 1 << 5, - SRC_MINMAX = 1 << 6, - SRC_HELI = 1 << 7, - SRC_TRIM = 1 << 8, - SRC_SWITCH = 1 << 9, - SRC_FUNC_SWITCH = 1 << 10, - SRC_LOGICAL_SWITCH = 1 << 11, - SRC_TRAINER = 1 << 12, - SRC_CHANNEL = 1 << 13, - SRC_CHANNEL_ALL = 1 << 14, - SRC_GVAR = 1 << 15, - SRC_TX = 1 << 16, - SRC_TIMER = 1 << 17, - SRC_TELEM = 1 << 18, - SRC_TELEM_COMP = 1 << 19, -}; - struct sourceAvailableCheck { uint16_t first; uint16_t last; @@ -310,6 +287,7 @@ static struct sourceAvailableCheck sourceChecks[] = { { MIXSRC_FIRST_TIMER, MIXSRC_LAST_TIMER, SRC_TIMER, isSourceTimerAvailable }, { MIXSRC_FIRST_TELEM, MIXSRC_LAST_TELEM, SRC_TELEM, isSourceTelemAvailable }, { MIXSRC_FIRST_TELEM, MIXSRC_LAST_TELEM, SRC_TELEM_COMP, isSourceTelemCompAvailable }, + { MIXSRC_NONE, MIXSRC_NONE, SRC_NONE, sourceIsAvailable }, }; bool checkSourceAvailable(int source, uint32_t sourceTypes) @@ -451,6 +429,106 @@ bool isSwitchAvailable(int swtch, SwitchContext context) return true; } +static bool switchIsAvailable(int swtch, bool invert) +{ + return true; +} + +static bool isSwitchSwitchAvailable(int swtch, bool invert) { + // Check normal switch + if (swtch < MAX_SWITCHES * 3) { + div_t swinfo = switchInfo(swtch); + if (swinfo.quot >= switchGetMaxSwitches() + switchGetMaxFctSwitches()) { + return false; + } + + if (!SWITCH_EXISTS(swinfo.quot)) { + return false; + } + + if (!IS_CONFIG_3POS(swinfo.quot)) { + if (swinfo.rem == 1) { + // mid position not available for 2POS switches + return false; + } + } + + return true; + } + + // Multipos switch + int index = (swtch - SWSRC_FIRST_MULTIPOS_SWITCH) / XPOTS_MULTIPOS_COUNT; + return (index < adcGetMaxInputs(ADC_INPUT_FLEX)) ? IS_POT_MULTIPOS(index) : false; +} + +static bool isSwitchTrimAvailable(int swtch, bool invert) { + int index = swtch / 2; + return index < keysGetMaxTrims(); +} + +static bool isSwitchLSAvailable(int swtch, bool invert) { + return isLogicalSwitchAvailable(swtch); +} + +static bool isSwitchFMAvailable(int swtch, bool invert) { + if (swtch == 0) + return true; + FlightModeData * fm = flightModeAddress(swtch); + return (fm->swtch != SWSRC_NONE); +} + +static bool isSwitchTelemAvailable(int swtch, bool invert) { + return isTelemetryFieldAvailable(swtch); +} + +static bool isSwitchOtherAvailable(int swtch, bool invert) { + swtch += SWSRC_ON; + if (invert && (swtch == SWSRC_ON || swtch == SWSRC_ONE)) + return false; + if (swtch == SWSRC_ON || swtch == SWSRC_ONE || swtch == SWSRC_TELEMETRY_STREAMING || + swtch == SWSRC_RADIO_ACTIVITY || swtch == SWSRC_TRAINER_CONNECTED) + return true; +#if defined(DEBUG_LATENCY) + if (swtch == SWSRC_LATENCY_TOGGLE) + return true; +#endif + return false; +} + +struct switchAvailableCheck { + uint16_t first; + uint16_t last; + SwitchTypes type; + bool (*check)(int, bool); +}; + +static struct switchAvailableCheck switchChecks[] = { + { SWSRC_FIRST_SWITCH, SWSRC_LAST_MULTIPOS_SWITCH, SW_SWITCH, isSwitchSwitchAvailable }, + { SWSRC_FIRST_TRIM, SWSRC_LAST_TRIM, SW_TRIM, isSwitchTrimAvailable }, + { SWSRC_FIRST_LOGICAL_SWITCH, SWSRC_LAST_LOGICAL_SWITCH, SW_LOGICAL_SWITCH, isSwitchLSAvailable }, + { SWSRC_FIRST_FLIGHT_MODE, SWSRC_LAST_FLIGHT_MODE, SW_FLIGHT_MODE, isSwitchFMAvailable }, + { SWSRC_FIRST_SENSOR, SWSRC_LAST_SENSOR, SW_TELEM, isSwitchTelemAvailable }, + { SWSRC_ON, SWSRC_COUNT - 1, SW_OTHER, isSwitchOtherAvailable }, + { SWSRC_NONE, SWSRC_NONE, SW_NONE, switchIsAvailable }, +}; + +bool checkSwitchAvailable(int swtch, uint32_t swtchTypes) +{ + bool invert = false; + if (swtch < 0) { + swtch = -swtch; + invert = true; + } + + for (size_t i = 0 ; i < DIM(switchChecks); i += 1) { + if (switchChecks[i].type & swtchTypes && swtch >= switchChecks[i].first && swtch <= switchChecks[i].last) { + return switchChecks[i].check(swtch - switchChecks[i].first, invert); + } + } + + return false; +} + bool isSerialModeAvailable(uint8_t port_nr, int mode) { #if defined(USB_SERIAL) diff --git a/radio/src/gui/gui_common.h b/radio/src/gui/gui_common.h index 0cdb5d5c4..ae7469480 100644 --- a/radio/src/gui/gui_common.h +++ b/radio/src/gui/gui_common.h @@ -57,6 +57,8 @@ enum SwitchContext int getFirstAvailable(int min, int max, IsValueAvailable isValueAvailable); +bool checkSourceAvailable(int source, uint32_t sourceTypes); +bool checkSwitchAvailable(int swtch, uint32_t swtchTypes); bool isInputAvailable(int input); bool isSourceAvailableInInputs(int source); bool isThrottleSourceAvailable(int source); diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index 6278c454b..5e910cd00 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -30,9 +30,9 @@ LuaLvglManager *luaLvglManager = nullptr; -static int luaLvglObj(lua_State *L, std::function create, bool standalone = false) +static int luaLvglObj(lua_State *L, std::function create, bool fullscreenOnly = false) { - if (luaLvglManager && (!standalone || !luaLvglManager->isWidget())) { + if (luaLvglManager && (!fullscreenOnly || luaLvglManager->isFullscreen())) { auto obj = create(); obj->getParams(L, 1); obj->build(L); @@ -44,9 +44,9 @@ static int luaLvglObj(lua_State *L, std::function create, b return 1; } -static int luaLvglObjEx(lua_State *L, std::function create, bool standalone = false) +static int luaLvglObjEx(lua_State *L, std::function create, bool fullscreenOnly = false) { - if (luaLvglManager && (!standalone || !luaLvglManager->isWidget())) { + if (luaLvglManager && (!fullscreenOnly || luaLvglManager->isFullscreen())) { LvglWidgetObjectBase* p = nullptr; LvglWidgetObjectBase* prevParent = nullptr; if (lua_gettop(L) == 2) { @@ -132,6 +132,24 @@ static int luaLvglHide(lua_State *L) return 0; } +static int luaLvglEnable(lua_State *L) +{ + auto p = LvglWidgetObjectBase::checkLvgl(L, 1); + if (p) { + p->enable(); + } + return 0; +} + +static int luaLvglDisable(lua_State *L) +{ + auto p = LvglWidgetObjectBase::checkLvgl(L, 1); + if (p) { + p->disable(); + } + return 0; +} + class LvglWidgetParams { public: @@ -183,11 +201,11 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) obj = new LvglWidgetQRCode(); else if (strcasecmp(p.type, "box") == 0) obj = new LvglWidgetBox(); - else if (strcasecmp(p.type, "setting") == 0) - obj = new LvglWidgetSetting(); - else if (!luaLvglManager->isWidget()) { + else if (luaLvglManager->isFullscreen()) { if (strcasecmp(p.type, "button") == 0) obj = new LvglWidgetTextButton(); + if (strcasecmp(p.type, "momentaryButton") == 0) + obj = new LvglWidgetMomentaryButton(); else if (strcasecmp(p.type, "toggle") == 0) obj = new LvglWidgetToggleSwitch(); else if (strcasecmp(p.type, "textEdit") == 0) @@ -198,6 +216,8 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) obj = new LvglWidgetChoice(); else if (strcasecmp(p.type, "slider") == 0) obj = new LvglWidgetSlider(); + else if (strcasecmp(p.type, "verticalSlider") == 0) + obj = new LvglWidgetVerticalSlider(); else if (strcasecmp(p.type, "page") == 0) obj = new LvglWidgetPage(); else if (strcasecmp(p.type, "font") == 0) @@ -212,6 +232,8 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) obj = new LvglWidgetSwitchPicker(); else if (strcasecmp(p.type, "source") == 0) obj = new LvglWidgetSourcePicker(); + else if (strcasecmp(p.type, "setting") == 0) + obj = new LvglWidgetSetting(); } if (obj) { obj->getParams(L, -1); @@ -269,12 +291,43 @@ static int luaLvglIsAppMode(lua_State *L) return 1; } +static int luaLvglIsFullscreen(lua_State *L) +{ + if (luaLvglManager) { + lua_pushboolean(L, luaLvglManager->isFullscreen()); + } else { + lua_pushboolean(L, false); + } + return 1; +} + +static int luaLvglExitFullscreen(lua_State *L) +{ + if (luaLvglManager) + luaLvglManager->exitFullscreen(); + return 0; +} + +static int luaLvglGetContext(lua_State *L) +{ + if (luaLvglManager && luaLvglManager->getContext() != LUA_REFNIL) { + // Push context tanle onto Lua stack (return object) + lua_rawgeti(L, LUA_REGISTRYINDEX, luaLvglManager->getContext()); + } else { + lua_pushnil(L); + } + return 1; +} + // lvgl functions LROT_BEGIN(lvgllib, NULL, 0) LROT_FUNCENTRY(clear, luaLvglClear) LROT_FUNCENTRY(build, luaLvglBuild) LROT_FUNCENTRY(isAppMode, luaLvglIsAppMode) - // Objects - widgets and standalone + LROT_FUNCENTRY(isFullScreen, luaLvglIsFullscreen) + LROT_FUNCENTRY(exitFullScreen, luaLvglExitFullscreen) + LROT_FUNCENTRY(getContext, luaLvglGetContext) + // Objects - widgets and standalone scripts LROT_FUNCENTRY(label, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetLabel(); }); }) LROT_FUNCENTRY(rectangle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetRectangle(); }); }) LROT_FUNCENTRY(hline, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetHLine(); }); }) @@ -285,13 +338,15 @@ LROT_BEGIN(lvgllib, NULL, 0) LROT_FUNCENTRY(arc, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetArc(); }); }) LROT_FUNCENTRY(image, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetImage(); }); }) LROT_FUNCENTRY(qrcode, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetQRCode(); }); }) - // Objects - standalone scripts only + // Objects - standalone scripts and full screen widgets only LROT_FUNCENTRY(button, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTextButton(); }, true); }) + LROT_FUNCENTRY(momentaryButton, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetMomentaryButton(); }, true); }) LROT_FUNCENTRY(toggle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetToggleSwitch(); }, true); }) LROT_FUNCENTRY(textEdit, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTextEdit(); }, true); }) LROT_FUNCENTRY(numberEdit, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetNumberEdit(); }, true); }) LROT_FUNCENTRY(choice, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetChoice(); }, true); }) LROT_FUNCENTRY(slider, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSlider(); }, true); }) + LROT_FUNCENTRY(verticalSlider, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetVerticalSlider(); }, true); }) LROT_FUNCENTRY(font, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetFontPicker(); }, true); }) LROT_FUNCENTRY(align, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetAlignPicker(); }, true); }) LROT_FUNCENTRY(color, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetColorPicker(); }, true); }) @@ -310,12 +365,37 @@ LROT_BEGIN(lvgllib, NULL, 0) LROT_FUNCENTRY(set, luaLvglSet) LROT_FUNCENTRY(show, luaLvglShow) LROT_FUNCENTRY(hide, luaLvglHide) + LROT_FUNCENTRY(enable, luaLvglEnable) + LROT_FUNCENTRY(disable, luaLvglDisable) LROT_NUMENTRY(FLOW_ROW, LV_FLEX_FLOW_ROW) LROT_NUMENTRY(FLOW_COLUMN, LV_FLEX_FLOW_COLUMN) LROT_NUMENTRY(PAD_TINY, PAD_TINY) LROT_NUMENTRY(PAD_SMALL, PAD_SMALL) LROT_NUMENTRY(PAD_MEDIUM, PAD_MEDIUM) LROT_NUMENTRY(PAD_LARGE, PAD_LARGE) + LROT_NUMENTRY(SRC_ALL, 0xFFFFFFFF) + LROT_NUMENTRY(SRC_INPUT, SRC_INPUT) + LROT_NUMENTRY(SRC_LUA, SRC_LUA) + LROT_NUMENTRY(SRC_STICK, SRC_STICK|SRC_TILT|SRC_SPACEMOUSE) + LROT_NUMENTRY(SRC_POT, SRC_POT) + LROT_NUMENTRY(SRC_OTHER, SRC_MINMAX|SRC_TX|SRC_TIMER) + LROT_NUMENTRY(SRC_HELI, SRC_HELI) + LROT_NUMENTRY(SRC_TRIM, SRC_TRIM) + LROT_NUMENTRY(SRC_SWITCH, SRC_SWITCH|SRC_FUNC_SWITCH) + LROT_NUMENTRY(SRC_LOGICAL_SWITCH, SRC_LOGICAL_SWITCH) + LROT_NUMENTRY(SRC_TRAINER, SRC_TRAINER) + LROT_NUMENTRY(SRC_CHANNEL, SRC_CHANNEL) + LROT_NUMENTRY(SRC_GVAR, SRC_GVAR) + LROT_NUMENTRY(SRC_TELEM, SRC_TELEM) + LROT_NUMENTRY(SRC_CLEAR, SRC_NONE) + LROT_NUMENTRY(SRC_INVERT, SRC_INVERT) + LROT_NUMENTRY(SW_ALL, 0xFFFFFFFF) + LROT_NUMENTRY(SW_SWITCH, SW_SWITCH) + LROT_NUMENTRY(SW_TRIM, SW_TRIM) + LROT_NUMENTRY(SW_LOGICAL_SWITCH, SW_LOGICAL_SWITCH) + LROT_NUMENTRY(SW_TELEM, SW_TELEM) + LROT_NUMENTRY(SW_OTHER, SW_OTHER) + LROT_NUMENTRY(SW_CLEAR, SW_NONE) LROT_END(lvgllib, NULL, 0) // Metatable for simple objects (line, arc, label) @@ -334,7 +414,7 @@ LROT_BEGIN(lvgl_mt, NULL, LROT_MASK_GC_INDEX) LROT_TABENTRY(__index, lvgl_mt) LROT_FUNCENTRY(clear, luaLvglClear) LROT_FUNCENTRY(build, luaLvglBuild) - // Objects - widgets and standalone + // Objects - widgets and standalone scripts LROT_FUNCENTRY(label, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetLabel(); }); }) LROT_FUNCENTRY(rectangle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetRectangle(); }); }) LROT_FUNCENTRY(hline, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetHLine(); }); }) @@ -345,13 +425,15 @@ LROT_BEGIN(lvgl_mt, NULL, LROT_MASK_GC_INDEX) LROT_FUNCENTRY(arc, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetArc(); }); }) LROT_FUNCENTRY(image, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetImage(); }); }) LROT_FUNCENTRY(qrcode, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetQRCode(); }); }) - // Objects - standalone scripts only + // Objects - standalone scripts and full screen widgets only LROT_FUNCENTRY(button, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTextButton(); }, true); }) + LROT_FUNCENTRY(momentaryButton, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetMomentaryButton(); }, true); }) LROT_FUNCENTRY(toggle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetToggleSwitch(); }, true); }) LROT_FUNCENTRY(textEdit, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTextEdit(); }, true); }) LROT_FUNCENTRY(numberEdit, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetNumberEdit(); }, true); }) LROT_FUNCENTRY(choice, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetChoice(); }, true); }) LROT_FUNCENTRY(slider, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSlider(); }, true); }) + LROT_FUNCENTRY(verticalSlider, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetVerticalSlider(); }, true); }) LROT_FUNCENTRY(font, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetFontPicker(); }, true); }) LROT_FUNCENTRY(align, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetAlignPicker(); }, true); }) LROT_FUNCENTRY(color, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetColorPicker(); }, true); }) @@ -362,6 +444,8 @@ LROT_BEGIN(lvgl_mt, NULL, LROT_MASK_GC_INDEX) LROT_FUNCENTRY(set, luaLvglSet) LROT_FUNCENTRY(show, luaLvglShow) LROT_FUNCENTRY(hide, luaLvglHide) + LROT_FUNCENTRY(enable, luaLvglEnable) + LROT_FUNCENTRY(disable, luaLvglDisable) LROT_END(lvgl_mt, NULL, LROT_MASK_GC_INDEX) extern "C" { diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 360ad09c2..161801362 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -2199,7 +2199,7 @@ static int luaGetUsage(lua_State * L) lua_pushinteger(L, instructionsPercent); } #else - lua_pushinteger(L, instructionsPercent); + lua_pushinteger(L, 100 * maxLuaDuration / LUA_TASK_PERIOD_TICKS); #endif return 1; } diff --git a/radio/src/lua/interface.cpp b/radio/src/lua/interface.cpp index 9d786f7c7..e9e0be0a8 100644 --- a/radio/src/lua/interface.cpp +++ b/radio/src/lua/interface.cpp @@ -55,7 +55,6 @@ extern "C" { #define LUA_WARNING_INFO_LEN 64 #endif #define PERMANENT_SCRIPTS_MAX_INSTRUCTIONS 100 -#define LUA_TASK_PERIOD_TICKS 5 // 50 ms // #if defined(HARDWARE_TOUCH) // #include "touch.h" @@ -74,7 +73,6 @@ ScriptInputsOutputs scriptInputsOutputs[MAX_SCRIPTS]; uint16_t maxLuaInterval = 0; uint16_t maxLuaDuration = 0; -uint8_t instructionsPercent = 0; tmr10ms_t luaCycleStart; char lua_warning_info[LUA_WARNING_INFO_LEN+1]; uint8_t errorState; @@ -1283,9 +1281,6 @@ bool luaTask(bool allowLcdUsage) // For preemption luaCycleStart = get_tmr10ms(); - // Trying to replace CPU usage measure - instructionsPercent = 100 * maxLuaDuration / LUA_TASK_PERIOD_TICKS; - switch (luaState) { case INTERPRETER_RELOAD_PERMANENT_SCRIPTS: init = true; diff --git a/radio/src/lua/lua_api.h b/radio/src/lua/lua_api.h index 1cc628909..4d408f746 100644 --- a/radio/src/lua/lua_api.h +++ b/radio/src/lua/lua_api.h @@ -208,6 +208,7 @@ extern struct our_longjmp * global_lj; extern uint16_t maxLuaInterval; extern uint16_t maxLuaDuration; extern uint8_t instructionsPercent; +#define LUA_TASK_PERIOD_TICKS 5 // 50 ms struct LuaField { uint16_t id; diff --git a/radio/src/lua/lua_lvgl_widget.cpp b/radio/src/lua/lua_lvgl_widget.cpp index c745569a5..4fadaff7f 100644 --- a/radio/src/lua/lua_lvgl_widget.cpp +++ b/radio/src/lua/lua_lvgl_widget.cpp @@ -126,9 +126,12 @@ void LvglWidgetObjectBase::pcallSimpleFunc(lua_State *L, int funcRef) if (funcRef != LUA_REFNIL) { PROTECT_LUA() { + auto save = luaLvglManager; + luaLvglManager = lvglManager; if (!pcallFunc(L, funcRef, 0)) { lvglManager->luaShowError(); } + luaLvglManager = save; } UNPROTECT_LUA(); } @@ -137,39 +140,54 @@ void LvglWidgetObjectBase::pcallSimpleFunc(lua_State *L, int funcRef) bool LvglWidgetObjectBase::pcallUpdateBool(lua_State *L, int getFuncRef, std::function update) { + bool res = true; if (getFuncRef != LUA_REFNIL) { + auto save = luaLvglManager; + luaLvglManager = lvglManager; int t = lua_gettop(L); if (pcallFunc(L, getFuncRef, 1)) { - bool val = lua_toboolean(L, -1); + bool val = false; + if (lua_isboolean(L, -1)) + val = lua_toboolean(L, -1); + else + val = luaL_optinteger(L, -1, 0) != 0; update(val); lua_settop(L, t); } else { - return false; + res = false; } + lvglManager = save; } - return true; + return res; } bool LvglWidgetObjectBase::pcallUpdate1Int(lua_State *L, int getFuncRef, std::function update) { + bool res = true; if (getFuncRef != LUA_REFNIL) { + auto save = luaLvglManager; + luaLvglManager = lvglManager; 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; + res = false; } + lvglManager = save; } - return true; + return res; } bool LvglWidgetObjectBase::pcallUpdate2Int(lua_State *L, int getFuncRef, std::function update) { + bool res = true; if (getFuncRef != LUA_REFNIL) { + auto save = luaLvglManager; + luaLvglManager = lvglManager; int t = lua_gettop(L); if (pcallFunc(L, getFuncRef, 2)) { int v1 = luaL_checkunsigned(L, -2); @@ -177,21 +195,27 @@ bool LvglWidgetObjectBase::pcallUpdate2Int(lua_State *L, int getFuncRef, update(v1, v2); lua_settop(L, t); } else { - return false; + res = false; } + lvglManager = save; } - return true; + return res; } int LvglWidgetObjectBase::pcallGetIntVal(lua_State *L, int getFuncRef) { int val = 0; if (getFuncRef != LUA_REFNIL) { + auto save = luaLvglManager; + luaLvglManager = lvglManager; int t = lua_gettop(L); PROTECT_LUA() { if (pcallFunc(L, getFuncRef, 1)) { - val = luaL_checkinteger(L, -1); + if (lua_isboolean(L, -1)) + val = lua_toboolean(L, -1); + else + val = luaL_checkinteger(L, -1); } else { lvglManager->luaShowError(); } @@ -202,6 +226,36 @@ int LvglWidgetObjectBase::pcallGetIntVal(lua_State *L, int getFuncRef) } UNPROTECT_LUA(); lua_settop(L, t); + lvglManager = save; + } + return val; +} + +int LvglWidgetObjectBase::pcallGetOptIntVal(lua_State *L, int getFuncRef, int defVal) +{ + int val = 0; + if (getFuncRef != LUA_REFNIL) { + auto save = luaLvglManager; + luaLvglManager = lvglManager; + int t = lua_gettop(L); + PROTECT_LUA() + { + if (pcallFunc(L, getFuncRef, 1)) { + if (lua_isboolean(L, -1)) + val = lua_toboolean(L, -1); + else + val = luaL_optinteger(L, -1, defVal); + } else { + lvglManager->luaShowError(); + } + } + else + { + lvglManager->luaShowError(); + } + UNPROTECT_LUA(); + lua_settop(L, t); + lvglManager = save; } return val; } @@ -209,6 +263,8 @@ int LvglWidgetObjectBase::pcallGetIntVal(lua_State *L, int getFuncRef) void LvglWidgetObjectBase::pcallSetIntVal(lua_State *L, int setFuncRef, int val) { if (setFuncRef != LUA_REFNIL) { + auto save = luaLvglManager; + luaLvglManager = lvglManager; int t = lua_gettop(L); PROTECT_LUA() { @@ -222,6 +278,7 @@ void LvglWidgetObjectBase::pcallSetIntVal(lua_State *L, int setFuncRef, int val) } UNPROTECT_LUA(); lua_settop(L, t); + lvglManager = save; } } @@ -461,7 +518,7 @@ void LvglWidgetLabel::setFont(LcdFlags font) { if (lvobj) { this->font = font; - lv_obj_set_style_text_font(lvobj, getFont(font), LV_PART_MAIN); + etx_font(lvobj, FONT_INDEX(font)); } } @@ -940,6 +997,8 @@ void LvglWidgetObject::parseParam(lua_State *L, const char *key) flexFlow = luaL_checkinteger(L, -1); } else if (!strcmp(key, "flexPad")) { flexPad = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "active")) { + getActiveFunction = luaL_ref(L, LUA_REGISTRYINDEX); } else { LvglWidgetObjectBase::parseParam(L, key); } @@ -971,12 +1030,32 @@ bool LvglWidgetObject::setFlex() } } +bool LvglWidgetObject::callRefs(lua_State *L) +{ + if (!pcallUpdateBool(L, getActiveFunction, + [=](bool active) { if (active) enable(); else disable(); })) + return false; + return LvglWidgetObjectBase::callRefs(L); +} + +void LvglWidgetObject::clearRefs(lua_State *L) +{ + clearRef(L, getActiveFunction); + LvglWidgetObjectBase::clearRefs(L); +} + //----------------------------------------------------------------------------- void LvglWidgetBox::build(lua_State* L) { window = new Window(lvglManager->getCurrentParent(), {x, y, w, h}, lv_obj_create); + lv_obj_add_flag(window->getLvObj(), LV_OBJ_FLAG_EVENT_BUBBLE); + if (luaLvglManager->isWidget()) { + lv_obj_clear_flag(window->getLvObj(), LV_OBJ_FLAG_CLICKABLE); + } else { + 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); } @@ -1266,10 +1345,16 @@ void LvglWidgetQRCode::build(lua_State *L) //----------------------------------------------------------------------------- -void LvglWidgetTextButton::parseParam(lua_State *L, const char *key) +void LvglWidgetTextButtonBase::parseParam(lua_State *L, const char *key) { if (!strcmp(key, "text")) { txt = luaL_checkstring(L, -1); + } else if (!strcmp(key, "font")) { + font = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "cornerRadius")) { + rounded = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "textColor")) { + textColor = luaL_checkunsigned(L, -1); } else if (!strcmp(key, "press")) { pressFunction = luaL_ref(L, LUA_REGISTRYINDEX); } else { @@ -1277,13 +1362,13 @@ void LvglWidgetTextButton::parseParam(lua_State *L, const char *key) } } -void LvglWidgetTextButton::clearRefs(lua_State *L) +void LvglWidgetTextButtonBase::clearRefs(lua_State *L) { clearRef(L, pressFunction); LvglWidgetObject::clearRefs(L); } -void LvglWidgetTextButton::setText(const char *s) +void LvglWidgetTextButtonBase::setText(const char *s) { uint32_t h = hash(s, strlen(s)); if (h != textHash) { @@ -1293,14 +1378,126 @@ void LvglWidgetTextButton::setText(const char *s) } } +void LvglWidgetTextButtonBase::setSize(coord_t w, coord_t h) +{ + if (w == LV_SIZE_CONTENT || w == 0) w = lv_obj_get_width(window->getLvObj()); + if (h == LV_SIZE_CONTENT || h == 0) h = lv_obj_get_height(window->getLvObj()); + LvglWidgetObject::setSize(w, h); +} + +void LvglWidgetTextButtonBase::setColor(LcdFlags newColor) +{ + color = newColor; + if (color != currentColor) { + currentColor = color; + etx_bg_color_from_flags(window->getLvObj(), color); + } + if (textColor != currentTextColor) { + currentTextColor = textColor; + etx_txt_color_from_flags(window->getLvObj(), textColor); + } +} + +void LvglWidgetTextButtonBase::setRounded() +{ + if (rounded >= 0) { + lv_obj_remove_style(window->getLvObj(), (lv_style_t*)&styles->rounded, LV_PART_MAIN); + if (rounded > 0) + lv_obj_set_style_radius(window->getLvObj(), rounded, LV_PART_MAIN); + } +} + +//----------------------------------------------------------------------------- + +void LvglWidgetTextButton::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "checked")) { + checked = lua_toboolean(L, -1); + } else if (!strcmp(key, "longpress")) { + longPressFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetTextButtonBase::parseParam(L, key); + } +} + +void LvglWidgetTextButton::clearRefs(lua_State *L) +{ + clearRef(L, longPressFunction); + LvglWidgetTextButtonBase::clearRefs(L); +} + +void LvglWidgetTextButton::setFont(LcdFlags font) +{ + if (window) { + this->font = font; + ((TextButton*)window)->setFont(FONT_INDEX(font)); + } +} + +void LvglWidgetTextButton::setChecked(bool checked) +{ + this->checked = checked; + ((TextButton*)window)->check(checked); +} + void LvglWidgetTextButton::build(lua_State *L) { if (h == LV_SIZE_CONTENT) h = 0; - window = + auto btn = new TextButton(lvglManager->getCurrentParent(), {x, y, w, h}, txt, [=]() { - pcallSimpleFunc(L, pressFunction); - return 0; + return pcallGetOptIntVal(L, pressFunction, 0); }); + if (longPressFunction != LUA_REFNIL) { + btn->setLongPressHandler([=]() { + return pcallGetOptIntVal(L, longPressFunction, 0); + }); + } + window = btn; + setFont(font); + setChecked(checked); + setColor(color); + setRounded(); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetMomentaryButton::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "release")) { + releaseFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetTextButtonBase::parseParam(L, key); + } +} + +void LvglWidgetMomentaryButton::setFont(LcdFlags font) +{ + if (window) { + this->font = font; + ((MomentaryButton*)window)->setFont(FONT_INDEX(font)); + } +} + +void LvglWidgetMomentaryButton::clearRefs(lua_State *L) +{ + clearRef(L, releaseFunction); + LvglWidgetTextButtonBase::clearRefs(L); +} + +void LvglWidgetMomentaryButton::build(lua_State *L) +{ + if (h == LV_SIZE_CONTENT) h = 0; + window = + new MomentaryButton(lvglManager->getCurrentParent(), {x, y, w, h}, txt, + [=]() { + pcallSimpleFunc(L, pressFunction); + }, + [=]() { + pcallSimpleFunc(L, releaseFunction); + }); + setFont(font); + setColor(color); + setRounded(); } //----------------------------------------------------------------------------- @@ -1437,7 +1634,7 @@ void LvglWidgetNumberEdit::build(lua_State *L) //----------------------------------------------------------------------------- -void LvglWidgetSlider::parseParam(lua_State *L, const char *key) +void LvglWidgetSliderBase::parseParam(lua_State *L, const char *key) { if (!strcmp(key, "get")) { getValueFunction = luaL_ref(L, LUA_REGISTRYINDEX); @@ -1452,20 +1649,44 @@ void LvglWidgetSlider::parseParam(lua_State *L, const char *key) } } -void LvglWidgetSlider::clearRefs(lua_State *L) +void LvglWidgetSliderBase::setColor(LcdFlags newColor) +{ + color = newColor; + if (color != currentColor) { + currentColor = color; + ((SliderBase*)window)->setColor(color); + } +} + +void LvglWidgetSliderBase::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); }); + [=](int val) { pcallSetIntVal(L, setValueFunction, val); }); window->setPos(x, y); + setColor(color); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetVerticalSlider::build(lua_State *L) +{ + window = new VerticalSlider( + lvglManager->getCurrentParent(), h, vmin, vmax, + [=]() { return pcallGetIntVal(L, getValueFunction); }, + [=](int val) { pcallSetIntVal(L, setValueFunction, val); }); + window->setPos(x, y); + setColor(color); } //----------------------------------------------------------------------------- @@ -1514,7 +1735,8 @@ class WidgetPage : public NavWindow, public LuaEventHandler void onEvent(event_t evt) override { - LuaEventHandler::onEvent(evt); + LuaEventHandler::onLuaEvent(evt); + parent->onEvent(evt); } protected: @@ -1733,6 +1955,7 @@ void LvglWidgetAlignPicker::build(lua_State *L) void LvglWidgetColorPicker::build(lua_State *L) { + if (w == LV_SIZE_CONTENT) w = 0; if (h == LV_SIZE_CONTENT) h = 0; window = new ColorPicker( lvglManager->getCurrentParent(), {x, y, w, h}, @@ -1759,10 +1982,8 @@ void LvglWidgetTimerPicker::build(lua_State *L) void LvglWidgetSwitchPicker::parseParam(lua_State *L, const char *key) { - if (!strcmp(key, "min")) { - vmin = luaL_checkinteger(L, -1); - } else if (!strcmp(key, "max")) { - vmax = luaL_checkinteger(L, -1); + if (!strcmp(key, "filter")) { + filter = luaL_checkunsigned(L, -1); } else { LvglWidgetPicker::parseParam(L, key); } @@ -1771,24 +1992,41 @@ void LvglWidgetSwitchPicker::parseParam(lua_State *L, const char *key) void LvglWidgetSwitchPicker::build(lua_State *L) { if (h == LV_SIZE_CONTENT) h = 0; - window = new SwitchChoice( + auto c = new SwitchChoice( lvglManager->getCurrentParent(), {x, y, w, h}, - vmin, vmax, + SWSRC_FIRST, SWSRC_LAST, [=]() { return pcallGetIntVal(L, getFunction); }, [=](uint32_t val) { pcallSetIntVal(L, setFunction, val); }); + c->setAvailableHandler([=](int value) { + return checkSwitchAvailable(value, filter); + }); + window = c; } //----------------------------------------------------------------------------- +void LvglWidgetSourcePicker::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "filter")) { + filter = luaL_checkunsigned(L, -1); + } else { + LvglWidgetPicker::parseParam(L, key); + } +} + void LvglWidgetSourcePicker::build(lua_State *L) { if (h == LV_SIZE_CONTENT) h = 0; - window = new SourceChoice( + auto c = new SourceChoice( lvglManager->getCurrentParent(), {x, y, w, h}, 0, MIXSRC_LAST_TELEM, [=]() { return pcallGetIntVal(L, getFunction); }, [=](uint32_t val) { pcallSetIntVal(L, setFunction, val); }, - true); + filter & SRC_INVERT); + c->setAvailableHandler([=](int value) { + return checkSourceAvailable(value, filter); + }); + window = c; } //----------------------------------------------------------------------------- diff --git a/radio/src/lua/lua_lvgl_widget.h b/radio/src/lua/lua_lvgl_widget.h index 7790ad9f4..fbed7f93a 100644 --- a/radio/src/lua/lua_lvgl_widget.h +++ b/radio/src/lua/lua_lvgl_widget.h @@ -45,6 +45,8 @@ class LvglWidgetObjectBase virtual void show() = 0; virtual void hide() = 0; + virtual void enable() {}; + virtual void disable() {}; virtual void setColor(LcdFlags newColor) {} virtual void setOpacity(uint8_t newOpa) {} @@ -91,6 +93,7 @@ class LvglWidgetObjectBase bool pcallUpdate2Int(lua_State *L, int getFuncRef, std::function update); int pcallGetIntVal(lua_State *L, int getFuncRef); + int pcallGetOptIntVal(lua_State *L, int getFuncRef, int defVal); void pcallSetIntVal(lua_State *L, int setFuncRef, int val); }; @@ -258,16 +261,22 @@ class LvglWidgetObject : public LvglWidgetObjectBase void show() override { window->show(); } void hide() override { window->hide(); } + void enable() override { window->enable(); } + void disable() override { window->disable(); } void setPos(coord_t x, coord_t y) override; void setSize(coord_t w, coord_t h) override; + bool callRefs(lua_State *L) override; + void clearRefs(lua_State *L) override; + Window *getWindow() const override { return window; } protected: Window *window = nullptr; int8_t flexFlow = -1; int8_t flexPad = PAD_TINY; + int getActiveFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; @@ -438,20 +447,29 @@ class LvglWidgetQRCode : public LvglWidgetObject //----------------------------------------------------------------------------- -class LvglWidgetTextButton : public LvglWidgetObject +class LvglWidgetTextButtonBase : public LvglWidgetObject { public: - LvglWidgetTextButton() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} + LvglWidgetTextButtonBase() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) + { + color = -1; // ignore unless overridden + } void setText(const char *s); + void setSize(coord_t w, coord_t h) override; + void setColor(LcdFlags newColor) override; + void setRounded(); - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: uint32_t textHash = -1; const char *txt = ""; + LcdFlags font = FONT(STD); + coord_t rounded = -1; + LcdFlags textColor = -1; + LcdFlags currentTextColor = -1; int pressFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; @@ -459,6 +477,49 @@ class LvglWidgetTextButton : public LvglWidgetObject //----------------------------------------------------------------------------- +class LvglWidgetTextButton : public LvglWidgetTextButtonBase +{ + public: + LvglWidgetTextButton() : LvglWidgetTextButtonBase() {} + + void setFont(LcdFlags font); + void setChecked(bool check); + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + bool checked = false; + int longPressFunction = LUA_REFNIL; + + void parseParam(lua_State *L, const char *key) override; + void refresh() override + { + setChecked(checked); + LvglWidgetObject::refresh(); + } +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetMomentaryButton : public LvglWidgetTextButtonBase +{ + public: + LvglWidgetMomentaryButton() : LvglWidgetTextButtonBase() {} + + void setFont(LcdFlags font); + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + int releaseFunction = LUA_REFNIL; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + class LvglWidgetToggleSwitch : public LvglWidgetObject { public: @@ -516,12 +577,16 @@ class LvglWidgetNumberEdit : public LvglWidgetObject //----------------------------------------------------------------------------- -class LvglWidgetSlider : public LvglWidgetObject +class LvglWidgetSliderBase : public LvglWidgetObject { public: - LvglWidgetSlider() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} + LvglWidgetSliderBase() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) + { + color = -1; + } + + void setColor(LcdFlags newColor) override; - void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: @@ -535,6 +600,26 @@ class LvglWidgetSlider : public LvglWidgetObject //----------------------------------------------------------------------------- +class LvglWidgetSlider : public LvglWidgetSliderBase +{ + public: + LvglWidgetSlider() : LvglWidgetSliderBase() {} + + void build(lua_State *L) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetVerticalSlider : public LvglWidgetSliderBase +{ + public: + LvglWidgetVerticalSlider() : LvglWidgetSliderBase() {} + + void build(lua_State *L) override; +}; + +//----------------------------------------------------------------------------- + class LvglWidgetPage : public LvglWidgetObject { public: @@ -690,8 +775,7 @@ class LvglWidgetSwitchPicker : public LvglWidgetPicker void build(lua_State *L) override; protected: - int16_t vmin = SWSRC_FIRST; - int16_t vmax = SWSRC_LAST; + uint32_t filter = 0xFFFFFFFF; void parseParam(lua_State *L, const char *key) override; }; @@ -704,6 +788,11 @@ class LvglWidgetSourcePicker : public LvglWidgetPicker LvglWidgetSourcePicker() : LvglWidgetPicker() {} void build(lua_State *L) override; + + protected: + uint32_t filter = 0xFFFFFFFF; + + 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 44ad9d90b..790560c1d 100644 --- a/radio/src/lua/lua_widget.cpp +++ b/radio/src/lua/lua_widget.cpp @@ -171,7 +171,7 @@ void LuaEventHandler::onCancel() { luaPushEvent(EVT_KEY_BREAK(KEY_EXIT)); } -void LuaEventHandler::onEvent(event_t event) +void LuaEventHandler::onLuaEvent(event_t event) { luaPushEvent(event); } @@ -223,11 +223,13 @@ void LuaWidget::redraw_cb(lv_event_t* e) LuaWidget::LuaWidget(const WidgetFactory* factory, Window* parent, const rect_t& rect, WidgetPersistentData* persistentData, - int luaWidgetDataRef, int zoneRectDataRef, int optionsDataRef) : + int luaScriptContextRef, int zoneRectDataRef, int optionsDataRef) : Widget(factory, parent, rect, persistentData), - zoneRectDataRef(zoneRectDataRef), optionsDataRef(optionsDataRef), luaWidgetDataRef(luaWidgetDataRef), + zoneRectDataRef(zoneRectDataRef), optionsDataRef(optionsDataRef), errorMessage(nullptr) { + this->luaScriptContextRef = luaScriptContextRef; + if (useLvglLayout()) { update(); } else { @@ -238,7 +240,7 @@ LuaWidget::LuaWidget(const WidgetFactory* factory, Window* parent, LuaWidget::~LuaWidget() { - luaL_unref(lsWidgets, LUA_REGISTRYINDEX, luaWidgetDataRef); + luaL_unref(lsWidgets, LUA_REGISTRYINDEX, luaScriptContextRef); luaL_unref(lsWidgets, LUA_REGISTRYINDEX, zoneRectDataRef); free(errorMessage); } @@ -318,7 +320,7 @@ void LuaWidget::update() luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaFactory()->updateFunction); - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaWidgetDataRef); + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaScriptContextRef); // Get options table and update values lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, optionsDataRef); @@ -472,7 +474,7 @@ void LuaWidget::refresh(BitmapBuffer* dc) luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaFactory()->refreshFunction); - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaWidgetDataRef); + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaScriptContextRef); // Pass key event to fullscreen Lua widget LuaEventData evt; @@ -518,7 +520,7 @@ void LuaWidget::background() if (luaFactory()->backgroundFunction) { luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaFactory()->backgroundFunction); - lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaWidgetDataRef); + lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaScriptContextRef); runningFS = this; if (lua_pcall(lsWidgets, 1, 0, 0) != 0) { setErrorMessage("background()"); @@ -530,7 +532,7 @@ void LuaWidget::background() void LuaWidget::onEvent(event_t event) { if (fullscreen) { - LuaEventHandler::onEvent(event); + LuaEventHandler::onLuaEvent(event); } Widget::onEvent(event); } diff --git a/radio/src/lua/lua_widget.h b/radio/src/lua/lua_widget.h index 11f649ba6..c450a6b34 100644 --- a/radio/src/lua/lua_widget.h +++ b/radio/src/lua/lua_widget.h @@ -48,24 +48,34 @@ class LuaLvglManager void setTempParent(LvglWidgetObjectBase *p) { tempParent = p; } LvglWidgetObjectBase* getTempParent() const { return tempParent; } + int getContext() const { return luaScriptContextRef; } + virtual Window* getCurrentParent() const = 0; virtual void clear() = 0; virtual bool useLvglLayout() const = 0; virtual bool isAppMode() const = 0; + virtual bool isWidget() = 0; + virtual bool isFullscreen() = 0; + virtual void exitFullscreen() {} virtual void luaShowError() = 0; - virtual bool isWidget() = 0; - uint8_t refreshInstructionsPercent; protected: + int luaScriptContextRef = 0; std::vector lvglObjectRefs; LvglWidgetObjectBase* tempParent = nullptr; }; class LuaEventHandler { +public: + LuaEventHandler() = default; + void setupHandler(Window* w); + void removeHandler(Window* w); + +protected: #if defined(HARDWARE_TOUCH) // "tap" handling static uint32_t downTime; @@ -79,15 +89,9 @@ class LuaEventHandler #endif static void event_cb(lv_event_t* e); -protected: void onClicked(); void onCancel(); - void onEvent(event_t event); - -public: - LuaEventHandler() = default; - void setupHandler(Window* w); - void removeHandler(Window* w); + void onLuaEvent(event_t event); }; class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager @@ -96,7 +100,7 @@ class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager public: LuaWidget(const WidgetFactory* factory, Window* parent, const rect_t& rect, - WidgetPersistentData* persistentData, int luaWidgetDataRef, int zoneRectDataRef, + WidgetPersistentData* persistentData, int luaScriptContextRef, int zoneRectDataRef, int optionsDataRef); ~LuaWidget() override; @@ -119,19 +123,22 @@ class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager bool useLvglLayout() const override; bool isAppMode() const override; + bool isWidget() override { return !inSettings; } + bool isFullscreen() override { return Widget::isFullscreen(); } + void exitFullscreen() override { closeFullscreen(); } + + // Should rotary encoder events be enabled when full screen + bool enableFullScreenRE() const override { return !useLvglLayout(); } void luaShowError() override {} void pushOptionsTable(); - bool isWidget() override { return !inSettings; } - protected: bool inSettings = false; lv_obj_t* errorLabel = nullptr; int zoneRectDataRef; int optionsDataRef; - int luaWidgetDataRef = 0; char* errorMessage; bool refreshed = false; diff --git a/radio/src/lua/lua_widget_factory.cpp b/radio/src/lua/lua_widget_factory.cpp index 4b41effed..20601d01e 100644 --- a/radio/src/lua/lua_widget_factory.cpp +++ b/radio/src/lua/lua_widget_factory.cpp @@ -109,7 +109,6 @@ Widget* LuaWidgetFactory::create(Window* parent, const rect_t& rect, return lw; } - void LuaWidgetFactory::translateOptions(ZoneOption * options) { if (lsWidgets == 0) return; diff --git a/radio/src/lua/widgets.cpp b/radio/src/lua/widgets.cpp index cd8857102..c7608ff50 100644 --- a/radio/src/lua/widgets.cpp +++ b/radio/src/lua/widgets.cpp @@ -38,6 +38,7 @@ #define LUA_WARNING_INFO_LEN 64 lua_State * lsWidgets = NULL; +uint8_t instructionsPercent = 0; extern int custom_lua_atpanic(lua_State *L);