diff --git a/radio/src/gui/colorlcd/controls/color_picker.cpp b/radio/src/gui/colorlcd/controls/color_picker.cpp index d57d796f4a4..4a679f561a7 100644 --- a/radio/src/gui/colorlcd/controls/color_picker.cpp +++ b/radio/src/gui/colorlcd/controls/color_picker.cpp @@ -61,9 +61,9 @@ class ColorEditorPopup : public BaseDialog } public: - ColorEditorPopup(Window* parent, uint32_t color, + ColorEditorPopup(uint32_t color, std::function _setValue) : - BaseDialog(parent, STR_COLOR_PICKER, false, COLOR_EDIT_WIDTH, + BaseDialog(STR_COLOR_PICKER, false, COLOR_EDIT_WIDTH, LV_SIZE_CONTENT) { FlexGridLayout grid(col_dsc, row_dsc); @@ -170,7 +170,7 @@ ColorPicker::ColorPicker(Window* parent, const rect_t& rect, void ColorPicker::onClicked() { - new ColorEditorPopup(this, getColor(), [=](uint32_t c) { setColor(c); }); + new ColorEditorPopup(getColor(), [=](uint32_t c) { setColor(c); }); } void ColorPicker::setColor(uint32_t c) diff --git a/radio/src/gui/colorlcd/controls/sourcechoice.cpp b/radio/src/gui/colorlcd/controls/sourcechoice.cpp index 82d34fcc989..0ac19cc9615 100644 --- a/radio/src/gui/colorlcd/controls/sourcechoice.cpp +++ b/radio/src/gui/colorlcd/controls/sourcechoice.cpp @@ -186,7 +186,7 @@ void SourceChoice::openMenu() inverted = getIntValue() < 0; inMenu = true; - auto menu = new Menu(this); + auto menu = new Menu(); if (menuTitle) menu->setTitle(menuTitle); auto tb = new SourceChoiceMenuToolbar(this, menu); diff --git a/radio/src/gui/colorlcd/controls/switchchoice.cpp b/radio/src/gui/colorlcd/controls/switchchoice.cpp index 8a1b0b6d37c..c7c305134c6 100644 --- a/radio/src/gui/colorlcd/controls/switchchoice.cpp +++ b/radio/src/gui/colorlcd/controls/switchchoice.cpp @@ -131,7 +131,7 @@ void SwitchChoice::openMenu() { setEditMode(true); // this needs to be done first before menu is created. - auto menu = new Menu(this); + auto menu = new Menu(); if (menuTitle) menu->setTitle(menuTitle); inverted = _getValue() < 0; diff --git a/radio/src/gui/colorlcd/libui/choice.cpp b/radio/src/gui/colorlcd/libui/choice.cpp index 166a94f2aea..431c3e6ff1e 100644 --- a/radio/src/gui/colorlcd/libui/choice.cpp +++ b/radio/src/gui/colorlcd/libui/choice.cpp @@ -215,7 +215,7 @@ void Choice::openMenu() { setEditMode(true); // this needs to be done first before menu is created. - auto menu = new Menu(this); + auto menu = new Menu(); if (menuTitle) menu->setTitle(menuTitle); fillMenu(menu); diff --git a/radio/src/gui/colorlcd/libui/dialog.cpp b/radio/src/gui/colorlcd/libui/dialog.cpp index b8123bd62ce..c542335e867 100644 --- a/radio/src/gui/colorlcd/libui/dialog.cpp +++ b/radio/src/gui/colorlcd/libui/dialog.cpp @@ -28,21 +28,22 @@ class BaseDialogForm : public Window { public: - BaseDialogForm(Window* parent, lv_coord_t width) : Window(parent, rect_t{}) + BaseDialogForm(Window* parent, lv_coord_t width, bool flexLayout) : Window(parent, rect_t{}) { etx_scrollbar(lvobj); - padAll(PAD_MEDIUM); - setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_MEDIUM, width, LV_SIZE_CONTENT); + padAll(PAD_TINY); + if (flexLayout) + setFlexLayout(LV_FLEX_FLOW_COLUMN, PAD_ZERO, width, LV_SIZE_CONTENT); } protected: void onClicked() override { Keyboard::hide(false); } }; -BaseDialog::BaseDialog(Window* parent, const char* title, +BaseDialog::BaseDialog(const char* title, bool closeIfClickedOutside, lv_coord_t width, - lv_coord_t maxHeight) : - ModalWindow(parent, closeIfClickedOutside) + lv_coord_t maxHeight, bool flexLayout) : + ModalWindow(closeIfClickedOutside) { auto content = new Window(this, rect_t{}); content->setWindowFlag(OPAQUE); @@ -56,7 +57,7 @@ BaseDialog::BaseDialog(Window* parent, const char* title, header->padAll(PAD_SMALL); header->show(title != nullptr); - form = new BaseDialogForm(content, width); + form = new BaseDialogForm(content, width, flexLayout); if (maxHeight != LV_SIZE_CONTENT) lv_obj_set_style_max_height(form->getLvObj(), maxHeight - EdgeTxStyles::UI_ELEMENT_HEIGHT, LV_PART_MAIN); } @@ -68,9 +69,9 @@ void BaseDialog::setTitle(const char* title) //----------------------------------------------------------------------------- -ProgressDialog::ProgressDialog(Window* parent, const char* title, +ProgressDialog::ProgressDialog(const char* title, std::function onClose) : - BaseDialog(parent, title, false), onClose(std::move(onClose)) + BaseDialog(title, false), onClose(std::move(onClose)) { progress = new Progress(form, rect_t{0, 0, LV_PCT(100), 32}); updateProgress(0); @@ -96,10 +97,10 @@ void ProgressDialog::closeDialog() //----------------------------------------------------------------------------- -MessageDialog::MessageDialog(Window* parent, const char* title, +MessageDialog::MessageDialog(const char* title, const char* message, const char* info, LcdFlags messageFlags, LcdFlags infoFlags) : - BaseDialog(parent, title, true) + BaseDialog(title, true) { messageWidget = new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT}, message, COLOR_THEME_PRIMARY1_INDEX, messageFlags); @@ -115,9 +116,9 @@ void MessageDialog::onClicked() { deleteLater(); } //----------------------------------------------------------------------------- DynamicMessageDialog::DynamicMessageDialog( - Window* parent, const char* title, std::function textHandler, + const char* title, std::function textHandler, const char* message, const int lineHeight, LcdColorIndex color, LcdFlags textFlags) : - BaseDialog(parent, title, true) + BaseDialog(title, true) { messageWidget = new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT}, message, COLOR_THEME_PRIMARY1_INDEX, CENTERED); @@ -130,11 +131,11 @@ void DynamicMessageDialog::onClicked() { deleteLater(); } //----------------------------------------------------------------------------- -ConfirmDialog::ConfirmDialog(Window* parent, const char* title, +ConfirmDialog::ConfirmDialog(const char* title, const char* message, std::function confirmHandler, std::function cancelHandler) : - BaseDialog(parent, title, false), + BaseDialog(title, false), confirmHandler(std::move(confirmHandler)), cancelHandler(std::move(cancelHandler)) { @@ -168,9 +169,9 @@ void ConfirmDialog::onCancel() //----------------------------------------------------------------------------- -LabelDialog::LabelDialog(Window *parent, const char *label, int length, const char* title, +LabelDialog::LabelDialog(const char *label, int length, const char* title, std::function _saveHandler) : - ModalWindow(parent, false), saveHandler(std::move(_saveHandler)) + ModalWindow(false), saveHandler(std::move(_saveHandler)) { assert(length <= MAX_LABEL_LENGTH); diff --git a/radio/src/gui/colorlcd/libui/dialog.h b/radio/src/gui/colorlcd/libui/dialog.h index abd90c646df..bcb00765e3c 100644 --- a/radio/src/gui/colorlcd/libui/dialog.h +++ b/radio/src/gui/colorlcd/libui/dialog.h @@ -33,9 +33,10 @@ class Progress; class BaseDialog : public ModalWindow { public: - BaseDialog(Window* parent, const char* title, bool closeIfClickedOutside, + BaseDialog(const char* title, bool closeIfClickedOutside, lv_coord_t width = DIALOG_DEFAULT_WIDTH, - lv_coord_t maxHeight = DIALOG_DEFAULT_HEIGHT); + lv_coord_t maxHeight = DIALOG_DEFAULT_HEIGHT, + bool flexLayout = true); void setTitle(const char* title); @@ -52,8 +53,7 @@ class BaseDialog : public ModalWindow class ProgressDialog : public BaseDialog { public: - ProgressDialog(Window* parent, const char* title, - std::function onClose); + ProgressDialog(const char* title, std::function onClose); void updateProgress(int percentage); void setTitle(std::string title); @@ -74,7 +74,7 @@ class ProgressDialog : public BaseDialog class MessageDialog : public BaseDialog { public: - MessageDialog(Window* parent, const char* title, const char* message, + MessageDialog(const char* title, const char* message, const char* info = nullptr, LcdFlags messageFlags = CENTERED, LcdFlags infoFlags = CENTERED); @@ -94,7 +94,7 @@ class MessageDialog : public BaseDialog class DynamicMessageDialog : public BaseDialog { public: - DynamicMessageDialog(Window* parent, const char* title, + DynamicMessageDialog(const char* title, std::function textHandler, const char* message = "", const int lineHeight = EdgeTxStyles::PAGE_LINE_HEIGHT, @@ -117,7 +117,7 @@ class DynamicMessageDialog : public BaseDialog class ConfirmDialog : public BaseDialog { public: - ConfirmDialog(Window* parent, const char* title, const char* message, + ConfirmDialog(const char* title, const char* message, std::function confirmHandler, std::function cancelHandler = nullptr); @@ -133,7 +133,7 @@ class ConfirmDialog : public BaseDialog class LabelDialog : public ModalWindow { public: - LabelDialog(Window *parent, const char *label, int length, const char* title, + LabelDialog(const char *label, int length, const char* title, std::function _saveHandler = nullptr); static constexpr int MAX_LABEL_LENGTH = 255; diff --git a/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp b/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp index 98b1f788be5..9be774bae8e 100644 --- a/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp +++ b/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp @@ -236,6 +236,7 @@ EdgeTxStyles::EdgeTxStyles() lv_style_set_img_recolor_opa(&img_color[i], LV_OPA_COVER); lv_style_init(&border_color[i]); lv_style_init(&arc_color[i]); + lv_style_init(&line_color[i]); } lv_style_init(&outline_color_light); lv_style_init(&outline_color_normal); @@ -310,6 +311,7 @@ void EdgeTxStyles::applyColors() lv_style_set_img_recolor(&img_color[i], c); lv_style_set_border_color(&border_color[i], c); lv_style_set_arc_color(&arc_color[i], c); + lv_style_set_line_color(&line_color[i], c); } lv_style_set_line_color(&graph_border, makeLvColor(COLOR_THEME_SECONDARY2)); @@ -466,12 +468,33 @@ void etx_arc_color(lv_obj_t* obj, LcdColorIndex colorIdx, etx_obj_add_style(obj, styles->arc_color[colorIdx], selector); } -void etx_img_color(lv_obj_t* obj, LcdColorIndex colorIdx, - lv_style_selector_t selector) +void etx_remove_line_color(lv_obj_t* obj, lv_style_selector_t selector) +{ + // Remove styles + for (int i = 0; i < TOTAL_COLOR_COUNT; i += 1) + lv_obj_remove_style(obj, &styles->line_color[i], selector); +} + +void etx_line_color(lv_obj_t* obj, LcdColorIndex colorIdx, + lv_style_selector_t selector) { // Remove old style first + etx_remove_line_color(obj, selector); + etx_obj_add_style(obj, styles->line_color[colorIdx], selector); +} + +void etx_remove_img_color(lv_obj_t* obj, lv_style_selector_t selector) +{ + // Remove styles for (int i = 0; i < TOTAL_COLOR_COUNT; i += 1) lv_obj_remove_style(obj, &styles->img_color[i], selector); +} + +void etx_img_color(lv_obj_t* obj, LcdColorIndex colorIdx, + lv_style_selector_t selector) +{ + // Remove old style first + etx_remove_img_color(obj, selector); etx_obj_add_style(obj, styles->img_color[colorIdx], selector); } diff --git a/radio/src/gui/colorlcd/libui/etx_lv_theme.h b/radio/src/gui/colorlcd/libui/etx_lv_theme.h index fa3fb3e787b..39dc37758fb 100644 --- a/radio/src/gui/colorlcd/libui/etx_lv_theme.h +++ b/radio/src/gui/colorlcd/libui/etx_lv_theme.h @@ -111,6 +111,11 @@ void etx_remove_arc_color(lv_obj_t* obj, lv_style_selector_t selector = LV_PART_ void etx_arc_color(lv_obj_t* obj, LcdColorIndex colorIdx, lv_style_selector_t selector = LV_PART_MAIN); +void etx_remove_line_color(lv_obj_t* obj, lv_style_selector_t selector = LV_PART_MAIN); +void etx_line_color(lv_obj_t* obj, LcdColorIndex colorIdx, + lv_style_selector_t selector = LV_PART_MAIN); + +void etx_remove_img_color(lv_obj_t* obj, lv_style_selector_t selector = LV_PART_MAIN); void etx_img_color(lv_obj_t* obj, LcdColorIndex colorIdx, lv_style_selector_t selector = LV_PART_MAIN); @@ -145,6 +150,7 @@ class EdgeTxStyles lv_style_t img_color[TOTAL_COLOR_COUNT]; lv_style_t border_color[TOTAL_COLOR_COUNT]; lv_style_t arc_color[TOTAL_COLOR_COUNT]; + lv_style_t line_color[TOTAL_COLOR_COUNT]; lv_style_t outline_color_light; lv_style_t outline_color_normal; lv_style_t outline_color_focus; diff --git a/radio/src/gui/colorlcd/libui/filechoice.cpp b/radio/src/gui/colorlcd/libui/filechoice.cpp index 0f5baf8da91..94fa282bad2 100644 --- a/radio/src/gui/colorlcd/libui/filechoice.cpp +++ b/radio/src/gui/colorlcd/libui/filechoice.cpp @@ -182,7 +182,7 @@ void FileChoice::openMenu() if (fileCount > 0) { setEditMode(true); // this needs to be done first before menu is created. - auto menu = new Menu(this); + auto menu = new Menu(); if (menuTitle) menu->setTitle(menuTitle); auto tb = new FileChoiceMenuToolbar(this, menu); @@ -192,6 +192,6 @@ void FileChoice::openMenu() menu->setCloseHandler([=]() { setEditMode(false); }); } else { - new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD); + new MessageDialog(STR_SDCARD, STR_NO_FILES_ON_SD); } } diff --git a/radio/src/gui/colorlcd/libui/menu.cpp b/radio/src/gui/colorlcd/libui/menu.cpp index f5066d7d84b..de5cc14dd68 100644 --- a/radio/src/gui/colorlcd/libui/menu.cpp +++ b/radio/src/gui/colorlcd/libui/menu.cpp @@ -364,8 +364,8 @@ class MenuWindowContent : public Window //----------------------------------------------------------------------------- -Menu::Menu(Window* parent, bool multiple) : - ModalWindow(parent, true), +Menu::Menu(bool multiple) : + ModalWindow(true), multiple(multiple), content(new MenuWindowContent(this)) { diff --git a/radio/src/gui/colorlcd/libui/menu.h b/radio/src/gui/colorlcd/libui/menu.h index c7867f75c3b..6ebdefe143e 100644 --- a/radio/src/gui/colorlcd/libui/menu.h +++ b/radio/src/gui/colorlcd/libui/menu.h @@ -29,7 +29,7 @@ class Menu : public ModalWindow friend class MenuBody; public: - explicit Menu(Window *parent, bool multiple = false); + explicit Menu(bool multiple = false); #if defined(DEBUG_WINDOWS) std::string getName() const override { return "Menu"; } diff --git a/radio/src/gui/colorlcd/libui/modal_window.cpp b/radio/src/gui/colorlcd/libui/modal_window.cpp index aa135f82e9a..973964753b6 100644 --- a/radio/src/gui/colorlcd/libui/modal_window.cpp +++ b/radio/src/gui/colorlcd/libui/modal_window.cpp @@ -20,6 +20,7 @@ #include "layer.h" #include "etx_lv_theme.h" +#include "mainwindow.h" // Modal overlay style (for dimming background) void modal_window_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) @@ -45,8 +46,8 @@ static lv_obj_t* modal_create(lv_obj_t* parent) return etx_create(&modal_window_class, parent); } -ModalWindow::ModalWindow(Window* parent, bool closeWhenClickOutside) : - Window(parent->getFullScreenWindow(), {0, 0, LCD_W, LCD_H}, modal_create), +ModalWindow::ModalWindow(bool closeWhenClickOutside) : + Window(MainWindow::instance(), {0, 0, LCD_W, LCD_H}, modal_create), closeWhenClickOutside(closeWhenClickOutside) { Layer::push(this); diff --git a/radio/src/gui/colorlcd/libui/modal_window.h b/radio/src/gui/colorlcd/libui/modal_window.h index a4e9bd02f3c..a8eafd48505 100644 --- a/radio/src/gui/colorlcd/libui/modal_window.h +++ b/radio/src/gui/colorlcd/libui/modal_window.h @@ -23,7 +23,7 @@ class ModalWindow : public Window { public: - explicit ModalWindow(Window* parent, bool closeWhenClickOutside = false); + explicit ModalWindow(bool closeWhenClickOutside = false); #if defined(DEBUG_WINDOWS) std::string getName() const override { return "ModalWindow"; } diff --git a/radio/src/gui/colorlcd/libui/popups.cpp b/radio/src/gui/colorlcd/libui/popups.cpp index 1af160dd9fd..131ba71b65a 100644 --- a/radio/src/gui/colorlcd/libui/popups.cpp +++ b/radio/src/gui/colorlcd/libui/popups.cpp @@ -38,7 +38,7 @@ static void _run_popup_dialog(const char* title, const char* msg, // RELEASED/CLICKED to be called in a loop lv_indev_reset(nullptr, nullptr); - auto md = new MessageDialog(MainWindow::instance(), title, msg, info); + auto md = new MessageDialog(title, msg, info); md->setCloseHandler([&]() { running = false; }); while (running) { // Allow power off while showing popup diff --git a/radio/src/gui/colorlcd/mainview/screen_setup.cpp b/radio/src/gui/colorlcd/mainview/screen_setup.cpp index ebf36dcda0e..2446964951d 100644 --- a/radio/src/gui/colorlcd/mainview/screen_setup.cpp +++ b/radio/src/gui/colorlcd/mainview/screen_setup.cpp @@ -56,7 +56,7 @@ class LayoutChoice : public Button void onPress() override { - auto menu = new Menu(parent); + auto menu = new Menu(); for (auto layout : LayoutFactory::getRegisteredLayouts()) { menu->addLine(layout->getBitmap(), layout->getName(), [=]() { setValue(layout); }); diff --git a/radio/src/gui/colorlcd/mainview/view_about.cpp b/radio/src/gui/colorlcd/mainview/view_about.cpp index 0b51f61a01d..6d75c432f99 100644 --- a/radio/src/gui/colorlcd/mainview/view_about.cpp +++ b/radio/src/gui/colorlcd/mainview/view_about.cpp @@ -39,7 +39,7 @@ const std::string copyright_str = "Copyright (C) " BUILD_YEAR " EdgeTX"; const std::string edgetx_url = "https://edgetx.org"; AboutUs::AboutUs() : - BaseDialog(MainWindow::instance(), STR_ABOUT_US, true, 220, LV_SIZE_CONTENT) + BaseDialog(STR_ABOUT_US, true, 220, LV_SIZE_CONTENT) { new StaticText(form, {0, 0, LV_PCT(100), LV_SIZE_CONTENT}, about_str + "\n" + copyright_str, diff --git a/radio/src/gui/colorlcd/mainview/view_main_menu.cpp b/radio/src/gui/colorlcd/mainview/view_main_menu.cpp index 55b79fa6101..e79062a34fc 100644 --- a/radio/src/gui/colorlcd/mainview/view_main_menu.cpp +++ b/radio/src/gui/colorlcd/mainview/view_main_menu.cpp @@ -128,7 +128,7 @@ ViewMainMenu::ViewMainMenu(Window* parent, std::function closeHandler) : carousel->addButton( ICON_MODEL_TELEMETRY, STR_MAIN_MENU_RESET_TELEMETRY, [=]() -> uint8_t { deleteLater(); - Menu* resetMenu = new Menu(parent); + Menu* resetMenu = new Menu(); resetMenu->addLine(STR_RESET_FLIGHT, []() { flightReset(); }); resetMenu->addLine(STR_RESET_TIMER1, []() { timerReset(0); }); resetMenu->addLine(STR_RESET_TIMER2, []() { timerReset(1); }); diff --git a/radio/src/gui/colorlcd/mainview/widget.cpp b/radio/src/gui/colorlcd/mainview/widget.cpp index 41c6a60c7f4..b7b7c3ff46b 100644 --- a/radio/src/gui/colorlcd/mainview/widget.cpp +++ b/radio/src/gui/colorlcd/mainview/widget.cpp @@ -58,11 +58,7 @@ void Widget::openMenu() } if (getOptions() || fsAllowed) { - // Widgets are placed on a full screen window which is underneath the main - // view menu bar Find the parent of this so that when the popup loads it - // covers the main view menu - Window* w = parent->getFullScreenWindow()->getParent(); - Menu* menu = new Menu(w ? w : this); + Menu* menu = new Menu(); menu->setTitle(getFactory()->getDisplayName()); if (fsAllowed) { menu->addLine(STR_WIDGET_FULLSCREEN, [&]() { setFullscreen(true); }); diff --git a/radio/src/gui/colorlcd/mainview/widget_settings.cpp b/radio/src/gui/colorlcd/mainview/widget_settings.cpp index 0e978f501e3..198969d9190 100644 --- a/radio/src/gui/colorlcd/mainview/widget_settings.cpp +++ b/radio/src/gui/colorlcd/mainview/widget_settings.cpp @@ -43,11 +43,9 @@ static const lv_coord_t line_row_dsc[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST}; WidgetSettings::WidgetSettings(Widget* w) : - BaseDialog(ViewMain::instance(), w->getFactory()->getDisplayName(), true), widget(w) + BaseDialog(w->getFactory()->getDisplayName(), true), widget(w) { - FlexGridLayout grid(line_col_dsc, line_row_dsc); - form->padAll(PAD_SMALL); - form->padRow(PAD_ZERO); + FlexGridLayout grid(line_col_dsc, line_row_dsc, PAD_TINY); uint8_t optIdx = 0; auto opt = widget->getOptions(); diff --git a/radio/src/gui/colorlcd/mainview/widgets_setup.cpp b/radio/src/gui/colorlcd/mainview/widgets_setup.cpp index 4d3f30d8b9f..9acef26110b 100644 --- a/radio/src/gui/colorlcd/mainview/widgets_setup.cpp +++ b/radio/src/gui/colorlcd/mainview/widgets_setup.cpp @@ -37,7 +37,7 @@ SetupWidgetsPageSlot::SetupWidgetsPageSlot(Window* parent, const rect_t& rect, { setPressHandler([=]() -> uint8_t { if (container->getWidget(slotIndex)) { - Menu* menu = new Menu(parent); + Menu* menu = new Menu(); menu->addLine(STR_SELECT_WIDGET, [=]() { addNewWidget(container, slotIndex); }); auto widget = container->getWidget(slotIndex); @@ -95,7 +95,7 @@ void SetupWidgetsPageSlot::addNewWidget(WidgetsContainer* container, auto w = container->getWidget((slotIndex)); if (w) cur = w->getFactory()->getDisplayName(); - Menu* menu = new Menu(parent); + Menu* menu = new Menu(); menu->setTitle(STR_SELECT_WIDGET); int selected = -1; int index = 0; diff --git a/radio/src/gui/colorlcd/mainview/widgets_setup.h b/radio/src/gui/colorlcd/mainview/widgets_setup.h index 5d185f88217..f9e37db733d 100644 --- a/radio/src/gui/colorlcd/mainview/widgets_setup.h +++ b/radio/src/gui/colorlcd/mainview/widgets_setup.h @@ -40,12 +40,13 @@ class SetupWidgetsPage : public Window void onClicked() override; void onCancel() override; - void deleteLater(bool detach = true, bool trash = true) override; protected: uint8_t customScreenIdx; unsigned savedView = 0; + void onEvent(event_t event) override; + void deleteLater(bool detach = true, bool trash = true) override; }; class SetupWidgetsPageSlot : public ButtonBase @@ -55,6 +56,9 @@ class SetupWidgetsPageSlot : public ButtonBase WidgetsContainer* container, uint8_t slotIndex); protected: + WidgetsContainer* container = nullptr; + uint8_t slotIndex = 0; + bool openSettings = false; lv_style_t borderStyle; lv_point_t borderPts[5]; lv_obj_t* border; diff --git a/radio/src/gui/colorlcd/model/model_curves.cpp b/radio/src/gui/colorlcd/model/model_curves.cpp index 3e692fa6b78..3feede69216 100644 --- a/radio/src/gui/colorlcd/model/model_curves.cpp +++ b/radio/src/gui/colorlcd/model/model_curves.cpp @@ -142,7 +142,7 @@ void ModelCurvesPage::editCurve(Window *window, uint8_t curve) void ModelCurvesPage::presetMenu(Window *window, uint8_t index) { - Menu *menu = new Menu(window); + Menu *menu = new Menu(); menu->setTitle(STR_CURVE_PRESET); for (int angle = -45; angle <= 45; angle += 15) { char label[16]; @@ -169,7 +169,7 @@ void ModelCurvesPage::presetMenu(Window *window, uint8_t index) void ModelCurvesPage::newCV(Window *window, bool presetCV) { - Menu *menu = new Menu(Layer::back()); + Menu *menu = new Menu(); menu->setTitle(STR_CURVE); char s[6] = "CVxx"; @@ -195,7 +195,7 @@ void ModelCurvesPage::newCV(Window *window, bool presetCV) void ModelCurvesPage::plusPopup(Window *window) { - Menu *menu = new Menu(window); + Menu *menu = new Menu(); menu->setTitle(STR_NEW); menu->addLine(STR_EDIT, [=]() { newCV(window, false); }); menu->addLine(STR_CURVE_PRESET, [=]() { newCV(window, true); }); @@ -235,7 +235,7 @@ void ModelCurvesPage::build(Window *window) auto button = new CurveButton(line, rect_t{0, 0, CurveButton::CURVE_BTN_W, CurveButton::CURVE_BTH_H}, index); button->setPressHandler([=]() -> uint8_t { - Menu *menu = new Menu(window); + Menu *menu = new Menu(); menu->setTitle(STR_CURVE); menu->addLine(STR_EDIT, [=]() { editCurve(window, index); }); menu->addLine(STR_CURVE_PRESET, [=]() { presetMenu(window, index); }); diff --git a/radio/src/gui/colorlcd/model/model_gvars.cpp b/radio/src/gui/colorlcd/model/model_gvars.cpp index 8175144319c..1bc98664778 100644 --- a/radio/src/gui/colorlcd/model/model_gvars.cpp +++ b/radio/src/gui/colorlcd/model/model_gvars.cpp @@ -561,7 +561,7 @@ void ModelGVarsPage::build(Window* window) auto button = new GVarButton(window, index); lv_obj_set_pos(button->getLvObj(), 0, yo + index * (GVarButton::BTN_H + PAD_TINY)); button->setPressHandler([=]() { - Menu* menu = new Menu(window); + Menu* menu = new Menu(); menu->addLine(STR_EDIT, [=]() { Window* editWindow = new GVarEditWindow(index); editWindow->setCloseHandler([=]() { rebuild(window); }); diff --git a/radio/src/gui/colorlcd/model/model_inputs.cpp b/radio/src/gui/colorlcd/model/model_inputs.cpp index 677ffd138dd..702afc229f4 100644 --- a/radio/src/gui/colorlcd/model/model_inputs.cpp +++ b/radio/src/gui/colorlcd/model/model_inputs.cpp @@ -202,7 +202,7 @@ ModelInputsPage::ModelInputsPage() : InputMixPageBase(STR_MENUINPUTS, ICON_MODEL bool ModelInputsPage::reachExposLimit() { if (getExposCount() >= MAX_EXPOS) { - new MessageDialog(form, STR_WARNING, STR_NOFREEEXPO); + new MessageDialog(STR_WARNING, STR_NOFREEEXPO); return true; } return false; @@ -233,7 +233,7 @@ InputMixButtonBase* ModelInputsPage::createLineButton(InputMixGroupBase* group, uint8_t input = group->getMixSrc() - MIXSRC_FIRST_INPUT; button->setPressHandler([=]() -> uint8_t { - Menu* menu = new Menu(form); + Menu* menu = new Menu(); menu->addLine(STR_EDIT, [=]() { uint8_t idx = button->getIndex(); editInput(input, idx); @@ -287,7 +287,7 @@ void ModelInputsPage::addLineButton(uint8_t index) void ModelInputsPage::newInput() { - Menu* menu = new Menu(Layer::back()); + Menu* menu = new Menu(); menu->setTitle(STR_MENU_INPUTS); uint8_t chn = 0; diff --git a/radio/src/gui/colorlcd/model/model_logical_switches.cpp b/radio/src/gui/colorlcd/model/model_logical_switches.cpp index bab7bf2e146..66a0f80cf7c 100644 --- a/radio/src/gui/colorlcd/model/model_logical_switches.cpp +++ b/radio/src/gui/colorlcd/model/model_logical_switches.cpp @@ -560,7 +560,7 @@ void ModelLogicalSwitchesPage::rebuild(Window* window) void ModelLogicalSwitchesPage::newLS(Window* window, bool pasteLS) { - Menu* menu = new Menu(Layer::back()); + Menu* menu = new Menu(); menu->setTitle(STR_MENU_LOGICAL_SWITCHES); // search for unused switches @@ -593,7 +593,7 @@ void ModelLogicalSwitchesPage::newLS(Window* window, bool pasteLS) void ModelLogicalSwitchesPage::plusPopup(Window* window) { if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_SWITCH) { - Menu* menu = new Menu(window); + Menu* menu = new Menu(); menu->addLine(STR_NEW, [=]() { newLS(window, false); }); menu->addLine(STR_PASTE, [=]() { newLS(window, true); }); } else { @@ -619,7 +619,7 @@ void ModelLogicalSwitchesPage::build(Window* window) auto button = new LogicalSwitchButton(window, i); button->setPressHandler([=]() { - Menu* menu = new Menu(window); + Menu* menu = new Menu(); menu->addLine(STR_EDIT, [=]() { Window* lsWindow = new LogicalSwitchEditPage(i); lsWindow->setCloseHandler([=]() { diff --git a/radio/src/gui/colorlcd/model/model_mixer_scripts.cpp b/radio/src/gui/colorlcd/model/model_mixer_scripts.cpp index f69292c0b32..ee7bd28fd97 100644 --- a/radio/src/gui/colorlcd/model/model_mixer_scripts.cpp +++ b/radio/src/gui/colorlcd/model/model_mixer_scripts.cpp @@ -301,7 +301,7 @@ void ModelMixerScriptsPage::build(Window* window, int8_t focusIdx) auto button = new ScriptLineButton(window, *sd, runtimeData, idx); button->setPressHandler([=]() -> uint8_t { - Menu* const menu = new Menu(window); + Menu* const menu = new Menu(); menu->addLine(STR_EDIT, [=]() { editLine(window, idx); }); if (runtimeData != nullptr) { diff --git a/radio/src/gui/colorlcd/model/model_mixes.cpp b/radio/src/gui/colorlcd/model/model_mixes.cpp index e82cf8edbc7..e1ff73594eb 100644 --- a/radio/src/gui/colorlcd/model/model_mixes.cpp +++ b/radio/src/gui/colorlcd/model/model_mixes.cpp @@ -231,7 +231,7 @@ ModelMixesPage::ModelMixesPage() : InputMixPageBase(STR_MIXES, ICON_MODEL_MIXER) bool ModelMixesPage::reachMixesLimit() { if (getMixCount() >= MAX_MIXERS) { - new MessageDialog(form, STR_WARNING, STR_NOFREEMIXER); + new MessageDialog(STR_WARNING, STR_NOFREEMIXER); return true; } return false; @@ -264,7 +264,7 @@ InputMixButtonBase* ModelMixesPage::createLineButton(InputMixGroupBase *group, u uint8_t ch = group->getMixSrc() - MIXSRC_FIRST_CH; button->setPressHandler([=]() -> uint8_t { - Menu *menu = new Menu(form); + Menu *menu = new Menu(); menu->addLine(STR_EDIT, [=]() { uint8_t idx = button->getIndex(); editMix(ch, idx); @@ -318,7 +318,7 @@ void ModelMixesPage::addLineButton(uint8_t index) void ModelMixesPage::newMix() { - Menu* menu = new Menu(Layer::back()); + Menu* menu = new Menu(); menu->setTitle(STR_MENU_CHANNELS); uint8_t index = 0; diff --git a/radio/src/gui/colorlcd/model/model_outputs.cpp b/radio/src/gui/colorlcd/model/model_outputs.cpp index 2bd49b90dae..3dd678d3a5a 100644 --- a/radio/src/gui/colorlcd/model/model_outputs.cpp +++ b/radio/src/gui/colorlcd/model/model_outputs.cpp @@ -240,7 +240,7 @@ void ModelOutputsPage::build(Window* window) new TextButton(window, {ADD_TRIMS_X, ADD_TRIMS_Y, ADD_TRIMS_W, ADD_TRIMS_H}, STR_ADD_ALL_TRIMS_TO_SUBTRIMS, [=]() { new ConfirmDialog( - window, STR_TRIMS2OFFSETS, STR_ADD_ALL_TRIMS_TO_SUBTRIMS, + STR_TRIMS2OFFSETS, STR_ADD_ALL_TRIMS_TO_SUBTRIMS, [=] { moveTrimsToOffsets(); }); @@ -258,7 +258,7 @@ void ModelOutputsPage::build(Window* window) LimitData* output = limitAddress(ch); btn->setPressHandler([=]() -> uint8_t { - Menu* menu = new Menu(window); + Menu* menu = new Menu(); menu->addLine(STR_EDIT, [=]() { editOutput(ch, btn); }); menu->addLine(STR_RESET, [=]() { output->min = 0; diff --git a/radio/src/gui/colorlcd/model/model_select.cpp b/radio/src/gui/colorlcd/model/model_select.cpp index 06895fb7033..489e9c2cdc5 100644 --- a/radio/src/gui/colorlcd/model/model_select.cpp +++ b/radio/src/gui/colorlcd/model/model_select.cpp @@ -334,7 +334,7 @@ class ModelsPageBody : public Window void openMenu() { - Menu *menu = new Menu(this); + Menu *menu = new Menu(); menu->setTitle(focusedModel->modelName); if (g_eeGeneral.modelQuickSelect || focusedModel != modelslist.getCurrentModel()) { @@ -393,7 +393,7 @@ class ModelsPageBody : public Window void duplicateModel(ModelCell *model) { new ConfirmDialog( - parent, STR_DUPLICATE_MODEL, + STR_DUPLICATE_MODEL, std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { storageFlushCurrentModel(); storageCheck(true); @@ -422,7 +422,7 @@ class ModelsPageBody : public Window void deleteModel(ModelCell *model) { new ConfirmDialog( - parent, STR_DELETE_MODEL, + STR_DELETE_MODEL, std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { modelslist.removeModel(model); if (refreshLabels != nullptr) refreshLabels(); @@ -437,7 +437,7 @@ class ModelsPageBody : public Window // dont display menu if there will be no labels if (labels.size()) { - auto menu = new Menu(getParent(), true); + auto menu = new Menu(true); menu->setTitle(model->modelName); menu->setCloseHandler([=]() { if (isDirty) { @@ -466,7 +466,7 @@ class ModelsPageBody : public Window void saveAsTemplate(ModelCell *model) { new ConfirmDialog( - parent, STR_SAVE_TEMPLATE, + STR_SAVE_TEMPLATE, std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { storageDirty(EE_MODEL); storageCheck(true); @@ -487,7 +487,7 @@ class ModelsPageBody : public Window snprintf(templatePath, FF_MAX_LFN, "%s%c%s", persFolder, '/', modelName); if (isFileAvailable(templatePath)) { - new ConfirmDialog(parent, STR_FILE_EXISTS, STR_ASK_OVERWRITE, [=] { + new ConfirmDialog(STR_FILE_EXISTS, STR_ASK_OVERWRITE, [=] { sdCopyFile(model->modelFilename, MODELS_PATH, modelName, persFolder); }); @@ -668,7 +668,7 @@ void ModelLabelsWindow::newModel() void ModelLabelsWindow::newLabel() { tmpLabel[0] = '\0'; - new LabelDialog(this, tmpLabel, LABEL_LENGTH, STR_ENTER_LABEL, [=](std::string label) { + new LabelDialog(tmpLabel, LABEL_LENGTH, STR_ENTER_LABEL, [=](std::string label) { int newlabindex = modelslabels.addLabel(label); if (newlabindex >= 0) { std::set newset; @@ -690,7 +690,7 @@ void ModelLabelsWindow::buildHead(Window *hdr) // new model button new TextButton(hdr, {LCD_W - NEW_BTN_W - PAD_LARGE, PAD_MEDIUM, NEW_BTN_W, EdgeTxStyles::UI_ELEMENT_HEIGHT}, STR_NEW, [=]() { - auto menu = new Menu(this); + auto menu = new Menu(); menu->setTitle(STR_CREATE_NEW); menu->addLine(STR_NEW_MODEL, [=]() { newModel(); }); menu->addLine(STR_NEW_LABEL, [=]() { newLabel(); }); @@ -811,16 +811,16 @@ void ModelLabelsWindow::buildBody(Window *window) std::string selectedLabel = labels.at(selected); if (selectedLabel != STR_UNLABELEDMODEL) { - Menu *menu = new Menu(window); + Menu *menu = new Menu(); menu->setTitle(selectedLabel); menu->addLine(STR_RENAME_LABEL, [=]() { auto oldLabel = labels[selected]; strncpy(tmpLabel, oldLabel.c_str(), LABEL_LENGTH); tmpLabel[LABEL_LENGTH] = '\0'; - new LabelDialog(this, tmpLabel, LABEL_LENGTH, STR_ENTER_LABEL, [=](std::string newLabel) { + new LabelDialog(tmpLabel, LABEL_LENGTH, STR_ENTER_LABEL, [=](std::string newLabel) { if (newLabel.size() > 0) { auto rndialog = - new ProgressDialog(this, STR_RENAME_LABEL, [=]() {}); + new ProgressDialog(STR_RENAME_LABEL, [=]() {}); modelslabels.renameLabel( oldLabel, newLabel, [=](const char *name, int percentage) { rndialog->setTitle(std::string(STR_RENAME_LABEL) + " " + @@ -838,9 +838,9 @@ void ModelLabelsWindow::buildBody(Window *window) menu->addLine(STR_DELETE_LABEL, [=]() { auto labelToDelete = labels[selected]; new ConfirmDialog( - parent, STR_DELETE_LABEL, labelToDelete.c_str(), [=]() { + STR_DELETE_LABEL, labelToDelete.c_str(), [=]() { auto deldialog = - new ProgressDialog(this, STR_DELETE_LABEL, [=]() {}); + new ProgressDialog(STR_DELETE_LABEL, [=]() {}); modelslabels.removeLabel( labelToDelete, [=](const char *name, int percentage) { deldialog->setTitle(std::string(STR_DELETE_LABEL) + " " + diff --git a/radio/src/gui/colorlcd/model/model_setup.cpp b/radio/src/gui/colorlcd/model/model_setup.cpp index 38e9b9a7098..3930c153d37 100644 --- a/radio/src/gui/colorlcd/model/model_setup.cpp +++ b/radio/src/gui/colorlcd/model/model_setup.cpp @@ -300,7 +300,7 @@ static SetupLineDef setupLines[] = { auto curmod = modelslist.getCurrentModel(); TextButton* btn = new TextButton(parent, {x, y, 0, 0}, modelslabels.getBulletLabelString(curmod, STR_UNLABELEDMODEL)); btn->setPressHandler([=]() { - Menu *menu = new Menu(MainWindow::instance(), true); + Menu *menu = new Menu(true); menu->setTitle(STR_LABELS); for (auto &label : modelslabels.getLabels()) { menu->addLineBuffered( diff --git a/radio/src/gui/colorlcd/model/model_telemetry.cpp b/radio/src/gui/colorlcd/model/model_telemetry.cpp index 99b429dfb45..2ae07419692 100644 --- a/radio/src/gui/colorlcd/model/model_telemetry.cpp +++ b/radio/src/gui/colorlcd/model/model_telemetry.cpp @@ -836,7 +836,7 @@ void ModelTelemetryPage::buildSensorList(int8_t focusSensorIndex) if (!first) first = button; button->setPressHandler([=]() -> uint8_t { - Menu* menu = new Menu(window); + Menu* menu = new Menu(); menu->addLine(STR_EDIT, [=]() { editSensor(window, idx); }); menu->addLine(STR_COPY, [=]() { auto newIndex = availableTelemetryIndex(); @@ -944,7 +944,7 @@ void ModelTelemetryPage::build(Window* window) // Delete all sensors button deleteAll = new TextButton(line, rect_t{}, STR_DELETE_ALL_SENSORS, [=]() -> uint8_t { - new ConfirmDialog(window, STR_DELETE_ALL_SENSORS, STR_CONFIRMDELETE, + new ConfirmDialog(STR_DELETE_ALL_SENSORS, STR_CONFIRMDELETE, [=]() { for (int i = 0; i < MAX_TELEMETRY_SENSORS; i++) { delTelemetryIndex(i); diff --git a/radio/src/gui/colorlcd/model/model_usbjoystick.cpp b/radio/src/gui/colorlcd/model/model_usbjoystick.cpp index a60f5c383cb..79a525e144c 100644 --- a/radio/src/gui/colorlcd/model/model_usbjoystick.cpp +++ b/radio/src/gui/colorlcd/model/model_usbjoystick.cpp @@ -562,7 +562,7 @@ ModelUSBJoystickPage::ModelUSBJoystickPage() : Page(ICON_MODEL_USB) if (cch->mode == USBJOYS_CH_NONE) { editChannel(ch, btn); } else { - Menu* menu = new Menu(parent); + Menu* menu = new Menu(); menu->addLine(STR_EDIT, [=]() { editChannel(ch, btn); }); menu->addLine(STR_CLEAR, [=]() { memset(cch, 0, sizeof(USBJoystickChData)); diff --git a/radio/src/gui/colorlcd/model/special_functions.cpp b/radio/src/gui/colorlcd/model/special_functions.cpp index 49005e8d73b..9d956dca8bc 100644 --- a/radio/src/gui/colorlcd/model/special_functions.cpp +++ b/radio/src/gui/colorlcd/model/special_functions.cpp @@ -720,7 +720,7 @@ void FunctionsPage::rebuild(Window *window) void FunctionsPage::newSF(Window *window, bool pasteSF) { - Menu *menu = new Menu(Layer::back()); + Menu *menu = new Menu(); menu->setTitle(title); // search for unused switches @@ -769,7 +769,7 @@ void FunctionsPage::editSpecialFunction(Window *window, uint8_t index, void FunctionsPage::plusPopup(Window *window) { if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) { - Menu *menu = new Menu(window); + Menu *menu = new Menu(); menu->addLine(STR_NEW, [=]() { newSF(window, false); }); menu->addLine(STR_PASTE, [=]() { newSF(window, true); }); } else { @@ -810,7 +810,7 @@ void FunctionsPage::build(Window *window) }); button->setPressHandler([=]() { - Menu *menu = new Menu(window); + Menu *menu = new Menu(); menu->addLine(STR_EDIT, [=]() { editSpecialFunction(window, i, button); }); if (isActive) { diff --git a/radio/src/gui/colorlcd/model/trainer_bluetooth.cpp b/radio/src/gui/colorlcd/model/trainer_bluetooth.cpp index dd66784c02b..b5758e09d01 100644 --- a/radio/src/gui/colorlcd/model/trainer_bluetooth.cpp +++ b/radio/src/gui/colorlcd/model/trainer_bluetooth.cpp @@ -42,7 +42,7 @@ class BTDiscoverMenu : public Menu }; BTDiscoverMenu::BTDiscoverMenu() : - Menu(Layer::back()) + Menu() { setTitle(STR_BT_SELECT_DEVICE); // TODO: set minimum height @@ -132,7 +132,7 @@ void BluetoothTrainerWindow::refresh() } } else if (bluetooth.state == BLUETOOTH_STATE_DISCOVER_END) { if(reusableBuffer.moduleSetup.bt.devicesCount == 0) { - new MessageDialog(this, STR_BLUETOOTH, STR_BLUETOOTH_NODEVICES); + new MessageDialog(STR_BLUETOOTH, STR_BLUETOOTH_NODEVICES); bluetooth.state = BLUETOOTH_STATE_OFF; } } else if (bluetooth.distantAddr[0]) { diff --git a/radio/src/gui/colorlcd/model/trims_setup.cpp b/radio/src/gui/colorlcd/model/trims_setup.cpp index 2699a514c14..6f9864e6d7e 100644 --- a/radio/src/gui/colorlcd/model/trims_setup.cpp +++ b/radio/src/gui/colorlcd/model/trims_setup.cpp @@ -46,7 +46,7 @@ static SetupLineDef setupLines[] = { new Choice(parent, {x, y, TrimsSetup::HATSMODE_W, 0}, STR_HATSOPT, HATSMODE_TRIMS_ONLY, HATSMODE_GLOBAL, GET_SET_DEFAULT(g_model.hatsMode)); new TextButton(parent, {x + TrimsSetup::HATSMODE_W + PAD_SMALL, y, 0, 0}, "?", [=]() { - new MessageDialog(parent, STR_HATSMODE_KEYS, STR_HATSMODE_KEYS_HELP, "", + new MessageDialog(STR_HATSMODE_KEYS, STR_HATSMODE_KEYS_HELP, "", LEFT); return 0; }); diff --git a/radio/src/gui/colorlcd/module/access_settings.cpp b/radio/src/gui/colorlcd/module/access_settings.cpp index d6c8c27d3bc..19e176f9e09 100644 --- a/radio/src/gui/colorlcd/module/access_settings.cpp +++ b/radio/src/gui/colorlcd/module/access_settings.cpp @@ -34,7 +34,7 @@ namespace pxx2 class BindRxChoiceMenu : public Menu { public: - BindRxChoiceMenu(Window* parent, uint8_t moduleIdx, uint8_t receiverIdx); + BindRxChoiceMenu(uint8_t moduleIdx, uint8_t receiverIdx); protected: uint8_t moduleIdx; @@ -44,11 +44,71 @@ class BindRxChoiceMenu : public Menu class BindWaitDialog : public BaseDialog { public: - BindWaitDialog(Window* parent, uint8_t moduleIdx, uint8_t receiverIdx); + BindWaitDialog(uint8_t moduleIdx, uint8_t receiverIdx) : + BaseDialog(STR_BIND, true), + moduleIdx(moduleIdx), + receiverIdx(receiverIdx) + { + new StaticText(form, rect_t{}, STR_WAITING_FOR_RX); - void checkEvents() override; + setCloseHandler([=]() { moduleState[moduleIdx].mode = MODULE_MODE_NORMAL; }); + } + + void checkEvents() override + { + auto& bindInfo = getPXX2BindInformationBuffer(); + + if (moduleState[moduleIdx].mode == MODULE_MODE_NORMAL) { + // returned to normal after bind + if (bindInfo.step > BIND_INIT) { + removePXX2ReceiverIfEmpty(moduleIdx, receiverIdx); + deleteLater(); + if (bindInfo.step == BIND_OK) { + POPUP_INFORMATION(STR_REG_OK); + setPXX2ReceiverUsed(moduleIdx, receiverIdx); + } + return; + } + + // pre-bind phase: fetching info for R9M + auto& modSetup = getPXX2ModuleSetupBuffer(); + switch (bindInfo.step) { + case BIND_MODULE_TX_INFORMATION_REQUEST: + if (modSetup.moduleInformation.information.variant == PXX2_VARIANT_EU) { + // In EU mode we will need the power of the module to know if + // telemetry can be proposed + bindInfo.step = BIND_MODULE_TX_SETTINGS_REQUEST; + #if defined(SIMU) + modSetup.moduleSettings.txPower = 14; + #else + moduleState[moduleIdx].readModuleSettings(&modSetup.moduleSettings); + #endif + } else { + bindInfo.step = 0; + moduleState[moduleIdx].startBind(&bindInfo); + } + break; + case BIND_MODULE_TX_SETTINGS_REQUEST: + // We just receive the module settings (for TX power) + bindInfo.step = 0; + moduleState[moduleIdx].startBind(&bindInfo); + break; + } + return; + } + + if (bindInfo.step == BIND_INIT && bindInfo.candidateReceiversCount > 0) { + // prevent module mode being reset to NORMAL before exiting + setCloseHandler(nullptr); + deleteLater(); - void deleteLater(bool detach = true, bool trash = true) override; + // ... and create RX choice dialog + new BindRxChoiceMenu(moduleIdx, receiverIdx); + return; + } + + BaseDialog::checkEvents(); + } protected: uint8_t moduleIdx; @@ -58,7 +118,7 @@ class BindWaitDialog : public BaseDialog class RxOptions : public BaseDialog { public: - RxOptions(Window* parent, uint8_t moduleIdx, uint8_t rxIdx); + RxOptions(uint8_t moduleIdx, uint8_t rxIdx); void checkEvents() override; protected: @@ -84,8 +144,7 @@ class RxOptions : public BaseDialog void writeSettings(); }; -static void startBindWaitDialog(Window* parent, uint8_t moduleIdx, - uint8_t receiverIdx) +static void startBindWaitDialog(uint8_t moduleIdx, uint8_t receiverIdx) { auto& bindInfo = getPXX2BindInformationBuffer(); #if defined(SIMU) @@ -96,32 +155,32 @@ static void startBindWaitDialog(Window* parent, uint8_t moduleIdx, bindInfo.step = BIND_OK; setPXX2ReceiverUsed(moduleIdx, receiverIdx); moduleState[moduleIdx].mode = MODULE_MODE_NORMAL; - new MessageDialog(parent, STR_BIND, STR_BIND_OK); + new MessageDialog(STR_BIND, STR_BIND_OK); #else bindInfo.step = BIND_START; - new BindWaitDialog(parent, moduleIdx, receiverIdx); + new BindWaitDialog(moduleIdx, receiverIdx); #endif } -static void startFlexBindWaitDialog(Window* parent, uint8_t moduleIdx, +static void startFlexBindWaitDialog(uint8_t moduleIdx, uint8_t receiverIdx, uint8_t flex) { auto& bindInfo = getPXX2BindInformationBuffer(); bindInfo.flexMode = flex; - startBindWaitDialog(parent, moduleIdx, receiverIdx); + startBindWaitDialog(moduleIdx, receiverIdx); } -static void startLBTBindWaitDialog(Window* parent, uint8_t moduleIdx, +static void startLBTBindWaitDialog(uint8_t moduleIdx, uint8_t receiverIdx, uint8_t lbt) { auto& bindInfo = getPXX2BindInformationBuffer(); bindInfo.lbtMode = lbt; - startBindWaitDialog(parent, moduleIdx, receiverIdx); + startBindWaitDialog(moduleIdx, receiverIdx); } -BindRxChoiceMenu::BindRxChoiceMenu(Window* parent, uint8_t moduleIdx, +BindRxChoiceMenu::BindRxChoiceMenu(uint8_t moduleIdx, uint8_t receiverIdx) : - Menu(parent), moduleIdx(moduleIdx), receiverIdx(receiverIdx) + Menu(), moduleIdx(moduleIdx), receiverIdx(receiverIdx) { const auto& bindInfo = getPXX2BindInformationBuffer(); auto receiversCount = min(bindInfo.candidateReceiversCount, @@ -138,26 +197,26 @@ BindRxChoiceMenu::BindRxChoiceMenu(Window* parent, uint8_t moduleIdx, auto& modSetup = getPXX2ModuleSetupBuffer(); if (modSetup.moduleSettings.txPower <= 14) { // with telemetry - startLBTBindWaitDialog(parent, moduleIdx, receiverIdx, 1); + startLBTBindWaitDialog(moduleIdx, receiverIdx, 1); } else { // without telemetry - startLBTBindWaitDialog(parent, moduleIdx, receiverIdx, 2); + startLBTBindWaitDialog(moduleIdx, receiverIdx, 2); } return; } else if (isModuleR9MAccess(moduleIdx) && modInfo.information.variant == PXX2_VARIANT_FLEX) { bindInfo.step = BIND_RX_NAME_SELECTED; - auto flexMenu = new Menu(parent); + auto flexMenu = new Menu(); flexMenu->addLine(STR_FLEX_868, [=]() { - startFlexBindWaitDialog(parent, moduleIdx, receiverIdx, 0); + startFlexBindWaitDialog(moduleIdx, receiverIdx, 0); }); flexMenu->addLine(STR_FLEX_915, [=]() { - startFlexBindWaitDialog(parent, moduleIdx, receiverIdx, 1); + startFlexBindWaitDialog(moduleIdx, receiverIdx, 1); }); return; } - startBindWaitDialog(parent, moduleIdx, receiverIdx); + startBindWaitDialog(moduleIdx, receiverIdx); }); } @@ -170,78 +229,6 @@ static const lv_coord_t line_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), static const lv_coord_t line_row_dsc[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST}; -BindWaitDialog::BindWaitDialog(Window* parent, uint8_t moduleIdx, - uint8_t receiverIdx) : - BaseDialog(parent, STR_BIND, true), - moduleIdx(moduleIdx), - receiverIdx(receiverIdx) -{ - new StaticText(form, rect_t{}, STR_WAITING_FOR_RX); - - setCloseHandler([=]() { moduleState[moduleIdx].mode = MODULE_MODE_NORMAL; }); -} - -void BindWaitDialog::deleteLater(bool detach, bool trash) -{ - BaseDialog::deleteLater(detach, trash); -} - -void BindWaitDialog::checkEvents() -{ - auto& bindInfo = getPXX2BindInformationBuffer(); - - if (moduleState[moduleIdx].mode == MODULE_MODE_NORMAL) { - // returned to normal after bind - if (bindInfo.step > BIND_INIT) { - removePXX2ReceiverIfEmpty(moduleIdx, receiverIdx); - deleteLater(); - if (bindInfo.step == BIND_OK) { - POPUP_INFORMATION(STR_REG_OK); - setPXX2ReceiverUsed(moduleIdx, receiverIdx); - } - return; - } - - // pre-bind phase: fetching info for R9M - auto& modSetup = getPXX2ModuleSetupBuffer(); - switch (bindInfo.step) { - case BIND_MODULE_TX_INFORMATION_REQUEST: - if (modSetup.moduleInformation.information.variant == PXX2_VARIANT_EU) { - // In EU mode we will need the power of the module to know if - // telemetry can be proposed - bindInfo.step = BIND_MODULE_TX_SETTINGS_REQUEST; -#if defined(SIMU) - modSetup.moduleSettings.txPower = 14; -#else - moduleState[moduleIdx].readModuleSettings(&modSetup.moduleSettings); -#endif - } else { - bindInfo.step = 0; - moduleState[moduleIdx].startBind(&bindInfo); - } - break; - case BIND_MODULE_TX_SETTINGS_REQUEST: - // We just receive the module settings (for TX power) - bindInfo.step = 0; - moduleState[moduleIdx].startBind(&bindInfo); - break; - } - return; - } - - if (bindInfo.step == BIND_INIT && bindInfo.candidateReceiversCount > 0) { - // prevent module mode being reset to NORMAL before exiting - setCloseHandler(nullptr); - deleteLater(); - - // ... and create RX choice dialog - new BindRxChoiceMenu(Layer::back(), moduleIdx, receiverIdx); - return; - } - - BaseDialog::checkEvents(); -} - ReceiverButton::ReceiverButton(Window* parent, rect_t rect, uint8_t moduleIdx, uint8_t receiverIdx) : TextButton(parent, rect, STR_BIND, @@ -256,13 +243,13 @@ uint8_t ReceiverButton::pressBind() if (g_model.moduleData[moduleIdx].pxx2.receiverName[receiverIdx][0] == '\0') { startBind(); } else { - auto menu = new Menu(parent); + auto menu = new Menu(); menu->addLine(STR_BIND, [=]() { startBind(); return 0; }); menu->addLine(STR_OPTIONS, [=]() { - new RxOptions(this, moduleIdx, receiverIdx); + new RxOptions(moduleIdx, receiverIdx); return 0; }); menu->addLine(STR_SHARE, [=]() { @@ -276,7 +263,7 @@ uint8_t ReceiverButton::pressBind() memclear(&modSetup, sizeof(modSetup)); modSetup.resetReceiverIndex = receiverIdx; modSetup.resetReceiverFlags = 0x01; - new ConfirmDialog(parent, STR_RECEIVER, STR_RECEIVER_DELETE, [=]() { + new ConfirmDialog(STR_RECEIVER, STR_RECEIVER_DELETE, [=]() { moduleState[moduleIdx].mode = MODULE_MODE_RESET; removePXX2Receiver(moduleIdx, receiverIdx); }); @@ -287,7 +274,7 @@ uint8_t ReceiverButton::pressBind() memclear(&modSetup, sizeof(modSetup)); modSetup.resetReceiverIndex = receiverIdx; modSetup.resetReceiverFlags = 0xFF; - new ConfirmDialog(parent, STR_RECEIVER, STR_RECEIVER_RESET, [=]() { + new ConfirmDialog(STR_RECEIVER, STR_RECEIVER_RESET, [=]() { moduleState[moduleIdx].mode = MODULE_MODE_RESET; removePXX2Receiver(moduleIdx, receiverIdx); }); @@ -321,7 +308,7 @@ void ReceiverButton::startBind() moduleState[moduleIdx].startBind(&bindInfo); } - new BindWaitDialog(parent, moduleIdx, receiverIdx); + new BindWaitDialog(moduleIdx, receiverIdx); } void ReceiverButton::checkEvents() @@ -342,8 +329,8 @@ void ReceiverButton::checkEvents() TextButton::checkEvents(); } -RegisterDialog::RegisterDialog(Window* parent, uint8_t moduleIdx) : - BaseDialog(parent, STR_REGISTER, true, DIALOG_DEFAULT_WIDTH, +RegisterDialog::RegisterDialog(uint8_t moduleIdx) : + BaseDialog(STR_REGISTER, true, DIALOG_DEFAULT_WIDTH, LV_SIZE_CONTENT), moduleIdx(moduleIdx) { @@ -439,8 +426,8 @@ void RegisterDialog::checkEvents() BaseDialog::checkEvents(); } -ModuleOptions::ModuleOptions(Window* parent, uint8_t moduleIdx) : - BaseDialog(parent, STR_MODULE_OPTIONS, true), moduleIdx(moduleIdx) +ModuleOptions::ModuleOptions(uint8_t moduleIdx) : + BaseDialog(STR_MODULE_OPTIONS, true), moduleIdx(moduleIdx) { new StaticText(form, rect_t{}, STR_WAITING_FOR_MODULE); @@ -632,8 +619,8 @@ void ModuleOptions::writeSettings() } } -RxOptions::RxOptions(Window* parent, uint8_t moduleIdx, uint8_t rxIdx) : - BaseDialog(parent, STR_RECEIVER_OPTIONS, true), +RxOptions::RxOptions(uint8_t moduleIdx, uint8_t rxIdx) : + BaseDialog(STR_RECEIVER_OPTIONS, true), moduleIdx(moduleIdx), receiverIdx(rxIdx) { diff --git a/radio/src/gui/colorlcd/module/access_settings.h b/radio/src/gui/colorlcd/module/access_settings.h index 9f67356f1ef..4018c348158 100644 --- a/radio/src/gui/colorlcd/module/access_settings.h +++ b/radio/src/gui/colorlcd/module/access_settings.h @@ -47,7 +47,7 @@ class ReceiverButton : public TextButton class RegisterDialog : public BaseDialog { public: - RegisterDialog(Window* parent, uint8_t moduleIdx); + RegisterDialog(uint8_t moduleIdx); void start(); void checkEvents() override; @@ -65,7 +65,7 @@ class RegisterDialog : public BaseDialog class ModuleOptions : public BaseDialog { public: - ModuleOptions(Window* parent, uint8_t moduleIdx); + ModuleOptions(uint8_t moduleIdx); void checkEvents() override; protected: diff --git a/radio/src/gui/colorlcd/module/bind_menu_d16.cpp b/radio/src/gui/colorlcd/module/bind_menu_d16.cpp index ca4d740a482..fd1f0e2e30f 100644 --- a/radio/src/gui/colorlcd/module/bind_menu_d16.cpp +++ b/radio/src/gui/colorlcd/module/bind_menu_d16.cpp @@ -26,10 +26,10 @@ #include "telemetry/multi.h" #endif -BindChoiceMenu::BindChoiceMenu(Window *parent, uint8_t moduleIdx, +BindChoiceMenu::BindChoiceMenu(uint8_t moduleIdx, std::function onPress, std::function onCancelFn) : - Menu(parent), moduleIdx(moduleIdx), onPressHandler(std::move(onPress)) + Menu(), moduleIdx(moduleIdx), onPressHandler(std::move(onPress)) { if (isTelemAllowedOnBind(moduleIdx)) { addLine(STR_BINDING_1_8_TELEM_ON, [=]() { onSelect(Bind_1_8_TELEM_ON); }); diff --git a/radio/src/gui/colorlcd/module/bind_menu_d16.h b/radio/src/gui/colorlcd/module/bind_menu_d16.h index 46a2f57b7cc..269b2d249dd 100644 --- a/radio/src/gui/colorlcd/module/bind_menu_d16.h +++ b/radio/src/gui/colorlcd/module/bind_menu_d16.h @@ -38,6 +38,6 @@ class BindChoiceMenu : public Menu void onSelect(BindChanMode mode); public: - BindChoiceMenu(Window *parent, uint8_t moduleIdx, + BindChoiceMenu(uint8_t moduleIdx, std::function onPress, std::function onCancel); }; diff --git a/radio/src/gui/colorlcd/module/module_setup.cpp b/radio/src/gui/colorlcd/module/module_setup.cpp index 959fd004734..f3ad81e6cc5 100644 --- a/radio/src/gui/colorlcd/module/module_setup.cpp +++ b/radio/src/gui/colorlcd/module/module_setup.cpp @@ -246,7 +246,7 @@ class ModuleWindow : public Window if (isModuleR9MNonAccess(moduleIdx) || isModuleD16(moduleIdx) || IS_R9_MULTI(moduleIdx)) { new BindChoiceMenu( - Layer::back(), moduleIdx, [=]() { bindButton->check(true); }, + moduleIdx, [=]() { bindButton->check(true); }, [=]() { bindButton->check(false); }); return 0; } @@ -316,7 +316,7 @@ class ModuleWindow : public Window if (isModuleISRM(moduleIdx)) { auto options = new TextButton(box, rect_t{}, LV_SYMBOL_SETTINGS); options->setPressHandler([=]() { - new pxx2::ModuleOptions(Layer::back(), moduleIdx); + new pxx2::ModuleOptions(moduleIdx); return 0; }); } @@ -336,7 +336,7 @@ class ModuleWindow : public Window registerButton = new TextButton(box, rect_t{}, STR_REGISTER); registerButton->setPressHandler([=]() -> uint8_t { - new pxx2::RegisterDialog(Layer::back(), moduleIdx); + new pxx2::RegisterDialog(moduleIdx); return 0; }); @@ -354,7 +354,7 @@ class ModuleWindow : public Window auto options = new TextButton(box, rect_t{}, LV_SYMBOL_SETTINGS); options->setPressHandler([=]() { - new pxx2::ModuleOptions(Layer::back(), moduleIdx); + new pxx2::ModuleOptions(moduleIdx); return 0; }); @@ -506,7 +506,7 @@ class ModuleWindow : public Window void startRSSIDialog(std::function closeHandler = nullptr) { auto rssiDialog = new DynamicMessageDialog( - parent, STR_RANGE_TEST, + STR_RANGE_TEST, [=]() { return std::to_string((int)TELEMETRY_RSSI()) + getRxStatLabels()->unit; }, @@ -664,7 +664,7 @@ class ModuleSubTypeChoice : public Choice protos->triggerScan(); if (protos->isScanning()) { - new RfScanDialog(parent, protos, [=]() { updateLayout(); }); + new RfScanDialog(protos, [=]() { updateLayout(); }); } else { TRACE("!protos->isScanning()"); } @@ -685,7 +685,7 @@ class ModuleSubTypeChoice : public Choice { #if defined(MULTIMODULE) if (isModuleMultimodule(moduleIdx)) { - auto menu = new Menu(this); + auto menu = new Menu(); if (menuTitle) menu->setTitle(menuTitle); menu->setCloseHandler([=]() { setEditMode(false); }); diff --git a/radio/src/gui/colorlcd/module/multi_rfprotos.cpp b/radio/src/gui/colorlcd/module/multi_rfprotos.cpp index 8c3d4684bd2..2fce5244aa2 100644 --- a/radio/src/gui/colorlcd/module/multi_rfprotos.cpp +++ b/radio/src/gui/colorlcd/module/multi_rfprotos.cpp @@ -28,9 +28,9 @@ // TODO: translation const char* const RFSCAN_TITLE = "MPM: Scanning protocols..."; -RfScanDialog::RfScanDialog(Window* parent, MultiRfProtocols* protos, +RfScanDialog::RfScanDialog(MultiRfProtocols* protos, std::function onClose) : - ProgressDialog(parent, RFSCAN_TITLE, onClose), + ProgressDialog(RFSCAN_TITLE, onClose), protos(protos) { } diff --git a/radio/src/gui/colorlcd/module/multi_rfprotos.h b/radio/src/gui/colorlcd/module/multi_rfprotos.h index 353d4d393a7..de8a5e66a55 100644 --- a/radio/src/gui/colorlcd/module/multi_rfprotos.h +++ b/radio/src/gui/colorlcd/module/multi_rfprotos.h @@ -31,7 +31,7 @@ class RfScanDialog : public ProgressDialog uint32_t lastUpdate = 0; public: - RfScanDialog(Window* parent, MultiRfProtocols* protos, + RfScanDialog(MultiRfProtocols* protos, std::function onClose); void showProgress(); diff --git a/radio/src/gui/colorlcd/radio/hw_bluetooth.cpp b/radio/src/gui/colorlcd/radio/hw_bluetooth.cpp index c26818b6836..0ce6b764021 100644 --- a/radio/src/gui/colorlcd/radio/hw_bluetooth.cpp +++ b/radio/src/gui/colorlcd/radio/hw_bluetooth.cpp @@ -33,7 +33,7 @@ static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST}; class BTDetailsDialog : public BaseDialog { public: - BTDetailsDialog(Window* parent) : BaseDialog(parent, STR_BLUETOOTH, true) + BTDetailsDialog() : BaseDialog(STR_BLUETOOTH, true) { FlexGridLayout grid(col_dsc, row_dsc); @@ -86,7 +86,7 @@ BluetoothConfigWindow::BluetoothConfigWindow(Window* parent, FlexGridLayout& gri settingsBtn = new TextButton(box, rect_t{}, LV_SYMBOL_SETTINGS, [=]() -> uint8_t { - new BTDetailsDialog(parent); + new BTDetailsDialog(); return 0; }); settingsBtn->show(g_eeGeneral.bluetoothMode != BLUETOOTH_OFF); diff --git a/radio/src/gui/colorlcd/radio/hw_inputs.cpp b/radio/src/gui/colorlcd/radio/hw_inputs.cpp index 248098758f1..56ae449a90e 100644 --- a/radio/src/gui/colorlcd/radio/hw_inputs.cpp +++ b/radio/src/gui/colorlcd/radio/hw_inputs.cpp @@ -267,7 +267,7 @@ HWSwitches::HWSwitches(Window* parent) : template HWInputDialog::HWInputDialog(const char* title) : - BaseDialog(Layer::back(), title, true) + BaseDialog(title, true) { new T(form); } diff --git a/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp b/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp index d1a99c3354e..9bd285f8561 100644 --- a/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp +++ b/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp @@ -95,8 +95,8 @@ ModuleCallback onUpdateStateChangedCallbackFor(FrskyOtaFlashDialog* dialog); class FrskyOtaFlashDialog : public BaseDialog { public: - explicit FrskyOtaFlashDialog(Window* parent, const char* title) : - BaseDialog(parent, title, true) + explicit FrskyOtaFlashDialog(const char* title) : + BaseDialog(title, true) { new StaticText(form, rect_t{}, STR_WAITING_FOR_RX); } @@ -137,8 +137,7 @@ class FrskyOtaFlashDialog : public BaseDialog *tmp++ = '.'; tmp = strAppendUnsigned(tmp, reusableBuffer.sdManager.otaUpdateInformation.receiverInformation.swVersion.revision); - updateConfirmDialog = new ConfirmDialog(Layer::back(), - getPXX2ReceiverName(modelId), + updateConfirmDialog = new ConfirmDialog(getPXX2ReceiverName(modelId), std::string(reusableBuffer.sdManager.otaReceiverVersion).c_str(), [=]() { onUpdateConfirmation(); }, [=]() { deleteLater(); }); @@ -156,7 +155,7 @@ class FrskyOtaFlashDialog : public BaseDialog if (reusableBuffer.sdManager.otaUpdateInformation.candidateReceiversCount > 0) { if (reusableBuffer.sdManager.otaUpdateInformation.candidateReceiversCount != popupReceiversCount) { if (rxChoiceMenu == nullptr) { - rxChoiceMenu = new Menu(Layer::back()); + rxChoiceMenu = new Menu(); rxChoiceMenu->setTitle(STR_PXX2_SELECT_RX); rxChoiceMenu->setCancelHandler([=]() { // Seems menu didn't delete itself before call cancelHandler(). @@ -293,8 +292,7 @@ void RadioSdManagerPage::dirAction(const char* path, const char* name, { if (strcmp(name, "..") == 0) return; - auto window = Layer::back(); - auto menu = new Menu(window); + auto menu = new Menu(); menu->addLine(STR_RENAME_FILE, [=]() { uint8_t nameLength; uint8_t extLength; @@ -308,7 +306,7 @@ void RadioSdManagerPage::dirAction(const char* path, const char* name, std::string extension(""); if (ext) extension = ext; - new LabelDialog(Layer::back(), fname.c_str(), maxNameLength, STR_RENAME_FILE, [=](std::string label) { + new LabelDialog(fname.c_str(), maxNameLength, STR_RENAME_FILE, [=](std::string label) { label += extension; f_rename((const TCHAR *)name, (const TCHAR *)label.c_str()); browser->refresh(); @@ -316,7 +314,7 @@ void RadioSdManagerPage::dirAction(const char* path, const char* name, }); menu->addLine(STR_DELETE_FILE, [=]() { if (f_unlink(fullpath) != FR_OK) { - new MessageDialog(window, STR_DELETE_FILE, STR_DEL_DIR_NOT_EMPTY); + new MessageDialog(STR_DELETE_FILE, STR_DEL_DIR_NOT_EMPTY); } browser->refresh(); }); @@ -325,8 +323,7 @@ void RadioSdManagerPage::dirAction(const char* path, const char* name, void RadioSdManagerPage::fileAction(const char* path, const char* name, const char* fullpath) { - auto window = Layer::back(); - auto menu = new Menu(window); + auto menu = new Menu(); const char* ext = getFileExtension(name); if (ext) { if (!strcasecmp(ext, SOUNDS_EXT)) { @@ -376,7 +373,7 @@ void RadioSdManagerPage::fileAction(const char* path, const char* name, char buf[64]; sprintf(buf, " %s %dkB. %s", STR_FILE_SIZE, fileLength / 1024, STR_FILE_OPEN); - new ConfirmDialog(window, STR_WARNING, buf, + new ConfirmDialog(STR_WARNING, buf, [=] { new ViewTextWindow(path, name, ICON_RADIO_SD_MANAGER); }); } else { new ViewTextWindow(path, name, ICON_RADIO_SD_MANAGER); @@ -434,7 +431,6 @@ void RadioSdManagerPage::fileAction(const char* path, const char* name, information.productId)) menu->addLine(STR_FLASH_RECEIVER_BY_INTERNAL_MODULE_OTA, [=]() { auto dialog = new FrskyOtaFlashDialog( - Layer::back(), STR_FLASH_RECEIVER_BY_INTERNAL_MODULE_OTA); dialog->flash(fullpath, INTERNAL_MODULE); }); @@ -443,7 +439,6 @@ void RadioSdManagerPage::fileAction(const char* path, const char* name, information.productId)) menu->addLine(STR_FLASH_RECEIVER_BY_EXTERNAL_MODULE_OTA, [=]() { auto dialog = new FrskyOtaFlashDialog( - Layer::back(), STR_FLASH_RECEIVER_BY_EXTERNAL_MODULE_OTA); dialog->flash(fullpath, EXTERNAL_MODULE); }); @@ -453,7 +448,6 @@ void RadioSdManagerPage::fileAction(const char* path, const char* name, menu->addLine(STR_FLASH_FLIGHT_CONTROLLER_BY_INTERNAL_MODULE_OTA, [=]() { auto dialog = new FrskyOtaFlashDialog( - Layer::back(), STR_FLASH_FLIGHT_CONTROLLER_BY_INTERNAL_MODULE_OTA); dialog->flash(fullpath, INTERNAL_MODULE); }); @@ -461,7 +455,6 @@ void RadioSdManagerPage::fileAction(const char* path, const char* name, menu->addLine(STR_FLASH_FLIGHT_CONTROLLER_BY_EXTERNAL_MODULE_OTA, [=]() { auto dialog = new FrskyOtaFlashDialog( - Layer::back(), STR_FLASH_FLIGHT_CONTROLLER_BY_EXTERNAL_MODULE_OTA); dialog->flash(fullpath, EXTERNAL_MODULE); }); @@ -527,7 +520,7 @@ void RadioSdManagerPage::fileAction(const char* path, const char* name, std::string extension(""); if (ext) extension = ext; - new LabelDialog(Layer::back(), fname.c_str(), maxNameLength, STR_RENAME_FILE, [=](std::string label) { + new LabelDialog(fname.c_str(), maxNameLength, STR_RENAME_FILE, [=](std::string label) { label += extension; f_rename((const TCHAR *)name, (const TCHAR *)label.c_str()); browser->refresh(); diff --git a/radio/src/gui/colorlcd/radio/radio_setup.cpp b/radio/src/gui/colorlcd/radio/radio_setup.cpp index ac0f1b8a6e3..81993448304 100644 --- a/radio/src/gui/colorlcd/radio/radio_setup.cpp +++ b/radio/src/gui/colorlcd/radio/radio_setup.cpp @@ -877,7 +877,7 @@ static SetupLineDef setupLines[] = { new Choice(parent, {x, y, 120, 0}, STR_HATSOPT, HATSMODE_TRIMS_ONLY, HATSMODE_SWITCHABLE, GET_SET_DEFAULT(g_eeGeneral.hatsMode)); new TextButton(parent, {x + 120 + PAD_MEDIUM, y, 0, 0}, "?", [=]() { - new MessageDialog(parent, STR_HATSMODE_KEYS, STR_HATSMODE_KEYS_HELP, "", + new MessageDialog(STR_HATSMODE_KEYS, STR_HATSMODE_KEYS_HELP, "", LEFT); return 0; }); diff --git a/radio/src/gui/colorlcd/radio/radio_theme.cpp b/radio/src/gui/colorlcd/radio/radio_theme.cpp index a2a0cdefddf..82ee7bf4d1a 100644 --- a/radio/src/gui/colorlcd/radio/radio_theme.cpp +++ b/radio/src/gui/colorlcd/radio/radio_theme.cpp @@ -99,9 +99,9 @@ class ThemeDetailsDialog : public BaseDialog { public: ThemeDetailsDialog( - Window *parent, ThemeFile theme, + ThemeFile theme, std::function saveHandler = nullptr) : - BaseDialog(parent, STR_EDIT_THEME_DETAILS, false, DIALOG_DEFAULT_WIDTH, + BaseDialog(STR_EDIT_THEME_DETAILS, false, DIALOG_DEFAULT_WIDTH, LV_SIZE_CONTENT), theme(theme), saveHandler(saveHandler) @@ -346,8 +346,7 @@ class ThemeEditPage : public Page void onCancel() override { if (_dirty) { - new ConfirmDialog( - this, STR_SAVE_THEME, _theme.getName().c_str(), + new ConfirmDialog(STR_SAVE_THEME, _theme.getName().c_str(), [=]() { if (saveHandler != nullptr) { saveHandler(_theme); @@ -391,7 +390,7 @@ class ThemeEditPage : public Page // save and cancel rect_t r = {LCD_W - (ColorEditPage::BUTTON_WIDTH + 5), PAD_MEDIUM, ColorEditPage::BUTTON_WIDTH, 0}; new TextButton(window, r, STR_DETAILS, [=]() { - new ThemeDetailsDialog(page, _theme, [=](ThemeFile t) { + new ThemeDetailsDialog(_theme, [=](ThemeFile t) { _theme.setAuthor(t.getAuthor()); _theme.setInfo(t.getInfo()); _theme.setName(t.getName()); @@ -480,7 +479,7 @@ void ThemeSetupPage::checkEvents() void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp) { - auto menu = new Menu(listBox, false); + auto menu = new Menu(false); // you can't activate the active theme if (listBox->getSelected() != tp->getThemeIndex()) { @@ -529,7 +528,7 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp) menu->addLine(STR_DUPLICATE, [=]() { ThemeFile newTheme; - new ThemeDetailsDialog(window, newTheme, [=](ThemeFile theme) { + new ThemeDetailsDialog(newTheme, [=](ThemeFile theme) { if (!theme.getName().empty()) { char name[SELECTED_THEME_NAME_LEN + 1]; int n = 0; @@ -565,8 +564,7 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp) if (listBox->getSelected() != 0 && listBox->getSelected() != tp->getThemeIndex()) { menu->addLine(STR_DELETE, [=]() { - new ConfirmDialog( - window, STR_DELETE_THEME, + new ConfirmDialog(STR_DELETE_THEME, tp->getThemeByIndex(listBox->getSelected())->getName().c_str(), [=] { tp->deleteThemeByIndex(listBox->getSelected()); listBox->setNames(tp->getNames()); diff --git a/radio/src/gui/colorlcd/radio/radio_tools.cpp b/radio/src/gui/colorlcd/radio/radio_tools.cpp index ffe5148d8a7..abac43a6cca 100644 --- a/radio/src/gui/colorlcd/radio/radio_tools.cpp +++ b/radio/src/gui/colorlcd/radio/radio_tools.cpp @@ -101,7 +101,7 @@ static void run_lua_tool(Window* parent, const std::string& path) *((char*)getBasename(toolPath) - 1) = '\0'; f_chdir(toolPath); - luaExec(path.c_str()); + luaExecStandalone(path.c_str()); } // LUA scripts in TOOLS diff --git a/radio/src/gui/colorlcd/radio/radio_version.cpp b/radio/src/gui/colorlcd/radio/radio_version.cpp index affc73b6c29..826ae4d4f5a 100644 --- a/radio/src/gui/colorlcd/radio/radio_version.cpp +++ b/radio/src/gui/colorlcd/radio/radio_version.cpp @@ -73,8 +73,8 @@ class VersionDialog : public BaseDialog StaticText* ext_rx_status; public: - VersionDialog(Window* parent) : - BaseDialog(parent, STR_MODULES_RX_VERSION, true) + VersionDialog() : + BaseDialog(STR_MODULES_RX_VERSION, true) { #if defined(PXX2) memclear(&reusableBuffer.hardwareAndSettings.modules, @@ -378,7 +378,7 @@ void RadioVersionPage::build(Window* window) // Module and receivers versions auto btn = new TextButton(window, rect_t{}, STR_MODULES_RX_VERSION); btn->setPressHandler([=]() -> uint8_t { - new VersionDialog(window); + new VersionDialog(); return 0; }); lv_obj_set_width(btn->getLvObj(), lv_pct(100)); diff --git a/radio/src/gui/colorlcd/standalone_lua.cpp b/radio/src/gui/colorlcd/standalone_lua.cpp index bec2d3c5f3c..1244cc85c2b 100644 --- a/radio/src/gui/colorlcd/standalone_lua.cpp +++ b/radio/src/gui/colorlcd/standalone_lua.cpp @@ -24,13 +24,110 @@ #include "translations.h" #include "view_main.h" #include "dma2d.h" +#include "lua/lua_event.h" + +lua_State *lsStandalone = nullptr; + +#if defined(LUA_ALLOCATOR_TRACER) +LuaMemTracer lsStandaloneTrace; + +static void luaStandaloneHook(lua_State * L, lua_Debug *ar) +{ + else if (ar->event == LUA_HOOKLINE) { + lua_getinfo(L, "nSl", ar); + LuaMemTracer * tracer = &lsStandaloneTrace; + if (tracer->alloc || tracer->free) { + TRACE("LT: [+%u,-%u] %s:%d", tracer->alloc, tracer->free, tracer->script, tracer->lineno); + } + tracer->script = ar->source; + tracer->lineno = ar->currentline; + tracer->alloc = 0; + tracer->free = 0; + } +} +#endif // #if defined(LUA_ALLOCATOR_TRACER) + +static void luaStandaloneInit() +{ + luaClose(&lsStandalone); + +#if defined(USE_BIN_ALLOCATOR) + lsStandalone = lua_newstate(bin_l_alloc, nullptr); //we use our own allocator! +#elif defined(LUA_ALLOCATOR_TRACER) + memclear(&lsStandaloneTrace, sizeof(lsStandaloneTrace)); + lsStandaloneTrace.script = "lua_newstate(scripts)"; + lsStandalone = lua_newstate(tracer_alloc, &lsStandaloneTrace); //we use tracer allocator +#else + lsStandalone = lua_newstate(l_alloc, nullptr); //we use Lua default allocator +#endif + if (lsStandalone) { + // install our panic handler + extern int custom_lua_atpanic(lua_State *L); + lua_atpanic(lsStandalone, &custom_lua_atpanic); + +#if defined(LUA_ALLOCATOR_TRACER) + lua_sethook(L, luaStandaloneHook, LUA_MASKLINE); +#endif + + // protect libs and constants registration + PROTECT_LUA() { + luaRegisterLibraries(lsStandalone); + } + else { + // if we got panic during registration + // we disable Lua for this session + luaClose(&lsStandalone); + lsStandalone = 0; + } + UNPROTECT_LUA(); + } +} + +void luaExecStandalone(const char * filename) +{ + if (lsStandalone == NULL) + luaStandaloneInit(); + + PROTECT_LUA() { + if (luaLoadScriptFileToState(lsStandalone, filename, LUA_SCRIPT_LOAD_MODE) == SCRIPT_OK) { + if (lua_pcall(lsStandalone, 0, 1, 0) == LUA_OK && lua_istable(lsStandalone, -1)) { + int initFunction = LUA_REFNIL, runFunction = LUA_REFNIL; + bool lvglLayout = false; + + for (lua_pushnil(lsStandalone); lua_next(lsStandalone, -2); lua_pop(lsStandalone, 1)) { + const char * key = lua_tostring(lsStandalone, -2); + if (!strcmp(key, "init")) { + initFunction = luaL_ref(lsStandalone, LUA_REGISTRYINDEX); + lua_pushnil(lsStandalone); + } else if (!strcmp(key, "run")) { + runFunction = luaL_ref(lsStandalone, LUA_REGISTRYINDEX); + lua_pushnil(lsStandalone); + } else if (!strcasecmp(key, "useLvgl")) { + lvglLayout = lua_toboolean(lsStandalone, -1); + } + } + + StandaloneLuaWindow::setup(lvglLayout, initFunction, runFunction); + } + else { + TRACE("luaLoadFile(%s): Error parsing script: %s", filename, lua_tostring(lsStandalone, -1)); + } + } + } + else { + // error while loading Lua widget/theme, + // do not disable whole Lua state, just ingnore bad script + return; + } + UNPROTECT_LUA(); +} // singleton instance StandaloneLuaWindow* StandaloneLuaWindow::_instance; -StandaloneLuaWindow::StandaloneLuaWindow(bool useLvgl) : +StandaloneLuaWindow::StandaloneLuaWindow(bool useLvgl, int initFn, int runFn) : Window(MainWindow::instance(), {0, 0, LCD_W, LCD_H}), - useLvgl(useLvgl) + useLvgl(useLvgl), initFunction(initFn), runFunction(runFn) { setWindowFlag(OPAQUE); @@ -45,6 +142,7 @@ StandaloneLuaWindow::StandaloneLuaWindow(bool useLvgl) : lv_obj_set_size(lbl, LCD_W, LCD_H); etx_solid_bg(lbl, COLOR_THEME_PRIMARY1_INDEX); etx_txt_color(lbl, COLOR_THEME_PRIMARY2_INDEX); + etx_font(lbl, FONT_XL_INDEX); lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); lv_obj_set_style_pad_top(lbl, (LCD_H - EdgeTxStyles::PAGE_LINE_HEIGHT) / 2, LV_PART_MAIN); lv_label_set_text(lbl, STR_LOADING); @@ -69,12 +167,22 @@ StandaloneLuaWindow::StandaloneLuaWindow(bool useLvgl) : setupHandler(this); attach(); + + lua_gc(lsStandalone, LUA_GCCOLLECT, 0); + + // Pause function and mixer scripts + prevLuaState = luaState; + luaState = INTERPRETER_PAUSED; + +#if defined(USE_HATS_AS_KEYS) + setTransposeHatsForLUA(true); +#endif } -void StandaloneLuaWindow::setup(bool useLvgl) +void StandaloneLuaWindow::setup(bool useLvgl, int initFn, int runFn) { if (_instance == nullptr) - _instance = new StandaloneLuaWindow(useLvgl); + _instance = new StandaloneLuaWindow(useLvgl, initFn, runFn); } StandaloneLuaWindow* StandaloneLuaWindow::instance() @@ -102,6 +210,11 @@ void StandaloneLuaWindow::deleteLater(bool detach, bool trash) { if (_deleted) return; + if (initFunction != LUA_REFNIL) luaL_unref(lsStandalone, LUA_REGISTRYINDEX, initFunction); + if (runFunction != LUA_REFNIL) luaL_unref(lsStandalone, LUA_REGISTRYINDEX, runFunction); + lua_settop(lsStandalone, 0); + luaLcdBuffer = nullptr; + if (lcdBuffer) delete lcdBuffer; lcdBuffer = nullptr; @@ -118,6 +231,12 @@ void StandaloneLuaWindow::deleteLater(bool detach, bool trash) _instance = nullptr; } +#if defined(USE_HATS_AS_KEYS) + setTransposeHatsForLUA(false); +#endif + + luaState = prevLuaState; + Window::deleteLater(detach, trash); } @@ -125,38 +244,78 @@ void StandaloneLuaWindow::checkEvents() { Window::checkEvents(); + if (initFunction != LUA_REFNIL) { + lua_rawgeti(lsStandalone, LUA_REGISTRYINDEX, initFunction); + if (lua_pcall(lsStandalone, 0, 0, 0) != LUA_OK) { + luaShowError(); + } + luaL_unref(lsStandalone, LUA_REGISTRYINDEX, initFunction); + initFunction = LUA_REFNIL; + return; + } + // Set global LUA LCD buffer luaLcdBuffer = lcdBuffer; + luaLcdAllowed = !useLvglLayout(); - if (luaState != INTERPRETER_RELOAD_PERMANENT_SCRIPTS) { - if (luaTask(true)) { - if (useLvglLayout() && !hasError) { - // if LUA finished a full cycle, - // call update functions - PROTECT_LUA() { - if (!callRefs(lsScripts)) { - luaShowError(); + LuaEventData evt; + // Pull a new event from the buffer + luaNextEvent(&evt); + if (evt.event == EVT_KEY_LONG(KEY_EXIT)) { + killEvents(evt.event); + deleteLater(); + } else { + if (runFunction != LUA_REFNIL) { + lua_rawgeti(lsStandalone, LUA_REGISTRYINDEX, runFunction); + + lua_pushunsigned(lsStandalone, evt.event); + int inputsCount = 1; + +#if defined(HARDWARE_TOUCH) + if (IS_TOUCH_EVENT(evt.event)) { + luaPushTouchEventTable(lsStandalone, &evt); + inputsCount += 1; + } +#endif + + if (lua_pcall(lsStandalone, inputsCount, 1, 0) != LUA_OK) { + luaShowError(); + } else { + if (lua_isnumber(lsStandalone, -1)) { + int scriptResult = lua_tointeger(lsStandalone, -1); + lua_pop(lsStandalone, 1); /* pop returned value */ + if (scriptResult != 0) { + deleteLater(); + } else { + if (useLvglLayout() && !hasError) { + PROTECT_LUA() { + if (!callRefs(lsStandalone)) { + luaShowError(); + } + } else { + luaShowError(); + } + UNPROTECT_LUA(); + } else { + invalidate(); + } } - } else { - luaShowError(); + } else if (lua_isstring(lsStandalone, -1)) { + char nextScript[FF_MAX_LFN+1]; + strncpy(nextScript, lua_tostring(lsStandalone, -1), FF_MAX_LFN); + nextScript[FF_MAX_LFN] = '\0'; + _instance = nullptr; + lua_settop(lsStandalone, 0); + deleteLater(); + luaExecStandalone(nextScript); } - UNPROTECT_LUA(); - } else { - // if LUA finished a full cycle, - // invalidate to display the screen buffer - invalidate(); } } } - if (luaState == INTERPRETER_RELOAD_PERMANENT_SCRIPTS) { - // Script does not run anymore... - TRACE("LUA standalone script exited: deleting window!"); - deleteLater(); - } - // Kill global LUA LCD buffer luaLcdBuffer = nullptr; + luaLcdAllowed = false; } void StandaloneLuaWindow::onClicked() { Keyboard::hide(false); LuaEventHandler::onClicked(); } @@ -212,19 +371,22 @@ bool StandaloneLuaWindow::displayPopup(event_t event, uint8_t type, void StandaloneLuaWindow::clear() { - clearRefs(lsScripts); + clearRefs(lsStandalone); Window::clear(); } void StandaloneLuaWindow::luaShowError() { + if (runFunction != LUA_REFNIL) luaL_unref(lsStandalone, LUA_REGISTRYINDEX, runFunction); + runFunction = LUA_REFNIL; hasError = true; extern void luaError(lua_State *L, uint8_t error); - luaError(lsScripts, SCRIPT_SYNTAX_ERROR); + luaError(lsStandalone, SCRIPT_SYNTAX_ERROR); } void StandaloneLuaWindow::showError(bool firstCall, const char* title, const char* msg) { + runFunction = LUA_REFNIL; hasError = true; if (!errorModal) { lv_obj_set_scroll_dir(lvobj, LV_DIR_NONE); diff --git a/radio/src/gui/colorlcd/standalone_lua.h b/radio/src/gui/colorlcd/standalone_lua.h index e6f9e99ae87..398c4961d87 100644 --- a/radio/src/gui/colorlcd/standalone_lua.h +++ b/radio/src/gui/colorlcd/standalone_lua.h @@ -26,15 +26,17 @@ #include "lua/lua_api.h" #include "lua/lua_widget.h" +extern void luaExecStandalone(const char * filename); + class StandaloneLuaWindow : public Window, public LuaEventHandler, public LuaLvglManager { static StandaloneLuaWindow* _instance; - explicit StandaloneLuaWindow(bool useLvgl); + explicit StandaloneLuaWindow(bool useLvgl, int initFn, int runFn); public: static StandaloneLuaWindow* instance(); - static void setup(bool useLvgl); + static void setup(bool useLvgl, int initFn, int runFn); void attach(); void deleteLater(bool detach = true, bool trash = true) override; @@ -46,10 +48,11 @@ class StandaloneLuaWindow : public Window, public LuaEventHandler, public LuaLvg bool displayPopup(event_t event, uint8_t type, const char* text, const char* info, bool& result); - Window* getCurrentParent() const override { return tempParent ? tempParent : (Window*)this; } + Window* getCurrentParent() const override { return (tempParent && tempParent->getWindow()) ? tempParent->getWindow() : (Window*)this; } void clear() override; bool useLvglLayout() const override { return useLvgl; } + bool isAppMode() const override { return false; } void luaShowError() override; @@ -68,6 +71,9 @@ class StandaloneLuaWindow : public Window, public LuaEventHandler, public LuaLvg lv_obj_t* errorMsg = nullptr; bool hasError = false; bool useLvgl = false; + int initFunction = LUA_REFNIL; + int runFunction = LUA_REFNIL; + uint8_t prevLuaState; // GFX BitmapBuffer *lcdBuffer = nullptr; @@ -76,9 +82,6 @@ class StandaloneLuaWindow : public Window, public LuaEventHandler, public LuaLvg void popupPaint(BitmapBuffer* dc, coord_t x, coord_t y, coord_t w, coord_t h, const char* text, const char* info); - // run LUA code - void runLua(event_t evt); - void onEvent(event_t evt) override; void checkEvents() override; void onClicked() override; diff --git a/radio/src/gui/gui_common.cpp b/radio/src/gui/gui_common.cpp index 490194e986a..d2783cd6ab5 100644 --- a/radio/src/gui/gui_common.cpp +++ b/radio/src/gui/gui_common.cpp @@ -648,7 +648,7 @@ class AntennaSelectionMenu : public Menu bool& done; public: - AntennaSelectionMenu(bool& done) : Menu(MainWindow::instance()), done(done) { + AntennaSelectionMenu(bool& done) : Menu(), done(done) { setTitle(STR_ANTENNA); addLine(STR_USE_INTERNAL_ANTENNA, [] { globalData.externalAntennaEnabled = false; }); diff --git a/radio/src/keys.h b/radio/src/keys.h index 2f55d585310..4c6d7792648 100644 --- a/radio/src/keys.h +++ b/radio/src/keys.h @@ -200,7 +200,6 @@ bool rotaryEncoderPollingCycle(); void setHatsAsKeys(bool val); bool getHatsAsKeys(); void setTransposeHatsForLUA(bool val); -bool getTransposeHatsForLUA(); #endif struct InactivityData diff --git a/radio/src/lua/api_colorlcd_lvgl.cpp b/radio/src/lua/api_colorlcd_lvgl.cpp index a4b4bebfa72..6278c454b3d 100644 --- a/radio/src/lua/api_colorlcd_lvgl.cpp +++ b/radio/src/lua/api_colorlcd_lvgl.cpp @@ -44,16 +44,16 @@ 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 standalone = false) { if (luaLvglManager && (!standalone || !luaLvglManager->isWidget())) { - LvglWidgetObject* p = nullptr; - Window* prevParent = nullptr; + LvglWidgetObjectBase* p = nullptr; + LvglWidgetObjectBase* prevParent = nullptr; if (lua_gettop(L) == 2) { - p = LvglWidgetObject::checkLvgl(L, 1); + p = LvglWidgetObjectBase::checkLvgl(L, 1); if (p) { prevParent = luaLvglManager->getTempParent(); - luaLvglManager->setTempParent(p->getWindow()); + luaLvglManager->setTempParent(p); } } @@ -71,32 +71,27 @@ static int luaLvglObjEx(lua_State *L, std::function create, return 1; } -static int luaLvglConfirm(lua_State *L) +static int luaLvglPopup(lua_State *L, std::function create) { - auto obj = new LvglWidgetConfirmDialog(); + auto obj = create(); obj->getParams(L, 1); obj->build(L); return 0; } -LvglWidgetObject *LvglWidgetObject::checkLvgl(lua_State *L, int index) -{ - LvglWidgetObject **p = - (LvglWidgetObject **)luaL_checkudata(L, index, LVGL_METATABLE); - if (p) return *p; - return nullptr; -} - static int luaDestroyLvglWidget(lua_State *L) { - auto p = LvglWidgetObject::checkLvgl(L, 1); - if (p) delete p; + auto p = LvglWidgetObjectBase::checkLvgl(L, 1); + if (p) { + p->clearRefs(L); + delete p; + } return 0; } static int luaLvglSet(lua_State *L) { - auto p = LvglWidgetObject::checkLvgl(L, 1); + auto p = LvglWidgetObjectBase::checkLvgl(L, 1); if (p) { p->update(L); } @@ -107,9 +102,10 @@ static int luaLvglClear(lua_State *L) { if (luaLvglManager) { if (lua_gettop(L) == 1) { - auto p = LvglWidgetObject::checkLvgl(L, 1); - if (p) - p->getWindow()->clear(); + auto p = LvglWidgetObjectBase::checkLvgl(L, 1); + if (p) { + p->clear(); + } } else { luaLvglManager->clear(); } @@ -120,7 +116,7 @@ static int luaLvglClear(lua_State *L) static int luaLvglShow(lua_State *L) { - auto p = LvglWidgetObject::checkLvgl(L, 1); + auto p = LvglWidgetObjectBase::checkLvgl(L, 1); if (p) { p->show(); } @@ -129,7 +125,7 @@ static int luaLvglShow(lua_State *L) static int luaLvglHide(lua_State *L) { - auto p = LvglWidgetObject::checkLvgl(L, 1); + auto p = LvglWidgetObjectBase::checkLvgl(L, 1); if (p) { p->hide(); } @@ -164,50 +160,72 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) luaL_checktype(L, srcIndex, LUA_TTABLE); for (lua_pushnil(L); lua_next(L, srcIndex - 1); lua_pop(L, 1)) { LvglWidgetParams p(L, -1); - LvglWidgetObject *lvobj = nullptr; + LvglWidgetObjectBase *obj = nullptr; if (strcasecmp(p.type, "label") == 0) - lvobj = new LvglWidgetLabel(); + obj = new LvglWidgetLabel(); else if (strcasecmp(p.type, "rectangle") == 0) - lvobj = new LvglWidgetRectangle(); + obj = new LvglWidgetRectangle(); else if (strcasecmp(p.type, "circle") == 0) - lvobj = new LvglWidgetCircle(); + obj = new LvglWidgetCircle(); else if (strcasecmp(p.type, "arc") == 0) - lvobj = new LvglWidgetArc(); + 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) - lvobj = new LvglWidgetImage(); + obj = new LvglWidgetImage(); else if (strcasecmp(p.type, "qrcode") == 0) - lvobj = new LvglWidgetQRCode(); - // else if (strcasecmp(p.type, "meter") == 0) - // lvobj = new LvglWidgetMeter(); + 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()) { if (strcasecmp(p.type, "button") == 0) - lvobj = new LvglWidgetTextButton(); + obj = new LvglWidgetTextButton(); else if (strcasecmp(p.type, "toggle") == 0) - lvobj = new LvglWidgetToggleSwitch(); + obj = new LvglWidgetToggleSwitch(); else if (strcasecmp(p.type, "textEdit") == 0) - lvobj = new LvglWidgetTextEdit(); + obj = new LvglWidgetTextEdit(); else if (strcasecmp(p.type, "numberEdit") == 0) - lvobj = new LvglWidgetNumberEdit(); + obj = new LvglWidgetNumberEdit(); else if (strcasecmp(p.type, "choice") == 0) - lvobj = new LvglWidgetChoice(); + obj = new LvglWidgetChoice(); else if (strcasecmp(p.type, "slider") == 0) - lvobj = new LvglWidgetSlider(); + obj = new LvglWidgetSlider(); else if (strcasecmp(p.type, "page") == 0) - lvobj = new LvglWidgetPage(); + obj = new LvglWidgetPage(); + else if (strcasecmp(p.type, "font") == 0) + obj = new LvglWidgetFontPicker(); + else if (strcasecmp(p.type, "align") == 0) + obj = new LvglWidgetAlignPicker(); + else if (strcasecmp(p.type, "color") == 0) + obj = new LvglWidgetColorPicker(); + else if (strcasecmp(p.type, "timer") == 0) + obj = new LvglWidgetTimerPicker(); + else if (strcasecmp(p.type, "switch") == 0) + obj = new LvglWidgetSwitchPicker(); + else if (strcasecmp(p.type, "source") == 0) + obj = new LvglWidgetSourcePicker(); } - if (lvobj) { - lvobj->getParams(L, -1); - lvobj->build(L); - auto ref = lvobj->getRef(L); + if (obj) { + obj->getParams(L, -1); + obj->build(L); + auto ref = obj->getRef(L); if (p.name) { lua_pushstring(L, p.name); lua_rawgeti(L, LUA_REGISTRYINDEX, ref); lua_settable(L, refIndex - 4); } - if (p.hasChildren) { + if (p.hasChildren && obj->getWindow()) { lua_getfield(L, -1, "children"); auto prevParent = luaLvglManager->getTempParent(); - luaLvglManager->setTempParent(lvobj->getWindow()); + luaLvglManager->setTempParent(obj); buildLvgl(L, -1, refIndex - 3); lua_pop(L, 1); luaLvglManager->setTempParent(prevParent); @@ -219,13 +237,13 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex) static int luaLvglBuild(lua_State *L) { if (luaLvglManager) { - LvglWidgetObject* p = nullptr; - Window* prevParent = nullptr; + LvglWidgetObjectBase* p = nullptr; + LvglWidgetObjectBase* prevParent = nullptr; if (lua_gettop(L) == 2) { - p = LvglWidgetObject::checkLvgl(L, 1); + p = LvglWidgetObjectBase::checkLvgl(L, 1); if (p) { prevParent = luaLvglManager->getTempParent(); - luaLvglManager->setTempParent(p->getWindow()); + luaLvglManager->setTempParent(p); } } @@ -241,40 +259,116 @@ static int luaLvglBuild(lua_State *L) return 1; } -LROT_BEGIN(lvgllib, NULL, LROT_MASK_GC_INDEX) -LROT_FUNCENTRY(__gc, luaDestroyLvglWidget) -LROT_TABENTRY(__index, lvgllib) -LROT_FUNCENTRY(clear, luaLvglClear) -LROT_FUNCENTRY(build, luaLvglBuild) -// Objects - widgets and standalone -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(circle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetCircle(); }); }) -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(); }); }) -// LROT_FUNCENTRY(meter, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetMeter(); }); }) -// Objects - standalone scripts only -LROT_FUNCENTRY(button, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTextButton(); }, 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); }) -// Containers -LROT_FUNCENTRY(page, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetPage(); }, true); }) -// Dialogs -LROT_FUNCENTRY(confirm, luaLvglConfirm) -// Manipulation functions -LROT_FUNCENTRY(set, luaLvglSet) -LROT_FUNCENTRY(show, luaLvglShow) -LROT_FUNCENTRY(hide, luaLvglHide) -LROT_END(lvgllib, NULL, LROT_MASK_GC_INDEX) +static int luaLvglIsAppMode(lua_State *L) +{ + if (luaLvglManager) { + lua_pushboolean(L, luaLvglManager->isAppMode()); + } else { + lua_pushboolean(L, false); + } + 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(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(); }); }) + LROT_FUNCENTRY(vline, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetVLine(); }); }) + LROT_FUNCENTRY(line, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetLine(); }); }) + LROT_FUNCENTRY(triangle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTriangle(); }); }) + LROT_FUNCENTRY(circle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetCircle(); }); }) + 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 + LROT_FUNCENTRY(button, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTextButton(); }, 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(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); }) + LROT_FUNCENTRY(timer, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTimerPicker(); }, true); }) + LROT_FUNCENTRY(switch, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSwitchPicker(); }, true); }) + LROT_FUNCENTRY(source, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSourcePicker(); }, true); }) + // Containers + LROT_FUNCENTRY(box, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetBox(); }, true); }) + LROT_FUNCENTRY(setting, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetSetting(); }, true); }) + LROT_FUNCENTRY(page, [](lua_State* L) { return luaLvglObj(L, []() { return new LvglWidgetPage(); }, true); }) + LROT_FUNCENTRY(dialog, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetDialog(); }, true); }) + // Dialogs + LROT_FUNCENTRY(confirm, [](lua_State* L) { return luaLvglPopup(L, []() { return new LvglWidgetConfirmDialog(); }); }) + LROT_FUNCENTRY(message, [](lua_State* L) { return luaLvglPopup(L, []() { return new LvglWidgetMessageDialog(); }); }) + // Object manipulation functions + LROT_FUNCENTRY(set, luaLvglSet) + LROT_FUNCENTRY(show, luaLvglShow) + LROT_FUNCENTRY(hide, luaLvglHide) + 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_END(lvgllib, NULL, 0) + +// Metatable for simple objects (line, arc, label) +LROT_BEGIN(lvgl_base_mt, NULL, LROT_MASK_GC_INDEX) + LROT_FUNCENTRY(__gc, luaDestroyLvglWidget) + LROT_TABENTRY(__index, lvgl_base_mt) + // Object manipulation functions + LROT_FUNCENTRY(set, luaLvglSet) + LROT_FUNCENTRY(show, luaLvglShow) + LROT_FUNCENTRY(hide, luaLvglHide) +LROT_END(lvgl_base_mt, NULL, LROT_MASK_GC_INDEX) + +// Metatable for complex objects +LROT_BEGIN(lvgl_mt, NULL, LROT_MASK_GC_INDEX) + LROT_FUNCENTRY(__gc, luaDestroyLvglWidget) + LROT_TABENTRY(__index, lvgl_mt) + LROT_FUNCENTRY(clear, luaLvglClear) + LROT_FUNCENTRY(build, luaLvglBuild) + // Objects - widgets and standalone + 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(); }); }) + LROT_FUNCENTRY(vline, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetVLine(); }); }) + LROT_FUNCENTRY(line, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetLine(); }); }) + LROT_FUNCENTRY(triangle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTriangle(); }); }) + LROT_FUNCENTRY(circle, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetCircle(); }); }) + 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 + LROT_FUNCENTRY(button, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTextButton(); }, 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(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); }) + LROT_FUNCENTRY(timer, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTimerPicker(); }, true); }) + LROT_FUNCENTRY(switch, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSwitchPicker(); }, true); }) + LROT_FUNCENTRY(source, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetSourcePicker(); }, true); }) + // Object manipulation functions + LROT_FUNCENTRY(set, luaLvglSet) + LROT_FUNCENTRY(show, luaLvglShow) + LROT_FUNCENTRY(hide, luaLvglHide) +LROT_END(lvgl_mt, NULL, LROT_MASK_GC_INDEX) extern "C" { LUALIB_API int luaopen_lvgl(lua_State *L) { - luaL_rometatable(L, LVGL_METATABLE, LROT_TABLEREF(lvgllib)); + luaL_rometatable(L, LVGL_SIMPLEMETATABLE, LROT_TABLEREF(lvgl_base_mt)); + luaL_rometatable(L, LVGL_METATABLE, LROT_TABLEREF(lvgl_mt)); return 0; } } diff --git a/radio/src/lua/interface.cpp b/radio/src/lua/interface.cpp index b75fe8532a2..61034e79242 100644 --- a/radio/src/lua/interface.cpp +++ b/radio/src/lua/interface.cpp @@ -83,13 +83,6 @@ struct our_longjmp * global_lj = nullptr; uint32_t luaExtraMemoryUsage = 0; #endif -#if defined(USE_HATS_AS_KEYS) -static bool _is_standalone_script() -{ - return scriptInternalData[0].reference == SCRIPT_STANDALONE; -} -#endif - #if defined(LUA_ALLOCATOR_TRACER) LuaMemTracer lsScriptsTrace; @@ -257,10 +250,6 @@ void luaDisable() { POPUP_WARNING("Lua disabled!"); luaState = INTERPRETER_PANIC; - -#if defined(USE_HATS_AS_KEYS) - if (_is_standalone_script()) setTransposeHatsForLUA(false); -#endif } void luaClose(lua_State ** L) @@ -893,9 +882,6 @@ static void luaLoadScripts(bool init, const char * filename = nullptr) ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; sid.reference = SCRIPT_STANDALONE; if (luaLoad(filename, sid)) { -#if defined(COLORLCD) - StandaloneLuaWindow::setup(true); -#endif luaError(lsScripts, sid.state); break; } @@ -933,14 +919,6 @@ static void luaLoadScripts(bool init, const char * filename = nullptr) sid.run = luaRegisterFunction("run"); sid.background = luaRegisterFunction("background"); initFunction = luaRegisterFunction("init"); -#if defined(COLORLCD) - if (sid.reference == SCRIPT_STANDALONE) { - lua_getfield(lsScripts, -1, "useLvgl"); - sid.useLvgl = lua_toboolean(lsScripts, -1); - lua_pop(lsScripts, 1); - StandaloneLuaWindow::setup(sid.useLvgl); - } -#endif if (sid.run == LUA_NOREF) { snprintf(lua_warning_info, LUA_WARNING_INFO_LEN, "luaLoadScripts(%.*s): No run function\n", LEN_SCRIPT_FILENAME, getScriptName(idx)); sid.state = SCRIPT_SYNTAX_ERROR; @@ -962,10 +940,6 @@ static void luaLoadScripts(bool init, const char * filename = nullptr) else { snprintf(lua_warning_info, LUA_WARNING_INFO_LEN, "luaLoadScripts(%.*s): The script did not return a table\n", LEN_SCRIPT_FILENAME, getScriptName(idx)); sid.state = SCRIPT_SYNTAX_ERROR; -#if defined(COLORLCD) - if (sid.reference == SCRIPT_STANDALONE) - StandaloneLuaWindow::setup(true); -#endif initFunction = LUA_NOREF; } @@ -1187,8 +1161,7 @@ static bool resumeLua(bool init, bool allowLcdUsage) luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; } else if (luaDisplayStatistics) { - #if defined(COLORLCD) - #else + #if !defined(COLORLCD) lcdDrawSolidHorizontalLine(0, 7*FH-1, lcdLastRightPos+6, ERASE); lcdDrawText(0, 7*FH, "GV Use: "); lcdDrawNumber(lcdLastRightPos, 7*FH, luaGetMemUsed(lsScripts), LEFT); @@ -1264,10 +1237,6 @@ bool luaTask(bool allowLcdUsage) case INTERPRETER_RELOAD_PERMANENT_SCRIPTS: init = true; luaState = INTERPRETER_LOADING; - -#if defined(USE_HATS_AS_KEYS) - if (_is_standalone_script()) setTransposeHatsForLUA(false); -#endif case INTERPRETER_LOADING: PROTECT_LUA() { @@ -1280,10 +1249,6 @@ bool luaTask(bool allowLcdUsage) case INTERPRETER_START_RUNNING: init = true; luaState = INTERPRETER_RUNNING; - -#if defined(USE_HATS_AS_KEYS) - if (_is_standalone_script()) setTransposeHatsForLUA(true); -#endif case INTERPRETER_RUNNING: PROTECT_LUA() { @@ -1291,6 +1256,13 @@ bool luaTask(bool allowLcdUsage) } else luaDisable(); UNPROTECT_LUA(); + break; + +#if defined(COLORLCD) + case INTERPRETER_PAUSED: + // stand alone script running + break; +#endif } return scriptWasRun; } diff --git a/radio/src/lua/lua_api.h b/radio/src/lua/lua_api.h index bdb650a5093..322dc060e4d 100644 --- a/radio/src/lua/lua_api.h +++ b/radio/src/lua/lua_api.h @@ -169,6 +169,9 @@ enum InterpreterState { INTERPRETER_LOADING, INTERPRETER_START_RUNNING, INTERPRETER_RUNNING, +#if defined(COLORLCD) + INTERPRETER_PAUSED, +#endif INTERPRETER_PANIC = 255 }; diff --git a/radio/src/lua/lua_lvgl_widget.cpp b/radio/src/lua/lua_lvgl_widget.cpp index bc4c27d9fc3..39852f8258e 100644 --- a/radio/src/lua/lua_lvgl_widget.cpp +++ b/radio/src/lua/lua_lvgl_widget.cpp @@ -20,14 +20,97 @@ */ #include "lua_widget.h" +#include "color_picker.h" #include "edgetx.h" +#include "lua_event.h" #include "page.h" #include "slider.h" +#include "sourcechoice.h" +#include "switchchoice.h" #include "toggleswitch.h" -#include "lua_event.h" //----------------------------------------------------------------------------- +static int pcallcont (lua_State *L) { + int status = lua_getctx(L, NULL); + return status == LUA_OK; +} + +static bool pcallFunc(lua_State *L, int funcRef, int nretval) +{ + if (funcRef != LUA_REFNIL) { + lua_rawgeti(L, LUA_REGISTRYINDEX, funcRef); + return lua_pcallk(L, 0, nretval, 0, 0, pcallcont) == LUA_OK; + } + return false; +} + +static bool pcallFuncWithInt(lua_State *L, int funcRef, int nretval, int val) +{ + if (funcRef != LUA_REFNIL) { + lua_rawgeti(L, LUA_REGISTRYINDEX, funcRef); + lua_pushinteger(L, val); + return lua_pcallk(L, 1, nretval, 0, 0, pcallcont) == LUA_OK; + } + return false; +} + +static bool pcallFuncWithString(lua_State *L, int funcRef, int nretval, const char* val) +{ + if (funcRef != LUA_REFNIL) { + lua_rawgeti(L, LUA_REGISTRYINDEX, funcRef); + lua_pushstring(L, val); + return lua_pcallk(L, 1, nretval, 0, 0, pcallcont) == LUA_OK; + } + return false; +} + +//----------------------------------------------------------------------------- + +LvglWidgetObjectBase *LvglWidgetObjectBase::checkLvgl(lua_State *L, int index) +{ + LvglWidgetObjectBase **p; + + p = (LvglWidgetObjectBase **)luaL_testudata(L, index, LVGL_METATABLE); + if (p) return *p; + + p = (LvglWidgetObjectBase **)luaL_testudata(L, index, LVGL_SIMPLEMETATABLE); + if (p) return *p; + + return nullptr; +} + +//----------------------------------------------------------------------------- + +LvglWidgetObjectBase::LvglWidgetObjectBase(const char* meta) : + metatable(meta), + lvglManager(luaLvglManager) +{ +} + +int LvglWidgetObjectBase::getRef(lua_State *L) +{ + LvglWidgetObjectBase **p = + (LvglWidgetObjectBase **)lua_newuserdata(L, sizeof(LvglWidgetObjectBase *)); + *p = this; + luaL_getmetatable(L, metatable); + lua_setmetatable(L, -2); + + // Save reference + luaRef = luaL_ref(L, LUA_REGISTRYINDEX); + lvglManager->saveLvglObjectRef(luaRef); + + return luaRef; +} + +void LvglWidgetObjectBase::push(lua_State *L) +{ + // Save reference + auto ref = getRef(L); + // Push userdata back into Lua stack (return object) + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); +} + void LvglWidgetObjectBase::getParams(lua_State *L, int index) { luaL_checktype(L, index, LUA_TTABLE); @@ -39,15 +122,9 @@ void LvglWidgetObjectBase::getParams(lua_State *L, int index) } } -bool LvglWidgetObjectBase::pcallFunc(lua_State *L, int getFuncRef, int nretval) -{ - lua_rawgeti(L, LUA_REGISTRYINDEX, getFuncRef); - return lua_pcall(L, 0, nretval, 0) == 0; -} - void LvglWidgetObjectBase::pcallSimpleFunc(lua_State *L, int funcRef) { - if (funcRef) { + if (funcRef != LUA_REFNIL) { PROTECT_LUA() { if (!pcallFunc(L, funcRef, 0)) { @@ -61,7 +138,7 @@ void LvglWidgetObjectBase::pcallSimpleFunc(lua_State *L, int funcRef) bool LvglWidgetObjectBase::pcallUpdateBool(lua_State *L, int getFuncRef, std::function update) { - if (getFuncRef) { + if (getFuncRef != LUA_REFNIL) { int t = lua_gettop(L); if (pcallFunc(L, getFuncRef, 1)) { bool val = lua_toboolean(L, -1); @@ -77,7 +154,7 @@ bool LvglWidgetObjectBase::pcallUpdateBool(lua_State *L, int getFuncRef, bool LvglWidgetObjectBase::pcallUpdate1Int(lua_State *L, int getFuncRef, std::function update) { - if (getFuncRef) { + if (getFuncRef != LUA_REFNIL) { int t = lua_gettop(L); if (pcallFunc(L, getFuncRef, 1)) { int val = luaL_checkunsigned(L, -1); @@ -93,7 +170,7 @@ bool LvglWidgetObjectBase::pcallUpdate1Int(lua_State *L, int getFuncRef, bool LvglWidgetObjectBase::pcallUpdate2Int(lua_State *L, int getFuncRef, std::function update) { - if (getFuncRef) { + if (getFuncRef != LUA_REFNIL) { int t = lua_gettop(L); if (pcallFunc(L, getFuncRef, 2)) { int v1 = luaL_checkunsigned(L, -2); @@ -110,7 +187,7 @@ bool LvglWidgetObjectBase::pcallUpdate2Int(lua_State *L, int getFuncRef, int LvglWidgetObjectBase::pcallGetIntVal(lua_State *L, int getFuncRef) { int val = 0; - if (getFuncRef) { + if (getFuncRef != LUA_REFNIL) { int t = lua_gettop(L); PROTECT_LUA() { @@ -132,14 +209,11 @@ int LvglWidgetObjectBase::pcallGetIntVal(lua_State *L, int getFuncRef) void LvglWidgetObjectBase::pcallSetIntVal(lua_State *L, int setFuncRef, int val) { - if (setFuncRef) { + if (setFuncRef != LUA_REFNIL) { int t = lua_gettop(L); PROTECT_LUA() { - lua_rawgeti(L, LUA_REGISTRYINDEX, setFuncRef); - lua_pushinteger(L, val); - bool rv = lua_pcall(L, 1, 0, 0) == 0; - if (!rv) { + if (!pcallFuncWithInt(L, setFuncRef, 0, val)) { lvglManager->luaShowError(); } } @@ -152,100 +226,674 @@ void LvglWidgetObjectBase::pcallSetIntVal(lua_State *L, int setFuncRef, int val) } } -void LvglWidgetObjectBase::clearRef(lua_State *L, int ref) +void LvglWidgetObjectBase::parseParam(lua_State *L, const char *key) { - if (ref) luaL_unref(L, LUA_REGISTRYINDEX, ref); + if (!strcmp(key, "x")) { + x = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "y")) { + y = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "w")) { + w = luaL_checkinteger(L, -1); + if (w == 0) w = LV_SIZE_CONTENT; + } else if (!strcmp(key, "h")) { + h = luaL_checkinteger(L, -1); + if (h == 0) h = LV_SIZE_CONTENT; + } else if (!strcmp(key, "color")) { + if (lua_isfunction(L, -1)) { + getColorFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + color = luaL_checkunsigned(L, -1); + } + } else if (!strcmp(key, "visible")) { + getVisibleFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "size")) { + getSizeFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "pos")) { + getPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } } -void LvglWidgetObjectBase::build(lua_State *L) {} - -//----------------------------------------------------------------------------- - -bool LvglWidgetObject::callRefs(lua_State *L) +bool LvglWidgetObjectBase::callRefs(lua_State *L) { + if (clearRequest) { + clearRequest = false; + if (getWindow()) { + getWindow()->clear(); + clearChildRefs(L); + } + return true; + } + if (!pcallUpdate1Int(L, getColorFunction, [=](int color) { setColor(color); })) return false; if (!pcallUpdateBool(L, getVisibleFunction, - [=](bool visible) { window->show(visible); })) + [=](bool visible) { if (visible) show(); else hide(); })) return false; if (!pcallUpdate2Int(L, getSizeFunction, [=](int w, int h) { setSize(w, h); })) return false; if (!pcallUpdate2Int(L, getPosFunction, [=](int x, int y) { setPos(x, y); })) return false; + + for (size_t i = 0; i < lvglObjectRefs.size(); i += 1) { + lua_rawgeti(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); + auto p = LvglWidgetObjectBase::checkLvgl(L, -1); + lua_pop(L, 1); + if (p) if (!p->callRefs(L)) return false; + } + return true; } -void LvglWidgetObject::clearRefs(lua_State *L) +void LvglWidgetObjectBase::saveLvglObjectRef(int ref) +{ + lvglObjectRefs.push_back(ref); +} + +void LvglWidgetObjectBase::clearRef(lua_State *L, int& ref) +{ + if (ref != LUA_REFNIL) + luaL_unref(L, LUA_REGISTRYINDEX, ref); + ref = LUA_REFNIL; +} + +void LvglWidgetObjectBase::clearChildRefs(lua_State *L) +{ + for (size_t i = 0; i < lvglObjectRefs.size(); i += 1) { + lua_rawgeti(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); + auto p = LvglWidgetObjectBase::checkLvgl(L, -1); + lua_pop(L, 1); + if (p) p->clearRefs(L); + } + lvglObjectRefs.clear(); +} + +void LvglWidgetObjectBase::clearRefs(lua_State *L) { + clearRef(L, luaRef); clearRef(L, getColorFunction); clearRef(L, getVisibleFunction); clearRef(L, getSizeFunction); clearRef(L, getPosFunction); + + clearChildRefs(L); } -void LvglWidgetObject::parseParam(lua_State *L, const char *key) +void LvglWidgetObjectBase::build(lua_State *L) {} + +void LvglWidgetObjectBase::refresh() { - if (!strcmp(key, "x")) { - x = luaL_checkinteger(L, -1); - } else if (!strcmp(key, "y")) { - y = luaL_checkinteger(L, -1); - } else if (!strcmp(key, "w")) { - w = luaL_checkinteger(L, -1); - if (w == 0) w = LV_SIZE_CONTENT; - } else if (!strcmp(key, "h")) { - h = luaL_checkinteger(L, -1); - if (h == 0) h = LV_SIZE_CONTENT; - } else if (!strcmp(key, "color")) { - if (lua_isfunction(L, -1)) { - getColorFunction = luaL_ref(L, LUA_REGISTRYINDEX); + setPos(x, y); + setSize(w, h); + setColor(color); +} + +void LvglWidgetObjectBase::update(lua_State *L) +{ + getParams(L, 2); + refresh(); +} + +bool LvglWidgetObjectBase::colorChanged(LcdFlags newColor) +{ + color = newColor; + LcdFlags c = color; + if (!(c & RGB_FLAG)) + c = COLOR(COLOR_VAL(c)) | RGB_FLAG; // Convert index to RGB + if (currentColor != c) { + currentColor = c; + return true; + } + return false; +} +//----------------------------------------------------------------------------- + +void LvglSimpleWidgetObject::setPos(coord_t x, coord_t y) +{ + this->x = x; + this->y = y; + if (lvobj) lv_obj_set_pos(lvobj, x, y); +} + +void LvglSimpleWidgetObject::setSize(coord_t w, coord_t h) +{ + this->w = w; + this->h = h; + if (lvobj) lv_obj_set_size(lvobj, w, h); +} + +void LvglSimpleWidgetObject::show() +{ + if (lvobj) lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_HIDDEN); +} + +void LvglSimpleWidgetObject::hide() +{ + if (lvobj) lv_obj_add_flag(lvobj, LV_OBJ_FLAG_HIDDEN); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetLabel::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "text")) { + if (lua_isfunction(L, -1)) + getTextFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else + txt = luaL_checkstring(L, -1); + } else if (!strcmp(key, "font")) { + if (lua_isfunction(L, -1)) + getFontFunction = luaL_ref(L, LUA_REGISTRYINDEX); + else + font = luaL_checkunsigned(L, -1); + } else { + LvglSimpleWidgetObject::parseParam(L, key); + } +} + +bool LvglWidgetLabel::callRefs(lua_State *L) +{ + int t = lua_gettop(L); + if (getTextFunction != LUA_REFNIL) { + if (pcallFunc(L, getTextFunction, 1)) { + const char *s = luaL_checkstring(L, -1); + setText(s); + lua_settop(L, t); } else { - color = luaL_checkunsigned(L, -1); + return false; } - } else if (!strcmp(key, "visible")) { - getVisibleFunction = luaL_ref(L, LUA_REGISTRYINDEX); - } else if (!strcmp(key, "size")) { - getSizeFunction = luaL_ref(L, LUA_REGISTRYINDEX); - } else if (!strcmp(key, "pos")) { - getPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); } + if (!pcallUpdate1Int(L, getFontFunction, [=](int val) { setFont(val); })) + return false; + return LvglSimpleWidgetObject::callRefs(L); } -int LvglWidgetObject::getRef(lua_State *L) +void LvglWidgetLabel::clearRefs(lua_State *L) { - LvglWidgetObject **p = - (LvglWidgetObject **)lua_newuserdata(L, sizeof(LvglWidgetObject *)); - *p = this; - luaL_getmetatable(L, LVGL_METATABLE); - lua_setmetatable(L, -2); - - // Save reference - auto ref = luaL_ref(L, LUA_REGISTRYINDEX); - lvglManager->saveLvglObjectRef(ref); + clearRef(L, getTextFunction); + clearRef(L, getFontFunction); + LvglSimpleWidgetObject::clearRefs(L); +} - return ref; +void LvglWidgetLabel::setText(const char *s) +{ + uint32_t h = hash(s, strlen(s)); + if (h != textHash) { + txt = s; + textHash = h; + if (lvobj) lv_label_set_text(lvobj, s); + } } -void LvglWidgetObject::push(lua_State *L) +void LvglWidgetLabel::setColor(LcdFlags newColor) { - // Save reference - auto ref = getRef(L); - // Push userdata back into Lua stack (return object) - lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + if (lvobj && colorChanged(newColor)) { + if (color & RGB_FLAG) { + etx_remove_txt_color(lvobj); + lv_obj_set_style_text_color(lvobj, makeLvColor(color), LV_PART_MAIN); + } else { + lv_obj_remove_local_style_prop(lvobj, LV_STYLE_TEXT_COLOR, LV_PART_MAIN); + etx_txt_color(lvobj, (LcdColorIndex)COLOR_VAL(color)); + } + } } -void LvglWidgetObject::update(lua_State *L) +void LvglWidgetLabel::setFont(LcdFlags font) { - getParams(L, 2); - refresh(); + if (lvobj) { + this->font = font; + if (font & VCENTERED) { + lv_obj_align(lvobj, LV_ALIGN_LEFT_MID, 0, 0); + } + lv_obj_set_style_text_align(lvobj, + (font & RIGHT) ? LV_TEXT_ALIGN_RIGHT + : (font & CENTERED) ? LV_TEXT_ALIGN_CENTER + : LV_TEXT_ALIGN_LEFT, + LV_PART_MAIN); + lv_obj_set_style_text_font(lvobj, getFont(font), LV_PART_MAIN); + } } -void LvglWidgetObject::refresh() +void LvglWidgetLabel::build(lua_State *L) { + lvobj = lv_label_create(lvglManager->getCurrentParent()->getLvObj()); setPos(x, y); setSize(w, h); + setText(txt); + setColor(color); + setFont(font); + callRefs(L); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetLineBase::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "rounded")) { + rounded = lua_toboolean(L, -1); + } else { + LvglSimpleWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetLineBase::setColor(LcdFlags newColor) +{ + if (lvobj && colorChanged(newColor)) { + if (color & RGB_FLAG) { + etx_remove_line_color(lvobj); + lv_obj_set_style_line_color(lvobj, makeLvColor(color), LV_PART_MAIN); + } else { + lv_obj_remove_local_style_prop(lvobj, LV_STYLE_LINE_COLOR, LV_PART_MAIN); + etx_line_color(lvobj, (LcdColorIndex)COLOR_VAL(color)); + } + } +} + +void LvglWidgetLineBase::setPos(coord_t x, coord_t y) +{ + this->x = x; + this->y = y; + setLine(); +} + +void LvglWidgetLineBase::setSize(coord_t w, coord_t h) +{ + this->w = w; + this->h = h; + setLine(); +} + +void LvglWidgetLineBase::build(lua_State* L) +{ + lvobj = lv_line_create(lvglManager->getCurrentParent()->getLvObj()); + lv_obj_set_style_line_opa(lvobj, LV_OPA_COVER, LV_PART_MAIN); + refresh(); +} + +void LvglWidgetLineBase::refresh() +{ setColor(color); + setLine(); + lv_obj_set_style_line_rounded(lvobj, rounded, LV_PART_MAIN); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetHLine::setLine() +{ + if (lvobj) { + pts[0].x = x; + pts[1].x = x + w; + pts[0].y = y; + pts[1].y = y; + lv_line_set_points(lvobj, pts, 2); + lv_obj_set_style_line_width(lvobj, abs(h), LV_PART_MAIN); + } +} + +//----------------------------------------------------------------------------- + +void LvglWidgetVLine::setLine() +{ + if (lvobj) { + pts[0].x = x; + pts[1].x = x; + pts[0].y = y; + pts[1].y = y + h; + lv_line_set_points(lvobj, pts, 2); + lv_obj_set_style_line_width(lvobj, abs(w), LV_PART_MAIN); + } +} + +//----------------------------------------------------------------------------- + +void LvglWidgetLine::getPt(lua_State* L, int n) +{ + lua_rawgeti(L, -1, n + 1); + luaL_checktype(L, -1, LUA_TTABLE); + lua_rawgeti(L, -1, 1); + pts[n].x = luaL_checkunsigned(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, -1, 2); + pts[n].y = luaL_checkunsigned(L, -1); + lua_pop(L, 2); +} + +void LvglWidgetLine::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "thickness")) { + thickness = luaL_checkunsigned(L, -1); + } else if (!strcmp(key, "rounded")) { + rounded = lua_toboolean(L, -1); + } else if (!strcmp(key, "pts")) { + luaL_checktype(L, -1, LUA_TTABLE); + ptCnt = lua_rawlen(L, -1); + if (pts) delete pts; + if (ptCnt > 1) { + pts = new lv_point_t[ptCnt]; + for (size_t i = 0; i < ptCnt; i += 1) + getPt(L, i); + } + } else { + LvglSimpleWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetLine::setColor(LcdFlags newColor) +{ + if (lvobj && colorChanged(newColor)) { + if (color & RGB_FLAG) { + etx_remove_line_color(lvobj); + lv_obj_set_style_line_color(lvobj, makeLvColor(color), LV_PART_MAIN); + } else { + lv_obj_remove_local_style_prop(lvobj, LV_STYLE_LINE_COLOR, LV_PART_MAIN); + etx_line_color(lvobj, (LcdColorIndex)COLOR_VAL(color)); + } + } +} + +void LvglWidgetLine::setPos(coord_t x, coord_t y) +{ + if (pts) { + coord_t dx = this->x - x; + coord_t dy = this->y - y; + for (size_t i = 0; i < ptCnt; i += 1) { + pts[i].x += dx; + pts[i].y += dy; + } + setLine(); + } +} + +void LvglWidgetLine::setSize(coord_t w, coord_t h) +{ +} + +void LvglWidgetLine::setLine() +{ + if (lvobj && pts) { + x = pts[0].x; + y = pts[0].y; + for (size_t i = 1; i < ptCnt; i += 1) { + if (pts[i].x < x) x = pts[i].x; + if (pts[i].y < y) y = pts[i].y; + } + + lv_line_set_points(lvobj, pts, ptCnt); + lv_obj_set_style_line_width(lvobj, thickness, LV_PART_MAIN); + lv_obj_set_style_line_rounded(lvobj, rounded, LV_PART_MAIN); + } +} + +void LvglWidgetLine::build(lua_State *L) +{ + lvobj = lv_line_create(lvglManager->getCurrentParent()->getLvObj()); + lv_obj_set_style_line_opa(lvobj, LV_OPA_COVER, LV_PART_MAIN); + refresh(); + callRefs(L); +} + +void LvglWidgetLine::refresh() +{ + setColor(color); + setLine(); +} + +//----------------------------------------------------------------------------- + +LvglWidgetTriangle::~LvglWidgetTriangle() +{ + if (mask) + free(mask); + mask = nullptr; +} + +void LvglWidgetTriangle::getPt(lua_State* L, int n) +{ + lua_rawgeti(L, -1, n + 1); + luaL_checktype(L, -1, LUA_TTABLE); + lua_rawgeti(L, -1, 1); + px[n] = luaL_checkunsigned(L, -1); + lua_pop(L, 1); + lua_rawgeti(L, -1, 2); + py[n] = luaL_checkunsigned(L, -1); + lua_pop(L, 2); +} + +void LvglWidgetTriangle::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "pts")) { + luaL_checktype(L, -1, LUA_TTABLE); + getPt(L, 0); + getPt(L, 1); + getPt(L, 2); + } else { + LvglSimpleWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetTriangle::setColor(LcdFlags newColor) +{ + if (lvobj && colorChanged(newColor)) { + if (color & RGB_FLAG) { + etx_remove_img_color(lvobj); + lv_obj_set_style_img_recolor(lvobj, makeLvColor(color), LV_PART_MAIN); + lv_obj_set_style_img_recolor_opa(lvobj, LV_OPA_COVER, LV_PART_MAIN); + } else { + lv_obj_remove_local_style_prop(lvobj, LV_STYLE_IMG_RECOLOR, LV_PART_MAIN); + etx_img_color(lvobj, (LcdColorIndex)COLOR_VAL(color)); + } + } +} + +void LvglWidgetTriangle::setSize(coord_t w, coord_t h) +{ + // TODO: Scale triangle? +} + +void LvglWidgetTriangle::fillLine(coord_t x1, coord_t x2, coord_t y) +{ + memset(&mask->data[y * w + x1], 255, x2 - x1 + 1); +} + +// Swap two bytes +#define SWAP(x,y) (x)=(x)^(y); (y)=(x)^(y); (x)=(x)^(y); + +void LvglWidgetTriangle::fillTriangle() +{ + if (!mask) return; + + coord_t x1 = px[0], y1 = py[0], x2 = px[1], y2 = py[1], x3 = px[2], y3 = py[2]; + + coord_t t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + coord_t signx1, signx2, dx1, dy1, dx2, dy2; + coord_t e1, e2; + + // Sort vertices + if (y1 > y2) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1 > y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2 > y3) { SWAP(y2, y3); SWAP(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + + dx1 = (coord_t)(x2 - x1); if(dx1 < 0) { dx1 = -dx1; signx1 = -1; } else signx1 = 1; + dy1 = (coord_t)(y2 - y1); + + dx2 = (coord_t)(x3 - x1); if(dx2 < 0) { dx2 = -dx2; signx2 = -1; } else signx2 = 1; + dy2 = (coord_t)(y3 - y1); + + if (dy1 > dx1) { // swap values + SWAP(dx1,dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + SWAP(dy2,dx2); + changed2 = true; + } + + e2 = (coord_t)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (coord_t)(dx1 >> 1); + + for (coord_t i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + i++; + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line +next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } +next2: + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + fillLine(minx, maxx, y); // Draw line from min to max points found on the y + // Now increase y + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y == y2) break; + } +next: + // Second half + dx1 = (int8_t)(x3 - x2); if(dx1<0) { dx1 = -dx1; signx1 = -1; } else signx1 = 1; + dy1 = (int8_t)(y3 - y2); + t1x = x2; + + if (dy1 > dx1) { // swap values + SWAP(dy1,dx1); + changed1 = true; + } else changed1 = false; + + e1 = (coord_t)(dx1 >> 1); + + for (coord_t i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; } + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i < dx1) i++; + } +next3: + // process second line until y value is about to change + while (t2x != x3) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if(changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } +next4: + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + fillLine(minx, maxx, y); // Draw line from min to max points found on the y + // Now increase y + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y > y3) return; + } +} + +void LvglWidgetTriangle::build(lua_State *L) +{ + // Bounds + x = min(min(px[0], px[1]), px[2]); + y = min(min(py[0], py[1]), py[2]); + w = max(max(px[0], px[1]), px[2]) - x + 1; + h = max(max(py[0], py[1]), py[2]) - y + 1; + + // Convert to relative coords + px[0] -= x; px[1] -= x; px[2] -= x; + py[0] -= y; py[1] -= y; py[2] -= y; + + // Allocate mask + size_t size = w * h; + mask = (MaskBitmap*)malloc(size + 4); + if (mask) { + mask->width = w; + mask->height = h; + memset(mask->data, 0, size); + + // Draw triangle + fillTriangle(); + + // Create canvas from mask buffer + if (lvobj == nullptr) + lvobj = lv_canvas_create(lvglManager->getCurrentParent()->getLvObj()); + lv_canvas_set_buffer(lvobj, (void*)mask->data, mask->width, mask->height, + LV_IMG_CF_ALPHA_8BIT); + + // Set position, size and color + setPos(x, y); + LvglSimpleWidgetObject::setSize(w,h); + setColor(color); + } + if (L) callRefs(L); +} + +void LvglWidgetTriangle::refresh() +{ + if (mask) free(mask); + mask = nullptr; + build(nullptr); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetObject::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "flexFlow")) { + flexFlow = luaL_checkinteger(L, -1); + } else if (!strcmp(key, "flexPad")) { + flexPad = luaL_checkinteger(L, -1); + } else { + LvglWidgetObjectBase::parseParam(L, key); + } } void LvglWidgetObject::setPos(coord_t x, coord_t y) @@ -262,6 +910,50 @@ void LvglWidgetObject::setSize(coord_t w, coord_t h) if (window) window->setSize(w, h); } +bool LvglWidgetObject::setFlex() +{ + if (flexFlow >= 0) { + window->padAll(PAD_TINY); + window->setFlexLayout((lv_flex_flow_t)flexFlow, flexPad, w, h); + return true; + } else { + window->padAll(PAD_ZERO); + return false; + } +} + +//----------------------------------------------------------------------------- + +void LvglWidgetBox::build(lua_State* L) +{ + window = + new Window(lvglManager->getCurrentParent(), {x, y, w, h}, lv_obj_create); + if (setFlex()) + lv_obj_set_flex_align(window->getLvObj(), LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_AROUND); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetSetting::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "title")) { + txt = luaL_checkstring(L, -1); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetSetting::build(lua_State* L) +{ + window = + new Window(lvglManager->getCurrentParent(), {x, y, w, h}, lv_obj_create); + window->padAll(PAD_TINY); + auto lbl = lv_label_create(window->getLvObj()); + lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 0, 0); + etx_txt_color(lbl, COLOR_THEME_PRIMARY1_INDEX); + lv_label_set_text(lbl, txt); +} + //----------------------------------------------------------------------------- void LvglWidgetBorderedObject::parseParam(lua_State *L, const char *key) @@ -275,18 +967,18 @@ void LvglWidgetBorderedObject::parseParam(lua_State *L, const char *key) } } -void LvglWidgetBorderedObject::setColor(LcdFlags color) +void LvglWidgetBorderedObject::setColor(LcdFlags newColor) { - if (color != currentColor) { - currentColor = color; + if (colorChanged(newColor)) { if (filled) { etx_bg_color_from_flags(window->getLvObj(), color); } else { if (color & RGB_FLAG) { etx_remove_border_color(window->getLvObj()); lv_obj_set_style_border_color(window->getLvObj(), - makeLvColor(colorToRGB(color)), LV_PART_MAIN); + makeLvColor(color), LV_PART_MAIN); } else { + lv_obj_remove_local_style_prop(window->getLvObj(), LV_STYLE_BORDER_COLOR, LV_PART_MAIN); etx_border_color(window->getLvObj(), (LcdColorIndex)COLOR_VAL(color)); } } @@ -332,115 +1024,28 @@ bool LvglWidgetRoundObject::callRefs(lua_State *L) return false; return LvglWidgetObject::callRefs(L); } - -void LvglWidgetRoundObject::clearRefs(lua_State *L) -{ - clearRef(L, getRadiusFunction); - LvglWidgetObject::clearRefs(L); -} - -void LvglWidgetRoundObject::setPos(coord_t x, coord_t y) -{ - LvglWidgetObject::setPos(x - radius, y - radius); -} - -void LvglWidgetRoundObject::setRadius(coord_t r) -{ - // Set position to center - x += radius; - y += radius; - radius = r; - w = radius * 2; - h = radius * 2; - setPos(x, y); - setSize(w, h); -} - -//----------------------------------------------------------------------------- - -void LvglWidgetLabel::parseParam(lua_State *L, const char *key) -{ - if (!strcmp(key, "text")) { - if (lua_isfunction(L, -1)) - getTextFunction = luaL_ref(L, LUA_REGISTRYINDEX); - else - txt = luaL_checkstring(L, -1); - } else if (!strcmp(key, "font")) { - if (lua_isfunction(L, -1)) - getFontFunction = luaL_ref(L, LUA_REGISTRYINDEX); - else - font = luaL_checkunsigned(L, -1); - } else { - LvglWidgetObject::parseParam(L, key); - } -} - -bool LvglWidgetLabel::callRefs(lua_State *L) -{ - int t = lua_gettop(L); - if (getTextFunction) { - if (pcallFunc(L, getTextFunction, 1)) { - const char *s = luaL_checkstring(L, -1); - setText(s); - lua_settop(L, t); - } else { - return false; - } - } - if (!pcallUpdate1Int(L, getFontFunction, [=](int val) { setFont(val); })) - return false; - return LvglWidgetObject::callRefs(L); -} - -void LvglWidgetLabel::clearRefs(lua_State *L) -{ - clearRef(L, getTextFunction); - clearRef(L, getFontFunction); - LvglWidgetObject::clearRefs(L); -} - -void LvglWidgetLabel::setText(const char *s) -{ - uint32_t h = hash(s, strlen(s)); - if (h != textHash) { - txt = s; - textHash = h; - lv_label_set_text(window->getLvObj(), s); - } -} - -void LvglWidgetLabel::setColor(LcdFlags color) -{ - if (color != currentColor) { - currentColor = color; - if (color & RGB_FLAG) { - etx_remove_txt_color(window->getLvObj()); - lv_obj_set_style_text_color(window->getLvObj(), - makeLvColor(colorToRGB(color)), LV_PART_MAIN); - } else { - etx_txt_color(window->getLvObj(), (LcdColorIndex)COLOR_VAL(color)); - } - } + +void LvglWidgetRoundObject::clearRefs(lua_State *L) +{ + clearRef(L, getRadiusFunction); + LvglWidgetObject::clearRefs(L); } -void LvglWidgetLabel::setFont(LcdFlags font) +void LvglWidgetRoundObject::setPos(coord_t x, coord_t y) { - this->font = font; - lv_obj_set_style_text_align(window->getLvObj(), - (font & RIGHT) ? LV_TEXT_ALIGN_RIGHT - : (font & CENTERED) ? LV_TEXT_ALIGN_CENTER - : LV_TEXT_ALIGN_LEFT, - LV_PART_MAIN); - lv_obj_set_style_text_font(window->getLvObj(), getFont(font), LV_PART_MAIN); + LvglWidgetObject::setPos(x - radius, y - radius); } -void LvglWidgetLabel::build(lua_State *L) +void LvglWidgetRoundObject::setRadius(coord_t r) { - window = new Window(lvglManager->getCurrentParent(), {x, y, w, h}, - lv_label_create); - setText(txt); - setColor(color); - setFont(font); + // Set position to center + x += radius; + y += radius; + radius = r; + w = radius * 2; + h = radius * 2; + setPos(x, y); + setSize(w, h); } //----------------------------------------------------------------------------- @@ -462,6 +1067,7 @@ void LvglWidgetRectangle::build(lua_State *L) (rounded >= thickness) ? rounded : thickness, LV_PART_MAIN); } + callRefs(L); } //----------------------------------------------------------------------------- @@ -474,6 +1080,7 @@ void LvglWidgetCircle::build(lua_State *L) setRadius(radius); LvglWidgetBorderedObject::build(L); lv_obj_set_style_radius(window->getLvObj(), LV_RADIUS_CIRCLE, LV_PART_MAIN); + callRefs(L); } //----------------------------------------------------------------------------- @@ -495,15 +1102,14 @@ void LvglWidgetArc::parseParam(lua_State *L, const char *key) } } -void LvglWidgetArc::setColor(LcdFlags color) +void LvglWidgetArc::setColor(LcdFlags newColor) { - if (color != currentColor) { - currentColor = color; + if (colorChanged(newColor)) { if (color & RGB_FLAG) { etx_remove_arc_color(window->getLvObj()); - lv_obj_set_style_arc_color(window->getLvObj(), makeLvColor(colorToRGB(color)), - LV_PART_INDICATOR); + lv_obj_set_style_arc_color(window->getLvObj(), makeLvColor(color), LV_PART_INDICATOR); } else { + lv_obj_remove_local_style_prop(window->getLvObj(), LV_STYLE_ARC_COLOR, LV_PART_MAIN); etx_arc_color(window->getLvObj(), (LcdColorIndex)COLOR_VAL(color), LV_PART_INDICATOR); } } @@ -558,6 +1164,7 @@ void LvglWidgetArc::build(lua_State *L) lv_obj_set_style_arc_width(window->getLvObj(), thickness, LV_PART_MAIN); lv_obj_set_style_arc_opa(window->getLvObj(), LV_OPA_COVER, LV_PART_INDICATOR); lv_obj_set_style_arc_width(window->getLvObj(), thickness, LV_PART_INDICATOR); + callRefs(L); } //----------------------------------------------------------------------------- @@ -599,365 +1206,6 @@ void LvglWidgetQRCode::build(lua_State *L) //----------------------------------------------------------------------------- -// class LvglWidgetScaleIndicator : public LvglWidgetObjectBase -// { -// public: -// LvglWidgetScaleIndicator() {} - -// virtual void buildIndicator(lv_obj_t *parent, lv_meter_scale_t *scale) = 0; - -// protected: -// lv_obj_t *meter = nullptr; -// lv_meter_indicator_t *indic = nullptr; -// }; - -// class LvglWidgetScaleArc : public LvglWidgetScaleIndicator -// { -// public: -// LvglWidgetScaleArc(lua_State *L) { getParams(L, -1); } - -// bool callRefs(lua_State *L) override -// { -// if (!pcallUpdate1Int(L, getStartPosFunction, [=](int val) { -// lv_meter_set_indicator_start_value(meter, indic, val); -// })) -// return false; -// if (!pcallUpdate1Int(L, getEndPosFunction, [=](int val) { -// lv_meter_set_indicator_end_value(meter, indic, val); -// })) -// return false; -// return true; -// } - -// void clearRefs(lua_State *L) override -// { -// clearRef(L, getStartPosFunction); -// clearRef(L, getEndPosFunction); -// } - -// void buildIndicator(lv_obj_t *parent, lv_meter_scale_t *scale) override -// { -// meter = parent; - -// indic = -// lv_meter_add_arc(meter, scale, w, makeLvColor(colorToRGB(color)), rmod); -// if (hasStart) lv_meter_set_indicator_start_value(meter, indic, startPos); -// if (hasEnd) lv_meter_set_indicator_end_value(meter, indic, endPos); -// } - -// protected: -// uint8_t w = 0; -// int8_t rmod = 0; -// LcdFlags color = COLOR2FLAGS(COLOR_THEME_PRIMARY1_INDEX); -// bool hasStart = false, hasEnd = false; -// int16_t startPos = 0, endPos = 0; - -// int getStartPosFunction = 0; -// int getEndPosFunction = 0; - -// void parseParam(lua_State *L, const char *key) override -// { -// if (!strcmp(key, "w")) { -// w = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "rmod")) { -// rmod = luaL_checkinteger(L, -1); -// } else if (!strcmp(key, "startPos")) { -// if (lua_isfunction(L, -1)) -// getStartPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); -// else { -// hasStart = true; -// startPos = luaL_checkinteger(L, -1); -// } -// } else if (!strcmp(key, "endPos")) { -// if (lua_isfunction(L, -1)) -// getEndPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); -// else { -// hasEnd = true; -// endPos = luaL_checkinteger(L, -1); -// } -// } else if (!strcmp(key, "color")) { -// color = luaL_checkunsigned(L, -1); -// } -// } -// }; - -// class LvglWidgetScaleLines : public LvglWidgetScaleIndicator -// { -// public: -// LvglWidgetScaleLines(lua_State *L) { getParams(L, -1); } - -// bool callRefs(lua_State *L) override -// { -// if (!pcallUpdate1Int(L, getStartPosFunction, [=](int val) { -// lv_meter_set_indicator_start_value(meter, indic, val); -// })) -// return false; -// if (!pcallUpdate1Int(L, getEndPosFunction, [=](int val) { -// lv_meter_set_indicator_end_value(meter, indic, val); -// })) -// return false; -// return true; -// } - -// void clearRefs(lua_State *L) override -// { -// clearRef(L, getStartPosFunction); -// clearRef(L, getEndPosFunction); -// } - -// void buildIndicator(lv_obj_t *parent, lv_meter_scale_t *scale) override -// { -// meter = parent; - -// indic = lv_meter_add_scale_lines( -// meter, scale, makeLvColor(colorToRGB(startColor)), -// makeLvColor(colorToRGB(endColor)), localFade, wmod); -// if (hasStart) lv_meter_set_indicator_start_value(meter, indic, startPos); -// if (hasEnd) lv_meter_set_indicator_end_value(meter, indic, endPos); -// } - -// protected: -// int8_t wmod = 0; -// LcdFlags startColor = COLOR2FLAGS(COLOR_THEME_PRIMARY1_INDEX); -// LcdFlags endColor = COLOR2FLAGS(COLOR_THEME_PRIMARY1_INDEX); -// bool hasStart = false, hasEnd = false; -// int16_t startPos = 0, endPos = 0; -// bool localFade = false; - -// int getStartPosFunction = 0; -// int getEndPosFunction = 0; - -// void parseParam(lua_State *L, const char *key) override -// { -// if (!strcmp(key, "wmod")) { -// wmod = luaL_checkinteger(L, -1); -// } else if (!strcmp(key, "startPos")) { -// if (lua_isfunction(L, -1)) { -// getStartPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); -// } else { -// hasStart = true; -// startPos = luaL_checkinteger(L, -1); -// } -// } else if (!strcmp(key, "endPos")) { -// if (lua_isfunction(L, -1)) { -// getEndPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); -// } else { -// hasEnd = true; -// endPos = luaL_checkinteger(L, -1); -// } -// } else if (!strcmp(key, "startColor")) { -// startColor = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "endColor")) { -// endColor = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "localFade")) { -// localFade = lua_toboolean(L, -1); -// } -// } -// }; - -// class LvglWidgetScaleNeedle : public LvglWidgetScaleIndicator -// { -// public: -// LvglWidgetScaleNeedle(lua_State *L) { getParams(L, -1); } - -// bool callRefs(lua_State *L) override -// { -// if (!pcallUpdate1Int(L, getPosFunction, [=](int val) { -// lv_meter_set_indicator_value(meter, indic, val); -// })) -// return false; -// return true; -// } - -// void clearRefs(lua_State *L) override { clearRef(L, getPosFunction); } - -// void buildIndicator(lv_obj_t *parent, lv_meter_scale_t *scale) override -// { -// meter = parent; - -// indic = lv_meter_add_needle_line(meter, scale, w, -// makeLvColor(colorToRGB(color)), rmod); -// } - -// protected: -// uint8_t w = 0; -// int8_t rmod = 0; -// LcdFlags color = COLOR2FLAGS(COLOR_THEME_PRIMARY1_INDEX); - -// int getPosFunction = 0; - -// void parseParam(lua_State *L, const char *key) override -// { -// if (!strcmp(key, "w")) { -// w = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "rmod")) { -// rmod = luaL_checkinteger(L, -1); -// } else if (!strcmp(key, "color")) { -// color = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "pos")) { -// getPosFunction = luaL_ref(L, LUA_REGISTRYINDEX); -// } -// } -// }; - -// class LvglWidgetMeterScale : public LvglWidgetObjectBase -// { -// public: -// LvglWidgetMeterScale(lua_State *L) { getParams(L, -1); } - -// bool callRefs(lua_State *L) override -// { -// for (auto it = indicators.cbegin(); it != indicators.cend(); ++it) { -// if (!(*it)->callRefs(L)) return false; -// } -// return true; -// } - -// void clearRefs(lua_State *L) override -// { -// for (auto it = indicators.cbegin(); it != indicators.cend(); ++it) { -// (*it)->clearRefs(L); -// } -// } - -// void build(lv_obj_t *parent) -// { -// meter = parent; -// lv_meter_scale_t *scale = lv_meter_add_scale(meter); - -// if (ticks) { -// lv_meter_set_scale_ticks(meter, scale, ticks, tickWidth, tickLen, -// makeLvColor(colorToRGB(tickColor))); -// if (majorNth) -// lv_meter_set_scale_major_ticks( -// meter, scale, majorNth, majorWidth, majorLen, -// makeLvColor(colorToRGB(majorColor)), labelGap); -// } - -// lv_meter_set_scale_range(meter, scale, scaleMin, scaleMax, scaleAngle, -// scaleRotate); - -// if (centerDotSize > 0) { -// lv_obj_set_style_size(meter, centerDotSize, LV_PART_INDICATOR); -// etx_bg_color_from_flags(meter, centerDotColor, LV_PART_INDICATOR); -// lv_obj_set_style_bg_opa(meter, LV_OPA_COVER, LV_PART_INDICATOR); -// lv_obj_set_style_radius(meter, LV_RADIUS_CIRCLE, LV_PART_INDICATOR); -// } - -// for (auto it = indicators.cbegin(); it != indicators.cend(); ++it) { -// (*it)->buildIndicator(meter, scale); -// } -// } - -// protected: -// lv_obj_t *meter; -// std::vector indicators; - -// int16_t scaleMin = 0, scaleMax = 100, scaleAngle = 360, scaleRotate = 0; -// uint8_t ticks = 0, majorNth = 0; -// uint8_t tickWidth = 0, majorWidth = 0; -// uint8_t tickLen = 0, majorLen = 0; -// LcdFlags tickColor = COLOR2FLAGS(COLOR_THEME_PRIMARY1); -// LcdFlags majorColor = COLOR2FLAGS(COLOR_THEME_PRIMARY1); -// uint8_t labelGap = 0, centerDotSize = 0; -// LcdFlags centerDotColor = COLOR2FLAGS(COLOR_THEME_PRIMARY1_INDEX); - -// void parseParam(lua_State *L, const char *key) override -// { -// if (!strcmp(key, "ticks")) { -// ticks = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "tickWidth")) { -// tickWidth = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "tickLen")) { -// tickLen = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "tickColor")) { -// tickColor = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "majorNth")) { -// majorNth = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "majorWidth")) { -// majorWidth = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "majorLen")) { -// majorLen = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "majorColor")) { -// majorColor = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "labelGap")) { -// labelGap = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "min")) { -// scaleMin = luaL_checkinteger(L, -1); -// } else if (!strcmp(key, "max")) { -// scaleMax = luaL_checkinteger(L, -1); -// } else if (!strcmp(key, "angle")) { -// scaleAngle = luaL_checkinteger(L, -1); -// } else if (!strcmp(key, "rotate")) { -// scaleRotate = luaL_checkinteger(L, -1); -// } else if (!strcmp(key, "dotSize")) { -// centerDotSize = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "dotColor")) { -// centerDotColor = luaL_checkunsigned(L, -1); -// } else if (!strcmp(key, "indicators")) { -// luaL_checktype(L, -1, LUA_TTABLE); -// for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { -// lua_getfield(L, -1, "type"); -// const char *key = lua_tostring(L, -1); -// lua_pop(L, 1); -// if (!strcmp(key, "arc")) { -// indicators.push_back(new LvglWidgetScaleArc(L)); -// } else if (!strcmp(key, "lines")) { -// indicators.push_back(new LvglWidgetScaleLines(L)); -// } else if (!strcmp(key, "needle")) { -// indicators.push_back(new LvglWidgetScaleNeedle(L)); -// } -// } -// } -// } -// }; - -// void LvglWidgetMeter::parseParam(lua_State *L, const char *key) -// { -// if (!strcmp(key, "scales")) { -// luaL_checktype(L, -1, LUA_TTABLE); -// for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { -// scales.push_back(new LvglWidgetMeterScale(L)); -// } -// } else { -// LvglWidgetRoundObject::parseParam(L, key); -// } -// } - -// bool LvglWidgetMeter::callRefs(lua_State *L) -// { -// for (auto it = scales.cbegin(); it != scales.cend(); ++it) { -// if (!(*it)->callRefs(L)) return false; -// } -// return LvglWidgetRoundObject::callRefs(L); -// } - -// void LvglWidgetMeter::clearRefs(lua_State *L) -// { -// for (auto it = scales.cbegin(); it != scales.cend(); ++it) { -// (*it)->clearRefs(L); -// } -// LvglWidgetRoundObject::clearRefs(L); -// } - -// void LvglWidgetMeter::build(lua_State *L) -// { -// window = new Window(lvglManager->getCurrentParent(), -// {x - radius, y - radius, radius * 2, radius * 2}, -// lv_meter_create); - -// lv_obj_t *meter = window->getLvObj(); - -// lv_obj_add_flag(meter, LV_OBJ_FLAG_EVENT_BUBBLE); -// lv_obj_clear_flag(meter, LV_OBJ_FLAG_CLICKABLE); - -// for (auto it = scales.cbegin(); it != scales.cend(); ++it) { -// (*it)->build(meter); -// } -// } - -//----------------------------------------------------------------------------- - void LvglWidgetTextButton::parseParam(lua_State *L, const char *key) { if (!strcmp(key, "text")) { @@ -1025,38 +1273,6 @@ void LvglWidgetToggleSwitch::build(lua_State *L) //----------------------------------------------------------------------------- -void LvglWidgetConfirmDialog::parseParam(lua_State *L, const char *key) -{ - if (!strcmp(key, "title")) { - title = luaL_checkstring(L, -1); - } else if (!strcmp(key, "message")) { - message = luaL_checkstring(L, -1); - } else if (!strcmp(key, "confirm")) { - confirmFunction = luaL_ref(L, LUA_REGISTRYINDEX); - } else if (!strcmp(key, "cancel")) { - cancelFunction = luaL_ref(L, LUA_REGISTRYINDEX); - } else { - LvglWidgetObject::parseParam(L, key); - } -} - -void LvglWidgetConfirmDialog::clearRefs(lua_State *L) -{ - clearRef(L, confirmFunction); - clearRef(L, cancelFunction); - LvglWidgetObject::clearRefs(L); -} - -void LvglWidgetConfirmDialog::build(lua_State *L) -{ - window = new ConfirmDialog( - MainWindow::instance(), title, message, - [=]() { pcallSimpleFunc(L, confirmFunction); }, - [=]() { pcallSimpleFunc(L, cancelFunction); }); -} - -//----------------------------------------------------------------------------- - void LvglWidgetTextEdit::parseParam(lua_State *L, const char *key) { if (!strcmp(key, "value")) { @@ -1084,14 +1300,11 @@ void LvglWidgetTextEdit::build(lua_State *L) if (h == LV_SIZE_CONTENT) h = 0; window = new TextEdit(lvglManager->getCurrentParent(), {x, y, w, h}, value, maxLen, [=]() { - if (setFunction) { + if (setFunction != LUA_REFNIL) { int t = lua_gettop(L); PROTECT_LUA() { - lua_rawgeti(L, LUA_REGISTRYINDEX, setFunction); - lua_pushstring(L, value); - bool rv = lua_pcall(L, 1, 0, 0) == 0; - if (!rv) { + if (!pcallFuncWithString(L, setFunction, 0, value)) { lvglManager->luaShowError(); } } @@ -1139,16 +1352,13 @@ void LvglWidgetNumberEdit::build(lua_State *L) lvglManager->getCurrentParent(), {x, y, w, h}, min, max, [=]() { return pcallGetIntVal(L, getFunction); }, [=](int val) { pcallSetIntVal(L, setFunction, val); }); - if (dispFunction) { + if (dispFunction != LUA_REFNIL) { ((NumberEdit *)window)->setDisplayHandler([=](int val) { const char *s = "???"; int t = lua_gettop(L); PROTECT_LUA() { - lua_rawgeti(L, LUA_REGISTRYINDEX, dispFunction); - lua_pushinteger(L, val); - bool rv = lua_pcall(L, 1, 1, 0) == 0; - if (rv) { + if (pcallFuncWithInt(L, dispFunction, 1, val)) { s = luaL_checkstring(L, -1); } else { lvglManager->luaShowError(); @@ -1167,42 +1377,6 @@ void LvglWidgetNumberEdit::build(lua_State *L) //----------------------------------------------------------------------------- -void LvglWidgetChoice::parseParam(lua_State *L, const char *key) -{ - if (!strcmp(key, "title")) { - title = luaL_checkstring(L, -1); - } else if (!strcmp(key, "values")) { - luaL_checktype(L, -1, LUA_TTABLE); - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - values.push_back(lua_tostring(L, -1)); - } - } else if (!strcmp(key, "get")) { - getFunction = luaL_ref(L, LUA_REGISTRYINDEX); - } else if (!strcmp(key, "set")) { - setFunction = luaL_ref(L, LUA_REGISTRYINDEX); - } else { - LvglWidgetObject::parseParam(L, key); - } -} - -void LvglWidgetChoice::clearRefs(lua_State *L) -{ - clearRef(L, getFunction); - clearRef(L, setFunction); - LvglWidgetObject::clearRefs(L); -} - -void LvglWidgetChoice::build(lua_State *L) -{ - if (h == LV_SIZE_CONTENT) h = 0; - window = new Choice( - lvglManager->getCurrentParent(), {x, y, w, h}, values, 0, - values.size() - 1, [=]() { return pcallGetIntVal(L, getFunction) - 1; }, - [=](int val) { pcallSetIntVal(L, setFunction, val + 1); }, title.c_str()); -} - -//----------------------------------------------------------------------------- - void LvglWidgetSlider::parseParam(lua_State *L, const char *key) { if (!strcmp(key, "get")) { @@ -1260,8 +1434,6 @@ class WidgetPage : public NavWindow, public LuaEventHandler LV_PART_MAIN); etx_scrollbar(body->getLvObj()); - body->padAll(PAD_ZERO); - #if defined(HARDWARE_TOUCH) addBackButton(); #endif @@ -1314,7 +1486,249 @@ void LvglWidgetPage::build(lua_State *L) auto page = new WidgetPage( lvglManager->getCurrentParent(), [=]() { pcallSimpleFunc(L, backActionFunction); }, title, subtitle, iconFile); + window = page->getBody(); + if (setFlex()) + lv_obj_set_flex_align(window->getLvObj(), LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_SPACE_AROUND); +} + +//----------------------------------------------------------------------------- + +class LvglDialog : public BaseDialog +{ + public: + LvglDialog(const char* title, coord_t w, coord_t h, std::function onClose) : + BaseDialog(title, true, w, h, false), + onClose(std::move(onClose)) + { + form->setHeight(h - EdgeTxStyles::UI_ELEMENT_HEIGHT); + } + + Window *getBody() { return form; } + + protected: + std::function onClose; + + void onCancel() override + { + onClose(); + BaseDialog::onCancel(); + } +}; + +void LvglWidgetDialog::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "close")) { + closeFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "title")) { + title = luaL_checkstring(L, -1); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetDialog::clearRefs(lua_State *L) +{ + clearRef(L, closeFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetDialog::build(lua_State *L) +{ + if (w == LV_SIZE_CONTENT) w = DIALOG_DEFAULT_WIDTH; + if (h == LV_SIZE_CONTENT) h = DIALOG_DEFAULT_HEIGHT; + auto dlg = new LvglDialog(title, w, h, + [=]() { pcallSimpleFunc(L, closeFunction); }); + window = dlg->getBody(); + window->setWidth(w); + setFlex(); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetConfirmDialog::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "title")) { + title = luaL_checkstring(L, -1); + } else if (!strcmp(key, "message")) { + message = luaL_checkstring(L, -1); + } else if (!strcmp(key, "confirm")) { + confirmFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "cancel")) { + cancelFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetConfirmDialog::clearRefs(lua_State *L) +{ + clearRef(L, confirmFunction); + clearRef(L, cancelFunction); + LvglWidgetObject::clearRefs(L); +} + +void LvglWidgetConfirmDialog::build(lua_State *L) +{ + window = new ConfirmDialog(title, message, + [=]() { pcallSimpleFunc(L, confirmFunction); }, + [=]() { pcallSimpleFunc(L, cancelFunction); }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetMessageDialog::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "title")) { + title = luaL_checkstring(L, -1); + } else if (!strcmp(key, "message")) { + message = luaL_checkstring(L, -1); + } else if (!strcmp(key, "details")) { + details = luaL_checkstring(L, -1); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetMessageDialog::build(lua_State *L) +{ + window = new MessageDialog(title, message, details); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetPicker::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "get")) { + getFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else if (!strcmp(key, "set")) { + setFunction = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + LvglWidgetObject::parseParam(L, key); + } +} + +void LvglWidgetPicker::clearRefs(lua_State *L) +{ + clearRef(L, getFunction); + clearRef(L, setFunction); + LvglWidgetObject::clearRefs(L); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetChoice::parseParam(lua_State *L, const char *key) +{ + if (!strcmp(key, "title")) { + title = luaL_checkstring(L, -1); + } else if (!strcmp(key, "values")) { + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + values.push_back(lua_tostring(L, -1)); + } + } else { + LvglWidgetPicker::parseParam(L, key); + } +} + +void LvglWidgetChoice::build(lua_State *L) +{ + if (h == LV_SIZE_CONTENT) h = 0; + window = new Choice( + lvglManager->getCurrentParent(), {x, y, w, h}, values, 0, values.size() - 1, + [=]() { return pcallGetIntVal(L, getFunction) - 1; }, + [=](int val) { pcallSetIntVal(L, setFunction, val + 1); }, title.c_str()); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetFontPicker::build(lua_State *L) +{ + if (h == LV_SIZE_CONTENT) h = 0; + window = new Choice( + lvglManager->getCurrentParent(), {x, y, w, h}, STR_FONT_SIZES, 0, FONTS_COUNT - 1, + [=]() { return FONT_INDEX(pcallGetIntVal(L, getFunction)); }, + [=](int val) { pcallSetIntVal(L, setFunction, val << 8u); }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetAlignPicker::build(lua_State *L) +{ + static LcdFlags alignments[3] = { LEFT, CENTERED, RIGHT }; + + if (h == LV_SIZE_CONTENT) h = 0; + window = new Choice( + lvglManager->getCurrentParent(), {x, y, w, h}, STR_ALIGN_OPTS, 0, ALIGN_COUNT - 1, + [=]() -> int { + auto v = (LcdFlags)pcallGetIntVal(L, getFunction); + for (size_t i = 0; i < DIM(alignments); i += 1) + if (alignments[i] == v) return i; + return 0; + }, + [=](int val) { pcallSetIntVal(L, setFunction, alignments[val]); }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetColorPicker::build(lua_State *L) +{ + if (h == LV_SIZE_CONTENT) h = 0; + window = new ColorPicker( + lvglManager->getCurrentParent(), {x, y, w, h}, + [=]() { return pcallGetIntVal(L, getFunction); }, + [=](uint32_t val) { pcallSetIntVal(L, setFunction, val); }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetTimerPicker::build(lua_State *L) +{ + if (h == LV_SIZE_CONTENT) h = 0; + auto tmChoice = new Choice( + lvglManager->getCurrentParent(), {x, y, w, h}, 0, TIMERS - 1, + [=]() { return pcallGetIntVal(L, getFunction); }, + [=](uint32_t val) { pcallSetIntVal(L, setFunction, val); }); + tmChoice->setTextHandler([](int value) { + return std::string(STR_TIMER) + std::to_string(value + 1); + }); + window = tmChoice; +} + +//----------------------------------------------------------------------------- + +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); + } else { + LvglWidgetPicker::parseParam(L, key); + } +} + +void LvglWidgetSwitchPicker::build(lua_State *L) +{ + if (h == LV_SIZE_CONTENT) h = 0; + window = new SwitchChoice( + lvglManager->getCurrentParent(), {x, y, w, h}, + vmin, vmax, + [=]() { return pcallGetIntVal(L, getFunction); }, + [=](uint32_t val) { pcallSetIntVal(L, setFunction, val); }); +} + +//----------------------------------------------------------------------------- + +void LvglWidgetSourcePicker::build(lua_State *L) +{ + if (h == LV_SIZE_CONTENT) h = 0; + window = new SourceChoice( + lvglManager->getCurrentParent(), {x, y, w, h}, + 0, MIXSRC_LAST_TELEM, + [=]() { return pcallGetIntVal(L, getFunction); }, + [=](uint32_t val) { pcallSetIntVal(L, setFunction, val); }, + true); } //----------------------------------------------------------------------------- diff --git a/radio/src/lua/lua_lvgl_widget.h b/radio/src/lua/lua_lvgl_widget.h index e4df51046a9..e4b90a5d18a 100644 --- a/radio/src/lua/lua_lvgl_widget.h +++ b/radio/src/lua/lua_lvgl_widget.h @@ -22,29 +22,64 @@ #pragma once #define LVGL_METATABLE "LVGL*" +#define LVGL_SIMPLEMETATABLE "LVGLSIMPLE*" //----------------------------------------------------------------------------- class LvglWidgetObjectBase { public: - LvglWidgetObjectBase() { lvglManager = luaLvglManager; } + LvglWidgetObjectBase(const char* meta); virtual ~LvglWidgetObjectBase() {} + int getRef(lua_State *L); + void push(lua_State *L); + void saveLvglObjectRef(int ref); + void getParams(lua_State *L, int index); virtual void build(lua_State *L); - virtual bool callRefs(lua_State *L) = 0; - virtual void clearRefs(lua_State *L) = 0; + virtual bool callRefs(lua_State *L); + virtual void clearRefs(lua_State *L); + void clearChildRefs(lua_State *L); + + virtual void show() = 0; + virtual void hide() = 0; + + virtual void setColor(LcdFlags newColor) {} + virtual void setPos(coord_t x, coord_t y) {} + virtual void setSize(coord_t w, coord_t h) {} + + void update(lua_State *L); + + virtual Window *getWindow() const = 0; + + static LvglWidgetObjectBase *checkLvgl(lua_State *L, int index); + + void clear() { clearRequest = true; } protected: + int luaRef = LUA_REFNIL; + std::vector lvglObjectRefs; + const char* metatable = nullptr; + bool clearRequest = false; LuaLvglManager *lvglManager = nullptr; + coord_t x = 0, y = 0, w = LV_SIZE_CONTENT, h = LV_SIZE_CONTENT; + LcdFlags color = COLOR2FLAGS(COLOR_THEME_SECONDARY1_INDEX); + LcdFlags currentColor = -1; + int getColorFunction = LUA_REFNIL; + int getVisibleFunction = LUA_REFNIL; + int getSizeFunction = LUA_REFNIL; + int getPosFunction = LUA_REFNIL; - virtual void parseParam(lua_State *L, const char *key) = 0; + virtual void refresh(); + + virtual void parseParam(lua_State *L, const char *key); - void clearRef(lua_State *L, int ref); + bool colorChanged(LcdFlags newColor); + + void clearRef(lua_State *L, int& ref); - bool pcallFunc(lua_State *L, int getFuncRef, int nret); void pcallSimpleFunc(lua_State *L, int funcRef); bool pcallUpdateBool(lua_State *L, int getFuncRef, std::function update); @@ -58,54 +93,32 @@ class LvglWidgetObjectBase //----------------------------------------------------------------------------- -class LvglWidgetObject : public LvglWidgetObjectBase +class LvglSimpleWidgetObject : public LvglWidgetObjectBase { public: - LvglWidgetObject() : LvglWidgetObjectBase() {} + LvglSimpleWidgetObject() : LvglWidgetObjectBase(LVGL_SIMPLEMETATABLE) {} - int getRef(lua_State *L); - void push(lua_State *L); - - void update(lua_State *L); - - virtual void setColor(LcdFlags color) {} - virtual void setPos(coord_t x, coord_t y); - void setSize(coord_t w, coord_t h); + void show() override; + void hide() override; - void show() { window->show(); } - void hide() { window->hide(); } - - Window *getWindow() const { return window; } + void setPos(coord_t x, coord_t y) override; + void setSize(coord_t w, coord_t h) override; - static LvglWidgetObject *checkLvgl(lua_State *L, int index); - - bool callRefs(lua_State *L) override; - void clearRefs(lua_State *L) override; + Window *getWindow() const override { return nullptr; } protected: - Window *window = nullptr; - LcdFlags currentColor = -1; - - coord_t x = 0, y = 0, w = LV_SIZE_CONTENT, h = LV_SIZE_CONTENT; - LcdFlags color = COLOR2FLAGS(COLOR_THEME_SECONDARY1_INDEX); - int getColorFunction = 0; - int getVisibleFunction = 0; - int getSizeFunction = 0; - int getPosFunction = 0; - - void parseParam(lua_State *L, const char *key) override; - virtual void refresh(); + lv_obj_t* lvobj = nullptr; }; //----------------------------------------------------------------------------- -class LvglWidgetLabel : public LvglWidgetObject +class LvglWidgetLabel : public LvglSimpleWidgetObject { public: - LvglWidgetLabel() : LvglWidgetObject() {} + LvglWidgetLabel() : LvglSimpleWidgetObject() {} void setText(const char *s); - void setColor(LcdFlags color) override; + void setColor(LcdFlags newColor) override; void setFont(LcdFlags font); void build(lua_State *L) override; @@ -117,26 +130,174 @@ class LvglWidgetLabel : public LvglWidgetObject const char *txt = ""; LcdFlags font = FONT(STD); - int getTextFunction = 0; - int getFontFunction = 0; + int getTextFunction = LUA_REFNIL; + int getFontFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; void refresh() override { setText(txt); setFont(font); - LvglWidgetObject::refresh(); + LvglSimpleWidgetObject::refresh(); } }; //----------------------------------------------------------------------------- +class LvglWidgetLineBase : public LvglSimpleWidgetObject +{ + public: + LvglWidgetLineBase() : LvglSimpleWidgetObject() {} + + void setColor(LcdFlags newColor) override; + void setPos(coord_t x, coord_t y) override; + void setSize(coord_t w, coord_t h) override; + + void build(lua_State *L) override; + + protected: + bool rounded = false; + lv_point_t pts[2]; + + virtual void setLine() = 0; + + void parseParam(lua_State *L, const char *key) override; + void refresh() override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetHLine : public LvglWidgetLineBase +{ + public: + LvglWidgetHLine() : LvglWidgetLineBase() {} + + protected: + void setLine() override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetVLine : public LvglWidgetLineBase +{ + public: + LvglWidgetVLine() : LvglWidgetLineBase() {} + + protected: + void setLine() override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetLine : public LvglSimpleWidgetObject +{ + public: + LvglWidgetLine() : LvglSimpleWidgetObject() {} + + void setColor(LcdFlags newColor) override; + void setPos(coord_t x, coord_t y) override; + void setSize(coord_t w, coord_t h) override; + + void build(lua_State *L) override; + + protected: + coord_t thickness = 1; + bool rounded = false; + size_t ptCnt = 0; + lv_point_t* pts = nullptr; + + void setLine(); + + void getPt(lua_State* L, int n); + void parseParam(lua_State *L, const char *key) override; + void refresh() override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetTriangle : public LvglSimpleWidgetObject +{ + public: + LvglWidgetTriangle() : LvglSimpleWidgetObject() {} + ~LvglWidgetTriangle(); + + void setColor(LcdFlags newColor) override; + void setSize(coord_t w, coord_t h) override; + + void build(lua_State *L) override; + + protected: + coord_t px[3] = {0}, py[3] = {0}; + MaskBitmap* mask = nullptr; + + void fillTriangle(); + void fillLine(coord_t x1, coord_t x2, coord_t y); + + void getPt(lua_State* L, int n); + void parseParam(lua_State *L, const char *key) override; + void refresh() override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetObject : public LvglWidgetObjectBase +{ + public: + LvglWidgetObject(const char* meta = LVGL_METATABLE) : LvglWidgetObjectBase(meta) {} + + void show() override { window->show(); } + void hide() override { window->hide(); } + + void setPos(coord_t x, coord_t y) override; + void setSize(coord_t w, coord_t h) override; + + Window *getWindow() const override { return window; } + + protected: + Window *window = nullptr; + int8_t flexFlow = -1; + int8_t flexPad = PAD_TINY; + + void parseParam(lua_State *L, const char *key) override; + + bool setFlex(); +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetBox : public LvglWidgetObject +{ + public: + LvglWidgetBox() : LvglWidgetObject() {} + + void build(lua_State *L) override; + + protected: +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetSetting : public LvglWidgetObject +{ + public: + LvglWidgetSetting() : LvglWidgetObject() {} + + void build(lua_State *L) override; + + protected: + const char *txt = ""; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + class LvglWidgetBorderedObject : public LvglWidgetObject { public: LvglWidgetBorderedObject() : LvglWidgetObject() {} - void setColor(LcdFlags color) override; + void setColor(LcdFlags newColor) override; void build(lua_State *L) override; @@ -163,7 +324,7 @@ class LvglWidgetRoundObject : public LvglWidgetBorderedObject protected: coord_t radius = 0; - int getRadiusFunction = 0; + int getRadiusFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; void refresh() override @@ -207,7 +368,7 @@ class LvglWidgetArc : public LvglWidgetRoundObject public: LvglWidgetArc() : LvglWidgetRoundObject() {} - void setColor(LcdFlags color) override; + void setColor(LcdFlags newColor) override; void setStartAngle(coord_t angle); void setEndAngle(coord_t angle); @@ -218,8 +379,8 @@ class LvglWidgetArc : public LvglWidgetRoundObject protected: coord_t startAngle = 0, endAngle = 0; - int getStartAngleFunction = 0; - int getEndAngleFunction = 0; + int getStartAngleFunction = LUA_REFNIL; + int getEndAngleFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; void refresh() override @@ -235,7 +396,7 @@ class LvglWidgetArc : public LvglWidgetRoundObject class LvglWidgetImage : public LvglWidgetObject { public: - LvglWidgetImage() : LvglWidgetObject() {} + LvglWidgetImage() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} void build(lua_State *L) override; @@ -251,7 +412,7 @@ class LvglWidgetImage : public LvglWidgetObject class LvglWidgetQRCode : public LvglWidgetObject { public: - LvglWidgetQRCode() : LvglWidgetObject() {} + LvglWidgetQRCode() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} void build(lua_State *L) override; @@ -264,29 +425,10 @@ class LvglWidgetQRCode : public LvglWidgetObject //----------------------------------------------------------------------------- -// class LvglWidgetMeterScale; - -// class LvglWidgetMeter : public LvglWidgetRoundObject -// { -// public: -// LvglWidgetMeter() : LvglWidgetRoundObject() {} - -// void build(lua_State *L) override; -// bool callRefs(lua_State *L) override; -// void clearRefs(lua_State *L) override; - -// protected: -// std::vector scales; - -// void parseParam(lua_State *L, const char *key) override; -// }; - -//----------------------------------------------------------------------------- - class LvglWidgetTextButton : public LvglWidgetObject { public: - LvglWidgetTextButton() : LvglWidgetObject() {} + LvglWidgetTextButton() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} void setText(const char *s); @@ -297,7 +439,7 @@ class LvglWidgetTextButton : public LvglWidgetObject uint32_t textHash = -1; const char *txt = ""; - int pressFunction = 0; + int pressFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; }; @@ -307,134 +449,248 @@ class LvglWidgetTextButton : public LvglWidgetObject class LvglWidgetToggleSwitch : public LvglWidgetObject { public: - LvglWidgetToggleSwitch() : LvglWidgetObject() {} + LvglWidgetToggleSwitch() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: - int getStateFunction = 0; - int setStateFunction = 0; + int getStateFunction = LUA_REFNIL; + int setStateFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; }; //----------------------------------------------------------------------------- -class LvglWidgetConfirmDialog : public LvglWidgetObject +class LvglWidgetTextEdit : public LvglWidgetObject { public: - LvglWidgetConfirmDialog() : LvglWidgetObject() {} + LvglWidgetTextEdit() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: - const char *title = nullptr; - const char *message = nullptr; + const char *txt = ""; + char value[129]; + int maxLen = 32; - int confirmFunction = 0; - int cancelFunction = 0; + int setFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; }; //----------------------------------------------------------------------------- -class LvglWidgetTextEdit : public LvglWidgetObject +class LvglWidgetNumberEdit : public LvglWidgetObject { public: - LvglWidgetTextEdit() : LvglWidgetObject() {} + LvglWidgetNumberEdit() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: - const char *txt = ""; - char value[129]; - int maxLen = 32; + int min = -1024, max = 1024; - int setFunction = 0; + int getFunction = LUA_REFNIL; + int setFunction = LUA_REFNIL; + int dispFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; }; //----------------------------------------------------------------------------- -class LvglWidgetNumberEdit : public LvglWidgetObject +class LvglWidgetSlider : public LvglWidgetObject { public: - LvglWidgetNumberEdit() : LvglWidgetObject() {} + LvglWidgetSlider() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: - int min = -1024, max = 1024; - - int getFunction = 0; - int setFunction = 0; - int dispFunction = 0; + int32_t vmin = 0; + int32_t vmax = 100; + int getValueFunction = LUA_REFNIL; + int setValueFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; }; //----------------------------------------------------------------------------- -class LvglWidgetChoice : public LvglWidgetObject +class LvglWidgetPage : public LvglWidgetObject { public: - LvglWidgetChoice() : LvglWidgetObject() {} + LvglWidgetPage() : LvglWidgetObject() {} void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: std::string title; - std::vector values; + std::string subtitle; + std::string iconFile; - int getFunction = 0; - int setFunction = 0; + int backActionFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; }; //----------------------------------------------------------------------------- -class LvglWidgetSlider : public LvglWidgetObject +class LvglWidgetDialog : public LvglWidgetObject { public: - LvglWidgetSlider() : LvglWidgetObject() {} + LvglWidgetDialog() : LvglWidgetObject() {} void build(lua_State *L) override; void clearRefs(lua_State *L) override; protected: - int32_t vmin = 0; - int32_t vmax = 100; - int getValueFunction = 0; - int setValueFunction = 0; + const char *title = nullptr; + + int closeFunction = LUA_REFNIL; void parseParam(lua_State *L, const char *key) override; }; //----------------------------------------------------------------------------- -class LvglWidgetPage : public LvglWidgetObject +class LvglWidgetConfirmDialog : public LvglWidgetObject { public: - LvglWidgetPage() : LvglWidgetObject() {} + LvglWidgetConfirmDialog() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} + + void build(lua_State *L) override; + void clearRefs(lua_State *L) override; + + protected: + const char *title = nullptr; + const char *message = nullptr; + + int confirmFunction = LUA_REFNIL; + int cancelFunction = LUA_REFNIL; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetMessageDialog : public LvglWidgetObject +{ + public: + LvglWidgetMessageDialog() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} void build(lua_State *L) override; + + protected: + const char *title = nullptr; + const char *message = nullptr; + const char *details = nullptr; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetPicker : public LvglWidgetObject +{ + public: + LvglWidgetPicker() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {} + void clearRefs(lua_State *L) override; protected: - int backActionFunction = 0; + int getFunction = LUA_REFNIL; + int setFunction = LUA_REFNIL; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetChoice : public LvglWidgetPicker +{ + public: + LvglWidgetChoice() : LvglWidgetPicker() {} + + void build(lua_State *L) override; + + protected: std::string title; - std::string subtitle; - std::string iconFile; + std::vector values; + + void parseParam(lua_State *L, const char *key) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetFontPicker : public LvglWidgetPicker +{ + public: + LvglWidgetFontPicker() : LvglWidgetPicker() {} + + void build(lua_State *L) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetAlignPicker : public LvglWidgetPicker +{ + public: + LvglWidgetAlignPicker() : LvglWidgetPicker() {} + + void build(lua_State *L) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetColorPicker : public LvglWidgetPicker +{ + public: + LvglWidgetColorPicker() : LvglWidgetPicker() {} + + void build(lua_State *L) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetTimerPicker : public LvglWidgetPicker +{ + public: + LvglWidgetTimerPicker() : LvglWidgetPicker() {} + + void build(lua_State *L) override; +}; + +//----------------------------------------------------------------------------- + +class LvglWidgetSwitchPicker : public LvglWidgetPicker +{ + public: + LvglWidgetSwitchPicker() : LvglWidgetPicker() {} + + void build(lua_State *L) override; + + protected: + int16_t vmin = SWSRC_FIRST; + int16_t vmax = SWSRC_LAST; void parseParam(lua_State *L, const char *key) override; }; //----------------------------------------------------------------------------- + +class LvglWidgetSourcePicker : public LvglWidgetPicker +{ + public: + LvglWidgetSourcePicker() : LvglWidgetPicker() {} + + void build(lua_State *L) override; +}; + +//----------------------------------------------------------------------------- diff --git a/radio/src/lua/lua_widget.cpp b/radio/src/lua/lua_widget.cpp index 95f4c864a78..35a696e47e3 100644 --- a/radio/src/lua/lua_widget.cpp +++ b/radio/src/lua/lua_widget.cpp @@ -221,12 +221,15 @@ LuaWidget::LuaWidget(const WidgetFactory* factory, Window* parent, const rect_t& rect, WidgetPersistentData* persistentData, int luaWidgetDataRef, int zoneRectDataRef, int optionsDataRef) : Widget(factory, parent, rect, persistentData), - zoneRectDataRef(zoneRectDataRef), optionsDataRef(optionsDataRef), + zoneRectDataRef(zoneRectDataRef), optionsDataRef(optionsDataRef), luaWidgetDataRef(luaWidgetDataRef), errorMessage(nullptr) { - this->luaWidgetDataRef = luaWidgetDataRef; - lv_obj_add_event_cb(lvobj, LuaWidget::redraw_cb, LV_EVENT_DRAW_MAIN, - nullptr); + if (useLvglLayout()) { + update(); + } else { + lv_obj_add_event_cb(lvobj, LuaWidget::redraw_cb, LV_EVENT_DRAW_MAIN, + nullptr); + } } LuaWidget::~LuaWidget() @@ -265,12 +268,6 @@ void LuaWidget::checkEvents() setFullscreen(false); } - // Call update once after widget first created - if (!created) { - created = true; - update(); - } - // refresh() has not been called if (!refreshed) background(); @@ -348,6 +345,24 @@ void LuaWidget::update() if (lua_pcall(lsWidgets, 2, 0, 0) != 0) setErrorMessage("update()"); + if (useLvglLayout()) { + if (!lv_obj_has_flag(lvobj, LV_OBJ_FLAG_HIDDEN)) { + lv_area_t a; + lv_obj_get_coords(lvobj, &a); + // Check widget is at least partially visible + if (a.x2 >= 0 && a.x1 < LCD_W) { + PROTECT_LUA() { + if (!callRefs(lsWidgets)) { + setErrorMessage("callRefs()"); + } + } else { + // TODO: error handling + } + UNPROTECT_LUA(); + } + } + } + luaLvglManager = nullptr; } @@ -495,9 +510,8 @@ void LuaWidget::background() { if (lsWidgets == 0 || errorMessage) return; - // TRACE("LuaWidget::background()"); - luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); if (luaFactory()->backgroundFunction) { + luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaFactory()->backgroundFunction); lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, luaWidgetDataRef); runningFS = this; @@ -524,25 +538,37 @@ void LuaWidget::clear() bool LuaWidget::useLvglLayout() const { return luaFactory()->useLvglLayout(); } +bool LuaWidget::isAppMode() const +{ + return fullscreen && ViewMain::instance()->isAppMode(); +} + +void LuaLvglManager::saveLvglObjectRef(int ref) +{ + if (tempParent) + tempParent->saveLvglObjectRef(ref); + else + lvglObjectRefs.push_back(ref); +} + void LuaLvglManager::clearRefs(lua_State *L) { for (size_t i = 0; i < lvglObjectRefs.size(); i += 1) { lua_rawgeti(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); - auto lvobj = LvglWidgetObject::checkLvgl(L, -1); - lvobj->clearRefs(L); - - luaL_unref(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); + auto p = LvglWidgetObjectBase::checkLvgl(L, -1); + lua_pop(L, 1); + if (p) p->clearRefs(L); } lvglObjectRefs.clear(); } bool LuaLvglManager::callRefs(lua_State *L) { - bool rv = true; for (size_t i = 0; i < lvglObjectRefs.size(); i += 1) { lua_rawgeti(L, LUA_REGISTRYINDEX, lvglObjectRefs[i]); - auto lvobj = LvglWidgetObject::checkLvgl(L, -1); - lvobj->callRefs(L); + auto p = LvglWidgetObjectBase::checkLvgl(L, -1); + lua_pop(L, 1); + if (p) if (!p->callRefs(L)) return false; } - return rv; + return true; } diff --git a/radio/src/lua/lua_widget.h b/radio/src/lua/lua_widget.h index 20210be8075..2e0e745823a 100644 --- a/radio/src/lua/lua_widget.h +++ b/radio/src/lua/lua_widget.h @@ -40,16 +40,17 @@ class LuaLvglManager LuaLvglManager() = default; std::vector getLvglObjectRefs() const { return lvglObjectRefs; } - void saveLvglObjectRef(int ref) { lvglObjectRefs.push_back(ref); } + void saveLvglObjectRef(int ref); void clearRefs(lua_State *L); bool callRefs(lua_State *L); - void setTempParent(Window *p) { tempParent = p; } - Window* getTempParent() const { return tempParent; } + void setTempParent(LvglWidgetObjectBase *p) { tempParent = p; } + LvglWidgetObjectBase* getTempParent() const { return tempParent; } virtual Window* getCurrentParent() const = 0; virtual void clear() = 0; virtual bool useLvglLayout() const = 0; + virtual bool isAppMode() const = 0; virtual void luaShowError() = 0; @@ -59,7 +60,7 @@ class LuaLvglManager protected: std::vector lvglObjectRefs; - Window* tempParent = nullptr; + LvglWidgetObjectBase* tempParent = nullptr; }; class LuaEventHandler @@ -92,25 +93,6 @@ class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager { friend class LuaWidgetFactory; - int zoneRectDataRef; - int optionsDataRef; - char* errorMessage; - bool refreshed = false; - - // Window interface - void onClicked() override; - void onCancel() override; - void checkEvents() override; - - // Widget interface - void onFullscreen(bool enable) override; - - void setErrorMessage(const char* funcName); - - // Update 'zone' data - void updateZoneRect(rect_t rect, bool updateUI = true) override; - bool updateTable(const char* idx, int val); - public: LuaWidget(const WidgetFactory* factory, Window* parent, const rect_t& rect, WidgetPersistentData* persistentData, int luaWidgetDataRef, int zoneRectDataRef, @@ -121,6 +103,8 @@ class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager std::string getName() const override { return "LuaWidget"; } #endif + void setErrorMessage(const char* funcName); + // Widget interface const char* getErrorMessage() const override; void update() override; @@ -130,24 +114,41 @@ class LuaWidget : public Widget, public LuaEventHandler, public LuaLvglManager LuaWidgetFactory* luaFactory() const { return (LuaWidgetFactory*)factory; } - Window* getCurrentParent() const override { return tempParent ? tempParent : (Window*)this; } + Window* getCurrentParent() const override { return (tempParent && tempParent->getWindow()) ? tempParent->getWindow() : (Window*)this; } bool useLvglLayout() const override; + bool isAppMode() const override; void luaShowError() override {} - bool isWidget() override { return true; } + void pushOptionsTable(); + + bool isWidget() override { return !inSettings; } protected: - bool created = false; + bool inSettings = false; lv_obj_t* errorLabel = nullptr; + int zoneRectDataRef; + int optionsDataRef; int luaWidgetDataRef = 0; - - // Calls LUA widget 'refresh' method - void refresh(BitmapBuffer* dc); + char* errorMessage; + bool refreshed = false; // Window interface + void onClicked() override; + void onCancel() override; + void checkEvents() override; void onEvent(event_t event) override; + // Widget interface + void onFullscreen(bool enable) override; + + // Update 'zone' data + void updateZoneRect(rect_t rect, bool updateUI = true) override; + bool updateTable(const char* idx, int val); + + // Calls LUA widget 'refresh' method + void refresh(BitmapBuffer* dc); + static void redraw_cb(lv_event_t *e); }; diff --git a/radio/src/lua/lua_widget_factory.h b/radio/src/lua/lua_widget_factory.h index f4e7632697c..1b4a3b11f3a 100644 --- a/radio/src/lua/lua_widget_factory.h +++ b/radio/src/lua/lua_widget_factory.h @@ -49,5 +49,6 @@ class LuaWidgetFactory : public WidgetFactory int refreshFunction; int backgroundFunction; int translateFunction; + int settingsFunction; bool lvglLayout; }; diff --git a/radio/src/lua/widgets.cpp b/radio/src/lua/widgets.cpp index b16c04cf562..3ed67d93cd2 100644 --- a/radio/src/lua/widgets.cpp +++ b/radio/src/lua/widgets.cpp @@ -217,7 +217,7 @@ void luaLoadWidgetCallback() TRACE("luaLoadWidgetCallback()"); const char * name=NULL; - int widgetOptions = 0, createFunction = 0, updateFunction = 0, + int widgetOptions = 0, createFunction = 0, updateFunction = 0, settingsFunction = 0, refreshFunction = 0, backgroundFunction = 0, translateFunction = 0; bool lvglLayout = false; @@ -252,7 +252,11 @@ void luaLoadWidgetCallback() translateFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); lua_pushnil(lsWidgets); } - else if (!strcasecmp(key, "uselvgl")) { + else if (!strcmp(key, "settings")) { + settingsFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX); + lua_pushnil(lsWidgets); + } + else if (!strcasecmp(key, "useLvgl")) { lvglLayout = lua_toboolean(lsWidgets, -1); } } @@ -263,10 +267,11 @@ void luaLoadWidgetCallback() LuaWidgetFactory * factory = new LuaWidgetFactory(name, options, createFunction); factory->updateFunction = updateFunction; factory->refreshFunction = refreshFunction; - factory->backgroundFunction = backgroundFunction; // NOSONAR - factory->lvglLayout = lvglLayout; + factory->backgroundFunction = backgroundFunction; factory->translateFunction = translateFunction; + factory->settingsFunction = settingsFunction; factory->translateOptions(options); + factory->lvglLayout = lvglLayout; TRACE("Loaded Lua widget %s", name); } } diff --git a/radio/src/main.cpp b/radio/src/main.cpp index 1e2a2627b79..4d3a76f281b 100644 --- a/radio/src/main.cpp +++ b/radio/src/main.cpp @@ -68,7 +68,7 @@ void openUsbMenu() { if (_usbMenu || _usbDisabled) return; - _usbMenu = new Menu(MainWindow::instance()); + _usbMenu = new Menu(); _usbMenu->setCloseHandler([]() { _usbMenu = nullptr; });