diff --git a/radio/src/gui/colorlcd/CMakeLists.txt b/radio/src/gui/colorlcd/CMakeLists.txt index 08c87bbf6..6238662ee 100644 --- a/radio/src/gui/colorlcd/CMakeLists.txt +++ b/radio/src/gui/colorlcd/CMakeLists.txt @@ -167,6 +167,10 @@ add_gui_src(curve.cpp) add_gui_src(sourcechoice.cpp) add_gui_src(switchchoice.cpp) add_gui_src(curveedit.cpp) +add_gui_src(input_edit.cpp) +add_gui_src(input_mix_group.cpp) +add_gui_src(input_mix_button.cpp) +add_gui_src(lvgl_widgets/input_mix_line.c) if(HARDWARE_TOUCH) add_gui_src(keyboard_curve.cpp) diff --git a/radio/src/gui/colorlcd/input_edit.cpp b/radio/src/gui/colorlcd/input_edit.cpp new file mode 100644 index 000000000..821df32b6 --- /dev/null +++ b/radio/src/gui/colorlcd/input_edit.cpp @@ -0,0 +1,315 @@ +/* + * 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 "input_edit.h" +#include "model_curves.h" + +#include "opentx.h" +#include "gvar_numberedit.h" + +#define SET_DIRTY() storageDirty(EE_MODEL) + +class SensorValue : Window +{ + public: + SensorValue(Window *parent, const rect_t &rect, ExpoData *expoData) : + Window(parent, rect), expoData(expoData) + { + } + void paint(BitmapBuffer *dc) override + { + if (isTelemetryValue()) { + uint8_t sensorIndex = (expoData->srcRaw - MIXSRC_FIRST_TELEM) / 3; +#if defined(SIMU) + if (true) { +#else + TelemetryItem &telemetryItem = telemetryItems[sensorIndex]; + if (telemetryItem.isAvailable()) { +#endif + LcdFlags flags = LEFT | COLOR_THEME_PRIMARY1; + drawSensorCustomValue(dc, 3, 2, sensorIndex, lastSensorVal, flags); + } else { + dc->drawText(3, 2, "---", COLOR_THEME_PRIMARY1); + } + } + } + bool isTelemetryValue() + { + return expoData->srcRaw >= MIXSRC_FIRST_TELEM && + expoData->srcRaw <= MIXSRC_LAST_TELEM; + } + void checkEvents() override + { + getvalue_t sensorVal = -1; + if (isTelemetryValue()) { + sensorVal = getValue(expoData->srcRaw); + } + if (lastSensorVal != sensorVal) { + lastSensorVal = sensorVal; + invalidate(); + } + } + + LcdFlags getSensorPrec() + { + LcdFlags prec = 0; + if (isTelemetryValue()) { + uint8_t sensorIndex = (expoData->srcRaw - MIXSRC_FIRST_TELEM) / 3; + TelemetrySensor sensor = g_model.telemetrySensors[sensorIndex]; + if (sensor.prec > 0) { + prec |= (sensor.prec == 1 ? PREC1 : PREC2); + } + } + return prec; + } + + protected: + getvalue_t lastSensorVal; + ExpoData *expoData; +}; + +InputEditWindow::InputEditWindow(int8_t input, uint8_t index) : + Page(ICON_MODEL_INPUTS), + input(input), + index(index), + preview( + this, + {INPUT_EDIT_CURVE_LEFT, INPUT_EDIT_CURVE_TOP, INPUT_EDIT_CURVE_WIDTH, + INPUT_EDIT_CURVE_HEIGHT}, + [=](int x) -> int { + ExpoData *line = expoAddress(index); + int16_t anas[MAX_INPUTS] = {0}; + applyExpos(anas, e_perout_mode_inactive_flight_mode, line->srcRaw, x); + return anas[line->chn]; + }, + [=]() -> int { return getValue(expoAddress(index)->srcRaw); }) +{ +#if LCD_W > LCD_H + auto BODY_WIDTH = LCD_W - preview.width() - PAGE_PADDING; + body.setWidth(BODY_WIDTH); + body.setLeft(preview.width() + PAGE_PADDING); +#else + body.setRect({0, INPUT_EDIT_CURVE_TOP + INPUT_EDIT_CURVE_HEIGHT, LCD_W, + LCD_H - INPUT_EDIT_CURVE_TOP - INPUT_EDIT_CURVE_HEIGHT}); +#endif + buildBody(&body); + buildHeader(&header); +} + +void InputEditWindow::deleteLater(bool detach, bool trash) +{ + if (_deleted) return; + + preview.deleteLater(true, false); + + Page::deleteLater(detach, trash); +} + +void InputEditWindow::buildHeader(Window *window) +{ + new StaticText(window, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, + PAGE_LINE_HEIGHT}, + STR_MENUINPUTS, 0, COLOR_THEME_PRIMARY2); + new StaticText(window, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT, + LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT}, + getSourceString(MIXSRC_FIRST_INPUT + input), 0, + COLOR_THEME_PRIMARY2); +} + +// TODO share this code with MIXER +void InputEditWindow::updateCurveParamField(ExpoData *line) +{ + curveParamField->clear(); + + const rect_t rect = {0, 0, curveParamField->width(), + curveParamField->height()}; + + switch (line->curve.type) { + case CURVE_REF_DIFF: + case CURVE_REF_EXPO: { + GVarNumberEdit *edit = new GVarNumberEdit( + curveParamField, rect, -100, 100, GET_SET_DEFAULT(line->curve.value)); + edit->setSuffix("%"); + break; + } + + case CURVE_REF_FUNC: + new Choice(curveParamField, rect, STR_VCURVEFUNC, 0, CURVE_BASE - 1, + GET_SET_DEFAULT(line->curve.value)); + break; + + case CURVE_REF_CUSTOM: { + auto choice = new ChoiceEx(curveParamField, rect, -MAX_CURVES, MAX_CURVES, + GET_SET_DEFAULT(line->curve.value)); + choice->setTextHandler([](int value) { return getCurveString(value); }); + choice->setLongPressHandler([this](event_t event) { + ExpoData *line = expoAddress(index); + + // if no curve is specified then dont link to curve page + if (line->curve.value != 0) + ModelCurvesPage::pushEditCurve(abs(line->curve.value) - 1); + }); + break; + } + } +} + +void InputEditWindow::buildBody(FormWindow *window) +{ + FormGridLayout grid; + grid.setLabelWidth(INPUT_EDIT_LABELS_WIDTH); + grid.spacer(PAGE_PADDING); + + ExpoData *line = expoAddress(index); + +#if LCD_W > LCD_H + grid.setMarginRight(180); +#endif + + // Input Name + new StaticText(window, grid.getLabelSlot(), STR_INPUTNAME, 0, + COLOR_THEME_PRIMARY1); + new ModelTextEdit(window, grid.getFieldSlot(), g_model.inputNames[line->chn], + sizeof(g_model.inputNames[line->chn])); + grid.nextLine(); + + // Switch + new StaticText(window, grid.getLabelSlot(), STR_SWITCH, 0, + COLOR_THEME_PRIMARY1); + new SwitchChoice(window, grid.getFieldSlot(), SWSRC_FIRST_IN_MIXES, + SWSRC_LAST_IN_MIXES, GET_SET_DEFAULT(line->swtch)); + grid.nextLine(); + + // Side + new StaticText(window, grid.getLabelSlot(), STR_SIDE, 0, + COLOR_THEME_PRIMARY1); + new Choice( + window, grid.getFieldSlot(), STR_VCURVEFUNC, 1, 3, + [=]() -> int16_t { return 4 - line->mode; }, + [=](int16_t newValue) { + line->mode = 4 - newValue; + SET_DIRTY(); + }); + grid.nextLine(); + + // Name + new StaticText(window, grid.getLabelSlot(), STR_EXPONAME, 0, + COLOR_THEME_PRIMARY1); + new ModelTextEdit(window, grid.getFieldSlot(), line->name, + sizeof(line->name)); + grid.nextLine(); + + // Source + new StaticText(window, grid.getLabelSlot(), STR_SOURCE, 0, + COLOR_THEME_PRIMARY1); + new SourceChoice( + window, grid.getFieldSlot(2, 0), INPUTSRC_FIRST, INPUTSRC_LAST, + GET_DEFAULT(line->srcRaw), [=](int32_t newValue) { + line->srcRaw = newValue; + if (line->srcRaw > MIXSRC_Ail && line->carryTrim == TRIM_ON) { + line->carryTrim = TRIM_OFF; + trimChoice->invalidate(); + } + window->clear(); + buildBody(window); + SET_DIRTY(); + }); + + SensorValue *sensor = nullptr; + if (line->srcRaw >= MIXSRC_FIRST_TELEM) { + sensor = new SensorValue(window, grid.getFieldSlot(2, 1), line); + + grid.nextLine(); + // Scale + new StaticText(window, grid.getLabelSlot(), STR_SCALE, 0, + COLOR_THEME_PRIMARY1); + new NumberEdit(window, grid.getFieldSlot(), 0, + maxTelemValue(line->srcRaw - MIXSRC_FIRST_TELEM + 1), + GET_SET_DEFAULT(line->scale), 0, sensor->getSensorPrec()); + adjustHeight(); + } + grid.nextLine(); + + // Weight + new StaticText(window, grid.getLabelSlot(), STR_WEIGHT, 0, + COLOR_THEME_PRIMARY1); + auto gvar = new GVarNumberEdit(window, grid.getFieldSlot(), -100, 100, + GET_SET_DEFAULT(line->weight)); + gvar->setSuffix("%"); + grid.nextLine(); + + // Offset + new StaticText(window, grid.getLabelSlot(), STR_OFFSET, 0, + COLOR_THEME_PRIMARY1); + gvar = new GVarNumberEdit(window, grid.getFieldSlot(), -100, 100, + GET_SET_DEFAULT(line->offset)); + gvar->setSuffix("%"); + grid.nextLine(); + + // Trim + new StaticText(window, grid.getLabelSlot(), STR_TRIM, 0, + COLOR_THEME_PRIMARY1); + trimChoice = new Choice(window, grid.getFieldSlot(), STR_VMIXTRIMS, -TRIM_OFF, + -TRIM_LAST, GET_VALUE(-line->carryTrim), + SET_VALUE(line->carryTrim, -newValue)); + trimChoice->setAvailableHandler([=](int value) { + return value != TRIM_ON || line->srcRaw <= MIXSRC_Ail; + }); + grid.nextLine(); + + // Curve + new StaticText(&body, grid.getLabelSlot(), STR_CURVE, 0, + COLOR_THEME_PRIMARY1); + new Choice(&body, grid.getFieldSlot(2, 0), "\004DiffExpoFuncCstm", 0, + CURVE_REF_CUSTOM, GET_DEFAULT(line->curve.type), + [=](int32_t newValue) { + line->curve.type = newValue; + line->curve.value = 0; + SET_DIRTY(); + updateCurveParamField(line); + }); + curveParamField = new FormGroup(&body, grid.getFieldSlot(2, 1) + // , FORM_FORWARD_FOCUS + ); + updateCurveParamField(line); + grid.nextLine(); + +#if defined(FLIGHT_MODES) + // Flight modes + new StaticText(window, grid.getLabelSlot(), STR_FLMODE, 0, + COLOR_THEME_PRIMARY1); + for (uint32_t i = 0; i < MAX_FLIGHT_MODES; i++) { + char fm[2] = {char('0' + i), '\0'}; + if (i > 0 && (i % 4) == 0) grid.nextLine(); + new TextButton( + window, grid.getFieldSlot(4, i % 4), fm, + [=]() -> uint32_t { + BFBIT_FLIP(line->flightModes, bfBit(i)); + SET_DIRTY(); + return !(bfSingleBitGet(line->flightModes, i)); + }, + OPAQUE | (bfSingleBitGet(line->flightModes, i) ? 0 : BUTTON_CHECKED)); + } + grid.nextLine(); +#endif +} diff --git a/radio/src/gui/colorlcd/input_edit.h b/radio/src/gui/colorlcd/input_edit.h new file mode 100644 index 000000000..854c02d22 --- /dev/null +++ b/radio/src/gui/colorlcd/input_edit.h @@ -0,0 +1,46 @@ +/* + * 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 "page.h" +#include "curve.h" +#include "choice.h" + +struct ExpoData; + +class InputEditWindow : public Page +{ + public: + InputEditWindow(int8_t input, uint8_t index); + void deleteLater(bool detach = true, bool trash = true) override; + + protected: + uint8_t input; + uint8_t index; + Curve preview; + Choice *trimChoice = nullptr; + FormGroup *curveParamField = nullptr; + + void buildHeader(Window *window); + void updateCurveParamField(ExpoData *line); + void buildBody(FormWindow *window); +}; diff --git a/radio/src/gui/colorlcd/input_mix_button.cpp b/radio/src/gui/colorlcd/input_mix_button.cpp new file mode 100644 index 000000000..b67ac9c58 --- /dev/null +++ b/radio/src/gui/colorlcd/input_mix_button.cpp @@ -0,0 +1,59 @@ +/* + * 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 "input_mix_button.h" +#include "opentx.h" + +#include "lvgl_widgets/input_mix_line.h" + +InputMixButton::InputMixButton(Window* parent, const rect_t& rect, uint8_t index) : + Button(parent, rect, nullptr, 0, COLOR_THEME_PRIMARY1, + input_mix_line_create), + index(index) +{ +} + +void InputMixButton::checkEvents() +{ + check(isActive()); + Button::checkEvents(); +} + +void InputMixButton::drawFlightModes(BitmapBuffer *dc, FlightModesType value, + LcdFlags textColor, coord_t x, coord_t y) +{ + dc->drawMask(x, y + 2, mixerSetupFlightmodeIcon, textColor); + x += 20; + + for (int i = 0; i < MAX_FLIGHT_MODES; i++) { + char s[] = " "; + s[0] = '0' + i; + if (value & (1 << i)) { + dc->drawText(x, y + 2, s, FONT(XS) | COLOR_THEME_DISABLED); + } else { + dc->drawSolidFilledRect(x, y + 2, 8, 3, COLOR_THEME_FOCUS); + dc->drawText(x, y + 2, s, FONT(XS) | textColor); + } + x += 8; + } +} + + diff --git a/radio/src/gui/colorlcd/input_mix_button.h b/radio/src/gui/colorlcd/input_mix_button.h new file mode 100644 index 000000000..6afa0bedf --- /dev/null +++ b/radio/src/gui/colorlcd/input_mix_button.h @@ -0,0 +1,46 @@ +/* + * 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 "button.h" + +class InputMixButton : public Button +{ + public: + InputMixButton(Window* parent, const rect_t& rect, uint8_t index); + + uint8_t getIndex() const { return index; } + void setIndex(uint8_t i) { index = i; } + + void checkEvents() override; + + void drawFlightModes(BitmapBuffer *dc, FlightModesType value, + LcdFlags textColor, coord_t x, coord_t y); + + // TODO: generic "getHeight()" and recalc on changes + + protected: + uint8_t index; + bool active = false; + + virtual bool isActive() const = 0; +}; diff --git a/radio/src/gui/colorlcd/input_mix_group.cpp b/radio/src/gui/colorlcd/input_mix_group.cpp new file mode 100644 index 000000000..2a3057cef --- /dev/null +++ b/radio/src/gui/colorlcd/input_mix_group.cpp @@ -0,0 +1,84 @@ +/* + * 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 "input_mix_group.h" +#include "opentx.h" + +static const lv_coord_t col_dsc[] = { + LV_DPI_DEF / 2, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST, +}; + +static const lv_coord_t row_dsc[] = { + LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST, +}; + +InputMixGroup::InputMixGroup(Window* parent, mixsrc_t idx) : + Window(parent, rect_t{}), idx(idx) +{ + lv_obj_set_style_bg_opa(lvobj, LV_OPA_100, 0); + lv_obj_set_style_radius(lvobj, lv_dpx(16), 0); + lv_obj_set_style_pad_hor(lvobj, lv_dpx(8), 0); + lv_obj_set_style_pad_ver(lvobj, lv_dpx(8), 0); + + lv_obj_set_width(lvobj, lv_pct(100)); + lv_obj_set_height(lvobj, LV_SIZE_CONTENT); + + lv_obj_set_layout(lvobj, LV_LAYOUT_GRID); + lv_obj_set_grid_dsc_array(lvobj, col_dsc, row_dsc); + + label = lv_label_create(lvobj); + lv_label_set_text(label, getSourceString(idx)); + lv_obj_set_grid_cell(label, + LV_GRID_ALIGN_START, 0, 1, + LV_GRID_ALIGN_START, 0, 1); + + auto box = window_create(lvobj); + lv_obj_set_size(box, lv_pct(100), LV_SIZE_CONTENT); + lv_obj_set_grid_cell(box, + LV_GRID_ALIGN_STRETCH, 1, 1, + LV_GRID_ALIGN_START, 0, 1); + + line_container = window_create(box); + lv_obj_set_size(line_container, lv_pct(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(line_container, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(line_container, lv_dpx(8), LV_PART_MAIN); +} + +void InputMixGroup::addLine(Window* line, const uint8_t* symbol) +{ + lines.emplace_back(line, symbol); + lv_obj_t* line_obj = line->getLvObj(); + lv_obj_set_parent(line_obj, line_container); +} + +bool InputMixGroup::removeLine(Window* line) +{ + auto l = std::find_if(lines.begin(), lines.begin(), [=](const Line& l) -> bool { + return l.win == line; + }); + + if (l != lines.end()) { + lines.erase(l); + return true; + } + + return false; +} diff --git a/radio/src/gui/colorlcd/input_mix_group.h b/radio/src/gui/colorlcd/input_mix_group.h new file mode 100644 index 000000000..46b558986 --- /dev/null +++ b/radio/src/gui/colorlcd/input_mix_group.h @@ -0,0 +1,54 @@ +/* + * 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 "window.h" +#include "opentx_types.h" + +#include + +class InputMixGroup : public Window +{ + struct Line { + Window* win; + const uint8_t* symbol; + + Line(Window* w, const uint8_t* s) : + win(w), symbol(s) + {} + }; + + mixsrc_t idx; + std::list lines; + + lv_obj_t* label; + lv_obj_t* line_container; + + public: + InputMixGroup(Window* parent, mixsrc_t idx); + + mixsrc_t getMixSrc() { return idx; } + size_t getLineCount() { return lines.size(); } + + void addLine(Window* line, const uint8_t* symbol = nullptr); + bool removeLine(Window* line); +}; diff --git a/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.c b/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.c new file mode 100644 index 000000000..22f098798 --- /dev/null +++ b/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.c @@ -0,0 +1,34 @@ +/* + * Copyright (C) EdgeTX + * + * 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 "input_mix_line.h" + +#define MY_CLASS &input_mix_line_class + +const lv_obj_class_t input_mix_line_class = { + .width_def = LV_PCT(100), + .height_def = LV_DPI_DEF / 4, + .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE, + .instance_size = sizeof(lv_btn_t), + .base_class = &lv_btn_class, +}; + +lv_obj_t* input_mix_line_create(lv_obj_t* parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} diff --git a/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.h b/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.h new file mode 100644 index 000000000..01068c18d --- /dev/null +++ b/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) EdgeTX + * + * 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. + */ + +#ifndef INPUT_MIX_LINE_H +#define INPUT_MIX_LINE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const lv_obj_class_t input_mix_line_class; +lv_obj_t* input_mix_line_create(lv_obj_t* parent); + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/radio/src/gui/colorlcd/model_inputs.cpp b/radio/src/gui/colorlcd/model_inputs.cpp index 7f4c20276..c6d6e0bf1 100644 --- a/radio/src/gui/colorlcd/model_inputs.cpp +++ b/radio/src/gui/colorlcd/model_inputs.cpp @@ -25,12 +25,12 @@ #include "libopenui.h" #include "choiceex.h" #include "model_curves.h" +#include "input_edit.h" +#include "input_mix_group.h" +#include "input_mix_button.h" #define SET_DIRTY() storageDirty(EE_MODEL) -#define PASTE_BEFORE -2 -#define PASTE_AFTER -1 - uint8_t getExposCount() { uint8_t count = 0; @@ -54,27 +54,19 @@ bool reachExposLimit() return false; } -void copyExpo(uint8_t source, uint8_t dest, int8_t input) +// TODO: these functions need to be added to the generic API +// used by all radios, and be removed from UI code +// +void copyExpo(uint8_t source, uint8_t dest, uint8_t input) { pauseMixerCalculations(); ExpoData sourceExpo; memcpy(&sourceExpo, expoAddress(source), sizeof(ExpoData)); ExpoData *expo = expoAddress(dest); size_t trailingExpos = MAX_EXPOS - (dest + 1); - if (input == PASTE_AFTER) { - trailingExpos--; - memmove(expo + 2, expo + 1, trailingExpos * sizeof(ExpoData)); - memcpy(expo + 1, &sourceExpo, sizeof(ExpoData)); - (expo + 1)->chn = (expo)->chn; - } else if (input == PASTE_BEFORE) { - memmove(expo + 1, expo, trailingExpos * sizeof(ExpoData)); - memcpy(expo, &sourceExpo, sizeof(ExpoData)); - expo->chn = (expo + 1)->chn; - } else { - memmove(expo + 1, expo, trailingExpos * sizeof(ExpoData)); - memcpy(expo, &sourceExpo, sizeof(ExpoData)); - expo->chn = input; - } + memmove(expo + 1, expo, trailingExpos * sizeof(ExpoData)); + memcpy(expo, &sourceExpo, sizeof(ExpoData)); + expo->chn = input; resumeMixerCalculations(); storageDirty(EE_MODEL); } @@ -93,559 +85,6 @@ void deleteExpo(uint8_t idx) storageDirty(EE_MODEL); } -class SensorValue : Window -{ - public: - SensorValue(Window *parent, const rect_t &rect, ExpoData *expoData) : - Window(parent, rect), expoData(expoData) - { - } - void paint(BitmapBuffer *dc) override - { - if (isTelemetryValue()) { - uint8_t sensorIndex = (expoData->srcRaw - MIXSRC_FIRST_TELEM) / 3; -#if defined(SIMU) - if(true) { -#else - TelemetryItem &telemetryItem = telemetryItems[sensorIndex]; - if (telemetryItem.isAvailable()) { -#endif - LcdFlags flags = LEFT | COLOR_THEME_PRIMARY1; - drawSensorCustomValue(dc, 3, 2, sensorIndex, lastSensorVal, flags); - } else { - dc->drawText(3, 2, "---", COLOR_THEME_PRIMARY1); - } - } - } - bool isTelemetryValue() - { - return expoData->srcRaw >= MIXSRC_FIRST_TELEM && - expoData->srcRaw <= MIXSRC_LAST_TELEM; - } - void checkEvents() override - { - getvalue_t sensorVal = -1; - if (isTelemetryValue()) { - sensorVal = getValue(expoData->srcRaw); - } - if (lastSensorVal != sensorVal) { - lastSensorVal = sensorVal; - invalidate(); - } - } - - LcdFlags getSensorPrec() - { - LcdFlags prec = 0; - if (isTelemetryValue()) { - uint8_t sensorIndex = (expoData->srcRaw - MIXSRC_FIRST_TELEM) / 3; - TelemetrySensor sensor = g_model.telemetrySensors[sensorIndex]; - if (sensor.prec > 0) { - prec |= (sensor.prec == 1 ? PREC1 : PREC2); - } - } - return prec; - } - - protected: - getvalue_t lastSensorVal; - ExpoData *expoData; -}; - -class InputEditWindow : public Page -{ - public: - InputEditWindow(int8_t input, uint8_t index) : - Page(ICON_MODEL_INPUTS), - input(input), - index(index), - preview( - this, - {INPUT_EDIT_CURVE_LEFT, INPUT_EDIT_CURVE_TOP, INPUT_EDIT_CURVE_WIDTH, - INPUT_EDIT_CURVE_HEIGHT}, - [=](int x) -> int { - ExpoData *line = expoAddress(index); - int16_t anas[MAX_INPUTS] = {0}; - applyExpos(anas, e_perout_mode_inactive_flight_mode, line->srcRaw, - x); - return anas[line->chn]; - }, - [=]() -> int { return getValue(expoAddress(index)->srcRaw); }) - { -#if LCD_W > LCD_H - auto BODY_WIDTH = LCD_W - preview.width() - PAGE_PADDING; - body.setWidth(BODY_WIDTH); - body.setLeft(preview.width() + PAGE_PADDING); -#else - body.setRect({0, INPUT_EDIT_CURVE_TOP + INPUT_EDIT_CURVE_HEIGHT, LCD_W, - LCD_H - INPUT_EDIT_CURVE_TOP - INPUT_EDIT_CURVE_HEIGHT}); -#endif - buildBody(&body); - buildHeader(&header); - } - - void deleteLater(bool detach = true, bool trash = true) override - { - if (_deleted) return; - - preview.deleteLater(true, false); - - Page::deleteLater(detach, trash); - } - - protected: - uint8_t input; - uint8_t index; - Curve preview; - Choice *trimChoice = nullptr; - FormGroup *curveParamField = nullptr; - - void buildHeader(Window *window) - { - new StaticText(window, - {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, - PAGE_LINE_HEIGHT}, - STR_MENUINPUTS, 0, COLOR_THEME_PRIMARY2); - new StaticText(window, - {PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT, - LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT}, - getSourceString(MIXSRC_FIRST_INPUT + input), 0, COLOR_THEME_PRIMARY2); - } - - // TODO share this code with MIXER - void updateCurveParamField(ExpoData *line) - { - curveParamField->clear(); - - const rect_t rect = {0, 0, curveParamField->width(), - curveParamField->height()}; - - switch (line->curve.type) { - case CURVE_REF_DIFF: - case CURVE_REF_EXPO: { - GVarNumberEdit *edit = - new GVarNumberEdit(curveParamField, rect, -100, 100, - GET_SET_DEFAULT(line->curve.value)); - edit->setSuffix("%"); - break; - } - - case CURVE_REF_FUNC: - new Choice(curveParamField, rect, STR_VCURVEFUNC, 0, CURVE_BASE - 1, - GET_SET_DEFAULT(line->curve.value)); - break; - - case CURVE_REF_CUSTOM: { - auto choice = new ChoiceEx(curveParamField, rect, -MAX_CURVES, MAX_CURVES, - GET_SET_DEFAULT(line->curve.value)); - choice->setTextHandler([](int value) { return getCurveString(value); }); - choice->setLongPressHandler([this](event_t event) { - ExpoData *line = expoAddress(index); - - // if no curve is specified then dont link to curve page - if (line->curve.value != 0) - ModelCurvesPage::pushEditCurve(abs(line->curve.value) - 1); - }); - break; - } - } - } - - void buildBody(FormWindow *window) - { - FormGridLayout grid; - grid.setLabelWidth(INPUT_EDIT_LABELS_WIDTH); - grid.spacer(PAGE_PADDING); - - ExpoData *line = expoAddress(index); - -#if LCD_W > LCD_H - grid.setMarginRight(180); -#endif - - // Input Name - new StaticText(window, grid.getLabelSlot(), STR_INPUTNAME, 0, COLOR_THEME_PRIMARY1); - new ModelTextEdit(window, grid.getFieldSlot(), g_model.inputNames[line->chn], sizeof(g_model.inputNames[line->chn])); - grid.nextLine(); - - // Switch - new StaticText(window, grid.getLabelSlot(), STR_SWITCH, 0, COLOR_THEME_PRIMARY1); - new SwitchChoice(window, grid.getFieldSlot(), SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, GET_SET_DEFAULT(line->swtch)); - grid.nextLine(); - - // Side - new StaticText(window, grid.getLabelSlot(), STR_SIDE, 0, COLOR_THEME_PRIMARY1); - new Choice(window, grid.getFieldSlot(), STR_VCURVEFUNC, 1, 3, - [=]() -> int16_t { - return 4 - line->mode; - }, - [=](int16_t newValue) { - line->mode = 4 - newValue; - SET_DIRTY(); - }); - grid.nextLine(); - - // Name - new StaticText(window, grid.getLabelSlot(), STR_EXPONAME, 0, COLOR_THEME_PRIMARY1); - new ModelTextEdit(window, grid.getFieldSlot(), line->name, sizeof(line->name)); - grid.nextLine(); - - // Source - new StaticText(window, grid.getLabelSlot(), STR_SOURCE, 0, COLOR_THEME_PRIMARY1); - new SourceChoice(window, grid.getFieldSlot(2, 0), INPUTSRC_FIRST, INPUTSRC_LAST, - GET_DEFAULT(line->srcRaw), - [=] (int32_t newValue) { - line->srcRaw = newValue; - if (line->srcRaw > MIXSRC_Ail && line->carryTrim == TRIM_ON) { - line->carryTrim = TRIM_OFF; - trimChoice->invalidate(); - } - window->clear(); - buildBody(window); - SET_DIRTY(); - } - ); - - SensorValue *sensor = nullptr; - if (line->srcRaw >= MIXSRC_FIRST_TELEM) { - sensor = new SensorValue(window, grid.getFieldSlot(2, 1), line); - - grid.nextLine(); - // Scale - new StaticText(window, grid.getLabelSlot(), STR_SCALE, 0, - COLOR_THEME_PRIMARY1); - new NumberEdit(window, grid.getFieldSlot(), 0, - maxTelemValue(line->srcRaw - MIXSRC_FIRST_TELEM + 1), - GET_SET_DEFAULT(line->scale), 0, - sensor->getSensorPrec()); - adjustHeight(); - } - grid.nextLine(); - - // Weight - new StaticText(window, grid.getLabelSlot(), STR_WEIGHT, 0, COLOR_THEME_PRIMARY1); - auto gvar = new GVarNumberEdit(window, grid.getFieldSlot(), -100, 100, GET_SET_DEFAULT(line->weight)); - gvar->setSuffix("%"); - grid.nextLine(); - - // Offset - new StaticText(window, grid.getLabelSlot(), STR_OFFSET, 0, COLOR_THEME_PRIMARY1); - gvar = new GVarNumberEdit(window, grid.getFieldSlot(), -100, 100, GET_SET_DEFAULT(line->offset)); - gvar->setSuffix("%"); - grid.nextLine(); - - // Trim - new StaticText(window, grid.getLabelSlot(), STR_TRIM, 0, COLOR_THEME_PRIMARY1); - trimChoice = new Choice(window, grid.getFieldSlot(), STR_VMIXTRIMS, -TRIM_OFF, -TRIM_LAST, - GET_VALUE(-line->carryTrim), - SET_VALUE(line->carryTrim, -newValue)); - trimChoice->setAvailableHandler([=](int value) { - return value != TRIM_ON || line->srcRaw <= MIXSRC_Ail; - }); - grid.nextLine(); - - // Curve - new StaticText(&body, grid.getLabelSlot(), STR_CURVE, 0, COLOR_THEME_PRIMARY1); - new Choice(&body, grid.getFieldSlot(2, 0), "\004DiffExpoFuncCstm", 0, CURVE_REF_CUSTOM, - GET_DEFAULT(line->curve.type), - [=](int32_t newValue) { - line->curve.type = newValue; - line->curve.value = 0; - SET_DIRTY(); - updateCurveParamField(line); - }); - curveParamField = new FormGroup(&body, grid.getFieldSlot(2, 1) - // , FORM_FORWARD_FOCUS - ); - updateCurveParamField(line); - grid.nextLine(); - -#if defined(FLIGHT_MODES) - // Flight modes - new StaticText(window, grid.getLabelSlot(), STR_FLMODE, 0, COLOR_THEME_PRIMARY1); - for (uint32_t i=0; i 0 && (i % 4) == 0) - grid.nextLine(); - new TextButton(window, grid.getFieldSlot(4, i % 4), fm, - [=]() -> uint32_t { - BFBIT_FLIP(line->flightModes, bfBit(i)); - SET_DIRTY(); - return !(bfSingleBitGet(line->flightModes, i)); - }, - OPAQUE | (bfSingleBitGet(line->flightModes, i) ? 0 : BUTTON_CHECKED)); - } - grid.nextLine(); -#endif - - } -}; - -void CommonInputOrMixButton::checkEvents() -{ - if (active != isActive()) { - invalidate(); - active = !active; - } - - Button::checkEvents(); -} - -void CommonInputOrMixButton::drawFlightModes(BitmapBuffer *dc, - FlightModesType value, - LcdFlags textColor) -{ - dc->drawMask(146, 2 + PAGE_LINE_HEIGHT + FIELD_PADDING_TOP, - mixerSetupFlightmodeIcon, textColor); - coord_t x = 166; - for (int i = 0; i < MAX_FLIGHT_MODES; i++) { - char s[] = " "; - s[0] = '0' + i; - if (value & (1 << i)) { - dc->drawText(x, PAGE_LINE_HEIGHT + FIELD_PADDING_TOP + 2, s, - FONT(XS) | COLOR_THEME_DISABLED); - } else { - dc->drawSolidFilledRect(x, PAGE_LINE_HEIGHT + FIELD_PADDING_TOP + 2, 8, 3, - COLOR_THEME_FOCUS); - dc->drawText(x, PAGE_LINE_HEIGHT + FIELD_PADDING_TOP + 2, s, FONT(XS) | textColor); - } - x += 8; - } -} - -void CommonInputOrMixButton::paint(BitmapBuffer * dc) -{ - dc->drawSolidFilledRect(0, 0, width(), height(), - isActive() ? COLOR_THEME_ACTIVE : COLOR_THEME_PRIMARY2); - paintBody(dc); - - if (!hasFocus()) - dc->drawSolidRect(0, 0, rect.w, rect.h, 1, COLOR_THEME_SECONDARY2); - else - dc->drawSolidRect(0, 0, rect.w, rect.h, 2, COLOR_THEME_FOCUS); -} - -class InputLineButton : public CommonInputOrMixButton -{ - public: - InputLineButton(FormGroup *parent, const rect_t &rect, uint8_t index) : - CommonInputOrMixButton(parent, rect, index) - { - const ExpoData &line = g_model.expoData[index]; - if (line.swtch || line.curve.value != 0 || line.flightModes) { - setHeight(height() + PAGE_LINE_HEIGHT + FIELD_PADDING_TOP); - } - } - - bool isActive() const override { return isExpoActive(index); } - - void paintBody(BitmapBuffer *dc) override - { - const ExpoData &line = g_model.expoData[index]; - - LcdFlags textColor = COLOR_THEME_SECONDARY1; - // if (hasFocus()) - // textColor = COLOR_THEME_PRIMARY2; - - // first line ... - drawValueOrGVar(dc, FIELD_PADDING_LEFT, FIELD_PADDING_TOP, line.weight, - -100, 100, textColor); - drawSource(dc, 60, FIELD_PADDING_TOP, line.srcRaw, textColor); - - if (line.name[0]) { - dc->drawMask(146, FIELD_PADDING_TOP, mixerSetupLabelIcon, textColor); - dc->drawSizedText(166, FIELD_PADDING_TOP, line.name, sizeof(line.name), - textColor); - } - - // second line ... - if (line.swtch) { - dc->drawMask(3, PAGE_LINE_HEIGHT + FIELD_PADDING_TOP, - mixerSetupSwitchIcon, textColor); - drawSwitch(dc, 21, PAGE_LINE_HEIGHT + FIELD_PADDING_TOP, line.swtch, - textColor); - } - - if (line.curve.value != 0) { - dc->drawMask(60, PAGE_LINE_HEIGHT + FIELD_PADDING_TOP, - mixerSetupCurveIcon, textColor); - drawCurveRef(dc, 80, PAGE_LINE_HEIGHT + FIELD_PADDING_TOP, line.curve, - textColor); - } - - if (line.flightModes) { - drawFlightModes(dc, line.flightModes, textColor); - } - - } -}; - -ModelInputsPage::ModelInputsPage(): - PageTab(STR_MENUINPUTS, ICON_MODEL_INPUTS) -{ - setOnSetVisibleHandler([]() { - // reset clipboard - s_copyMode = 0; - }); -} - -void ModelInputsPage::rebuild(FormWindow * window, int8_t focusIndex) -{ - auto scroll_y = lv_obj_get_scroll_y(window->getLvObj()); - window->clear(); - build(window, focusIndex); - lv_obj_scroll_to_y(window->getLvObj(), scroll_y, LV_ANIM_OFF); -} - -void ModelInputsPage::editInput(FormWindow * window, uint8_t input, uint8_t index) -{ - // Window::clearFocus(); - Window * editWindow = new InputEditWindow(input, index); - editWindow->setCloseHandler([=]() { - rebuild(window, index); - }); -} - -void ModelInputsPage::build(FormWindow *window, int8_t focusIndex) -{ - FormGridLayout grid; - grid.spacer(PAGE_PADDING); - grid.setLabelWidth(66); - - int inputIndex = 0; - ExpoData *line = g_model.expoData; - for (uint8_t input = 0; input < MAX_INPUTS; input++) { - if (inputIndex < MAX_EXPOS && line->chn == input && EXPO_VALID(line)) { - coord_t h = grid.getWindowHeight(); - auto txt = new StaticText(window, grid.getLabelSlot(), - getSourceString(MIXSRC_FIRST_INPUT + input), - BUTTON_BACKGROUND, CENTERED | COLOR_THEME_PRIMARY1); - while (inputIndex < MAX_EXPOS && line->chn == input && EXPO_VALID(line)) { - Button *button = - new InputLineButton(window, grid.getFieldSlot(), inputIndex); - button->setPressHandler([=]() -> uint8_t { - button->bringToTop(); - Menu *menu = new Menu(window); - menu->addLine(STR_EDIT, - [=]() { editInput(window, input, inputIndex); }); - if (!reachExposLimit()) { - menu->addLine(STR_INSERT_BEFORE, [=]() { - insertExpo(inputIndex, input); - editInput(window, input, inputIndex); - }); - menu->addLine(STR_INSERT_AFTER, [=]() { - insertExpo(inputIndex + 1, input); - editInput(window, input, inputIndex + 1); - }); - menu->addLine(STR_COPY, [=]() { - s_copyMode = COPY_MODE; - s_copySrcIdx = inputIndex; - }); - if (s_copyMode != 0) { - menu->addLine(STR_PASTE_BEFORE, [=]() { - copyExpo(s_copySrcIdx, inputIndex, PASTE_BEFORE); - if (s_copyMode == MOVE_MODE) { - deleteExpo((s_copySrcIdx > inputIndex) ? s_copySrcIdx + 1 - : s_copySrcIdx); - s_copyMode = 0; - } - rebuild(window, inputIndex); - }); - menu->addLine(STR_PASTE_AFTER, [=]() { - copyExpo(s_copySrcIdx, inputIndex, PASTE_AFTER); - if (s_copyMode == MOVE_MODE) { - deleteExpo((s_copySrcIdx > inputIndex) ? s_copySrcIdx + 1 - : s_copySrcIdx); - s_copyMode = 0; - } - rebuild(window, inputIndex + 1); - }); - } - } - menu->addLine(STR_MOVE, [=]() { - s_copyMode = MOVE_MODE; - s_copySrcIdx = inputIndex; - }); - menu->addLine(STR_DELETE, [=]() { - deleteExpo(inputIndex); - rebuild(window, inputIndex); - }); - return 0; - }); - button->setFocusHandler([=](bool focus) { - if (focus) { - txt->setBackgroundColor(COLOR_THEME_FOCUS); - txt->setTextFlags(COLOR_THEME_PRIMARY2 | LEFT); - } else { - txt->setBackgroundColor(COLOR_THEME_SECONDARY2); - txt->setTextFlags(COLOR_THEME_PRIMARY1 | LEFT); - } - txt->invalidate(); - if (focus) button->bringToTop(); - }); - - // if (focusIndex == inputIndex) { - // button->setFocus(SET_FOCUS_DEFAULT); - // txt->setBackgroundColor(COLOR_THEME_FOCUS); - // txt->setTextFlags(COLOR_THEME_PRIMARY2 | LEFT); - // txt->invalidate(); - // } - grid.spacer(button->height() - 1); - ++inputIndex; - ++line; - } - - h = grid.getWindowHeight() - h + 1; - txt->setHeight(h); - - grid.spacer(7); - } else { - auto button = new TextButton(window, grid.getLabelSlot(), - getSourceString(MIXSRC_FIRST_INPUT + input), - nullptr, BUTTON_BACKGROUND, - COLOR_THEME_PRIMARY1 | RIGHT); - button->setPressHandler([=]() -> uint8_t { - button->bringToTop(); - Menu *menu = new Menu(window); - menu->addLine(STR_EDIT, [=]() { - insertExpo(inputIndex, input); - editInput(window, input, inputIndex); - return 0; - }); - if (!reachExposLimit()) { - if (s_copyMode != 0) { - menu->addLine(STR_PASTE, [=]() { - copyExpo(s_copySrcIdx, inputIndex, input); - if (s_copyMode == MOVE_MODE) { - int sourceInd = s_copySrcIdx >= inputIndex ? s_copySrcIdx + 1 - : s_copySrcIdx; - memcpy(g_model.inputNames[expoAddress(inputIndex)->chn], - g_model.inputNames[expoAddress(sourceInd)->chn], - LEN_INPUT_NAME); - deleteExpo(sourceInd); - s_copyMode = 0; - } - rebuild(window, inputIndex); - return 0; - }); - } - } - return 0; - }); - - grid.spacer(button->height() + 5); - } - } - - // Window *focus = Window::getFocus(); - // if (focus) { - // focus->bringToTop(); - // } - - grid.nextLine(); - -} - // TODO port: avoid global s_currCh on ARM boards (as done here)... int8_t s_currCh; uint8_t s_copyMode; @@ -665,3 +104,371 @@ void insertExpo(uint8_t idx, uint8_t input) resumeMixerCalculations(); storageDirty(EE_MODEL); } + +static bool getFreeInput(uint8_t& input, uint8_t& index) +{ + uint8_t chn = 0; + ExpoData* line = g_model.expoData; + for (uint8_t i = 0; i < MAX_EXPOS; i++) { + if (!EXPO_VALID(line) || (line->chn > chn)) { + if (i >= MAX_EXPOS) break; + if (chn >= MAX_INPUTS) break; + index = i; + input = chn; + return true; + } + chn = line->chn + 1; + ++line; + } + + return false; +} + +class InputLineButton : public InputMixButton +{ + public: + InputLineButton(Window* parent, uint8_t index) : + InputMixButton(parent, rect_t{}, index) + { + auto h = getFontHeight(FONT(STD)); + h += 2 * lv_obj_get_style_border_width(lvobj, LV_PART_MAIN); + h += lv_obj_get_style_pad_top(lvobj, LV_PART_MAIN); + h += lv_obj_get_style_pad_bottom(lvobj, LV_PART_MAIN); + + const ExpoData &line = g_model.expoData[index]; + if (line.swtch || line.curve.value != 0 || line.flightModes) { + h += lv_obj_get_style_text_line_space(lvobj, LV_PART_MAIN); + h += getFontHeight(FONT(STD)); + } + setHeight(h); + } + + bool isActive() const override { return isExpoActive(index); } + + void paint(BitmapBuffer *dc) override + { + const ExpoData &line = g_model.expoData[index]; + LcdFlags textColor = COLOR_THEME_SECONDARY1; + + // first line ... + coord_t y = 0; + y += lv_obj_get_style_border_width(lvobj, LV_PART_MAIN); + y += lv_obj_get_style_pad_top(lvobj, LV_PART_MAIN); + + coord_t x = 0; + x += lv_obj_get_style_border_width(lvobj, LV_PART_MAIN); + x += lv_obj_get_style_pad_left(lvobj, LV_PART_MAIN); + + drawValueOrGVar(dc, x, y, line.weight, -100, 100, textColor); + drawSource(dc, x + 65, y, line.srcRaw, textColor); + + if (line.name[0]) { + dc->drawMask(146, y, mixerSetupLabelIcon, textColor); + dc->drawSizedText(166, y, line.name, sizeof(line.name), textColor); + } + + // second line ... + y += lv_obj_get_style_text_line_space(lvobj, LV_PART_MAIN); + y += getFontHeight(FONT(STD)); + + if (line.swtch) { + dc->drawMask(x, y, mixerSetupSwitchIcon, textColor); + drawSwitch(dc, x + 21, y, line.swtch, textColor); + } + + if (line.curve.value != 0) { + dc->drawMask(x + 65, y, mixerSetupCurveIcon, textColor); + drawCurveRef(dc, x + 85, y, line.curve, textColor); + } + + if (line.flightModes) { + drawFlightModes(dc, line.flightModes, textColor, 146, y); + } + } +}; + +ModelInputsPage::ModelInputsPage(): + PageTab(STR_MENUINPUTS, ICON_MODEL_INPUTS) +{ + setOnSetVisibleHandler([=]() { + // reset clipboard + _copyMode = 0; + }); +} + +InputMixGroup* ModelInputsPage::getGroupBySrc(mixsrc_t src) +{ + auto g = + std::find_if(groups.begin(), groups.end(), [=](InputMixGroup* g) -> bool { + return g->getMixSrc() == src; + }); + + if (g != groups.end()) return *g; + + return nullptr; +} + +InputMixGroup* ModelInputsPage::getGroupByIndex(uint8_t index) +{ + ExpoData* expo = expoAddress(index); + if (!EXPO_VALID(expo)) return nullptr; + + int input = expo->chn; + return getGroupBySrc(MIXSRC_FIRST_INPUT + input); +} + +InputMixButton* ModelInputsPage::getLineByIndex(uint8_t index) +{ + auto l = std::find_if(lines.begin(), lines.end(), [=](InputMixButton* l) { + return l->getIndex() == index; + }); + + if (l != lines.end()) return *l; + + return nullptr; +} + +void ModelInputsPage::removeGroup(InputMixGroup* g) +{ + auto group = std::find_if(groups.begin(), groups.end(), + [=](InputMixGroup* lh) -> bool { return lh == g; }); + if (group != groups.end()) groups.erase(group); +} + +void ModelInputsPage::removeLine(InputMixButton* l) +{ + auto line = std::find_if(lines.begin(), lines.end(), + [=](InputMixButton* lh) -> bool { return lh == l; }); + if (line == lines.end()) return; + + line = lines.erase(line); + while (line != lines.end()) { + (*line)->setIndex((*line)->getIndex() - 1); + ++line; + } +} + +InputMixButton* ModelInputsPage::createLineButton(InputMixGroup *group, + uint8_t index) +{ + auto button = new InputLineButton(group, index); + lines.emplace_back(button); + group->addLine(button); + + uint8_t input = group->getMixSrc() - MIXSRC_FIRST_INPUT; + button->setPressHandler([=]() -> uint8_t { + Menu *menu = new Menu(form); + menu->addLine(STR_EDIT, [=]() { editInput(input, index); }); + if (!reachExposLimit()) { + menu->addLine(STR_INSERT_BEFORE, + [=]() { + uint8_t idx = button->getIndex(); + insertInput(input, idx); + _copyMode = 0; + }); + menu->addLine(STR_INSERT_AFTER, + [=]() { + uint8_t idx = button->getIndex(); + insertInput(input, idx + 1); + _copyMode = 0; + }); + menu->addLine(STR_COPY, [=]() { + _copyMode = COPY_MODE; + _copySrc = button; + }); + if (_copyMode != 0) { + menu->addLine(STR_PASTE_BEFORE, [=]() { + uint8_t idx = button->getIndex(); + pasteInputBefore(idx); + _copyMode = 0; + }); + menu->addLine(STR_PASTE_AFTER, [=]() { + uint8_t idx = button->getIndex(); + pasteInputAfter(idx); + _copyMode = 0; + }); + } + } + menu->addLine(STR_MOVE, [=]() { + _copyMode = MOVE_MODE; + _copySrc = button; + }); + menu->addLine(STR_DELETE, [=]() { + uint8_t idx = button->getIndex(); + deleteInput(idx); + _copyMode = 0; + }); + return 0; + }); + + return button; +} + +void ModelInputsPage::addLineButton(uint8_t index) +{ + ExpoData* expo = expoAddress(index); + if (!EXPO_VALID(expo)) return; + int input = expo->chn; + + addLineButton(MIXSRC_FIRST_INPUT + input, index); +} + +void ModelInputsPage::addLineButton(mixsrc_t src, uint8_t index) +{ + InputMixGroup* group_w = getGroupBySrc(src); + if (!group_w) { + group_w = new InputMixGroup(form, src); + // insertion sort + groups.emplace_back(group_w); + auto g = groups.rbegin(); + if (g != groups.rend()) { + auto g_prev = g; ++g_prev; + while (g_prev != groups.rend()) { + if ((*g_prev)->getMixSrc() < (*g)->getMixSrc()) break; + lv_obj_swap((*g)->getLvObj(), (*g_prev)->getLvObj()); + std::swap(*g, *g_prev); + ++g; ++g_prev; + } + } + } + + // create new line button + auto btn = createLineButton(group_w, index); + lv_group_focus_obj(btn->getLvObj()); + + // insertion sort for the focus group + auto l = lines.rbegin(); + if (l != lines.rend()) { + auto l_prev = l; ++l_prev; + while (l_prev != lines.rend()) { + if ((*l_prev)->getIndex() < (*l)->getIndex()) break; + // Swap elements (focus + line list) + lv_obj_t* obj1 = (*l)->getLvObj(); + lv_obj_t* obj2 = (*l_prev)->getLvObj(); + if (lv_obj_get_parent(obj1) == lv_obj_get_parent(obj2)) { + // same input group: swap obj + focus group + lv_obj_swap(obj1, obj2); + } else { + // different input group: swap only focus group + lv_group_swap_obj(obj1, obj2); + } + std::swap(*l, *l_prev); + // Inc index of elements after + (*l)->setIndex((*l)->getIndex() + 1); + ++l; ++l_prev; + } + } +} + +void ModelInputsPage::editInput(uint8_t input, uint8_t index) +{ + new InputEditWindow(input, index); +} + +void ModelInputsPage::insertInput(uint8_t input, uint8_t index) +{ + ::insertExpo(index, input); + addLineButton(MIXSRC_FIRST_INPUT + input, index); + editInput(input, index); +} + +void ModelInputsPage::deleteInput(uint8_t index) +{ + auto group = getGroupByIndex(index); + if (!group) return; + + auto line = getLineByIndex(index); + if (!line) return; + + group->removeLine(line); + if (group->getLineCount() == 0) { + group->deleteLater(); + removeGroup(group); + removeLine(line); + } else { + line->deleteLater(); + removeLine(line); + } + + ::deleteExpo(index); +} + +void ModelInputsPage::pasteInput(uint8_t dst_idx, uint8_t input) +{ + if (!_copyMode || !_copySrc) return; + uint8_t src_idx = _copySrc->getIndex(); + + ::copyExpo(src_idx, dst_idx, input); + addLineButton(dst_idx); + + if (_copyMode == MOVE_MODE) { + deleteInput(src_idx); + } +} + +static int _inputChnFromIndex(uint8_t index) +{ + ExpoData* expo = expoAddress(index); + if (!EXPO_VALID(expo)) return -1; + return expo->chn; +} + +void ModelInputsPage::pasteInputBefore(uint8_t dst_idx) +{ + int input = _inputChnFromIndex(dst_idx); + pasteInput(dst_idx, input); +} + +void ModelInputsPage::pasteInputAfter(uint8_t dst_idx) +{ + int input = _inputChnFromIndex(dst_idx); + pasteInput(dst_idx + 1, input); +} + +void ModelInputsPage::build(FormWindow *window) +{ + window->setFlexLayout(LV_FLEX_FLOW_COLUMN, 8); + lv_obj_set_style_pad_all(window->getLvObj(), 8, 0); + + form = new FormGroup(window, rect_t{}); + form->setFlexLayout(LV_FLEX_FLOW_COLUMN, 8); + + auto form_obj = form->getLvObj(); + lv_obj_set_width(form_obj, lv_pct(100)); + + auto btn = new TextButton(window, rect_t{}, LV_SYMBOL_PLUS, [=]() { + uint8_t input, index; + if (getFreeInput(input, index)) insertInput(input, index); + return 0; + }); + auto btn_obj = btn->getLvObj(); + lv_obj_set_width(btn_obj, lv_pct(100)); + + groups.clear(); + lines.clear(); + + uint8_t index = 0; + ExpoData* line = g_model.expoData; + for (uint8_t input = 0; input < MAX_INPUTS; input++) { + + if (index >= MAX_EXPOS) break; + + if (line->chn == input && EXPO_VALID(line)) { + // one group for the complete input channel + auto group = new InputMixGroup(form, MIXSRC_FIRST_INPUT + input); + groups.emplace_back(group); + while (index < MAX_EXPOS && line->chn == input && EXPO_VALID(line)) { + // one button per input line + createLineButton(group, index); + ++index; + ++line; + } + } else if (line->chn > input && EXPO_VALID(line)) { + TRACE("missing input for channel #%d", input); + } else if (!EXPO_VALID(line)) { + TRACE("invalid line #%d", index); + break; + } + } +} + diff --git a/radio/src/gui/colorlcd/model_inputs.h b/radio/src/gui/colorlcd/model_inputs.h index 214c24fa5..d630392db 100644 --- a/radio/src/gui/colorlcd/model_inputs.h +++ b/radio/src/gui/colorlcd/model_inputs.h @@ -19,54 +19,43 @@ * GNU General Public License for more details. */ -#ifndef _MODEL_INPUTS_H -#define _MODEL_INPUTS_H +#pragma once #include "tabsgroup.h" -class ModelInputsPage: public PageTab { - public: - ModelInputsPage(); +class InputMixGroup; +class InputMixButton; - void build(FormWindow * window) override - { - build(window, 0); - } +class ModelInputsPage : public PageTab +{ + public: + ModelInputsPage(); - protected: - void build(FormWindow * window, int8_t focusIndex); - void rebuild(FormWindow * window, int8_t focusIndex); - void editInput(FormWindow * window, uint8_t channel, uint8_t index); - uint8_t s_copySrcIdx; + void build(FormWindow *window) override; + + protected: + FormGroup* form = nullptr; + std::list groups; + std::list lines; + InputMixButton* _copySrc = nullptr; + uint8_t _copyMode = 0; + + InputMixGroup* getGroupBySrc(mixsrc_t src); + virtual InputMixGroup* getGroupByIndex(uint8_t index); + InputMixButton* getLineByIndex(uint8_t index); + + void removeGroup(InputMixGroup* g); + void removeLine(InputMixButton* l); + + virtual void addLineButton(uint8_t index); + virtual void addLineButton(mixsrc_t src, uint8_t index); + virtual InputMixButton* createLineButton(InputMixGroup *group, uint8_t index); + + void editInput(uint8_t input, uint8_t index); + void insertInput(uint8_t input, uint8_t index); + void deleteInput(uint8_t index); + + void pasteInput(uint8_t dst_idx, uint8_t input); + void pasteInputBefore(uint8_t dst_idx); + void pasteInputAfter(uint8_t dst_idx); }; - -class CommonInputOrMixButton : public Button { - public: - CommonInputOrMixButton(FormGroup * parent, const rect_t & rect, uint8_t index): - Button(parent, rect, nullptr, 0, COLOR_THEME_PRIMARY1), - index(index) - { - setFocusHandler([=](bool active) { - if (active) { - bringToTop(); - } - }); - } - - virtual bool isActive() const = 0; - - void checkEvents() override; - - void drawFlightModes(BitmapBuffer *dc, FlightModesType value, LcdFlags textColor); - - void paint(BitmapBuffer * dc) override; - - virtual void paintBody(BitmapBuffer * dc) = 0; - - protected: - uint8_t index; - bool active = false; -}; - - -#endif //_MODEL_INPUTS_H diff --git a/radio/src/gui/colorlcd/model_mixes.cpp b/radio/src/gui/colorlcd/model_mixes.cpp index fc6af2111..b5e3f8a24 100644 --- a/radio/src/gui/colorlcd/model_mixes.cpp +++ b/radio/src/gui/colorlcd/model_mixes.cpp @@ -515,13 +515,6 @@ void ModelMixesPage::build(FormWindow * window, int8_t focusMixIndex) if (focus) button->bringToTop(); }); - // if (focusMixIndex == mixIndex) { - // button->setFocus(SET_FOCUS_DEFAULT); - // txt->setBackgroundColor(COLOR_THEME_FOCUS); - // txt->setTextFlags(COLOR_THEME_PRIMARY2 | CENTERED); - // txt->invalidate(); - // } - grid.spacer(button->height() - 1); ++mixIndex; ++mix; @@ -537,7 +530,6 @@ void ModelMixesPage::build(FormWindow * window, int8_t focusMixIndex) else { auto button = new TextButton(window, grid.getLabelSlot(), getSourceString(MIXSRC_CH1 + ch)); - // if (focusMixIndex == mixIndex) button->setFocus(SET_FOCUS_DEFAULT); button->setPressHandler([=]() -> uint8_t { button->bringToTop(); Menu *menu = new Menu(window); @@ -567,13 +559,7 @@ void ModelMixesPage::build(FormWindow * window, int8_t focusMixIndex) } } - // Window * focus = Window::getFocus(); - // if (focus) { - // focus->bringToTop(); - // } - grid.nextLine(); - } void deleteMix(uint8_t idx) diff --git a/radio/src/gui/colorlcd/themes/etx_lv_theme.cpp b/radio/src/gui/colorlcd/themes/etx_lv_theme.cpp index 6b94422eb..5934120f9 100644 --- a/radio/src/gui/colorlcd/themes/etx_lv_theme.cpp +++ b/radio/src/gui/colorlcd/themes/etx_lv_theme.cpp @@ -8,6 +8,7 @@ *********************/ #include "etx_lv_theme.h" #include "../colors.h" +#include "lvgl_widgets/input_mix_line.h" extern lv_color_t makeLvColor(uint32_t colorFlags); @@ -44,6 +45,7 @@ typedef struct { lv_style_t scrollbar_scrolled; lv_style_t card; lv_style_t btn; + lv_style_t line_btn; /*Utility*/ lv_style_t bg_color_primary; @@ -294,6 +296,20 @@ static void style_init(void) lv_style_set_pad_column(&styles.btn, lv_disp_dpx(theme.disp, 5)); lv_style_set_pad_row(&styles.btn, lv_disp_dpx(theme.disp, 5)); + style_init_reset(&styles.line_btn); + lv_style_set_radius( + &styles.line_btn, + (disp_size == DISP_LARGE ? lv_disp_dpx(theme.disp, 16) + : disp_size == DISP_MEDIUM ? lv_disp_dpx(theme.disp, 12) + : lv_disp_dpx(theme.disp, 8))); + + lv_style_set_bg_opa(&styles.line_btn, LV_OPA_COVER); + lv_style_set_bg_color(&styles.line_btn, makeLvColor(COLOR_THEME_PRIMARY2)); + + lv_style_set_border_opa(&styles.line_btn, LV_OPA_COVER); + lv_style_set_border_width(&styles.line_btn, BORDER_WIDTH); + lv_style_set_border_color(&styles.line_btn, makeLvColor(COLOR_THEME_SECONDARY2)); + static lv_color_filter_dsc_t dark_filter; lv_color_filter_dsc_init(&dark_filter, dark_color_filter_cb); @@ -779,6 +795,12 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj) } #endif } + else if(lv_obj_check_type(obj, &input_mix_line_class)) { + lv_obj_add_style(obj, &styles.line_btn, 0); + lv_obj_add_style(obj, &styles.pad_tiny, 0); + lv_obj_add_style(obj, &styles.bg_color_active, LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles.focus_border, LV_STATE_FOCUSED); + } #endif #if LV_USE_LINE