From 86c72e3a2ad4895f87f2635481cea015651c88c2 Mon Sep 17 00:00:00 2001 From: Raphael Coeffic Date: Sat, 4 Jun 2022 10:11:12 +0200 Subject: [PATCH] Mixers rework --- radio/src/gui/colorlcd/CMakeLists.txt | 1 + radio/src/gui/colorlcd/bitmaps.cpp | 15 - radio/src/gui/colorlcd/bitmaps.h | 11 + radio/src/gui/colorlcd/input_mix_button.cpp | 38 +- radio/src/gui/colorlcd/input_mix_button.h | 10 +- .../colorlcd/lvgl_widgets/input_mix_line.c | 2 +- radio/src/gui/colorlcd/mixer_edit.cpp | 262 +++++ radio/src/gui/colorlcd/mixer_edit.h | 48 + radio/src/gui/colorlcd/model_inputs.cpp | 38 +- radio/src/gui/colorlcd/model_mixes.cpp | 939 ++++++++---------- radio/src/gui/colorlcd/model_mixes.h | 41 +- radio/src/strhelpers.cpp | 7 +- 12 files changed, 853 insertions(+), 559 deletions(-) create mode 100644 radio/src/gui/colorlcd/mixer_edit.cpp create mode 100644 radio/src/gui/colorlcd/mixer_edit.h diff --git a/radio/src/gui/colorlcd/CMakeLists.txt b/radio/src/gui/colorlcd/CMakeLists.txt index 6238662ee..6a81da9d8 100644 --- a/radio/src/gui/colorlcd/CMakeLists.txt +++ b/radio/src/gui/colorlcd/CMakeLists.txt @@ -168,6 +168,7 @@ add_gui_src(sourcechoice.cpp) add_gui_src(switchchoice.cpp) add_gui_src(curveedit.cpp) add_gui_src(input_edit.cpp) +add_gui_src(mixer_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) diff --git a/radio/src/gui/colorlcd/bitmaps.cpp b/radio/src/gui/colorlcd/bitmaps.cpp index 6178e9036..74ee5c8d4 100644 --- a/radio/src/gui/colorlcd/bitmaps.cpp +++ b/radio/src/gui/colorlcd/bitmaps.cpp @@ -116,15 +116,6 @@ static const uint8_t mask_monitor_lockch[] = { static const uint8_t mask_monitor_logsw[] = { #include "mask_monitor_logsw.lbm" }; -static const uint8_t mask_mplex_add[] = { -#include "mask_mplex_add.lbm" -}; -static const uint8_t mask_mplex_multi[] = { -#include "mask_mplex_multi.lbm" -}; -static const uint8_t mask_mplex_replace[] = { -#include "mask_mplex_replace.lbm" -}; static const uint8_t mask_edgetx[] = { #include "mask_edgetx.lbm" }; @@ -256,9 +247,6 @@ BitmapBuffer * chanMonInvertedBitmap = nullptr; BitmapBuffer * mixerSetupMixerBitmap = nullptr; BitmapBuffer * mixerSetupToBitmap = nullptr; BitmapBuffer * mixerSetupOutputBitmap = nullptr; -BitmapBuffer * mixerSetupAddBitmap = nullptr; -BitmapBuffer * mixerSetupMultiBitmap = nullptr; -BitmapBuffer * mixerSetupReplaceBitmap = nullptr; BitmapBuffer * mixerSetupCurveIcon = nullptr; BitmapBuffer * mixerSetupSwitchIcon = nullptr; BitmapBuffer * mixerSetupLabelIcon = nullptr; @@ -295,9 +283,6 @@ static const _BuiltinBitmap _builtinBitmaps[] = { {BMP_8BIT, mask_sbar_to, &mixerSetupToBitmap}, {BMP_8BIT, mask_sbar_output, &mixerSetupOutputBitmap}, - {BMP_8BIT, mask_mplex_add, &mixerSetupAddBitmap}, - {BMP_8BIT, mask_mplex_multi, &mixerSetupMultiBitmap}, - {BMP_8BIT, mask_mplex_replace, &mixerSetupReplaceBitmap}, {BMP_8BIT, mask_textline_label, &mixerSetupLabelIcon}, {BMP_8BIT, mask_textline_curve, &mixerSetupCurveIcon}, diff --git a/radio/src/gui/colorlcd/bitmaps.h b/radio/src/gui/colorlcd/bitmaps.h index 947a4aa0f..0fa5b6ac2 100644 --- a/radio/src/gui/colorlcd/bitmaps.h +++ b/radio/src/gui/colorlcd/bitmaps.h @@ -21,6 +21,7 @@ #pragma once +#include "definitions.h" #include "lz4_bitmaps.h" DEFINE_LZ4_BITMAP(LBM_POINT); @@ -59,3 +60,13 @@ extern BitmapBuffer * mixerSetupDelaySlowIcon; void loadBuiltinBitmaps(); const uint8_t* getBuiltinIcon(MenuIcons id); + +PACK(struct _bitmap_mask { + uint16_t w; + uint16_t h; + uint8_t mask[0]; +}); + +#define MASK_WIDTH(m) (((_bitmap_mask*)m)->w) +#define MASK_HEIGHT(m) (((_bitmap_mask*)m)->h) +#define MASK_DATA(m) (((_bitmap_mask*)m)->mask) diff --git a/radio/src/gui/colorlcd/input_mix_button.cpp b/radio/src/gui/colorlcd/input_mix_button.cpp index b67ac9c58..baac29058 100644 --- a/radio/src/gui/colorlcd/input_mix_button.cpp +++ b/radio/src/gui/colorlcd/input_mix_button.cpp @@ -24,11 +24,30 @@ #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, +void InputMixButton::self_size(lv_event_t* e) +{ + auto obj = lv_event_get_target(e); + auto p = (lv_point_t*)lv_event_get_param(e); + + auto btn = (InputMixButton*)lv_obj_get_user_data(obj); + if (!btn) return; + + p->y = btn->calcHeight(); +} + +void InputMixButton::value_changed(lv_event_t* e) +{ + auto obj = lv_event_get_target(e); + lv_obj_refresh_self_size(obj); +} + +InputMixButton::InputMixButton(Window* parent, uint8_t index) : + Button(parent, rect_t{}, nullptr, 0, COLOR_THEME_PRIMARY1, input_mix_line_create), index(index) { + lv_obj_add_event_cb(lvobj, InputMixButton::self_size, LV_EVENT_GET_SELF_SIZE, nullptr); + lv_obj_add_event_cb(lvobj, InputMixButton::value_changed, LV_EVENT_VALUE_CHANGED, nullptr); } void InputMixButton::checkEvents() @@ -56,4 +75,19 @@ void InputMixButton::drawFlightModes(BitmapBuffer *dc, FlightModesType value, } } +lv_coord_t InputMixButton::calcHeight() const +{ + 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); + size_t lines = getLines(); + if (lines > 1) { + lines -= 1; + h += lines * lv_obj_get_style_text_line_space(lvobj, LV_PART_MAIN); + h += lines * getFontHeight(FONT(STD)); + } + + return h; +} diff --git a/radio/src/gui/colorlcd/input_mix_button.h b/radio/src/gui/colorlcd/input_mix_button.h index 6afa0bedf..96fe4fd11 100644 --- a/radio/src/gui/colorlcd/input_mix_button.h +++ b/radio/src/gui/colorlcd/input_mix_button.h @@ -26,7 +26,7 @@ class InputMixButton : public Button { public: - InputMixButton(Window* parent, const rect_t& rect, uint8_t index); + InputMixButton(Window* parent, uint8_t index); uint8_t getIndex() const { return index; } void setIndex(uint8_t i) { index = i; } @@ -36,11 +36,13 @@ class InputMixButton : public Button 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; + static void self_size(lv_event_t* e); + static void value_changed(lv_event_t* e); + + lv_coord_t calcHeight() const; + virtual size_t getLines() const = 0; virtual bool isActive() const = 0; }; diff --git a/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.c b/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.c index 22f098798..7af9a68ac 100644 --- a/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.c +++ b/radio/src/gui/colorlcd/lvgl_widgets/input_mix_line.c @@ -19,7 +19,7 @@ const lv_obj_class_t input_mix_line_class = { .width_def = LV_PCT(100), - .height_def = LV_DPI_DEF / 4, + .height_def = LV_SIZE_CONTENT, .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE, .instance_size = sizeof(lv_btn_t), .base_class = &lv_btn_class, diff --git a/radio/src/gui/colorlcd/mixer_edit.cpp b/radio/src/gui/colorlcd/mixer_edit.cpp new file mode 100644 index 000000000..2e99dee35 --- /dev/null +++ b/radio/src/gui/colorlcd/mixer_edit.cpp @@ -0,0 +1,262 @@ +/* + * 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 "mixer_edit.h" +#include "channel_bar.h" +#include "gvar_numberedit.h" +#include "model_curves.h" + +#include "opentx.h" + +#define SET_DIRTY() storageDirty(EE_MODEL) + +#if (LCD_W > LCD_H) + #define MIX_STATUS_BAR_WIDTH 250 + #define MIX_STATUS_BAR_MARGIN 3 + #define MIX_RIGHT_MARGIN 0 +#else + #define MIX_STATUS_BAR_WIDTH 180 + #define MIX_STATUS_BAR_MARGIN 0 + #define MIX_RIGHT_MARGIN 3 +#endif + +class MixerEditStatusBar : public Window +{ + public: + MixerEditStatusBar(Window *parent, const rect_t &rect, int8_t channel) : + Window(parent, rect), + _channel(channel) + { + channelBar = new ComboChannelBar(this, {MIX_STATUS_BAR_MARGIN, 0, rect.w - (MIX_STATUS_BAR_MARGIN * 2), rect.h}, channel); + channelBar->setLeftMargin(0); + channelBar->setTextColor(COLOR_THEME_PRIMARY2); + channelBar->setOutputChannelBarLimitColor(COLOR_THEME_EDIT); + } + + void paint(BitmapBuffer *dc) override + { + // dc->clear(COLOR_THEME_SECONDARY2); + } + + protected: + ComboChannelBar *channelBar; + int8_t _channel; +}; + +MixEditWindow::MixEditWindow(int8_t channel, uint8_t mixIndex) : + Page(ICON_MODEL_MIXER), channel(channel), mixIndex(mixIndex) +{ + buildBody(&body); + buildHeader(&header); +} + +void MixEditWindow::buildHeader(Window *window) +{ + new StaticText(window, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, + PAGE_LINE_HEIGHT}, + STR_MIXES, 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_CH1 + channel), 0, + COLOR_THEME_PRIMARY2); + + statusBar = new MixerEditStatusBar( + window, + {window->getRect().w - MIX_STATUS_BAR_WIDTH - MIX_RIGHT_MARGIN, 0, + MIX_STATUS_BAR_WIDTH, MENU_HEADER_HEIGHT + 3}, + channel); +} + +void MixEditWindow::buildBody(FormWindow *window) +{ + FormGridLayout grid; + grid.spacer(8); + + MixData *mix = mixAddress(mixIndex); + + // Mix name + new StaticText(window, grid.getLabelSlot(), STR_MIXNAME, 0, + COLOR_THEME_PRIMARY1); + new ModelTextEdit(window, grid.getFieldSlot(), mix->name, sizeof(mix->name)); + grid.nextLine(); + + // Source + new StaticText(window, grid.getLabelSlot(), STR_SOURCE, 0, + COLOR_THEME_PRIMARY1); + new SourceChoice(window, grid.getFieldSlot(), 0, MIXSRC_LAST, + GET_SET_DEFAULT(mix->srcRaw)); + grid.nextLine(); + + // Weight + new StaticText(window, grid.getLabelSlot(), STR_WEIGHT, 0, + COLOR_THEME_PRIMARY1); + auto gvar = new GVarNumberEdit(window, grid.getFieldSlot(), MIX_WEIGHT_MIN, + MIX_WEIGHT_MAX, GET_SET_DEFAULT(mix->weight)); + gvar->setSuffix("%"); + grid.nextLine(); + + // Offset + new StaticText(window, grid.getLabelSlot(), STR_OFFSET, 0, + COLOR_THEME_PRIMARY1); + gvar = new GVarNumberEdit(window, grid.getFieldSlot(), MIX_OFFSET_MIN, + MIX_OFFSET_MAX, GET_SET_DEFAULT(mix->offset)); + gvar->setSuffix("%"); + grid.nextLine(); + + // Trim + new StaticText(window, grid.getLabelSlot(), STR_TRIM, 0, + COLOR_THEME_PRIMARY1); + new CheckBox(window, grid.getFieldSlot(), GET_SET_INVERTED(mix->carryTrim)); + 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(mix->curve.type), + [=](int32_t newValue) { + mix->curve.type = newValue; + mix->curve.value = 0; + SET_DIRTY(); + updateCurveParamField(mix); + }); + curveParamField = + new FormGroup(&body, grid.getFieldSlot(2, 1) // , FORM_FORWARD_FOCUS + ); + updateCurveParamField(mix); + 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(mix->flightModes, bfBit(i)); + SET_DIRTY(); + return !(bfSingleBitGet(mix->flightModes, i)); + }, + OPAQUE | (bfSingleBitGet(mix->flightModes, i) ? 0 : BUTTON_CHECKED)); + } + grid.nextLine(); +#endif + + // 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(mix->swtch)); + grid.nextLine(); + + // Warning + new StaticText(window, grid.getLabelSlot(), STR_MIXWARNING, 0, + COLOR_THEME_PRIMARY1); + auto edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, 3, + GET_SET_DEFAULT(mix->mixWarn)); + edit->setZeroText(STR_OFF); + grid.nextLine(); + + // Multiplex + new StaticText(window, grid.getLabelSlot(), STR_MULTPX, 0, + COLOR_THEME_PRIMARY1); + new Choice(window, grid.getFieldSlot(), STR_VMLTPX, 0, 2, + GET_SET_DEFAULT(mix->mltpx)); + grid.nextLine(); + + // Delay up + new StaticText(window, grid.getLabelSlot(), STR_DELAYUP, 0, + COLOR_THEME_PRIMARY1); + edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, DELAY_MAX, + GET_DEFAULT(mix->delayUp), + SET_VALUE(mix->delayUp, newValue), 0, PREC1); + edit->setSuffix("s"); + grid.nextLine(); + + // Delay down + new StaticText(window, grid.getLabelSlot(), STR_DELAYDOWN, 0, + COLOR_THEME_PRIMARY1); + edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, DELAY_MAX, + GET_DEFAULT(mix->delayDown), + SET_VALUE(mix->delayDown, newValue), 0, PREC1); + edit->setSuffix("s"); + grid.nextLine(); + + // Slow up + new StaticText(window, grid.getLabelSlot(), STR_SLOWUP, 0, + COLOR_THEME_PRIMARY1); + edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, DELAY_MAX, + GET_DEFAULT(mix->speedUp), + SET_VALUE(mix->speedUp, newValue), 0, PREC1); + edit->setSuffix("s"); + grid.nextLine(); + + // Slow down + new StaticText(window, grid.getLabelSlot(), STR_SLOWDOWN, 0, + COLOR_THEME_PRIMARY1); + edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, DELAY_MAX, + GET_DEFAULT(mix->speedDown), + SET_VALUE(mix->speedDown, newValue), 0, PREC1); + edit->setSuffix("s"); + grid.nextLine(); +} + +// TODO share this code with INPUT +void MixEditWindow::updateCurveParamField(MixData *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) { + MixData *mix = mixAddress(mixIndex); + // if no curve is specified then dont link to curve page + if (mix->curve.value != 0) + ModelCurvesPage::pushEditCurve(abs(mix->curve.value) - 1); + }); + break; + } + } +} diff --git a/radio/src/gui/colorlcd/mixer_edit.h b/radio/src/gui/colorlcd/mixer_edit.h new file mode 100644 index 000000000..12e981151 --- /dev/null +++ b/radio/src/gui/colorlcd/mixer_edit.h @@ -0,0 +1,48 @@ +/* + * 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 "form.h" +#include "curve.h" + +struct MixData; +class FormGroup; +class MixerEditStatusBar; + +class MixEditWindow : public Page +{ + public: + MixEditWindow(int8_t channel, uint8_t mixIndex); + + protected: + uint8_t channel; + uint8_t mixIndex; + FormGroup *curveParamField = nullptr; + MixerEditStatusBar *statusBar = nullptr; + + void buildHeader(Window *window); + void buildBody(FormWindow *window); + + // TODO share this code with INPUT + void updateCurveParamField(MixData *line); +}; diff --git a/radio/src/gui/colorlcd/model_inputs.cpp b/radio/src/gui/colorlcd/model_inputs.cpp index c6d6e0bf1..ebbd9b982 100644 --- a/radio/src/gui/colorlcd/model_inputs.cpp +++ b/radio/src/gui/colorlcd/model_inputs.cpp @@ -127,23 +127,7 @@ static bool getFreeInput(uint8_t& input, uint8_t& index) 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); } + using InputMixButton::InputMixButton; void paint(BitmapBuffer *dc) override { @@ -185,6 +169,17 @@ class InputLineButton : public InputMixButton drawFlightModes(dc, line.flightModes, textColor, 146, y); } } + +protected: + bool isActive() const override { return isExpoActive(index); } + size_t getLines() const override { + const ExpoData* line = expoAddress(index); + if (line->swtch || line->curve.value != 0 || line->flightModes) { + return 2; + } else { + return 1; + } + } }; ModelInputsPage::ModelInputsPage(): @@ -362,7 +357,14 @@ void ModelInputsPage::addLineButton(mixsrc_t src, uint8_t index) void ModelInputsPage::editInput(uint8_t input, uint8_t index) { - new InputEditWindow(input, index); + auto line = getLineByIndex(index); + if (!line) return; + + auto line_obj = line->getLvObj(); + auto edit = new InputEditWindow(input, index); + edit->setCloseHandler([=]() { + lv_event_send(line_obj, LV_EVENT_VALUE_CHANGED, nullptr); + }); } void ModelInputsPage::insertInput(uint8_t input, uint8_t index) diff --git a/radio/src/gui/colorlcd/model_mixes.cpp b/radio/src/gui/colorlcd/model_mixes.cpp index b5e3f8a24..05b37d332 100644 --- a/radio/src/gui/colorlcd/model_mixes.cpp +++ b/radio/src/gui/colorlcd/model_mixes.cpp @@ -26,15 +26,30 @@ #include "bitfield.h" #include "model_inputs.h" #include "gvar_numberedit.h" -#include "model_curves.h" #include "dataconstants.h" -#include "channel_bar.h" - +#include "input_mix_group.h" +#include "input_mix_button.h" +#include "mixer_edit.h" #define SET_DIRTY() storageDirty(EE_MODEL) #define PASTE_BEFORE -2 #define PASTE_AFTER -1 +static const uint8_t _mask_mplex_add[] = { +#include "mask_mplex_add.lbm" +}; +STATIC_LZ4_BITMAP(mask_mplex_add); + +static const uint8_t _mask_mplex_multi[] = { +#include "mask_mplex_multi.lbm" +}; +STATIC_LZ4_BITMAP(mask_mplex_multi); + +static const uint8_t _mask_mplex_replace[] = { +#include "mask_mplex_replace.lbm" +}; +STATIC_LZ4_BITMAP(mask_mplex_replace); + uint8_t getMixesCount() { uint8_t count = 0; @@ -58,298 +73,6 @@ bool reachMixesLimit() return false; } - -#if (LCD_W > LCD_H) - #define MIX_STATUS_BAR_WIDTH 250 - #define MIX_STATUS_BAR_MARGIN 3 - #define MIX_RIGHT_MARGIN 0 -#else - #define MIX_STATUS_BAR_WIDTH 180 - #define MIX_STATUS_BAR_MARGIN 0 - #define MIX_RIGHT_MARGIN 3 -#endif - -class MixerEditStatusBar : public Window -{ - public: - MixerEditStatusBar(Window *parent, const rect_t &rect, int8_t channel) : - Window(parent, rect), - _channel(channel) - { - channelBar = new ComboChannelBar(this, {MIX_STATUS_BAR_MARGIN, 0, rect.w - (MIX_STATUS_BAR_MARGIN * 2), rect.h}, channel); - channelBar->setLeftMargin(0); - channelBar->setTextColor(COLOR_THEME_PRIMARY2); - channelBar->setOutputChannelBarLimitColor(COLOR_THEME_EDIT); - } - - void paint(BitmapBuffer *dc) override - { - // dc->clear(COLOR_THEME_SECONDARY2); - } - - protected: - ComboChannelBar *channelBar; - int8_t _channel; -}; - -class MixEditWindow : public Page -{ - public: - MixEditWindow(int8_t channel, uint8_t mixIndex) : - Page(ICON_MODEL_MIXER), channel(channel), mixIndex(mixIndex) - { - buildBody(&body); - buildHeader(&header); - } - - protected: - uint8_t channel; - uint8_t mixIndex; - FormGroup *curveParamField = nullptr; - MixerEditStatusBar *statusBar = nullptr; - - void buildHeader(Window *window) - { - new StaticText(window, - {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, - PAGE_LINE_HEIGHT}, - STR_MIXES, 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_CH1 + channel), 0, COLOR_THEME_PRIMARY2); - - statusBar = new MixerEditStatusBar( - window, {window->getRect().w - MIX_STATUS_BAR_WIDTH - MIX_RIGHT_MARGIN, 0, MIX_STATUS_BAR_WIDTH, MENU_HEADER_HEIGHT + 3}, - channel); - } - - void buildBody(FormWindow *window) - { - FormGridLayout grid; - grid.spacer(8); - - MixData *mix = mixAddress(mixIndex); - - // Mix name - new StaticText(window, grid.getLabelSlot(), STR_MIXNAME, 0, COLOR_THEME_PRIMARY1); - new ModelTextEdit(window, grid.getFieldSlot(), mix->name, - sizeof(mix->name)); - grid.nextLine(); - - // Source - new StaticText(window, grid.getLabelSlot(), STR_SOURCE, 0, COLOR_THEME_PRIMARY1); - new SourceChoice(window, grid.getFieldSlot(), 0, MIXSRC_LAST, - GET_SET_DEFAULT(mix->srcRaw)); - grid.nextLine(); - - // Weight - new StaticText(window, grid.getLabelSlot(), STR_WEIGHT, 0, COLOR_THEME_PRIMARY1); - auto gvar = - new GVarNumberEdit(window, grid.getFieldSlot(), MIX_WEIGHT_MIN, - MIX_WEIGHT_MAX, GET_SET_DEFAULT(mix->weight)); - gvar->setSuffix("%"); - grid.nextLine(); - - // Offset - new StaticText(window, grid.getLabelSlot(), STR_OFFSET, 0, COLOR_THEME_PRIMARY1); - gvar = new GVarNumberEdit(window, grid.getFieldSlot(), MIX_OFFSET_MIN, - MIX_OFFSET_MAX, GET_SET_DEFAULT(mix->offset)); - gvar->setSuffix("%"); - grid.nextLine(); - - // Trim - new StaticText(window, grid.getLabelSlot(), STR_TRIM, 0, COLOR_THEME_PRIMARY1); - new CheckBox(window, grid.getFieldSlot(), GET_SET_INVERTED(mix->carryTrim)); - 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(mix->curve.type), - [=](int32_t newValue) { - mix->curve.type = newValue; - mix->curve.value = 0; - SET_DIRTY(); - updateCurveParamField(mix); - }); - curveParamField = - new FormGroup(&body, grid.getFieldSlot(2, 1)// , FORM_FORWARD_FOCUS - ); - updateCurveParamField(mix); - 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(mix->flightModes, bfBit(i)); - SET_DIRTY(); - return !(bfSingleBitGet(mix->flightModes, i)); - }, - OPAQUE | (bfSingleBitGet(mix->flightModes, i) ? 0 : BUTTON_CHECKED)); - } - grid.nextLine(); -#endif - - // 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(mix->swtch)); - grid.nextLine(); - - // Warning - new StaticText(window, grid.getLabelSlot(), STR_MIXWARNING, 0, COLOR_THEME_PRIMARY1); - auto edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, 3, - GET_SET_DEFAULT(mix->mixWarn)); - edit->setZeroText(STR_OFF); - grid.nextLine(); - - // Multiplex - new StaticText(window, grid.getLabelSlot(), STR_MULTPX, 0, COLOR_THEME_PRIMARY1); - new Choice(window, grid.getFieldSlot(), STR_VMLTPX, 0, 2, - GET_SET_DEFAULT(mix->mltpx)); - grid.nextLine(); - - // Delay up - new StaticText(window, grid.getLabelSlot(), STR_DELAYUP, 0, COLOR_THEME_PRIMARY1); - edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, DELAY_MAX, - GET_DEFAULT(mix->delayUp), - SET_VALUE(mix->delayUp, newValue), 0, PREC1); - edit->setSuffix("s"); - grid.nextLine(); - - // Delay down - new StaticText(window, grid.getLabelSlot(), STR_DELAYDOWN, 0, COLOR_THEME_PRIMARY1); - edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, DELAY_MAX, - GET_DEFAULT(mix->delayDown), - SET_VALUE(mix->delayDown, newValue), 0, PREC1); - edit->setSuffix("s"); - grid.nextLine(); - - // Slow up - new StaticText(window, grid.getLabelSlot(), STR_SLOWUP, 0, COLOR_THEME_PRIMARY1); - edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, DELAY_MAX, - GET_DEFAULT(mix->speedUp), - SET_VALUE(mix->speedUp, newValue), 0, PREC1); - edit->setSuffix("s"); - grid.nextLine(); - - // Slow down - new StaticText(window, grid.getLabelSlot(), STR_SLOWDOWN, 0, COLOR_THEME_PRIMARY1); - edit = new NumberEdit(window, grid.getFieldSlot(2, 0), 0, DELAY_MAX, - GET_DEFAULT(mix->speedDown), - SET_VALUE(mix->speedDown, newValue), 0, PREC1); - edit->setSuffix("s"); - grid.nextLine(); - - } - - // TODO share this code with INPUT - void updateCurveParamField(MixData *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) { - MixData *mix = mixAddress(mixIndex); - // if no curve is specified then dont link to curve page - if (mix->curve.value != 0) - ModelCurvesPage::pushEditCurve(abs(mix->curve.value) - 1); - }); - break; - } - } - } -}; - -class MixLineButton : public CommonInputOrMixButton -{ - public: - MixLineButton(FormGroup *parent, const rect_t &rect, uint8_t index) : - CommonInputOrMixButton(parent, rect, index) - { - const MixData &mix = g_model.mixData[index]; - if (mix.swtch || mix.curve.value != 0 || mix.flightModes) { - setHeight(height() + PAGE_LINE_HEIGHT + FIELD_PADDING_TOP); - } - } - - bool isActive() const override { return isMixActive(index); } - - void paintBody(BitmapBuffer *dc) override - { - const MixData &line = g_model.mixData[index]; - - LcdFlags textColor = COLOR_THEME_SECONDARY1; - - // first line ... - drawValueOrGVar(dc, FIELD_PADDING_LEFT, FIELD_PADDING_TOP, line.weight, - MIX_WEIGHT_MIN, MIX_WEIGHT_MAX, 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) { - 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); - } - - // Put this icon on the first line, since we have more space there - uint8_t delayslow = 0; - if (line.speedDown || line.speedUp) delayslow = 1; - if (line.delayUp || line.delayDown) delayslow += 2; - if (delayslow) { - BitmapBuffer *delayslowbmp[] = {mixerSetupSlowIcon, mixerSetupDelayIcon, mixerSetupDelaySlowIcon}; - dc->drawMask(width() - 16, FIELD_PADDING_TOP, delayslowbmp[delayslow - 1], textColor); - } - } -}; - void insertMix(uint8_t idx, uint8_t channel) { pauseMixerCalculations(); @@ -370,198 +93,6 @@ void insertMix(uint8_t idx, uint8_t channel) storageDirty(EE_MODEL); } -ModelMixesPage::ModelMixesPage() : - PageTab(STR_MIXES, ICON_MODEL_MIXER) -{ - setOnSetVisibleHandler([]() { - // reset clipboard - s_copyMode = 0; - }); -} - -void ModelMixesPage::rebuild(FormWindow * window, int8_t focusMixIndex) -{ - auto scroll_y = lv_obj_get_scroll_y(window->getLvObj()); - window->clear(); - build(window, focusMixIndex); - lv_obj_scroll_to_y(window->getLvObj(), scroll_y, LV_ANIM_OFF); -} - -void ModelMixesPage::editMix(FormWindow * window, uint8_t channel, uint8_t mixIndex) -{ - // Window::clearFocus(); - Window *editWindow = new MixEditWindow(channel, mixIndex); - editWindow->setCloseHandler([=]() { - int8_t newIndex = mixIndex; - if (is_memclear(&g_model.mixData[mixIndex], sizeof(MixData))) { - deleteMix(mixIndex); - newIndex--; - } - rebuild(window, newIndex); - }); -} - -class MixLineTitle : public StaticText -{ - public: - using StaticText::StaticText; - - void setTextFlags(LcdFlags flags) - { - textFlags = flags; - for (auto child : children) { - auto bitmap = dynamic_cast(child); - if (bitmap) bitmap->setMaskColor(flags & 0xFFFF0000); - } - } -}; - -void ModelMixesPage::build(FormWindow * window, int8_t focusMixIndex) -{ - FormGridLayout grid; - grid.spacer(PAGE_PADDING); - grid.setLabelWidth(66); - - const BitmapBuffer * const mixerMultiplexBitmap[] = { - mixerSetupAddBitmap, - mixerSetupMultiBitmap, - mixerSetupReplaceBitmap - }; - - int mixIndex = 0; - MixData * mix = g_model.mixData; - for (uint8_t ch = 0; ch < MAX_OUTPUT_CHANNELS; ch++) { - - bool skip_mix = (ch == 0 && is_memclear(mix, sizeof(MixData))); - - if (mixIndex < MAX_MIXERS && mix->destCh == ch && !skip_mix) { - - coord_t h = grid.getWindowHeight(); - auto txt = new MixLineTitle(window, grid.getLabelSlot(), - getSourceString(MIXSRC_CH1 + ch), - BUTTON_BACKGROUND, COLOR_THEME_PRIMARY1 | CENTERED); - - uint8_t count = 0; - while (mixIndex < MAX_MIXERS && mix->destCh == ch && !skip_mix) { - - Button * button = new MixLineButton(window, grid.getFieldSlot(), mixIndex); - button->setPressHandler([=]() -> uint8_t { - button->bringToTop(); - Menu * menu = new Menu(window); - menu->addLine(STR_EDIT, [=]() { - editMix(window, ch, mixIndex); - }); - if (!reachMixesLimit()) { - menu->addLine(STR_INSERT_BEFORE, [=]() { - insertMix(mixIndex, ch); - editMix(window, ch, mixIndex); - }); - menu->addLine(STR_INSERT_AFTER, [=]() { - insertMix(mixIndex + 1, ch); - editMix(window, ch, mixIndex + 1); - }); - menu->addLine(STR_COPY, [=]() { - s_copyMode = COPY_MODE; - s_copySrcIdx =mixIndex; - }); - if (s_copyMode != 0) { - menu->addLine(STR_PASTE_BEFORE, [=]() { - copyMix(s_copySrcIdx, mixIndex, PASTE_BEFORE); - if(s_copyMode == MOVE_MODE) { - deleteMix((s_copySrcIdx > mixIndex) ? s_copySrcIdx+1 : s_copySrcIdx); - s_copyMode = 0; - } - rebuild(window, mixIndex); - }); - menu->addLine(STR_PASTE_AFTER, [=]() { - copyMix(s_copySrcIdx, mixIndex, PASTE_AFTER); - if(s_copyMode == MOVE_MODE) { - deleteMix((s_copySrcIdx > mixIndex) ? s_copySrcIdx+1 : s_copySrcIdx); - s_copyMode = 0; - } - rebuild(window, mixIndex+1); - }); - } - } - menu->addLine(STR_MOVE, [=]() { - s_copyMode = MOVE_MODE; - s_copySrcIdx = mixIndex; - }); - menu->addLine(STR_DELETE, [=]() { - deleteMix(mixIndex); - rebuild(window, -1); - }); - return 0; - }); - - if (count++ > 0) { - new StaticBitmap( - txt, - {35 - txt->left(), - button->top() - txt->top() + (button->height() - 18) / 2, 25, - 17}, - mixerMultiplexBitmap[mix->mltpx], COLOR_THEME_SECONDARY1); - } - - button->setFocusHandler([=](bool focus) { - if (focus) { - txt->setBackgroundColor(COLOR_THEME_FOCUS); - txt->setTextFlags(COLOR_THEME_PRIMARY2 | CENTERED); - } else { - txt->setBackgroundColor(COLOR_THEME_SECONDARY2); - txt->setTextFlags(COLOR_THEME_PRIMARY1 | CENTERED); - } - txt->invalidate(); - if (focus) button->bringToTop(); - }); - - grid.spacer(button->height() - 1); - ++mixIndex; - ++mix; - - skip_mix = (ch == 0 && is_memclear(mix, sizeof(MixData))); - } - - h = grid.getWindowHeight() - h + 1; - txt->setHeight(h); - - grid.spacer(7); - } - else { - auto button = new TextButton(window, grid.getLabelSlot(), - getSourceString(MIXSRC_CH1 + ch)); - button->setPressHandler([=]() -> uint8_t { - button->bringToTop(); - Menu *menu = new Menu(window); - menu->addLine(STR_EDIT, [=]() { - insertMix(mixIndex, ch); - editMix(window, ch, mixIndex); - return 0; - }); - if (!reachMixesLimit()) { - if (s_copyMode != 0) { - menu->addLine(STR_PASTE, [=]() { - copyMix(s_copySrcIdx, mixIndex, ch); - if (s_copyMode == MOVE_MODE) { - deleteMix((s_copySrcIdx >= mixIndex) ? s_copySrcIdx + 1 - : s_copySrcIdx); - s_copyMode = 0; - } - rebuild(window, -1); - return 0; - }); - } - } - return 0; - }); - - grid.spacer(button->height() + 5); - } - } - - grid.nextLine(); -} - void deleteMix(uint8_t idx) { pauseMixerCalculations(); @@ -589,20 +120,9 @@ void copyMix(uint8_t source, uint8_t dest, int8_t ch) memcpy(&sourceMix, mixAddress(source), sizeof(MixData)); MixData *mix = mixAddress(dest); size_t trailingMixes = MAX_MIXERS - (dest + 1); - if (ch == PASTE_AFTER) { - trailingMixes--; - memmove(mix + 2, mix + 1, trailingMixes * sizeof(MixData)); - memcpy(mix + 1, &sourceMix, sizeof(MixData)); - (mix + 1)->destCh = (mix)->destCh; - } else if (ch == PASTE_BEFORE) { - memmove(mix + 1, mix, trailingMixes * sizeof(MixData)); - memcpy(mix, &sourceMix, sizeof(MixData)); - mix->destCh = (mix + 1)->destCh; - } else { - memmove(mix + 1, mix, trailingMixes * sizeof(MixData)); - memcpy(mix, &sourceMix, sizeof(MixData)); - mix->destCh = ch; - } + memmove(mix + 1, mix, trailingMixes * sizeof(MixData)); + memcpy(mix, &sourceMix, sizeof(MixData)); + mix->destCh = ch; resumeMixerCalculations(); storageDirty(EE_MODEL); } @@ -649,3 +169,420 @@ bool swapMixes(uint8_t &idx, uint8_t up) idx = tgt_idx; return true; } + +class MixLineButton : public InputMixButton +{ + public: + MixLineButton(Window* parent, uint8_t index); + void paint(BitmapBuffer* dc) override; + + protected: + size_t getLines() const override; + bool isActive() const override { return isMixActive(index); } +}; + +static void mix_draw_mplex(lv_event_t* e) +{ + auto obj = (lv_obj_t*)lv_event_get_user_data(e); + if (!obj || (lv_obj_get_index(obj) == 0)) return; + + auto btn = (MixLineButton*)lv_obj_get_user_data(obj); + if (!btn) return; + + lv_obj_draw_part_dsc_t* dsc = lv_event_get_draw_part_dsc(e); + if (dsc->part != LV_PART_MAIN) return; + + MixData* mix = mixAddress(btn->getIndex()); + const uint8_t* mask_map = nullptr; + if (mix->mltpx == MLTPX_ADD) { + mask_map = mask_mplex_add; + } else if (mix->mltpx == MLTPX_MUL) { + mask_map = mask_mplex_multi; + } else if (mix->mltpx == MLTPX_REPL) { + mask_map = mask_mplex_replace; + } else { + return; + } + + lv_area_t coords; + lv_coord_t area_h = lv_area_get_height(&obj->coords); + lv_coord_t mask_w = MASK_WIDTH(mask_map); + lv_coord_t mask_h = MASK_HEIGHT(mask_map); + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + + coords.x1 = obj->coords.x1 - mask_w - pad_left; + coords.x2 = coords.x1 + mask_w - 1; + coords.y1 = obj->coords.y1 + (area_h - mask_h) / 2; + coords.y2 = coords.y1 + mask_h - 1; + + lv_draw_rect_dsc_t rect_dsc; + lv_draw_rect_dsc_init(&rect_dsc); + rect_dsc.bg_opa = LV_OPA_COVER; + rect_dsc.bg_color = makeLvColor(COLOR_THEME_SECONDARY1); + + lv_draw_mask_map_param_t m; + int16_t mask_id; + + lv_draw_mask_map_init(&m, &coords, MASK_DATA(mask_map)); + mask_id = lv_draw_mask_add(&m, NULL); + + // draw masked symbol + lv_draw_rect(dsc->draw_ctx, &rect_dsc, &coords); + + // free ressources + lv_draw_mask_free_param(&m); + lv_draw_mask_remove_id(mask_id); +} + +MixLineButton::MixLineButton(Window* parent, uint8_t index) : + InputMixButton(parent, index) +{ + lv_obj_t* p_obj = parent->getLvObj(); + lv_obj_add_event_cb(p_obj, mix_draw_mplex, LV_EVENT_DRAW_PART_END, lvobj); +} + +size_t MixLineButton::getLines() const +{ + const MixData* mix = mixAddress(index); + if (mix->swtch || mix->curve.value != 0 || mix->flightModes) { + return 2; + } else { + return 1; + } +} + +void MixLineButton::paint(BitmapBuffer* dc) +{ + const MixData& line = g_model.mixData[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, MIX_WEIGHT_MIN, MIX_WEIGHT_MAX, + 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) { + 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); + } + + // Put this icon on the first line, since we have more space there + uint8_t delayslow = 0; + if (line.speedDown || line.speedUp) delayslow = 1; + if (line.delayUp || line.delayDown) delayslow += 2; + if (delayslow) { + BitmapBuffer* delayslowbmp[] = {mixerSetupSlowIcon, mixerSetupDelayIcon, + mixerSetupDelaySlowIcon}; + dc->drawMask(width() - 16, y, delayslowbmp[delayslow - 1], textColor); + } +} + +ModelMixesPage::ModelMixesPage() : + ModelInputsPage() +{ + setTitle(STR_MIXES); + setIcon(ICON_MODEL_MIXER); +} + +InputMixGroup* ModelMixesPage::getGroupByIndex(uint8_t index) +{ + MixData* mix = mixAddress(index); + if (is_memclear(mix, sizeof(MixData))) return nullptr; + + int ch = mix->destCh; + return getGroupBySrc(MIXSRC_CH1 + ch); +} + +InputMixButton* ModelMixesPage::createLineButton(InputMixGroup *group, uint8_t index) +{ + auto button = new MixLineButton(group, index); + lines.emplace_back(button); + group->addLine(button); + + uint8_t ch = group->getMixSrc() - MIXSRC_CH1; + button->setPressHandler([=]() -> uint8_t { + Menu *menu = new Menu(form); + menu->addLine(STR_EDIT, [=]() { editMix(ch, index); }); + if (!reachMixesLimit()) { + menu->addLine(STR_INSERT_BEFORE, [=]() { + uint8_t idx = button->getIndex(); + insertMix(ch, idx); + _copyMode = 0; + }); + menu->addLine(STR_INSERT_AFTER, [=]() { + uint8_t idx = button->getIndex(); + insertMix(ch, idx + 1); + _copyMode = 0; + }); + menu->addLine(STR_COPY, [=]() { + _copyMode = COPY_MODE; + _copySrc = button; + }); + if (s_copyMode != 0) { + menu->addLine(STR_PASTE_BEFORE, [=]() { + uint8_t idx = button->getIndex(); + pasteMixBefore(idx); + _copyMode = 0; + }); + menu->addLine(STR_PASTE_AFTER, [=]() { + uint8_t idx = button->getIndex(); + pasteMixAfter(idx); + _copyMode = 0; + }); + } + } + menu->addLine(STR_MOVE, [=]() { + _copyMode = MOVE_MODE; + _copySrc = button; + }); + menu->addLine(STR_DELETE, [=]() { + uint8_t idx = button->getIndex(); + deleteMix(idx); + _copyMode = 0; + }); + return 0; + }); + + return button; +} + +void ModelMixesPage::addLineButton(mixsrc_t src, uint8_t index) +{ + ModelInputsPage::addLineButton(src, index); +} + +void ModelMixesPage::addLineButton(uint8_t index) +{ + MixData* mix = mixAddress(index); + if (is_memclear(mix, sizeof(MixData))) return; + int channel = mix->destCh; + + addLineButton(MIXSRC_CH1 + channel, index); +} + +void ModelMixesPage::newMix() +{ + Menu* menu = new Menu(Layer::back()); + menu->setTitle(STR_MENU_CHANNELS); + + uint8_t index = 0; + MixData* line = mixAddress(0); + + // search for unused channels + for (uint8_t ch = 0; ch < MAX_OUTPUT_CHANNELS; ch++) { + if (index >= MAX_MIXERS) break; + bool skip_mix = (ch == 0 && is_memclear(line, sizeof(MixData))); + if (line->destCh == ch && !skip_mix) { + while (index < MAX_MIXERS && (line->destCh == ch) && !skip_mix) { + ++index; + ++line; + skip_mix = (ch == 0 && is_memclear(line, sizeof(MixData))); + } + } else { + std::string ch_name(getSourceString(MIXSRC_CH1 + ch)); + menu->addLine(ch_name.c_str(), [=]() { insertMix(ch, index); }); + } + } +} + +void ModelMixesPage::editMix(uint8_t channel, uint8_t index) +{ + auto line = getLineByIndex(index); + if (!line) return; + + auto line_obj = line->getLvObj(); + auto edit = new MixEditWindow(channel, index); + edit->setCloseHandler([=]() { + MixData* mix = mixAddress(index); + if (is_memclear(mix, sizeof(MixData))) { + deleteMix(index); + } else { + lv_event_send(line_obj, LV_EVENT_VALUE_CHANGED, nullptr); + } + }); +} + +void ModelMixesPage::insertMix(uint8_t channel, uint8_t index) +{ + ::insertMix(index, channel); + addLineButton(MIXSRC_CH1 + channel, index); + editMix(channel, index); +} + +void ModelMixesPage::deleteMix(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); + } + + ::deleteMix(index); +} + +void ModelMixesPage::pasteMix(uint8_t dst_idx, uint8_t channel) +{ + if (!_copyMode || !_copySrc) return; + uint8_t src_idx = _copySrc->getIndex(); + + ::copyMix(src_idx, dst_idx, channel); + addLineButton(dst_idx); + + if (_copyMode == MOVE_MODE) { + deleteInput(src_idx); + } +} + +static int _mixChnFromIndex(uint8_t index) +{ + MixData* mix = mixAddress(index); + if (is_memclear(mix, sizeof(MixData))) return -1; + return mix->destCh; +} + +void ModelMixesPage::pasteMixBefore(uint8_t dst_idx) +{ + int channel = _mixChnFromIndex(dst_idx); + pasteInput(dst_idx, channel); +} + +void ModelMixesPage::pasteMixAfter(uint8_t dst_idx) +{ + int channel = _mixChnFromIndex(dst_idx); + pasteInput(dst_idx + 1, channel); +} + +void ModelMixesPage::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, [=]() { + newMix(); + return 0; + }); + auto btn_obj = btn->getLvObj(); + lv_obj_set_width(btn_obj, lv_pct(100)); + + groups.clear(); + lines.clear(); + + uint8_t index = 0; + MixData* line = g_model.mixData; + for (uint8_t ch = 0; ch < MAX_OUTPUT_CHANNELS; ch++) { + + if (index >= MAX_MIXERS) break; + + bool skip_mix = (ch == 0 && is_memclear(line, sizeof(MixData))); + if (line->destCh == ch && !skip_mix) { + + // one group for the complete mixer channel + auto group = new InputMixGroup(form, MIXSRC_CH1 + ch); + groups.emplace_back(group); + while (index < MAX_MIXERS && (line->destCh == ch) && !skip_mix) { + // one button per input line + createLineButton(group, index); + ++index; + ++line; + skip_mix = (ch == 0 && is_memclear(line, sizeof(MixData))); + } + } + } + + // int mixIndex = 0; + // MixData * mix = g_model.mixData; + // for (uint8_t ch = 0; ch < MAX_OUTPUT_CHANNELS; ch++) { + + // bool skip_mix = (ch == 0 && is_memclear(mix, sizeof(MixData))); + + // if (mixIndex < MAX_MIXERS && mix->destCh == ch && !skip_mix) { + + // coord_t h = grid.getWindowHeight(); + // auto txt = new MixLineTitle(window, grid.getLabelSlot(), + // getSourceString(MIXSRC_CH1 + ch), + // BUTTON_BACKGROUND, COLOR_THEME_PRIMARY1 | CENTERED); + + // uint8_t count = 0; + // while (mixIndex < MAX_MIXERS && mix->destCh == ch && !skip_mix) { + + // TODO: + // if (count++ > 0) { + // new StaticBitmap( + // txt, + // {35 - txt->left(), + // button->top() - txt->top() + (button->height() - 18) / 2, 25, + // 17}, + // mixerMultiplexBitmap[mix->mltpx], COLOR_THEME_SECONDARY1); + // } + + // button->setFocusHandler([=](bool focus) { + // if (focus) { + // txt->setBackgroundColor(COLOR_THEME_FOCUS); + // txt->setTextFlags(COLOR_THEME_PRIMARY2 | CENTERED); + // } else { + // txt->setBackgroundColor(COLOR_THEME_SECONDARY2); + // txt->setTextFlags(COLOR_THEME_PRIMARY1 | CENTERED); + // } + // txt->invalidate(); + // if (focus) button->bringToTop(); + // }); + + // grid.spacer(button->height() - 1); + // ++mixIndex; + // ++mix; + + // skip_mix = (ch == 0 && is_memclear(mix, sizeof(MixData))); + // } + + // h = grid.getWindowHeight() - h + 1; + // txt->setHeight(h); + + // grid.spacer(7); + // } + // } + + // grid.nextLine(); +} + diff --git a/radio/src/gui/colorlcd/model_mixes.h b/radio/src/gui/colorlcd/model_mixes.h index 0680f2e53..300a70ecb 100644 --- a/radio/src/gui/colorlcd/model_mixes.h +++ b/radio/src/gui/colorlcd/model_mixes.h @@ -19,25 +19,32 @@ * GNU General Public License for more details. */ -#ifndef _MODEL_MIXES_H_ -#define _MODEL_MIXES_H_ +#pragma once -#include "tabsgroup.h" +#include "model_inputs.h" -class ModelMixesPage: public PageTab { - public: - ModelMixesPage(); - void build(FormWindow * window) override - { - build(window, 0); - } +class ModelMixesPage : public ModelInputsPage +{ + public: + ModelMixesPage(); - protected: - void build(FormWindow * window, int8_t focusMixIndex); - void rebuild(FormWindow * window, int8_t focusMixIndex); - void editMix(FormWindow * window, uint8_t channel, uint8_t mixIndex); - uint8_t s_copySrcIdx; + void build(FormWindow* window) override; + + protected: + InputMixGroup* getGroupByIndex(uint8_t index) override; + + void addLineButton(uint8_t index) override; + void addLineButton(mixsrc_t src, uint8_t index) override; + InputMixButton* createLineButton(InputMixGroup* group, + uint8_t index) override; + + void newMix(); + void editMix(uint8_t input, uint8_t index); + void insertMix(uint8_t input, uint8_t index); + void deleteMix(uint8_t index); + + void pasteMix(uint8_t dst_idx, uint8_t channel); + void pasteMixBefore(uint8_t dst_idx); + void pasteMixAfter(uint8_t dst_idx); }; - -#endif // _MODEL_MIXES_H_ \ No newline at end of file diff --git a/radio/src/strhelpers.cpp b/radio/src/strhelpers.cpp index a5fc4c6a2..068312638 100644 --- a/radio/src/strhelpers.cpp +++ b/radio/src/strhelpers.cpp @@ -662,7 +662,12 @@ char *getSourceString(char (&dest)[L], mixsrc_t idx) strAppendStringWithIndex(dest, STR_PPM_TRAINER, idx - MIXSRC_FIRST_TRAINER + 1); } else if (idx <= MIXSRC_LAST_CH) { - strAppendStringWithIndex(dest, STR_CH, idx - MIXSRC_CH1 + 1); + auto ch = idx - MIXSRC_CH1; + if (g_model.limitData[ch].name[0] != '\0') { + copyToTerminated(dest, g_model.limitData[ch].name); + } else { + strAppendStringWithIndex(dest, STR_CH, ch + 1); + } } else if (idx <= MIXSRC_LAST_GVAR) { strAppendStringWithIndex(dest, STR_GV, idx - MIXSRC_GVAR1 + 1); } else if (idx < MIXSRC_FIRST_TIMER) {