diff --git a/radio/src/gui/colorlcd/CMakeLists.txt b/radio/src/gui/colorlcd/CMakeLists.txt index de3a1d804..138aef38d 100644 --- a/radio/src/gui/colorlcd/CMakeLists.txt +++ b/radio/src/gui/colorlcd/CMakeLists.txt @@ -76,6 +76,7 @@ endmacro(add_gui_src) if(LUA) add_gui_src(standalone_lua.cpp) + add_gui_src(model_mixer_scripts.cpp) endif() if(HELI) diff --git a/radio/src/gui/colorlcd/getset_helpers.h b/radio/src/gui/colorlcd/getset_helpers.h index a13695433..7d7c930dc 100644 --- a/radio/src/gui/colorlcd/getset_helpers.h +++ b/radio/src/gui/colorlcd/getset_helpers.h @@ -36,7 +36,7 @@ #define GET_SET_DEFAULT(value) GET_DEFAULT(value), SET_DEFAULT(value) #define GET_SET_INVERTED(value) GET_INVERTED(value), SET_INVERTED(value) #define GET_SET_VALUE_WITH_OFFSET(value, offset) GET_VALUE_WITH_OFFSET(value, offset), SET_VALUE_WITH_OFFSET(value, offset) -#define GET_SET_WITH_OFFSET(value, offset) GET_VALUE_WITH_OFFSET(value, offset), SET_VALUE_WITH_OFFSET(value, offset) +#define GET_SET_WITH_OFFSET(value, offset) GET_VALUE_WITH_OFFSET(value, offset), SET_VALUE_WITH_OFFSET(value, offset) #define GET_SET_BF(value, offset, bits) GET_VALUE_WITH_BF(value, offset, bits), SET_VALUE_WITH_BF(value, offset, bits) #endif // _GETSET_HELPERS_H_ diff --git a/radio/src/gui/colorlcd/menu_model.cpp b/radio/src/gui/colorlcd/menu_model.cpp index 3d19a8a41..1b9f028d2 100644 --- a/radio/src/gui/colorlcd/menu_model.cpp +++ b/radio/src/gui/colorlcd/menu_model.cpp @@ -29,6 +29,7 @@ #include "model_gvars.h" #include "model_curves.h" #include "model_logical_switches.h" +#include "model_mixer_scripts.h" #include "special_functions.h" #include "model_telemetry.h" @@ -47,5 +48,8 @@ ModelMenu::ModelMenu(): addTab(new ModelGVarsPage()); addTab(new ModelLogicalSwitchesPage()); addTab(new SpecialFunctionsPage(g_model.customFn)); +#if defined(LUA) + addTab(new ModelMixerScriptsPage()); +#endif addTab(new ModelTelemetryPage()); } diff --git a/radio/src/gui/colorlcd/model_mixer_scripts.cpp b/radio/src/gui/colorlcd/model_mixer_scripts.cpp new file mode 100644 index 000000000..dd57d5658 --- /dev/null +++ b/radio/src/gui/colorlcd/model_mixer_scripts.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "opentx.h" + +#include "model_mixer_scripts.h" +#include "dataconstants.h" +#include "lua/lua_api.h" + +#include "translations.h" +#include "menus.h" +#include "libopenui.h" + +#include + +#define SET_DIRTY() storageDirty(EE_MODEL) + +class ScriptEditWindow : public Page { + public: + explicit ScriptEditWindow(uint8_t idx) : + Page(ICON_MODEL_LUA_SCRIPTS), + idx(idx) + { + buildBody(&body); + buildHeader(&header); + } + + protected: + uint8_t idx; + bool update = false; + + void checkEvents() override + { + if (update) { + TRACE("rebuilding ScriptEditWindow..."); + rebuildBody(&body); + update = false; + } + // note: 'update' is set from Page::checkEvents() + Page::checkEvents(); + } + + void buildHeader(Window * window) + { + new StaticText(window, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, + PAGE_LINE_HEIGHT}, + STR_MENUCUSTOMSCRIPTS, 0, FOCUS_COLOR); + new StaticText(window, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT, + LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT}, + std::string("LUA") + std::to_string(idx + 1), 0, FOCUS_COLOR); + } + + void buildBody(FormWindow * window, bool focusScript = false) + { + FormGridLayout grid; + grid.spacer(PAGE_PADDING); + + ScriptData* sd = &(g_model.scriptsData[idx]); + + // Filename + new StaticText(window, grid.getLabelSlot(), STR_SCRIPT); + auto fc = new FileChoice( + window, grid.getFieldSlot(), SCRIPTS_MIXES_PATH, SCRIPT_EXT, + LEN_SCRIPT_FILENAME, + [=]() { return std::string(sd->file, LEN_SCRIPT_FILENAME); }, + [=](std::string newValue) { + strncpy(sd->file, newValue.c_str(), LEN_SCRIPT_FILENAME); + if (newValue.empty()) { memset((void*)sd, 0, sizeof(ScriptData)); } + storageDirty(EE_MODEL); + LUA_LOAD_MODEL_SCRIPT(idx); // async reload... + update = true; + }, true); + grid.nextLine(); + + // Custom name + new StaticText(window, grid.getLabelSlot(), STR_NAME); + new ModelTextEdit(window, grid.getFieldSlot(), sd->name, sizeof(sd->name)); + grid.nextLine(); + + // scriptInputsOutputs + ScriptInputsOutputs& sio = scriptInputsOutputs[idx]; + + if (sio.inputsCount > 0) { + new Subtitle(window, grid.getLineSlot(), STR_INPUTS); + grid.nextLine(); + + auto gInputs = + new FormGroup(window, grid.getFieldSlot(), + FORM_BORDER_FOCUS_ONLY | PAINT_CHILDREN_FIRST); + GridLayout inputsGrid(gInputs); + + for (int i = 0; i < sio.inputsCount; i++) { + ScriptInput& si = sio.inputs[i]; + new StaticText(window, grid.getLabelSlot(true), si.name); + grid.nextLine(); + if (si.type == INPUT_TYPE_VALUE) { + new NumberEdit(gInputs, inputsGrid.getSlot(), si.min - si.def, + si.max - si.def, + GET_SET_WITH_OFFSET(sd->inputs[i].value, si.def)); + } else { + new SourceChoice(gInputs, inputsGrid.getSlot(), 0, + MIXSRC_LAST_TELEM, + GET_SET_DEFAULT(sd->inputs[i].source)); + } + inputsGrid.nextLine(); + } + gInputs->setHeight(inputsGrid.getWindowHeight()); + } + + if (sio.outputsCount > 0) { + new Subtitle(window, grid.getLabelSlot(), STR_OUTPUTS); + grid.nextLine(); + + auto gOutputs = + new FormGroup(window, grid.getLineSlot(), + FORM_BORDER_FOCUS_ONLY | PAINT_CHILDREN_FIRST); + FormGridLayout outputsGrid(gOutputs->width()); + + for (int i = 0; i < sio.outputsCount; i++) { + ScriptOutput* so = &(sio.outputs[i]); + new DynamicText(gOutputs, outputsGrid.getLabelSlot(), [=]() { + char s[16]; + getSourceString(s, + MIXSRC_FIRST_LUA + (idx * MAX_SCRIPT_OUTPUTS) + i); + return std::string(s, sizeof(s) - 1); + }); + new DynamicNumber(gOutputs, outputsGrid.getFieldSlot(), + [=]() { return calcRESXto1000(so->value); }); + + outputsGrid.nextLine(); + } + gOutputs->setHeight(outputsGrid.getWindowHeight()); + grid.addWindow(gOutputs); + } + + window->setInnerHeight(grid.getWindowHeight()); + if (focusScript) { fc->setFocus(); } + } + + void rebuildBody(FormWindow * window) + { + coord_t scrollPosition = window->getScrollPositionY(); + window->clear(); + buildBody(window); + window->setScrollPositionY(scrollPosition); + } +}; + +class ScriptLineButton : public Button +{ + public: + ScriptLineButton(FormGroup* parent, const rect_t& rect, + const ScriptData& scriptData, + const ScriptInternalData* runtimeData) : + Button(parent, rect), scriptData(scriptData), runtimeData(runtimeData) + { + } + + void paint(BitmapBuffer* dc) override + { + LcdFlags textColor = DEFAULT_COLOR; + LcdFlags bgColor = FIELD_BGCOLOR; + + dc->drawSolidFilledRect(0, 0, width(), height(), bgColor); + + if (runtimeData) { + dc->drawSizedText(FIELD_PADDING_LEFT, FIELD_PADDING_TOP, + scriptData.file, LEN_SCRIPT_FILENAME, + textColor); + } + + // bounding rect + if (hasFocus()) + dc->drawSolidRect(0, 0, rect.w, rect.h, 2, FOCUS_BGCOLOR); + else + dc->drawSolidRect(0, 0, rect.w, rect.h, 1, FIELD_FRAME_COLOR); + } + + protected: + const ScriptData& scriptData; + const ScriptInternalData* runtimeData; +}; + +ModelMixerScriptsPage::ModelMixerScriptsPage() : + PageTab(STR_MENUCUSTOMSCRIPTS, ICON_MODEL_LUA_SCRIPTS) +{ +} + +void ModelMixerScriptsPage::rebuild(FormWindow * window, int8_t focusIdx) +{ + coord_t scrollPosition = window->getScrollPositionY(); + window->clear(); + build(window, focusIdx); + window->setScrollPositionY(scrollPosition); +} + +void ModelMixerScriptsPage::build(FormWindow * window, int8_t focusIdx) +{ + FormGridLayout grid; + grid.spacer(PAGE_PADDING); + grid.setLabelWidth(66); + + int8_t scriptIdx = 0; + for (int8_t idx = 0; idx < MAX_SCRIPTS; idx++) { + + ScriptInternalData* runtimeData = nullptr; + ScriptData &sd = g_model.scriptsData[idx]; + + if (sd.file[0] != '\0') { + runtimeData = &(scriptInternalData[scriptIdx++]); + } + + // LUAx label + auto txt = new StaticText(window, grid.getLabelSlot(), + std::string("LUA") + std::to_string(idx + 1), + BUTTON_BACKGROUND, CENTERED); + + Button* button = new ScriptLineButton(window, grid.getFieldSlot(), sd, runtimeData); + + button->setPressHandler([=]() -> uint8_t { + Menu* menu = new Menu(window); + menu->addLine(STR_EDIT, [=]() { editLine(window, idx); }); + + if (runtimeData != nullptr) { + menu->addLine(STR_RESET, [=]() { + memset((void*)&sd, 0, sizeof(sd)); + // TODO: anything else? reload scripts??? + storageDirty(EE_MODEL); + rebuild(window, idx); + }); + return 0; + } + return 0; + }); + + button->setFocusHandler([=](bool focus) { + if (focus) { + txt->setBackgroundColor(FOCUS_BGCOLOR); + txt->setTextFlags(FOCUS_COLOR | CENTERED); + } else { + txt->setBackgroundColor(FIELD_FRAME_COLOR); + txt->setTextFlags(CENTERED); + } + txt->invalidate(); + }); + + if (focusIdx == idx) { + button->setFocus(SET_FOCUS_DEFAULT); + txt->setBackgroundColor(FOCUS_BGCOLOR); + txt->setTextFlags(FOCUS_COLOR | CENTERED); + txt->invalidate(); + } + + txt->setHeight(button->height()); + grid.spacer(button->height() + 5); + } + + grid.nextLine(); + window->setInnerHeight(grid.getWindowHeight()); +} + +void ModelMixerScriptsPage::editLine(FormWindow * window, uint8_t idx) +{ + Window::clearFocus(); + Window * editWindow = new ScriptEditWindow(idx); + editWindow->setCloseHandler([=]() { + rebuild(window, idx); + }); +} diff --git a/radio/src/gui/colorlcd/model_mixer_scripts.h b/radio/src/gui/colorlcd/model_mixer_scripts.h new file mode 100644 index 000000000..8486a193c --- /dev/null +++ b/radio/src/gui/colorlcd/model_mixer_scripts.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include "tabsgroup.h" + +class ModelMixerScriptsPage: public PageTab { + public: + ModelMixerScriptsPage(); + + virtual void build(FormWindow * window) override + { + build(window, 0); + } + + protected: + void build(FormWindow * window, int8_t focusIdx); + void rebuild(FormWindow * window, int8_t focusIdx); + void editLine(FormWindow * window, uint8_t idx); +}; diff --git a/radio/src/gui/colorlcd/model_setup.cpp b/radio/src/gui/colorlcd/model_setup.cpp index a69df8222..640f547ce 100644 --- a/radio/src/gui/colorlcd/model_setup.cpp +++ b/radio/src/gui/colorlcd/model_setup.cpp @@ -1035,15 +1035,16 @@ void ModelSetupPage::build(FormWindow * window) // Model name new StaticText(window, grid.getLabelSlot(), STR_MODELNAME); - auto text = new RadioTextEdit(window, grid.getFieldSlot(), g_model.header.name, sizeof(g_model.header.name), - 0, MODEL_NAME_EXTRA_CHARS); + auto text = + new ModelTextEdit(window, grid.getFieldSlot(), g_model.header.name, + sizeof(g_model.header.name), 0, MODEL_NAME_EXTRA_CHARS); text->setChangeHandler([=] { - modelslist.load(); - auto model = modelslist.getCurrentModel(); - if (model) { - model->setModelName(g_model.header.name); - } - SET_DIRTY(); + modelslist.load(); + auto model = modelslist.getCurrentModel(); + if (model) { + model->setModelName(g_model.header.name); + } + SET_DIRTY(); }); grid.nextLine();