From f5c422cdcd1287da3798472cda5420aa51a2ccf1 Mon Sep 17 00:00:00 2001 From: eshifri Date: Sat, 14 Jan 2023 18:19:38 -0800 Subject: [PATCH 01/14] Add "Enable" button to all CF (#3024) --- radio/src/gui/colorlcd/special_functions.cpp | 47 +++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/radio/src/gui/colorlcd/special_functions.cpp b/radio/src/gui/colorlcd/special_functions.cpp index ac6fc243f8e..3aba8e97de3 100644 --- a/radio/src/gui/colorlcd/special_functions.cpp +++ b/radio/src/gui/colorlcd/special_functions.cpp @@ -352,13 +352,8 @@ class SpecialFunctionEditPage : public Page line = specialFunctionOneWindow->newLine(&grid); } } - - if (HAS_ENABLE_PARAM(func)) { - new StaticText(line, rect_t{}, STR_ENABLE, 0, COLOR_THEME_PRIMARY1); - new CheckBox(line, rect_t{}, - GET_SET_DEFAULT(CFN_ACTIVE(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - } else if (HAS_REPEAT_PARAM(func)) { // !1x 1x 1s 2s 3s ... + + if (HAS_REPEAT_PARAM(func)) { // !1x 1x 1s 2s 3s ... new StaticText(line, rect_t{}, STR_REPEAT, 0, COLOR_THEME_PRIMARY1); auto repeat = new NumberEdit( @@ -377,6 +372,12 @@ class SpecialFunctionEditPage : public Page }); line = specialFunctionOneWindow->newLine(&grid); } + + { + new StaticText(line, rect_t{}, STR_ENABLE, 0, COLOR_THEME_PRIMARY1); + new CheckBox(line, rect_t{}, GET_SET_DEFAULT(CFN_ACTIVE(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + } } void buildBody(FormWindow *window) @@ -387,8 +388,12 @@ class SpecialFunctionEditPage : public Page CustomFunctionData *cfn = &functions[index]; - // Switch - auto line = window->newLine(&grid); + // Set new function to "enabled" by default + if (!CFN_SWITCH(cfn)) + CFN_ACTIVE(cfn) = true; + + // Switch + auto line = window->newLine(&grid); new StaticText(line, rect_t{}, STR_SWITCH, 0, COLOR_THEME_PRIMARY1); auto switchChoice = new SwitchChoice(line, rect_t{}, SWSRC_FIRST, SWSRC_LAST, @@ -419,8 +424,10 @@ class SpecialFunctionEditPage : public Page 0, FUNC_MAX - 1, GET_DEFAULT(CFN_FUNC(cfn))); functionChoice->setSetValueHandler([=](int32_t newValue) { + int enableState = CFN_ACTIVE(cfn); CFN_FUNC(cfn) = newValue; CFN_RESET(cfn); + CFN_ACTIVE(cfn) = enableState; SET_DIRTY(); updateSpecialFunctionOneWindow(); }); @@ -439,6 +446,8 @@ static constexpr coord_t line2 = line1 + PAGE_LINE_HEIGHT; static constexpr coord_t col1 = 20; static constexpr coord_t col2 = (LCD_W - 100) / 3 + col1; static constexpr coord_t col3 = ((LCD_W - 100) / 3) * 2 + col1 + 20; +// Special location used only for "enabled" check-box +static constexpr coord_t col4 = (LCD_W - 100); static const char* _failsafe_module[] = { "Ext.", "Int.", @@ -595,9 +604,10 @@ class SpecialFunctionButton : public Button } } } - if (HAS_ENABLE_PARAM(func)) { - theme->drawCheckBox(dc, CFN_ACTIVE(cfn), col3, line2); - } else if (HAS_REPEAT_PARAM(func)) { + + theme->drawCheckBox(dc, CFN_ACTIVE(cfn), col4, line2); + + if (HAS_REPEAT_PARAM(func)) { if (CFN_PLAY_REPEAT(cfn) == 0) { dc->drawText(col3, line2, "1x", COLOR_THEME_SECONDARY1); } else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { @@ -610,11 +620,16 @@ class SpecialFunctionButton : public Button void paint(BitmapBuffer *dc) override { - if (active) - dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_ACTIVE); - else - dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_PRIMARY2); + const CustomFunctionData *cfn = &functions[index]; + if(CFN_ACTIVE(cfn)) { + if (active) + dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_ACTIVE); + else + dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_PRIMARY2); + } else { + dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_SECONDARY3); + } paintSpecialFunctionLine(dc); // The bounding rect From cfe681a739456dffb3b257fc92a470cbdde09e0d Mon Sep 17 00:00:00 2001 From: eshifri Date: Sat, 14 Jan 2023 18:26:53 -0800 Subject: [PATCH 02/14] Formatting. --- radio/src/gui/colorlcd/special_functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio/src/gui/colorlcd/special_functions.cpp b/radio/src/gui/colorlcd/special_functions.cpp index 3aba8e97de3..7e06df107c8 100644 --- a/radio/src/gui/colorlcd/special_functions.cpp +++ b/radio/src/gui/colorlcd/special_functions.cpp @@ -393,7 +393,7 @@ class SpecialFunctionEditPage : public Page CFN_ACTIVE(cfn) = true; // Switch - auto line = window->newLine(&grid); + auto line = window->newLine(&grid); new StaticText(line, rect_t{}, STR_SWITCH, 0, COLOR_THEME_PRIMARY1); auto switchChoice = new SwitchChoice(line, rect_t{}, SWSRC_FIRST, SWSRC_LAST, From 72356bc279e4fe94e0c0d8df1fa009684bb6e110 Mon Sep 17 00:00:00 2001 From: eshifri Date: Mon, 16 Jan 2023 16:22:07 -0800 Subject: [PATCH 03/14] Add B/W and CP support --- .../edgetx/yaml_customfunctiondata.cpp | 23 +- companion/src/modeledit/customfunctions.cpp | 9 +- radio/src/datastructs_private.h | 3 +- .../gui/128x64/model_special_functions.cpp | 34 +- .../gui/212x64/model_special_functions.cpp | 27 +- .../gui/colorlcd/special_functions+my_1.cpp | 827 ++++++++++++++++++ radio/src/gui/colorlcd/special_functions.cpp | 2 +- radio/src/myeeprom.h | 2 +- .../storage/yaml/yaml_datastructs_funcs.cpp | 40 +- 9 files changed, 905 insertions(+), 62 deletions(-) create mode 100644 radio/src/gui/colorlcd/special_functions+my_1.cpp diff --git a/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp b/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp index 50f82a1fbd9..6cc8e6d9e73 100644 --- a/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp +++ b/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp @@ -216,12 +216,12 @@ Node convert::encode(const CustomFunctionData& rhs) break; } - if (fnHasEnable(rhs.func)) { - if (add_comma) { - def += ","; - } - def += std::to_string((int)rhs.enabled); - } else if(fnHasRepeat(rhs.func)) { + + if (add_comma) { + def += ","; + } + def += std::to_string((int)rhs.enabled); + if(fnHasRepeat(rhs.func)) { if (add_comma) { def += ","; } @@ -367,11 +367,12 @@ bool convert::decode(const Node& node, def.ignore(); } - if (fnHasEnable(rhs.func)) { - int en = 0; - def >> en; - rhs.enabled = en; - } else if(fnHasRepeat(rhs.func)) { + + int en = 0; + def >> en; + rhs.enabled = en; + + if(fnHasRepeat(rhs.func)) { std::string repeat; getline(def, repeat); if (repeat == "1x") { diff --git a/companion/src/modeledit/customfunctions.cpp b/companion/src/modeledit/customfunctions.cpp index af6430b6ced..84a58c74727 100644 --- a/companion/src/modeledit/customfunctions.cpp +++ b/companion/src/modeledit/customfunctions.cpp @@ -199,11 +199,13 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, repeatLayout->addWidget(fswtchRepeat[i], i + 1); connect(fswtchRepeat[i], SIGNAL(currentIndexChanged(int)), this, SLOT(customFunctionEdited())); + QHBoxLayout *enableLayout = new QHBoxLayout(); + tableLayout->addLayout(i, 5, enableLayout); fswtchEnable[i] = new QCheckBox(this); fswtchEnable[i]->setProperty("index", i); fswtchEnable[i]->setText(tr("ON")); fswtchEnable[i]->setFixedWidth(200); - repeatLayout->addWidget(fswtchEnable[i], i + 1); + enableLayout->addWidget(fswtchEnable[i], i + 1); connect(fswtchEnable[i], SIGNAL(stateChanged(int)), this, SLOT(customFunctionEdited())); } @@ -347,7 +349,7 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) } if (!cfn.isEmpty()) { - widgetsMask |= CUSTOM_FUNCTION_SHOW_FUNC; + widgetsMask |= CUSTOM_FUNCTION_SHOW_FUNC | CUSTOM_FUNCTION_ENABLE; if (func >= FuncOverrideCH1 && func <= FuncOverrideCH32) { if (model) { @@ -533,7 +535,8 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) fswtchParam[i]->setDecimals(0); fswtchParam[i]->setSingleStep(1); fswtchParam[i]->setValue(cfn.param); - if (func <= FuncInstantTrim) { + //if (func <= FuncInstantTrim) + { widgetsMask |= CUSTOM_FUNCTION_ENABLE; } } diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index 5f8364e3b62..b6b96156cce 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -172,7 +172,8 @@ PACK(struct CustomFunctionData { NOBACKUP(CFN_SPARE_TYPE val2); }) clear); }) NAME(fp) SKIP; - uint8_t active SKIP; + int8_t repeat:7 SKIP; + uint8_t active:1 SKIP; bool isEmpty() const { diff --git a/radio/src/gui/128x64/model_special_functions.cpp b/radio/src/gui/128x64/model_special_functions.cpp index 3f821ea0af9..0f1fca258fe 100644 --- a/radio/src/gui/128x64/model_special_functions.cpp +++ b/radio/src/gui/128x64/model_special_functions.cpp @@ -21,14 +21,17 @@ #include "opentx.h" -#define MODEL_SPECIAL_FUNC_1ST_COLUMN (0) -#define MODEL_SPECIAL_FUNC_2ND_COLUMN (4*FW-1) -#define MODEL_SPECIAL_FUNC_3RD_COLUMN (15*FW-3) -#define MODEL_SPECIAL_FUNC_4TH_COLUMN (20*FW) +#define MODEL_SPECIAL_FUNC_1ST_COLUMN (0) +#define MODEL_SPECIAL_FUNC_2ND_COLUMN (4*FW-1) +#define MODEL_SPECIAL_FUNC_3RD_COLUMN (15*FW-3) #if defined(GRAPHICS) - #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (20*FW) + #define MODEL_SPECIAL_FUNC_4TH_COLUMN (19 * FW - 3) + #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (19 * FW - 3) + #define MODEL_SPECIAL_FUNC_5TH_COLUMN_ONOFF (20 * FW + 1) #else - #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (18*FW+2) + #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (17 * FW) + #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (17 * FW) + #define MODEL_SPECIAL_FUNC_5TH_COLUMN_ONOFF (18 * FW + 3) #endif #if defined(SDCARD) @@ -179,7 +182,7 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF CustomFunctionData * cfn = &functions[k]; uint8_t func = CFN_FUNC(cfn); - for (uint8_t j=0; j<5; j++) { + for (uint8_t j=0; j<6; j++) { uint8_t attr = ((sub==k && menuHorizontalPosition==j) ? ((s_editMode>0) ? BLINK|INVERS : INVERS) : 0); uint8_t active = (attr && s_editMode > 0); switch (j) { @@ -427,26 +430,27 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF } case 4: - if (HAS_ENABLE_PARAM(func)) { - drawCheckBox(MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), attr); - if (active) CFN_ACTIVE(cfn) = checkIncDec(event, CFN_ACTIVE(cfn), 0, 1, eeFlags); - } - else if (HAS_REPEAT_PARAM(func)) { + if (HAS_REPEAT_PARAM(func)) { if (CFN_PLAY_REPEAT(cfn) == 0) { lcdDrawChar(MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF+3, y, '-', attr); } - else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { + else if (CFN_PLAY_REPEAT(cfn) == (int8_t)CFN_PLAY_REPEAT_NOSTART) { lcdDrawText(MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF+1, y, "!-", attr); } else { lcdDrawNumber(MODEL_SPECIAL_FUNC_4TH_COLUMN+2+FW, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, RIGHT | attr); } - if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); + if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==(int8_t)CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); } else if (attr) { REPEAT_LAST_CURSOR_MOVE(); } break; + + case 5: + drawCheckBox(MODEL_SPECIAL_FUNC_5TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), attr); + if (active) CFN_ACTIVE(cfn) = checkIncDec(event, CFN_ACTIVE(cfn), 0, 1, eeFlags); + break; } } #if defined(NAVIGATION_X7) @@ -465,7 +469,7 @@ void menuModelSpecialFunctions(event_t event) menuHorizontalPosition = 0; } #endif - MENU(STR_MENUCUSTOMFUNC, menuTabModel, MENU_MODEL_SPECIAL_FUNCTIONS, HEADER_LINE+MAX_SPECIAL_FUNCTIONS, { HEADER_LINE_COLUMNS NAVIGATION_LINE_BY_LINE|4/*repeated*/ }); + MENU(STR_MENUCUSTOMFUNC, menuTabModel, MENU_MODEL_SPECIAL_FUNCTIONS, HEADER_LINE+MAX_SPECIAL_FUNCTIONS, { HEADER_LINE_COLUMNS NAVIGATION_LINE_BY_LINE|5/*repeated*/ }); menuSpecialFunctions(event, g_model.customFn, &modelFunctionsContext); diff --git a/radio/src/gui/212x64/model_special_functions.cpp b/radio/src/gui/212x64/model_special_functions.cpp index a12e61e3554..121bf7f4ea7 100644 --- a/radio/src/gui/212x64/model_special_functions.cpp +++ b/radio/src/gui/212x64/model_special_functions.cpp @@ -24,7 +24,7 @@ #define MODEL_SPECIAL_FUNC_1ST_COLUMN (4*FW+2) #define MODEL_SPECIAL_FUNC_2ND_COLUMN (8*FW+2) #define MODEL_SPECIAL_FUNC_3RD_COLUMN (21*FW) -#define MODEL_SPECIAL_FUNC_4TH_COLUMN (33*FW-3) +#define MODEL_SPECIAL_FUNC_4TH_COLUMN (31*FW-3) #define MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF (34*FW-3) void onCustomFunctionsFileSelectionMenu(const char * result) @@ -141,8 +141,9 @@ enum CustomFunctionsItems { ITEM_CUSTOM_FUNCTIONS_PARAM1, ITEM_CUSTOM_FUNCTIONS_PARAM2, ITEM_CUSTOM_FUNCTIONS_REPEAT, + ITEM_CUSTOM_FUNCTIONS_ENABLE, ITEM_CUSTOM_FUNCTIONS_COUNT, - ITEM_CUSTOM_FUNCTIONS_LAST = ITEM_CUSTOM_FUNCTIONS_COUNT-1 + ITEM_CUSTOM_FUNCTIONS_LAST = ITEM_CUSTOM_FUNCTIONS_COUNT - 1 }; void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomFunctionsContext * functionsContext) @@ -177,7 +178,7 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF CustomFunctionData * cfn = &functions[k]; uint8_t func = CFN_FUNC(cfn); - for (uint8_t j=0; j<5; j++) { + for (uint8_t j=0; j<6; j++) { uint8_t attr = ((sub==k && menuHorizontalPosition==j) ? ((s_editMode>0) ? BLINK|INVERS : INVERS) : 0); uint8_t active = (attr && s_editMode>0); switch (j) { @@ -405,15 +406,11 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF } case ITEM_CUSTOM_FUNCTIONS_REPEAT: - if (HAS_ENABLE_PARAM(func)) { - drawCheckBox(MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), attr); - if (active) CFN_ACTIVE(cfn) = checkIncDec(event, CFN_ACTIVE(cfn), 0, 1, eeFlags); - } - else if (HAS_REPEAT_PARAM(func)) { + if (HAS_REPEAT_PARAM(func)) { if (CFN_PLAY_REPEAT(cfn) == 0) { lcdDrawText(MODEL_SPECIAL_FUNC_4TH_COLUMN+2, y, "1x", attr); } - else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { + else if (CFN_PLAY_REPEAT(cfn) == (int8_t)CFN_PLAY_REPEAT_NOSTART) { lcdDrawChar(MODEL_SPECIAL_FUNC_4TH_COLUMN-1, y, '!', attr); lcdDrawText(MODEL_SPECIAL_FUNC_4TH_COLUMN+2, y, "1x", attr); } @@ -421,12 +418,20 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF lcdDrawNumber(MODEL_SPECIAL_FUNC_4TH_COLUMN+2+FW, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr|RIGHT); lcdDrawChar(MODEL_SPECIAL_FUNC_4TH_COLUMN+2+FW, y, 's', attr); } - if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); + if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==(int8_t)CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); } else if (attr) { REPEAT_LAST_CURSOR_MOVE(); } break; + + case ITEM_CUSTOM_FUNCTIONS_ENABLE: + drawCheckBox(MODEL_SPECIAL_FUNC_4TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), + attr); + if (active) + CFN_ACTIVE(cfn) = + checkIncDec(event, CFN_ACTIVE(cfn), 0, 1, eeFlags); + break; } } } @@ -434,6 +439,6 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF void menuModelSpecialFunctions(event_t event) { - MENU(STR_MENUCUSTOMFUNC, menuTabModel, MENU_MODEL_SPECIAL_FUNCTIONS, MAX_SPECIAL_FUNCTIONS, { NAVIGATION_LINE_BY_LINE|4/*repeated*/ }); + MENU(STR_MENUCUSTOMFUNC, menuTabModel, MENU_MODEL_SPECIAL_FUNCTIONS, MAX_SPECIAL_FUNCTIONS, { NAVIGATION_LINE_BY_LINE|5/*repeated*/ }); return menuSpecialFunctions(event, g_model.customFn, &modelFunctionsContext); } diff --git a/radio/src/gui/colorlcd/special_functions+my_1.cpp b/radio/src/gui/colorlcd/special_functions+my_1.cpp new file mode 100644 index 00000000000..67b2f3b53d4 --- /dev/null +++ b/radio/src/gui/colorlcd/special_functions+my_1.cpp @@ -0,0 +1,827 @@ +/* + * 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 "special_functions.h" +#include "opentx.h" +#include "libopenui.h" +#include "view_main.h" + +#define SET_DIRTY() storageDirty(functions == g_model.customFn ? EE_MODEL : EE_GENERAL) + +static const lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2), + LV_GRID_TEMPLATE_LAST}; +static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; + +class SpecialFunctionEditPage : public Page +{ + public: + SpecialFunctionEditPage(CustomFunctionData *functions, uint8_t index) : + Page(functions == g_model.customFn ? ICON_MODEL_SPECIAL_FUNCTIONS + : ICON_RADIO_GLOBAL_FUNCTIONS), + functions(functions), + index(index) + { + buildHeader(&header); + buildBody(&body); + lv_obj_set_style_max_height(body.getLvObj(), LCD_H - header.height(), 0); + lv_obj_set_style_max_width(body.getLvObj(), LCD_W, 0); + } + + protected: + CustomFunctionData *functions; + uint8_t index; + FormGroup *specialFunctionOneWindow = nullptr; + StaticText *headerSF = nullptr; + bool active = false; + + bool isActive() const + { + return ((functions == g_model.customFn + ? modelFunctionsContext.activeSwitches + : globalFunctionsContext.activeSwitches) & + ((MASK_CFN_TYPE)1 << index) + ? 1 + : 0); + } + + void checkEvents() override + { + Page::checkEvents(); + if (active != isActive()) { + if(isActive()) { + lv_obj_add_state(headerSF->getLvObj(), LV_STATE_USER_1); + } else { + lv_obj_clear_state(headerSF->getLvObj(), LV_STATE_USER_1); + } + active = isActive(); + invalidate(); + } + } + + void buildHeader(Window *window) + { + new StaticText( + window, {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, 20}, + functions == g_model.customFn ? STR_MENUCUSTOMFUNC + : STR_MENUSPECIALFUNCS, + 0, COLOR_THEME_PRIMARY2); + headerSF = new StaticText( + window, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT, + LCD_W - PAGE_TITLE_LEFT, 20}, + (functions == g_model.customFn ? "SF" : "GF") + std::to_string(index+1), + 0, COLOR_THEME_PRIMARY2); + + lv_obj_set_style_text_color(headerSF->getLvObj(), makeLvColor(COLOR_THEME_ACTIVE), LV_STATE_USER_1); + lv_obj_set_style_text_font(headerSF->getLvObj(), getFont(FONT(BOLD)), LV_STATE_USER_1); + } + + void updateSpecialFunctionOneWindow() + { + specialFunctionOneWindow->clear(); + specialFunctionOneWindow->setFlexLayout(); + FlexGridLayout grid(col_dsc, row_dsc, 2); + auto line = specialFunctionOneWindow->newLine(&grid); + + CustomFunctionData *cfn = &functions[index]; + uint8_t func = CFN_FUNC(cfn); + + // Func param + switch (func) { + case FUNC_OVERRIDE_CHANNEL: { + new StaticText(line, rect_t{}, STR_CH, 0, COLOR_THEME_PRIMARY1); + new NumberEdit(line, rect_t{}, 1, + MAX_OUTPUT_CHANNELS, + GET_SET_VALUE_WITH_OFFSET(CFN_CH_INDEX(cfn), 1)); + line = specialFunctionOneWindow->newLine(&grid); + + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + int limit = (g_model.extendedLimits ? LIMIT_EXT_PERCENT : LIMIT_STD_PERCENT); + new NumberEdit(line, rect_t{}, -limit, limit, + GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + } + + case FUNC_TRAINER: { + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + auto choice = + new Choice(line, rect_t{}, 0, + NUM_STICKS + 1, GET_SET_DEFAULT(CFN_CH_INDEX(cfn))); + choice->setTextHandler([=](int32_t value) { + if (value == 0) + return std::string(STR_STICKS); + else if (value == NUM_STICKS + 1) + return std::string(STR_CHANS); + else + return TEXT_AT_INDEX(STR_VSRCRAW, value); + ; + }); + line = specialFunctionOneWindow->newLine(&grid); + break; + } + + case FUNC_RESET: + if (CFN_PARAM(cfn) < FUNC_RESET_PARAM_FIRST_TELEM) { + new StaticText(line, rect_t{}, STR_RESET, 0, COLOR_THEME_PRIMARY1); + auto choice = new Choice( + line, rect_t{}, 0, + FUNC_RESET_PARAM_FIRST_TELEM + lastUsedTelemetryIndex(), + GET_SET_DEFAULT(CFN_PARAM(cfn))); + choice->setAvailableHandler(isSourceAvailableInResetSpecialFunction); + choice->setTextHandler([=](int32_t value) { + if (value < FUNC_RESET_PARAM_FIRST_TELEM) + return TEXT_AT_INDEX(STR_VFSWRESET, value); + else + return std::string( + g_model.telemetrySensors[value - FUNC_RESET_PARAM_FIRST_TELEM] + .label, + TELEM_LABEL_LEN); + }); + line = specialFunctionOneWindow->newLine(&grid); + } + break; + + case FUNC_VOLUME: + new StaticText(line, rect_t{}, STR_VOLUME, 0, COLOR_THEME_PRIMARY1); + new SourceChoice(line, rect_t{}, 0, + MIXSRC_LAST_CH, GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + + case FUNC_BACKLIGHT: + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + new SourceChoice(line, rect_t{}, 0, + MIXSRC_LAST_CH, GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + + case FUNC_PLAY_SOUND: + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + new Choice(line, rect_t{}, + STR_FUNCSOUNDS, 0, + AU_SPECIAL_SOUND_LAST - AU_SPECIAL_SOUND_FIRST - 1, + GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + + case FUNC_PLAY_TRACK: + case FUNC_BACKGND_MUSIC: + case FUNC_PLAY_SCRIPT: + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + new FileChoice( + line, rect_t{}, + func == FUNC_PLAY_SCRIPT + ? SCRIPTS_FUNCS_PATH + : std::string(SOUNDS_PATH, SOUNDS_PATH_LNG_OFS) + + std::string(currentLanguagePack->id, 2), + func == FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, + sizeof(cfn->play.name), + [=]() { return std::string(cfn->play.name, ZLEN(cfn->play.name)); }, + [=](std::string newValue) { + strncpy(cfn->play.name, newValue.c_str(), sizeof(cfn->play.name)); + SET_DIRTY(); + LUA_LOAD_MODEL_SCRIPTS(); + }, + true); // strip extension + line = specialFunctionOneWindow->newLine(&grid); + break; + + case FUNC_SET_TIMER: { + new StaticText(line, rect_t{}, STR_TIMER, 0, COLOR_THEME_PRIMARY1); + auto timerchoice = + new Choice(line, rect_t{}, 0, + TIMERS - 1, GET_SET_DEFAULT(CFN_TIMER_INDEX(cfn))); + timerchoice->setTextHandler([](int32_t value) { + return std::string(STR_TIMER) + std::to_string(value + 1); + }); + line = specialFunctionOneWindow->newLine(&grid); + + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + new TimeEdit(line, rect_t{}, 0, + 9 * 60 * 60 - 1, GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + } + + case FUNC_SET_FAILSAFE: + new StaticText(line, rect_t{}, STR_MODULE, 0, COLOR_THEME_PRIMARY1); + new Choice(line, rect_t{}, + "\004Int.Ext.", 0, NUM_MODULES - 1, + GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + + case FUNC_PLAY_VALUE: + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + new SourceChoice(line, rect_t{}, 0, + MIXSRC_LAST_TELEM, GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + + case FUNC_HAPTIC: + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + new NumberEdit(line, rect_t{}, 0, 3, + GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + + case FUNC_LOGS: { + new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); + auto edit = + new NumberEdit(line, rect_t{}, 0, + 255, GET_SET_DEFAULT(CFN_PARAM(cfn))); + edit->setDisplayHandler( + [=](int32_t value) { + return formatNumberAsString(CFN_PARAM(cfn), PREC1, sizeof(CFN_PARAM(cfn)), nullptr, "s"); + }); + break; + } + + case FUNC_SET_SCREEN: + new StaticText(line, rect_t{}, STR_VALUE, + 0, COLOR_THEME_PRIMARY1); + CFN_PARAM(cfn) = (int16_t)max(CFN_PARAM(cfn), (int16_t)1); + CFN_PARAM(cfn) = (int16_t)min( + CFN_PARAM(cfn), (int16_t)ViewMain::instance()->getMainViewsCount()); + new NumberEdit(line, rect_t{}, 1, + ViewMain::instance()->getMainViewsCount(), + GET_SET_DEFAULT(CFN_PARAM(cfn))); + line = specialFunctionOneWindow->newLine(&grid); + break; + + case FUNC_ADJUST_GVAR: { + new StaticText(line, rect_t{}, STR_GLOBALVAR, 0, COLOR_THEME_PRIMARY1); + auto gvarchoice = + new Choice(line, rect_t{}, 0, + MAX_GVARS - 1, GET_SET_DEFAULT(CFN_GVAR_INDEX(cfn))); + gvarchoice->setTextHandler([](int32_t value) { + return std::string(STR_GV) + std::to_string(value + 1); + }); + line = specialFunctionOneWindow->newLine(&grid); + + new StaticText(line, rect_t{}, STR_MODE, 0, COLOR_THEME_PRIMARY1); + auto modechoice = + new Choice(line, rect_t{}, + FUNC_ADJUST_GVAR_CONSTANT, FUNC_ADJUST_GVAR_INCDEC, + GET_DEFAULT(CFN_GVAR_MODE(cfn)), nullptr); + line = specialFunctionOneWindow->newLine(&grid); + + modechoice->setTextHandler([](int32_t value) { + switch (value) { + case FUNC_ADJUST_GVAR_CONSTANT: + return std::string(STR_CONSTANT); + case FUNC_ADJUST_GVAR_SOURCE: + return std::string(STR_MIXSOURCE); + case FUNC_ADJUST_GVAR_GVAR: + return std::string(STR_GLOBALVAR); + case FUNC_ADJUST_GVAR_INCDEC: + return std::string(STR_INCDEC); + } + return std::string("---"); + }); + + modechoice->setSetValueHandler([=](int32_t newValue) { + CFN_GVAR_MODE(cfn) = newValue; + CFN_PARAM(cfn) = 0; + SET_DIRTY(); + updateSpecialFunctionOneWindow(); + }); + + switch (CFN_GVAR_MODE(cfn)) { + case FUNC_ADJUST_GVAR_CONSTANT: { + new StaticText(line, rect_t{}, STR_CONSTANT, 0, COLOR_THEME_PRIMARY1); + int16_t val_min, val_max; + getMixSrcRange(CFN_GVAR_INDEX(cfn) + MIXSRC_FIRST_GVAR, val_min, + val_max); + new NumberEdit(line, rect_t{}, + val_min, val_max, GET_SET_DEFAULT(CFN_PARAM(cfn))); + break; + } + case FUNC_ADJUST_GVAR_SOURCE: + new StaticText(line, rect_t{}, STR_MIXSOURCE, 0, COLOR_THEME_PRIMARY1); + new SourceChoice(line, rect_t{}, + 0, MIXSRC_LAST_CH, GET_SET_DEFAULT(CFN_PARAM(cfn))); + break; + case FUNC_ADJUST_GVAR_GVAR: { + new StaticText(line, rect_t{}, STR_GLOBALVAR, 0, COLOR_THEME_PRIMARY1); + auto gvarchoice = + new Choice(line, rect_t{}, 0, + MAX_GVARS - 1, GET_SET_DEFAULT(CFN_PARAM(cfn))); + gvarchoice->setTextHandler([](int32_t value) { + return std::string(STR_GV) + std::to_string(value + 1); + }); + gvarchoice->setAvailableHandler([=](int value) { + return CFN_GVAR_INDEX(cfn) != value; + }); + break; + } + case FUNC_ADJUST_GVAR_INCDEC: { + new StaticText(line, rect_t{}, STR_INCDEC, 0, COLOR_THEME_PRIMARY1); + int16_t val_min, val_max; + getMixSrcRange(CFN_GVAR_INDEX(cfn) + MIXSRC_FIRST_GVAR, val_min, val_max); + getGVarIncDecRange(val_min, val_max); + auto numedit = new NumberEdit(line, rect_t{}, val_min, val_max, + GET_SET_DEFAULT(CFN_PARAM(cfn))); + numedit->setDisplayHandler( + [](int value) { + return formatNumberAsString(abs(value), 0, 0, value >= 0 ? "+=" : "--", nullptr); + }); + break; + } + } + line = specialFunctionOneWindow->newLine(&grid); + } + } + + if (HAS_REPEAT_PARAM(func)) { // !1x 1x 1s 2s 3s ... + new StaticText(line, rect_t{}, STR_REPEAT, 0, COLOR_THEME_PRIMARY1); + auto repeat = new NumberEdit( + line, rect_t{}, -1, 60 / CFN_PLAY_REPEAT_MUL, + [=]() -> int { + int8_t val = int8_t(uint8_t(CFN_PLAY_REPEAT(cfn)) & int8_t(~CFN_ENABLE_MASK)); + TRACE("Get: %d", val); + if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART || + int8_t(CFN_PLAY_REPEAT(cfn)) == int8_t(CFN_PLAY_REPEAT_NOSTART & (~CFN_ENABLE_MASK))) + val = CFN_PLAY_REPEAT_NOSTART; + + TRACE("Get 2: %d\t%d", val, int8_t(CFN_PLAY_REPEAT(cfn))); + return int(val); + }, + [=](int value) { + if(CFN_ACTIVE(cfn) & CFN_ENABLE_MASK) { + CFN_ACTIVE(cfn) = value | CFN_ENABLE_MASK; + } else { + CFN_ACTIVE(cfn) = value & ~CFN_ENABLE_MASK; + } + TRACE("Set %d\t%d", value, int8_t(CFN_ACTIVE(cfn))); + }); + // GET_DEFAULT((int8_t)CFN_PLAY_REPEAT(cfn)), + // SET_DEFAULT(CFN_PLAY_REPEAT(cfn))); + repeat->setDisplayHandler([=](int32_t value) { + if (value == 0) + return std::string("1x"); + else if (value == CFN_PLAY_REPEAT_NOSTART || + CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART & (~CFN_ENABLE_MASK)) + return std::string("!1x"); + else { + return formatNumberAsString( + (value & ~CFN_ENABLE_MASK) * CFN_PLAY_REPEAT_MUL, 0, 0, nullptr, + "s"); + } + }); + line = specialFunctionOneWindow->newLine(&grid); + } + + { + new StaticText(line, rect_t{}, STR_ENABLE, 0, COLOR_THEME_PRIMARY1); + new CheckBox( + line, rect_t{}, // GET_SET_DEFAULT(CFN_ACTIVE(cfn)) + [=]() -> uint8_t { + return (CFN_ACTIVE(cfn) & int8_t(CFN_ENABLE_MASK)) ? 1 : 0; + }, + [=](uint8_t value) { + if(value) + CFN_ACTIVE(cfn) |= uint8_t(CFN_ENABLE_MASK); + else + CFN_ACTIVE(cfn) &= uint8_t(~CFN_ENABLE_MASK); + }); + line = specialFunctionOneWindow->newLine(&grid); + } + } + + void buildBody(FormWindow *window) + { + window->setFlexLayout(); + FlexGridLayout grid(col_dsc, row_dsc, 2); + lv_obj_set_style_pad_all(window->getLvObj(), lv_dpx(8), 0); + + CustomFunctionData *cfn = &functions[index]; + + // Set new function to "enabled" by default + if (!CFN_SWITCH(cfn)) + CFN_ACTIVE(cfn) |= 0x40; + + // Switch + auto line = window->newLine(&grid); + new StaticText(line, rect_t{}, STR_SWITCH, 0, COLOR_THEME_PRIMARY1); + auto switchChoice = + new SwitchChoice(line, rect_t{}, SWSRC_FIRST, SWSRC_LAST, + GET_SET_DEFAULT(CFN_SWITCH(cfn))); + switchChoice->setAvailableHandler([=](int value) { + return (functions == g_model.customFn + ? isSwitchAvailable(value, ModelCustomFunctionsContext) + : isSwitchAvailable(value, GeneralCustomFunctionsContext)); + }); + line = window->newLine(&grid); + + // Patch function in case not available + if (!isAssignableFunctionAvailable(CFN_FUNC(cfn), functions)) { + auto func = 0; + while(!isAssignableFunctionAvailable(func, functions) + && (func < FUNC_MAX - 1)) { + func++; + } + if (func < FUNC_MAX - 1) { + CFN_FUNC(cfn) = func; + } + } + + // Function + new StaticText(line, rect_t{}, STR_FUNC, 0, COLOR_THEME_PRIMARY1); + auto functionChoice = + new Choice(line, rect_t{}, STR_VFSWFUNC, + 0, FUNC_MAX - 1, + GET_DEFAULT(CFN_FUNC(cfn))); + functionChoice->setSetValueHandler([=](int32_t newValue) { + int enableState = CFN_ACTIVE(cfn); + CFN_FUNC(cfn) = newValue; + CFN_RESET(cfn); + CFN_ACTIVE(cfn) = enableState; + SET_DIRTY(); + updateSpecialFunctionOneWindow(); + }); + functionChoice->setAvailableHandler([=](int value) { + return isAssignableFunctionAvailable(value, functions); + }); + line = window->newLine(&grid); + + specialFunctionOneWindow = new FormGroup(window, rect_t{}); + updateSpecialFunctionOneWindow(); + } +}; + +static constexpr coord_t line1 = FIELD_PADDING_TOP; +static constexpr coord_t line2 = line1 + PAGE_LINE_HEIGHT; +static constexpr coord_t col1 = 20; +static constexpr coord_t col2 = (LCD_W - 100) / 3 + col1; +static constexpr coord_t col3 = ((LCD_W - 100) / 3) * 2 + col1 + 20; +// Special location used only for "enabled" check-box +static constexpr coord_t col4 = (LCD_W - 100); + +static const char* _failsafe_module[] = { + "Ext.", "Int.", +}; + +class SpecialFunctionButton : public Button +{ + public: + SpecialFunctionButton(FormWindow *parent, const rect_t &rect, + CustomFunctionData *functions, uint8_t index) : + Button(parent, rect), functions(functions), index(index) + { + const CustomFunctionData *cfn = &functions[index]; + uint8_t func = CFN_FUNC(cfn); + if (!cfn->isEmpty() && + (HAS_ENABLE_PARAM(func) || HAS_REPEAT_PARAM(func) || + (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || + func == FUNC_PLAY_SCRIPT))) { + setHeight(line2 + PAGE_LINE_HEIGHT); + } + } + +#if defined(DEBUG_WINDOWS) + std::string getName() const override { return "SpecialFunctionButton"; } +#endif + + bool isActive() const + { + return ((functions == g_model.customFn + ? modelFunctionsContext.activeSwitches + : globalFunctionsContext.activeSwitches) & + ((MASK_CFN_TYPE)1 << index) + ? 1 + : 0); + } + + void checkEvents() override + { + Button::checkEvents(); + if (active != isActive()) { + invalidate(); + active = !active; + } + } + + void paintSpecialFunctionLine(BitmapBuffer *dc) + { + const CustomFunctionData *cfn = &functions[index]; + if (functions[index].func == FUNC_OVERRIDE_CHANNEL && + functions != g_model.customFn) { + functions[index].func = FUNC_OVERRIDE_CHANNEL + 1; + } + uint8_t func = CFN_FUNC(cfn); + + drawSwitch(dc, col1, line1, CFN_SWITCH(cfn), COLOR_THEME_SECONDARY1); + if (cfn->isEmpty()) return; + + dc->drawTextAtIndex(col2, line1, STR_VFSWFUNC, func, COLOR_THEME_SECONDARY1); + int16_t val_min = 0; + int16_t val_max = 255; + + switch (func) { + case FUNC_OVERRIDE_CHANNEL: + drawChn(dc, col1, line2, CFN_CH_INDEX(cfn) + 1, COLOR_THEME_SECONDARY1); + getMixSrcRange(MIXSRC_FIRST_CH, val_min, val_max); + dc->drawNumber(col2, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + + case FUNC_TRAINER: { + std::string text; + int16_t value = CFN_CH_INDEX(cfn); + if (value == 0) + text = std::string(STR_STICKS); + else if (value == NUM_STICKS + 1) + text = std::string(STR_CHANS); + else + text = TEXT_AT_INDEX(STR_VSRCRAW, value); + dc->drawText(col1, line2, text.c_str(), COLOR_THEME_SECONDARY1); + break; + } + case FUNC_RESET: + if (CFN_PARAM(cfn) < FUNC_RESET_PARAM_FIRST_TELEM) { + dc->drawTextAtIndex(col1, line2, STR_VFSWRESET, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + } else { + TelemetrySensor *sensor = + &g_model.telemetrySensors[CFN_PARAM(cfn) - + FUNC_RESET_PARAM_FIRST_TELEM]; + dc->drawSizedText(col1, line2, sensor->label, TELEM_LABEL_LEN, COLOR_THEME_SECONDARY1); + } + break; + + case FUNC_VOLUME: + drawSource(dc, col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + + case FUNC_BACKLIGHT: + drawSource(dc, col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + + case FUNC_PLAY_SOUND: + dc->drawTextAtIndex(col1, line2, STR_FUNCSOUNDS, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + + case FUNC_PLAY_TRACK: + case FUNC_BACKGND_MUSIC: + case FUNC_PLAY_SCRIPT: + if (ZEXIST(cfn->play.name)) + dc->drawSizedText(col1, line2, cfn->play.name, sizeof(cfn->play.name), COLOR_THEME_SECONDARY1); + else + dc->drawTextAtIndex(col1, line2, STR_VCSWFUNC, 0, COLOR_THEME_SECONDARY1); + break; + + case FUNC_SET_TIMER: + drawStringWithIndex(dc, col1, line2, STR_TIMER, CFN_TIMER_INDEX(cfn) + 1, COLOR_THEME_SECONDARY1); + break; + + case FUNC_SET_FAILSAFE: + dc->drawTextAtIndex(col1, line2, _failsafe_module, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + + case FUNC_PLAY_VALUE: + drawSource(dc, col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + + case FUNC_HAPTIC: + dc->drawNumber(col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + + case FUNC_LOGS: + dc->drawNumber(col3, line1, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1 | PREC1, sizeof(CFN_PARAM(cfn)), nullptr, "s"); + break; + + case FUNC_SET_SCREEN: + dc->drawNumber(col2, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + + case FUNC_ADJUST_GVAR: + switch(CFN_GVAR_MODE(cfn)) { + case FUNC_ADJUST_GVAR_CONSTANT: + dc->drawNumber(col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + case FUNC_ADJUST_GVAR_SOURCE: + drawSource(dc, col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + break; + case FUNC_ADJUST_GVAR_GVAR: + drawSource(dc, col1, line2, CFN_PARAM(cfn) + MIXSRC_FIRST_GVAR, COLOR_THEME_SECONDARY1); + break; + case FUNC_ADJUST_GVAR_INCDEC: { + int16_t value = CFN_PARAM(cfn); + std::string text(value >= 0 ? "+= " : "-= "); + text += std::to_string(abs(value)); + dc->drawText(col1, line2, text.c_str(), COLOR_THEME_SECONDARY1); + break; + } + } + } + + theme->drawCheckBox(dc, CFN_ACTIVE(cfn) & CFN_ENABLE_MASK, col4, line2); + + if (HAS_REPEAT_PARAM(func)) { + if (CFN_PLAY_REPEAT(cfn) == 0) { + dc->drawText(col3, line2, "1x", COLOR_THEME_SECONDARY1); + } else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART || + CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART & + (~CFN_ENABLE_MASK)) { + dc->drawText(col3, line2, "!1x", COLOR_THEME_SECONDARY1); + } else { + dc->drawNumber(col3 + 12, line2, CFN_PLAY_REPEAT(cfn) * CFN_PLAY_REPEAT_MUL, COLOR_THEME_SECONDARY1 | RIGHT, 0, nullptr, "s"); + } + } + } + + void paint(BitmapBuffer *dc) override + { + const CustomFunctionData *cfn = &functions[index]; + + if (CFN_ACTIVE(cfn) & CFN_ENABLE_MASK) { + if (active) + dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_ACTIVE); + else + dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_PRIMARY2); + } else { + dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_SECONDARY3); + } + paintSpecialFunctionLine(dc); + + // The bounding rect + if (hasFocus()) + dc->drawSolidRect(0, 0, rect.w, rect.h, 2, COLOR_THEME_FOCUS); + else + dc->drawSolidRect(0, 0, rect.w, rect.h, 1, COLOR_THEME_SECONDARY2); + } + + protected: + CustomFunctionData *functions; + uint8_t index; + bool active = false; +}; + +SpecialFunctionsPage::SpecialFunctionsPage(CustomFunctionData *functions) : + PageTab(functions == g_model.customFn ? STR_MENUCUSTOMFUNC + : STR_MENUSPECIALFUNCS, + functions == g_model.customFn ? ICON_MODEL_SPECIAL_FUNCTIONS + : ICON_RADIO_GLOBAL_FUNCTIONS), + functions(functions) +{ +} + +void SpecialFunctionsPage::rebuild(FormWindow *window, + int8_t focusSpecialFunctionIndex) +{ + auto scroll_y = lv_obj_get_scroll_y(window->getLvObj()); + window->clear(); + build(window, focusSpecialFunctionIndex); + lv_obj_scroll_to_y(window->getLvObj(), scroll_y, LV_ANIM_OFF); +} + +void SpecialFunctionsPage::editSpecialFunction(FormWindow *window, + uint8_t index) +{ + auto editPage = new SpecialFunctionEditPage(functions, index); + editPage->setCloseHandler([=]() { rebuild(window, index); }); +} + +void SpecialFunctionsPage::build(FormWindow *window, int8_t focusIndex) +{ + FormGridLayout grid; + grid.spacer(PAGE_PADDING); + grid.setLabelWidth(66); + window->padAll(0); + + // Window::clearFocus(); + + char s[] = "SFxx"; + if (functions == g_eeGeneral.customFn) s[0] = 'G'; + + for (uint8_t i = 0; i < MAX_SPECIAL_FUNCTIONS; i++) { + CustomFunctionData *cfn = &functions[i]; + strAppendUnsigned(&s[2], i+1); + + Button *button; + if (cfn->swtch == 0) { + button = new TextButton(window, grid.getLabelSlot(), s); + button->setPressHandler([=]() { + if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) { + Menu *menu = new Menu(window); + menu->addLine(STR_EDIT, [=]() { editSpecialFunction(window, i); }); + menu->addLine(STR_PASTE, [=]() { + *cfn = clipboard.data.cfn; + SET_DIRTY(); + if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) + LUA_LOAD_MODEL_SCRIPTS(); + rebuild(window, i); + }); + } else { + editSpecialFunction(window, i); + } + return 0; + }); + grid.spacer(button->height() + 5); + } else { + auto txt = new StaticText(window, grid.getLabelSlot(), s, BUTTON_BACKGROUND, COLOR_THEME_PRIMARY1 | CENTERED); + + button = new SpecialFunctionButton(window, grid.getFieldSlot(), functions, i); + button->setPressHandler([=]() { + button->bringToTop(); + Menu *menu = new Menu(window); + menu->addLine(STR_EDIT, [=]() { editSpecialFunction(window, i); }); + menu->addLine(STR_COPY, [=]() { + clipboard.type = CLIPBOARD_TYPE_CUSTOM_FUNCTION; + clipboard.data.cfn = *cfn; + }); + if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) { + menu->addLine(STR_PASTE, [=]() { + if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) + LUA_LOAD_MODEL_SCRIPTS(); + *cfn = clipboard.data.cfn; + if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) + LUA_LOAD_MODEL_SCRIPTS(); + SET_DIRTY(); + rebuild(window, i); + }); + } + if (functions[MAX_SPECIAL_FUNCTIONS - 1].isEmpty()) { + menu->addLine(STR_INSERT, [=]() { + memmove( + cfn + 1, cfn, + (MAX_SPECIAL_FUNCTIONS - i - 1) * sizeof(CustomFunctionData)); + memset(cfn, 0, sizeof(CustomFunctionData)); + SET_DIRTY(); + rebuild(window, i); + }); + } + menu->addLine(STR_CLEAR, [=]() { + if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) + LUA_LOAD_MODEL_SCRIPTS(); + memset(cfn, 0, sizeof(CustomFunctionData)); + SET_DIRTY(); + rebuild(window, i); + }); + for (int j = i; j < MAX_SPECIAL_FUNCTIONS; j++) { + if (!functions[j].isEmpty()) { + menu->addLine(STR_DELETE, [=]() { + if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) + LUA_LOAD_MODEL_SCRIPTS(); + memmove( + cfn, cfn + 1, + (MAX_SPECIAL_FUNCTIONS - i - 1) * sizeof(CustomFunctionData)); + memset(&functions[MAX_SPECIAL_FUNCTIONS - 1], 0, + sizeof(CustomFunctionData)); + SET_DIRTY(); + rebuild(window, i); + }); + break; + } + } + return 0; + }); + 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 (focusIndex == i) { + // txt->setBackgroundColor(COLOR_THEME_FOCUS); + // txt->setTextFlags(COLOR_THEME_PRIMARY2 | CENTERED); + // txt->invalidate(); + // } + + txt->setHeight(button->height()); + grid.spacer(button->height() + 5); + } + + // if (focusIndex == i) { // fix focus #303 + // button->setFocus(SET_FOCUS_DEFAULT); + // } + } + + grid.nextLine(); + + // window->setLastField(); +} diff --git a/radio/src/gui/colorlcd/special_functions.cpp b/radio/src/gui/colorlcd/special_functions.cpp index 7e06df107c8..a43f73630f7 100644 --- a/radio/src/gui/colorlcd/special_functions.cpp +++ b/radio/src/gui/colorlcd/special_functions.cpp @@ -610,7 +610,7 @@ class SpecialFunctionButton : public Button if (HAS_REPEAT_PARAM(func)) { if (CFN_PLAY_REPEAT(cfn) == 0) { dc->drawText(col3, line2, "1x", COLOR_THEME_SECONDARY1); - } else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { + } else if (CFN_PLAY_REPEAT(cfn) == int8_t(CFN_PLAY_REPEAT_NOSTART)) { dc->drawText(col3, line2, "!1x", COLOR_THEME_SECONDARY1); } else { dc->drawNumber(col3 + 12, line2, CFN_PLAY_REPEAT(cfn) * CFN_PLAY_REPEAT_MUL, COLOR_THEME_SECONDARY1 | RIGHT, 0, nullptr, "s"); diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 84e74307cfc..4f0b20a92b8 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -73,7 +73,7 @@ #define CFN_CH_INDEX(p) ((p)->all.param) #define CFN_GVAR_INDEX(p) ((p)->all.param) #define CFN_TIMER_INDEX(p) ((p)->all.param) -#define CFN_PLAY_REPEAT(p) ((p)->active) +#define CFN_PLAY_REPEAT(p) ((p)->repeat) #define CFN_PLAY_REPEAT_MUL 1 #define CFN_PLAY_REPEAT_NOSTART 0xFF #define CFN_GVAR_MODE(p) ((p)->all.mode) diff --git a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp index da804d3f291..c99b7e9954a 100644 --- a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp @@ -1463,16 +1463,17 @@ static void r_customFn(void* user, uint8_t* data, uint32_t bitoffs, val++; val_len--; } - if (HAS_ENABLE_PARAM(func)) { - // "0/1" - if (val_len > 0) { - if (val[0] == '0') { - CFN_ACTIVE(cfn) = 0; - } else if (val[0] == '1') { - CFN_ACTIVE(cfn) = 1; - } + + // "0/1" + if (val_len > 0) { + if (val[0] == '0') { + CFN_ACTIVE(cfn) = 0; + } else if (val[0] == '1') { + CFN_ACTIVE(cfn) = 1; } - } else if (HAS_REPEAT_PARAM(func)) { + } + + if (HAS_REPEAT_PARAM(func)) { if (val_len == 2 && val[0] == '1' && val[1] == 'x') { @@ -1481,7 +1482,7 @@ static void r_customFn(void* user, uint8_t* data, uint32_t bitoffs, && val[0] == '!' && val[1] == '1' && val[2] == 'x') { - CFN_PLAY_REPEAT(cfn) = CFN_PLAY_REPEAT_NOSTART; + CFN_PLAY_REPEAT(cfn) = (int8_t)CFN_PLAY_REPEAT_NOSTART; } else { // repeat time in seconds CFN_PLAY_REPEAT(cfn) = yaml_str2uint(val,val_len) / CFN_PLAY_REPEAT_MUL; @@ -1613,14 +1614,15 @@ static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, break; } - if (HAS_ENABLE_PARAM(func)) { - if (add_comma) { - // "," - if (!wf(opaque,",",1)) return false; - } - // "0/1" - if (!wf(opaque,CFN_ACTIVE(cfn) ? "1":"0",1)) return false; - } else if (HAS_REPEAT_PARAM(func)) { + + if (add_comma) { + // "," + if (!wf(opaque,",",1)) return false; + } + // "0/1" + if (!wf(opaque,CFN_ACTIVE(cfn) ? "1":"0",1)) return false; + + if (HAS_REPEAT_PARAM(func)) { if (add_comma) { // "," if (!wf(opaque,",",1)) return false; @@ -1628,7 +1630,7 @@ static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, if (CFN_PLAY_REPEAT(cfn) == 0) { // "1x" if (!wf(opaque,"1x",2)) return false; - } else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { + } else if (CFN_PLAY_REPEAT(cfn) == (int8_t)CFN_PLAY_REPEAT_NOSTART) { // "!1x" if (!wf(opaque,"!1x",3)) return false; } else { From a68d2c9531cd6bfc2bf11446dffaca2f2d9b5ec9 Mon Sep 17 00:00:00 2001 From: eshifri Date: Mon, 16 Jan 2023 22:14:40 -0800 Subject: [PATCH 04/14] Init enabled to 1 --- radio/src/gui/128x64/model_special_functions.cpp | 1 + radio/src/gui/212x64/model_special_functions.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/radio/src/gui/128x64/model_special_functions.cpp b/radio/src/gui/128x64/model_special_functions.cpp index 0f1fca258fe..89a2f5e2218 100644 --- a/radio/src/gui/128x64/model_special_functions.cpp +++ b/radio/src/gui/128x64/model_special_functions.cpp @@ -188,6 +188,7 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF switch (j) { case 0: if (sub==k && menuHorizontalPosition < 1 && CFN_SWITCH(cfn) == SWSRC_NONE) { + CFN_ACTIVE(cfn) = 1; drawSwitch(MODEL_SPECIAL_FUNC_1ST_COLUMN, y, CFN_SWITCH(cfn), attr | INVERS | ((functionsContext->activeSwitches & ((MASK_CFN_TYPE)1 << k)) ? BOLD : 0)); if (active) CHECK_INCDEC_SWITCH(event, CFN_SWITCH(cfn), SWSRC_FIRST, SWSRC_LAST, eeFlags, isSwitchAvailableInCustomFunctions); } diff --git a/radio/src/gui/212x64/model_special_functions.cpp b/radio/src/gui/212x64/model_special_functions.cpp index 121bf7f4ea7..0478af57707 100644 --- a/radio/src/gui/212x64/model_special_functions.cpp +++ b/radio/src/gui/212x64/model_special_functions.cpp @@ -183,6 +183,7 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF uint8_t active = (attr && s_editMode>0); switch (j) { case ITEM_CUSTOM_FUNCTIONS_SWITCH: + if(CFN_SWITCH(cfn) == SWSRC_NONE) CFN_ACTIVE(cfn) = 1; drawSwitch(MODEL_SPECIAL_FUNC_1ST_COLUMN, y, CFN_SWITCH(cfn), attr | ((functionsContext->activeSwitches & ((MASK_CFN_TYPE)1 << k)) ? BOLD : 0)); if (active || AUTOSWITCH_ENTER_LONG()) CHECK_INCDEC_SWITCH(event, CFN_SWITCH(cfn), SWSRC_FIRST, SWSRC_LAST, eeFlags, isSwitchAvailableInCustomFunctions); if (func == FUNC_OVERRIDE_CHANNEL && functions != g_model.customFn) { From 6500ffb52665ddb3f76fa076d1e327b2c16671c3 Mon Sep 17 00:00:00 2001 From: eshifri Date: Wed, 18 Jan 2023 19:44:53 -0800 Subject: [PATCH 05/14] delete extra file --- .../gui/colorlcd/special_functions+my_1.cpp | 827 ------------------ 1 file changed, 827 deletions(-) delete mode 100644 radio/src/gui/colorlcd/special_functions+my_1.cpp diff --git a/radio/src/gui/colorlcd/special_functions+my_1.cpp b/radio/src/gui/colorlcd/special_functions+my_1.cpp deleted file mode 100644 index 67b2f3b53d4..00000000000 --- a/radio/src/gui/colorlcd/special_functions+my_1.cpp +++ /dev/null @@ -1,827 +0,0 @@ -/* - * 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 "special_functions.h" -#include "opentx.h" -#include "libopenui.h" -#include "view_main.h" - -#define SET_DIRTY() storageDirty(functions == g_model.customFn ? EE_MODEL : EE_GENERAL) - -static const lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2), - LV_GRID_TEMPLATE_LAST}; -static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_CONTENT, - LV_GRID_TEMPLATE_LAST}; - -class SpecialFunctionEditPage : public Page -{ - public: - SpecialFunctionEditPage(CustomFunctionData *functions, uint8_t index) : - Page(functions == g_model.customFn ? ICON_MODEL_SPECIAL_FUNCTIONS - : ICON_RADIO_GLOBAL_FUNCTIONS), - functions(functions), - index(index) - { - buildHeader(&header); - buildBody(&body); - lv_obj_set_style_max_height(body.getLvObj(), LCD_H - header.height(), 0); - lv_obj_set_style_max_width(body.getLvObj(), LCD_W, 0); - } - - protected: - CustomFunctionData *functions; - uint8_t index; - FormGroup *specialFunctionOneWindow = nullptr; - StaticText *headerSF = nullptr; - bool active = false; - - bool isActive() const - { - return ((functions == g_model.customFn - ? modelFunctionsContext.activeSwitches - : globalFunctionsContext.activeSwitches) & - ((MASK_CFN_TYPE)1 << index) - ? 1 - : 0); - } - - void checkEvents() override - { - Page::checkEvents(); - if (active != isActive()) { - if(isActive()) { - lv_obj_add_state(headerSF->getLvObj(), LV_STATE_USER_1); - } else { - lv_obj_clear_state(headerSF->getLvObj(), LV_STATE_USER_1); - } - active = isActive(); - invalidate(); - } - } - - void buildHeader(Window *window) - { - new StaticText( - window, {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, 20}, - functions == g_model.customFn ? STR_MENUCUSTOMFUNC - : STR_MENUSPECIALFUNCS, - 0, COLOR_THEME_PRIMARY2); - headerSF = new StaticText( - window, - {PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT, - LCD_W - PAGE_TITLE_LEFT, 20}, - (functions == g_model.customFn ? "SF" : "GF") + std::to_string(index+1), - 0, COLOR_THEME_PRIMARY2); - - lv_obj_set_style_text_color(headerSF->getLvObj(), makeLvColor(COLOR_THEME_ACTIVE), LV_STATE_USER_1); - lv_obj_set_style_text_font(headerSF->getLvObj(), getFont(FONT(BOLD)), LV_STATE_USER_1); - } - - void updateSpecialFunctionOneWindow() - { - specialFunctionOneWindow->clear(); - specialFunctionOneWindow->setFlexLayout(); - FlexGridLayout grid(col_dsc, row_dsc, 2); - auto line = specialFunctionOneWindow->newLine(&grid); - - CustomFunctionData *cfn = &functions[index]; - uint8_t func = CFN_FUNC(cfn); - - // Func param - switch (func) { - case FUNC_OVERRIDE_CHANNEL: { - new StaticText(line, rect_t{}, STR_CH, 0, COLOR_THEME_PRIMARY1); - new NumberEdit(line, rect_t{}, 1, - MAX_OUTPUT_CHANNELS, - GET_SET_VALUE_WITH_OFFSET(CFN_CH_INDEX(cfn), 1)); - line = specialFunctionOneWindow->newLine(&grid); - - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - int limit = (g_model.extendedLimits ? LIMIT_EXT_PERCENT : LIMIT_STD_PERCENT); - new NumberEdit(line, rect_t{}, -limit, limit, - GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - } - - case FUNC_TRAINER: { - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - auto choice = - new Choice(line, rect_t{}, 0, - NUM_STICKS + 1, GET_SET_DEFAULT(CFN_CH_INDEX(cfn))); - choice->setTextHandler([=](int32_t value) { - if (value == 0) - return std::string(STR_STICKS); - else if (value == NUM_STICKS + 1) - return std::string(STR_CHANS); - else - return TEXT_AT_INDEX(STR_VSRCRAW, value); - ; - }); - line = specialFunctionOneWindow->newLine(&grid); - break; - } - - case FUNC_RESET: - if (CFN_PARAM(cfn) < FUNC_RESET_PARAM_FIRST_TELEM) { - new StaticText(line, rect_t{}, STR_RESET, 0, COLOR_THEME_PRIMARY1); - auto choice = new Choice( - line, rect_t{}, 0, - FUNC_RESET_PARAM_FIRST_TELEM + lastUsedTelemetryIndex(), - GET_SET_DEFAULT(CFN_PARAM(cfn))); - choice->setAvailableHandler(isSourceAvailableInResetSpecialFunction); - choice->setTextHandler([=](int32_t value) { - if (value < FUNC_RESET_PARAM_FIRST_TELEM) - return TEXT_AT_INDEX(STR_VFSWRESET, value); - else - return std::string( - g_model.telemetrySensors[value - FUNC_RESET_PARAM_FIRST_TELEM] - .label, - TELEM_LABEL_LEN); - }); - line = specialFunctionOneWindow->newLine(&grid); - } - break; - - case FUNC_VOLUME: - new StaticText(line, rect_t{}, STR_VOLUME, 0, COLOR_THEME_PRIMARY1); - new SourceChoice(line, rect_t{}, 0, - MIXSRC_LAST_CH, GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - - case FUNC_BACKLIGHT: - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - new SourceChoice(line, rect_t{}, 0, - MIXSRC_LAST_CH, GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - - case FUNC_PLAY_SOUND: - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - new Choice(line, rect_t{}, - STR_FUNCSOUNDS, 0, - AU_SPECIAL_SOUND_LAST - AU_SPECIAL_SOUND_FIRST - 1, - GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - - case FUNC_PLAY_TRACK: - case FUNC_BACKGND_MUSIC: - case FUNC_PLAY_SCRIPT: - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - new FileChoice( - line, rect_t{}, - func == FUNC_PLAY_SCRIPT - ? SCRIPTS_FUNCS_PATH - : std::string(SOUNDS_PATH, SOUNDS_PATH_LNG_OFS) + - std::string(currentLanguagePack->id, 2), - func == FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, - sizeof(cfn->play.name), - [=]() { return std::string(cfn->play.name, ZLEN(cfn->play.name)); }, - [=](std::string newValue) { - strncpy(cfn->play.name, newValue.c_str(), sizeof(cfn->play.name)); - SET_DIRTY(); - LUA_LOAD_MODEL_SCRIPTS(); - }, - true); // strip extension - line = specialFunctionOneWindow->newLine(&grid); - break; - - case FUNC_SET_TIMER: { - new StaticText(line, rect_t{}, STR_TIMER, 0, COLOR_THEME_PRIMARY1); - auto timerchoice = - new Choice(line, rect_t{}, 0, - TIMERS - 1, GET_SET_DEFAULT(CFN_TIMER_INDEX(cfn))); - timerchoice->setTextHandler([](int32_t value) { - return std::string(STR_TIMER) + std::to_string(value + 1); - }); - line = specialFunctionOneWindow->newLine(&grid); - - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - new TimeEdit(line, rect_t{}, 0, - 9 * 60 * 60 - 1, GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - } - - case FUNC_SET_FAILSAFE: - new StaticText(line, rect_t{}, STR_MODULE, 0, COLOR_THEME_PRIMARY1); - new Choice(line, rect_t{}, - "\004Int.Ext.", 0, NUM_MODULES - 1, - GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - - case FUNC_PLAY_VALUE: - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - new SourceChoice(line, rect_t{}, 0, - MIXSRC_LAST_TELEM, GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - - case FUNC_HAPTIC: - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - new NumberEdit(line, rect_t{}, 0, 3, - GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - - case FUNC_LOGS: { - new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); - auto edit = - new NumberEdit(line, rect_t{}, 0, - 255, GET_SET_DEFAULT(CFN_PARAM(cfn))); - edit->setDisplayHandler( - [=](int32_t value) { - return formatNumberAsString(CFN_PARAM(cfn), PREC1, sizeof(CFN_PARAM(cfn)), nullptr, "s"); - }); - break; - } - - case FUNC_SET_SCREEN: - new StaticText(line, rect_t{}, STR_VALUE, - 0, COLOR_THEME_PRIMARY1); - CFN_PARAM(cfn) = (int16_t)max(CFN_PARAM(cfn), (int16_t)1); - CFN_PARAM(cfn) = (int16_t)min( - CFN_PARAM(cfn), (int16_t)ViewMain::instance()->getMainViewsCount()); - new NumberEdit(line, rect_t{}, 1, - ViewMain::instance()->getMainViewsCount(), - GET_SET_DEFAULT(CFN_PARAM(cfn))); - line = specialFunctionOneWindow->newLine(&grid); - break; - - case FUNC_ADJUST_GVAR: { - new StaticText(line, rect_t{}, STR_GLOBALVAR, 0, COLOR_THEME_PRIMARY1); - auto gvarchoice = - new Choice(line, rect_t{}, 0, - MAX_GVARS - 1, GET_SET_DEFAULT(CFN_GVAR_INDEX(cfn))); - gvarchoice->setTextHandler([](int32_t value) { - return std::string(STR_GV) + std::to_string(value + 1); - }); - line = specialFunctionOneWindow->newLine(&grid); - - new StaticText(line, rect_t{}, STR_MODE, 0, COLOR_THEME_PRIMARY1); - auto modechoice = - new Choice(line, rect_t{}, - FUNC_ADJUST_GVAR_CONSTANT, FUNC_ADJUST_GVAR_INCDEC, - GET_DEFAULT(CFN_GVAR_MODE(cfn)), nullptr); - line = specialFunctionOneWindow->newLine(&grid); - - modechoice->setTextHandler([](int32_t value) { - switch (value) { - case FUNC_ADJUST_GVAR_CONSTANT: - return std::string(STR_CONSTANT); - case FUNC_ADJUST_GVAR_SOURCE: - return std::string(STR_MIXSOURCE); - case FUNC_ADJUST_GVAR_GVAR: - return std::string(STR_GLOBALVAR); - case FUNC_ADJUST_GVAR_INCDEC: - return std::string(STR_INCDEC); - } - return std::string("---"); - }); - - modechoice->setSetValueHandler([=](int32_t newValue) { - CFN_GVAR_MODE(cfn) = newValue; - CFN_PARAM(cfn) = 0; - SET_DIRTY(); - updateSpecialFunctionOneWindow(); - }); - - switch (CFN_GVAR_MODE(cfn)) { - case FUNC_ADJUST_GVAR_CONSTANT: { - new StaticText(line, rect_t{}, STR_CONSTANT, 0, COLOR_THEME_PRIMARY1); - int16_t val_min, val_max; - getMixSrcRange(CFN_GVAR_INDEX(cfn) + MIXSRC_FIRST_GVAR, val_min, - val_max); - new NumberEdit(line, rect_t{}, - val_min, val_max, GET_SET_DEFAULT(CFN_PARAM(cfn))); - break; - } - case FUNC_ADJUST_GVAR_SOURCE: - new StaticText(line, rect_t{}, STR_MIXSOURCE, 0, COLOR_THEME_PRIMARY1); - new SourceChoice(line, rect_t{}, - 0, MIXSRC_LAST_CH, GET_SET_DEFAULT(CFN_PARAM(cfn))); - break; - case FUNC_ADJUST_GVAR_GVAR: { - new StaticText(line, rect_t{}, STR_GLOBALVAR, 0, COLOR_THEME_PRIMARY1); - auto gvarchoice = - new Choice(line, rect_t{}, 0, - MAX_GVARS - 1, GET_SET_DEFAULT(CFN_PARAM(cfn))); - gvarchoice->setTextHandler([](int32_t value) { - return std::string(STR_GV) + std::to_string(value + 1); - }); - gvarchoice->setAvailableHandler([=](int value) { - return CFN_GVAR_INDEX(cfn) != value; - }); - break; - } - case FUNC_ADJUST_GVAR_INCDEC: { - new StaticText(line, rect_t{}, STR_INCDEC, 0, COLOR_THEME_PRIMARY1); - int16_t val_min, val_max; - getMixSrcRange(CFN_GVAR_INDEX(cfn) + MIXSRC_FIRST_GVAR, val_min, val_max); - getGVarIncDecRange(val_min, val_max); - auto numedit = new NumberEdit(line, rect_t{}, val_min, val_max, - GET_SET_DEFAULT(CFN_PARAM(cfn))); - numedit->setDisplayHandler( - [](int value) { - return formatNumberAsString(abs(value), 0, 0, value >= 0 ? "+=" : "--", nullptr); - }); - break; - } - } - line = specialFunctionOneWindow->newLine(&grid); - } - } - - if (HAS_REPEAT_PARAM(func)) { // !1x 1x 1s 2s 3s ... - new StaticText(line, rect_t{}, STR_REPEAT, 0, COLOR_THEME_PRIMARY1); - auto repeat = new NumberEdit( - line, rect_t{}, -1, 60 / CFN_PLAY_REPEAT_MUL, - [=]() -> int { - int8_t val = int8_t(uint8_t(CFN_PLAY_REPEAT(cfn)) & int8_t(~CFN_ENABLE_MASK)); - TRACE("Get: %d", val); - if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART || - int8_t(CFN_PLAY_REPEAT(cfn)) == int8_t(CFN_PLAY_REPEAT_NOSTART & (~CFN_ENABLE_MASK))) - val = CFN_PLAY_REPEAT_NOSTART; - - TRACE("Get 2: %d\t%d", val, int8_t(CFN_PLAY_REPEAT(cfn))); - return int(val); - }, - [=](int value) { - if(CFN_ACTIVE(cfn) & CFN_ENABLE_MASK) { - CFN_ACTIVE(cfn) = value | CFN_ENABLE_MASK; - } else { - CFN_ACTIVE(cfn) = value & ~CFN_ENABLE_MASK; - } - TRACE("Set %d\t%d", value, int8_t(CFN_ACTIVE(cfn))); - }); - // GET_DEFAULT((int8_t)CFN_PLAY_REPEAT(cfn)), - // SET_DEFAULT(CFN_PLAY_REPEAT(cfn))); - repeat->setDisplayHandler([=](int32_t value) { - if (value == 0) - return std::string("1x"); - else if (value == CFN_PLAY_REPEAT_NOSTART || - CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART & (~CFN_ENABLE_MASK)) - return std::string("!1x"); - else { - return formatNumberAsString( - (value & ~CFN_ENABLE_MASK) * CFN_PLAY_REPEAT_MUL, 0, 0, nullptr, - "s"); - } - }); - line = specialFunctionOneWindow->newLine(&grid); - } - - { - new StaticText(line, rect_t{}, STR_ENABLE, 0, COLOR_THEME_PRIMARY1); - new CheckBox( - line, rect_t{}, // GET_SET_DEFAULT(CFN_ACTIVE(cfn)) - [=]() -> uint8_t { - return (CFN_ACTIVE(cfn) & int8_t(CFN_ENABLE_MASK)) ? 1 : 0; - }, - [=](uint8_t value) { - if(value) - CFN_ACTIVE(cfn) |= uint8_t(CFN_ENABLE_MASK); - else - CFN_ACTIVE(cfn) &= uint8_t(~CFN_ENABLE_MASK); - }); - line = specialFunctionOneWindow->newLine(&grid); - } - } - - void buildBody(FormWindow *window) - { - window->setFlexLayout(); - FlexGridLayout grid(col_dsc, row_dsc, 2); - lv_obj_set_style_pad_all(window->getLvObj(), lv_dpx(8), 0); - - CustomFunctionData *cfn = &functions[index]; - - // Set new function to "enabled" by default - if (!CFN_SWITCH(cfn)) - CFN_ACTIVE(cfn) |= 0x40; - - // Switch - auto line = window->newLine(&grid); - new StaticText(line, rect_t{}, STR_SWITCH, 0, COLOR_THEME_PRIMARY1); - auto switchChoice = - new SwitchChoice(line, rect_t{}, SWSRC_FIRST, SWSRC_LAST, - GET_SET_DEFAULT(CFN_SWITCH(cfn))); - switchChoice->setAvailableHandler([=](int value) { - return (functions == g_model.customFn - ? isSwitchAvailable(value, ModelCustomFunctionsContext) - : isSwitchAvailable(value, GeneralCustomFunctionsContext)); - }); - line = window->newLine(&grid); - - // Patch function in case not available - if (!isAssignableFunctionAvailable(CFN_FUNC(cfn), functions)) { - auto func = 0; - while(!isAssignableFunctionAvailable(func, functions) - && (func < FUNC_MAX - 1)) { - func++; - } - if (func < FUNC_MAX - 1) { - CFN_FUNC(cfn) = func; - } - } - - // Function - new StaticText(line, rect_t{}, STR_FUNC, 0, COLOR_THEME_PRIMARY1); - auto functionChoice = - new Choice(line, rect_t{}, STR_VFSWFUNC, - 0, FUNC_MAX - 1, - GET_DEFAULT(CFN_FUNC(cfn))); - functionChoice->setSetValueHandler([=](int32_t newValue) { - int enableState = CFN_ACTIVE(cfn); - CFN_FUNC(cfn) = newValue; - CFN_RESET(cfn); - CFN_ACTIVE(cfn) = enableState; - SET_DIRTY(); - updateSpecialFunctionOneWindow(); - }); - functionChoice->setAvailableHandler([=](int value) { - return isAssignableFunctionAvailable(value, functions); - }); - line = window->newLine(&grid); - - specialFunctionOneWindow = new FormGroup(window, rect_t{}); - updateSpecialFunctionOneWindow(); - } -}; - -static constexpr coord_t line1 = FIELD_PADDING_TOP; -static constexpr coord_t line2 = line1 + PAGE_LINE_HEIGHT; -static constexpr coord_t col1 = 20; -static constexpr coord_t col2 = (LCD_W - 100) / 3 + col1; -static constexpr coord_t col3 = ((LCD_W - 100) / 3) * 2 + col1 + 20; -// Special location used only for "enabled" check-box -static constexpr coord_t col4 = (LCD_W - 100); - -static const char* _failsafe_module[] = { - "Ext.", "Int.", -}; - -class SpecialFunctionButton : public Button -{ - public: - SpecialFunctionButton(FormWindow *parent, const rect_t &rect, - CustomFunctionData *functions, uint8_t index) : - Button(parent, rect), functions(functions), index(index) - { - const CustomFunctionData *cfn = &functions[index]; - uint8_t func = CFN_FUNC(cfn); - if (!cfn->isEmpty() && - (HAS_ENABLE_PARAM(func) || HAS_REPEAT_PARAM(func) || - (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || - func == FUNC_PLAY_SCRIPT))) { - setHeight(line2 + PAGE_LINE_HEIGHT); - } - } - -#if defined(DEBUG_WINDOWS) - std::string getName() const override { return "SpecialFunctionButton"; } -#endif - - bool isActive() const - { - return ((functions == g_model.customFn - ? modelFunctionsContext.activeSwitches - : globalFunctionsContext.activeSwitches) & - ((MASK_CFN_TYPE)1 << index) - ? 1 - : 0); - } - - void checkEvents() override - { - Button::checkEvents(); - if (active != isActive()) { - invalidate(); - active = !active; - } - } - - void paintSpecialFunctionLine(BitmapBuffer *dc) - { - const CustomFunctionData *cfn = &functions[index]; - if (functions[index].func == FUNC_OVERRIDE_CHANNEL && - functions != g_model.customFn) { - functions[index].func = FUNC_OVERRIDE_CHANNEL + 1; - } - uint8_t func = CFN_FUNC(cfn); - - drawSwitch(dc, col1, line1, CFN_SWITCH(cfn), COLOR_THEME_SECONDARY1); - if (cfn->isEmpty()) return; - - dc->drawTextAtIndex(col2, line1, STR_VFSWFUNC, func, COLOR_THEME_SECONDARY1); - int16_t val_min = 0; - int16_t val_max = 255; - - switch (func) { - case FUNC_OVERRIDE_CHANNEL: - drawChn(dc, col1, line2, CFN_CH_INDEX(cfn) + 1, COLOR_THEME_SECONDARY1); - getMixSrcRange(MIXSRC_FIRST_CH, val_min, val_max); - dc->drawNumber(col2, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - - case FUNC_TRAINER: { - std::string text; - int16_t value = CFN_CH_INDEX(cfn); - if (value == 0) - text = std::string(STR_STICKS); - else if (value == NUM_STICKS + 1) - text = std::string(STR_CHANS); - else - text = TEXT_AT_INDEX(STR_VSRCRAW, value); - dc->drawText(col1, line2, text.c_str(), COLOR_THEME_SECONDARY1); - break; - } - case FUNC_RESET: - if (CFN_PARAM(cfn) < FUNC_RESET_PARAM_FIRST_TELEM) { - dc->drawTextAtIndex(col1, line2, STR_VFSWRESET, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - } else { - TelemetrySensor *sensor = - &g_model.telemetrySensors[CFN_PARAM(cfn) - - FUNC_RESET_PARAM_FIRST_TELEM]; - dc->drawSizedText(col1, line2, sensor->label, TELEM_LABEL_LEN, COLOR_THEME_SECONDARY1); - } - break; - - case FUNC_VOLUME: - drawSource(dc, col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - - case FUNC_BACKLIGHT: - drawSource(dc, col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - - case FUNC_PLAY_SOUND: - dc->drawTextAtIndex(col1, line2, STR_FUNCSOUNDS, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - - case FUNC_PLAY_TRACK: - case FUNC_BACKGND_MUSIC: - case FUNC_PLAY_SCRIPT: - if (ZEXIST(cfn->play.name)) - dc->drawSizedText(col1, line2, cfn->play.name, sizeof(cfn->play.name), COLOR_THEME_SECONDARY1); - else - dc->drawTextAtIndex(col1, line2, STR_VCSWFUNC, 0, COLOR_THEME_SECONDARY1); - break; - - case FUNC_SET_TIMER: - drawStringWithIndex(dc, col1, line2, STR_TIMER, CFN_TIMER_INDEX(cfn) + 1, COLOR_THEME_SECONDARY1); - break; - - case FUNC_SET_FAILSAFE: - dc->drawTextAtIndex(col1, line2, _failsafe_module, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - - case FUNC_PLAY_VALUE: - drawSource(dc, col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - - case FUNC_HAPTIC: - dc->drawNumber(col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - - case FUNC_LOGS: - dc->drawNumber(col3, line1, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1 | PREC1, sizeof(CFN_PARAM(cfn)), nullptr, "s"); - break; - - case FUNC_SET_SCREEN: - dc->drawNumber(col2, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - - case FUNC_ADJUST_GVAR: - switch(CFN_GVAR_MODE(cfn)) { - case FUNC_ADJUST_GVAR_CONSTANT: - dc->drawNumber(col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - case FUNC_ADJUST_GVAR_SOURCE: - drawSource(dc, col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); - break; - case FUNC_ADJUST_GVAR_GVAR: - drawSource(dc, col1, line2, CFN_PARAM(cfn) + MIXSRC_FIRST_GVAR, COLOR_THEME_SECONDARY1); - break; - case FUNC_ADJUST_GVAR_INCDEC: { - int16_t value = CFN_PARAM(cfn); - std::string text(value >= 0 ? "+= " : "-= "); - text += std::to_string(abs(value)); - dc->drawText(col1, line2, text.c_str(), COLOR_THEME_SECONDARY1); - break; - } - } - } - - theme->drawCheckBox(dc, CFN_ACTIVE(cfn) & CFN_ENABLE_MASK, col4, line2); - - if (HAS_REPEAT_PARAM(func)) { - if (CFN_PLAY_REPEAT(cfn) == 0) { - dc->drawText(col3, line2, "1x", COLOR_THEME_SECONDARY1); - } else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART || - CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART & - (~CFN_ENABLE_MASK)) { - dc->drawText(col3, line2, "!1x", COLOR_THEME_SECONDARY1); - } else { - dc->drawNumber(col3 + 12, line2, CFN_PLAY_REPEAT(cfn) * CFN_PLAY_REPEAT_MUL, COLOR_THEME_SECONDARY1 | RIGHT, 0, nullptr, "s"); - } - } - } - - void paint(BitmapBuffer *dc) override - { - const CustomFunctionData *cfn = &functions[index]; - - if (CFN_ACTIVE(cfn) & CFN_ENABLE_MASK) { - if (active) - dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_ACTIVE); - else - dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_PRIMARY2); - } else { - dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_SECONDARY3); - } - paintSpecialFunctionLine(dc); - - // The bounding rect - if (hasFocus()) - dc->drawSolidRect(0, 0, rect.w, rect.h, 2, COLOR_THEME_FOCUS); - else - dc->drawSolidRect(0, 0, rect.w, rect.h, 1, COLOR_THEME_SECONDARY2); - } - - protected: - CustomFunctionData *functions; - uint8_t index; - bool active = false; -}; - -SpecialFunctionsPage::SpecialFunctionsPage(CustomFunctionData *functions) : - PageTab(functions == g_model.customFn ? STR_MENUCUSTOMFUNC - : STR_MENUSPECIALFUNCS, - functions == g_model.customFn ? ICON_MODEL_SPECIAL_FUNCTIONS - : ICON_RADIO_GLOBAL_FUNCTIONS), - functions(functions) -{ -} - -void SpecialFunctionsPage::rebuild(FormWindow *window, - int8_t focusSpecialFunctionIndex) -{ - auto scroll_y = lv_obj_get_scroll_y(window->getLvObj()); - window->clear(); - build(window, focusSpecialFunctionIndex); - lv_obj_scroll_to_y(window->getLvObj(), scroll_y, LV_ANIM_OFF); -} - -void SpecialFunctionsPage::editSpecialFunction(FormWindow *window, - uint8_t index) -{ - auto editPage = new SpecialFunctionEditPage(functions, index); - editPage->setCloseHandler([=]() { rebuild(window, index); }); -} - -void SpecialFunctionsPage::build(FormWindow *window, int8_t focusIndex) -{ - FormGridLayout grid; - grid.spacer(PAGE_PADDING); - grid.setLabelWidth(66); - window->padAll(0); - - // Window::clearFocus(); - - char s[] = "SFxx"; - if (functions == g_eeGeneral.customFn) s[0] = 'G'; - - for (uint8_t i = 0; i < MAX_SPECIAL_FUNCTIONS; i++) { - CustomFunctionData *cfn = &functions[i]; - strAppendUnsigned(&s[2], i+1); - - Button *button; - if (cfn->swtch == 0) { - button = new TextButton(window, grid.getLabelSlot(), s); - button->setPressHandler([=]() { - if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) { - Menu *menu = new Menu(window); - menu->addLine(STR_EDIT, [=]() { editSpecialFunction(window, i); }); - menu->addLine(STR_PASTE, [=]() { - *cfn = clipboard.data.cfn; - SET_DIRTY(); - if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) - LUA_LOAD_MODEL_SCRIPTS(); - rebuild(window, i); - }); - } else { - editSpecialFunction(window, i); - } - return 0; - }); - grid.spacer(button->height() + 5); - } else { - auto txt = new StaticText(window, grid.getLabelSlot(), s, BUTTON_BACKGROUND, COLOR_THEME_PRIMARY1 | CENTERED); - - button = new SpecialFunctionButton(window, grid.getFieldSlot(), functions, i); - button->setPressHandler([=]() { - button->bringToTop(); - Menu *menu = new Menu(window); - menu->addLine(STR_EDIT, [=]() { editSpecialFunction(window, i); }); - menu->addLine(STR_COPY, [=]() { - clipboard.type = CLIPBOARD_TYPE_CUSTOM_FUNCTION; - clipboard.data.cfn = *cfn; - }); - if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) { - menu->addLine(STR_PASTE, [=]() { - if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) - LUA_LOAD_MODEL_SCRIPTS(); - *cfn = clipboard.data.cfn; - if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) - LUA_LOAD_MODEL_SCRIPTS(); - SET_DIRTY(); - rebuild(window, i); - }); - } - if (functions[MAX_SPECIAL_FUNCTIONS - 1].isEmpty()) { - menu->addLine(STR_INSERT, [=]() { - memmove( - cfn + 1, cfn, - (MAX_SPECIAL_FUNCTIONS - i - 1) * sizeof(CustomFunctionData)); - memset(cfn, 0, sizeof(CustomFunctionData)); - SET_DIRTY(); - rebuild(window, i); - }); - } - menu->addLine(STR_CLEAR, [=]() { - if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) - LUA_LOAD_MODEL_SCRIPTS(); - memset(cfn, 0, sizeof(CustomFunctionData)); - SET_DIRTY(); - rebuild(window, i); - }); - for (int j = i; j < MAX_SPECIAL_FUNCTIONS; j++) { - if (!functions[j].isEmpty()) { - menu->addLine(STR_DELETE, [=]() { - if (CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) - LUA_LOAD_MODEL_SCRIPTS(); - memmove( - cfn, cfn + 1, - (MAX_SPECIAL_FUNCTIONS - i - 1) * sizeof(CustomFunctionData)); - memset(&functions[MAX_SPECIAL_FUNCTIONS - 1], 0, - sizeof(CustomFunctionData)); - SET_DIRTY(); - rebuild(window, i); - }); - break; - } - } - return 0; - }); - 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 (focusIndex == i) { - // txt->setBackgroundColor(COLOR_THEME_FOCUS); - // txt->setTextFlags(COLOR_THEME_PRIMARY2 | CENTERED); - // txt->invalidate(); - // } - - txt->setHeight(button->height()); - grid.spacer(button->height() + 5); - } - - // if (focusIndex == i) { // fix focus #303 - // button->setFocus(SET_FOCUS_DEFAULT); - // } - } - - grid.nextLine(); - - // window->setLastField(); -} From f0d8751efef88c053be9abb820f223b015e797e3 Mon Sep 17 00:00:00 2001 From: eshifri Date: Wed, 18 Jan 2023 23:06:29 -0800 Subject: [PATCH 06/14] Fix Save/Read operations --- .../src/firmwares/customfunctiondata.cpp | 14 +++------- .../edgetx/yaml_customfunctiondata.cpp | 12 +++++---- companion/src/modeledit/customfunctions.cpp | 5 +--- .../storage/yaml/yaml_datastructs_funcs.cpp | 27 +++++++++++++------ 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/companion/src/firmwares/customfunctiondata.cpp b/companion/src/firmwares/customfunctiondata.cpp index 4d3ef911280..876b69894e0 100644 --- a/companion/src/firmwares/customfunctiondata.cpp +++ b/companion/src/firmwares/customfunctiondata.cpp @@ -199,18 +199,10 @@ QString CustomFunctionData::repeatToString(const int value) QString CustomFunctionData::enabledToString() const { - if ((func >= FuncOverrideCH1 && func <= FuncOverrideCHLast) || - (func >= FuncAdjustGV1 && func <= FuncAdjustGVLast) || - (func == FuncReset) || - (func >= FuncSetTimer1 && func <= FuncSetTimerLast) || - (func == FuncVolume) || - (func == FuncBacklight) || - (func <= FuncInstantTrim)) { - if (!enabled) { - return tr("DISABLED"); - } + if (!enabled) { + return tr("DISABLED"); } - return ""; + return "ENABLED"; } // static diff --git a/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp b/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp index 6cc8e6d9e73..e268cc1b71d 100644 --- a/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp +++ b/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp @@ -216,15 +216,15 @@ Node convert::encode(const CustomFunctionData& rhs) break; } - if (add_comma) { def += ","; } + def += std::to_string((int)rhs.enabled); + if(fnHasRepeat(rhs.func)) { - if (add_comma) { - def += ","; - } + def += ","; + if (rhs.repeatParam == 0) { def += "1x"; } else if (rhs.repeatParam == -1) { @@ -367,12 +367,14 @@ bool convert::decode(const Node& node, def.ignore(); } - int en = 0; def >> en; rhs.enabled = en; if(fnHasRepeat(rhs.func)) { + if (def.peek() == ',') { + def.ignore(); + } std::string repeat; getline(def, repeat); if (repeat == "1x") { diff --git a/companion/src/modeledit/customfunctions.cpp b/companion/src/modeledit/customfunctions.cpp index 84a58c74727..7135bdda78d 100644 --- a/companion/src/modeledit/customfunctions.cpp +++ b/companion/src/modeledit/customfunctions.cpp @@ -346,6 +346,7 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) else { fswtchSwtch[i]->setCurrentIndex(fswtchSwtch[i]->findData(cfn.swtch.toValue())); fswtchFunc[i]->setCurrentIndex(fswtchFunc[i]->findData(cfn.func)); + fswtchEnable[i]->setChecked(cfn.enabled); } if (!cfn.isEmpty()) { @@ -549,10 +550,6 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) fswtchParamT[i]->setVisible(widgetsMask & CUSTOM_FUNCTION_SOURCE_PARAM); fswtchParamArmT[i]->setVisible(widgetsMask & CUSTOM_FUNCTION_FILE_PARAM); fswtchEnable[i]->setVisible(widgetsMask & CUSTOM_FUNCTION_ENABLE); - if (widgetsMask & CUSTOM_FUNCTION_ENABLE) - fswtchEnable[i]->setChecked(cfn.enabled); - else - fswtchEnable[i]->setChecked(false); fswtchRepeat[i]->setVisible(widgetsMask & CUSTOM_FUNCTION_REPEAT); fswtchGVmode[i]->setVisible(widgetsMask & CUSTOM_FUNCTION_GV_MODE); playBT[i]->setVisible(widgetsMask & CUSTOM_FUNCTION_PLAY); diff --git a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp index c99b7e9954a..14ac0c1f743 100644 --- a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp @@ -1463,17 +1463,24 @@ static void r_customFn(void* user, uint8_t* data, uint32_t bitoffs, val++; val_len--; } - // "0/1" if (val_len > 0) { if (val[0] == '0') { - CFN_ACTIVE(cfn) = 0; + CFN_ACTIVE(cfn) = 0; } else if (val[0] == '1') { - CFN_ACTIVE(cfn) = 1; + CFN_ACTIVE(cfn) = 1; } + val += l_sep; + val_len -= l_sep; + + if (val_len == 0 || val[0] != ',') return; + + val++; val_len--; +// val++; val_len--; } - + if (HAS_REPEAT_PARAM(func)) { + TRACE("REPEAT: %c%c%c", val[0],val[1], val[2]); if (val_len == 2 && val[0] == '1' && val[1] == 'x') { @@ -1488,6 +1495,7 @@ static void r_customFn(void* user, uint8_t* data, uint32_t bitoffs, CFN_PLAY_REPEAT(cfn) = yaml_str2uint(val,val_len) / CFN_PLAY_REPEAT_MUL; } } + } static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, @@ -1614,13 +1622,13 @@ static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, break; } - if (add_comma) { - // "," - if (!wf(opaque,",",1)) return false; + // "," + if (!wf(opaque, ",", 1)) return false; } + // "0/1" - if (!wf(opaque,CFN_ACTIVE(cfn) ? "1":"0",1)) return false; + if (!wf(opaque, CFN_ACTIVE(cfn) ? "1" : "0", 1)) return false; if (HAS_REPEAT_PARAM(func)) { if (add_comma) { @@ -1638,7 +1646,10 @@ static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, str = yaml_unsigned2str(CFN_PLAY_REPEAT(cfn) * CFN_PLAY_REPEAT_MUL); if (!wf(opaque, str, strlen(str))) return false; } + // "," + if (!wf(opaque, ",", 1)) return false; } + if (!wf(opaque, "\"", 1)) return false; return true; } From fcfc8095fbd272db0d53c0e92a299a4ddeb1c5f2 Mon Sep 17 00:00:00 2001 From: eshifri Date: Thu, 19 Jan 2023 18:29:10 -0800 Subject: [PATCH 07/14] Cleanup and fix R/W --- .../storage/yaml/yaml_datastructs_funcs.cpp | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp index 14ac0c1f743..4c2c8eaa30d 100644 --- a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp @@ -1470,32 +1470,26 @@ static void r_customFn(void* user, uint8_t* data, uint32_t bitoffs, } else if (val[0] == '1') { CFN_ACTIVE(cfn) = 1; } + } else return; + + if (HAS_REPEAT_PARAM(func)) { + // eat_comma unconditionally val += l_sep; val_len -= l_sep; - if (val_len == 0 || val[0] != ',') return; - - val++; val_len--; -// val++; val_len--; - } - - if (HAS_REPEAT_PARAM(func)) { - TRACE("REPEAT: %c%c%c", val[0],val[1], val[2]); - if (val_len == 2 - && val[0] == '1' - && val[1] == 'x') { - CFN_PLAY_REPEAT(cfn) = 0; - } else if (val_len == 3 - && val[0] == '!' - && val[1] == '1' - && val[2] == 'x') { - CFN_PLAY_REPEAT(cfn) = (int8_t)CFN_PLAY_REPEAT_NOSTART; + val++; + val_len--; + + if (val_len == 2 && val[0] == '1' && val[1] == 'x') { + CFN_PLAY_REPEAT(cfn) = 0; + } else if (val_len == 3 && val[0] == '!' && val[1] == '1' && + val[2] == 'x') { + CFN_PLAY_REPEAT(cfn) = (int8_t)CFN_PLAY_REPEAT_NOSTART; } else { - // repeat time in seconds - CFN_PLAY_REPEAT(cfn) = yaml_str2uint(val,val_len) / CFN_PLAY_REPEAT_MUL; + // repeat time in seconds + CFN_PLAY_REPEAT(cfn) = yaml_str2uint(val, val_len) / CFN_PLAY_REPEAT_MUL; } } - } static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, @@ -1618,7 +1612,7 @@ static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, break; default: - add_comma = false; + //add_comma = false; break; } @@ -1631,10 +1625,7 @@ static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, if (!wf(opaque, CFN_ACTIVE(cfn) ? "1" : "0", 1)) return false; if (HAS_REPEAT_PARAM(func)) { - if (add_comma) { - // "," - if (!wf(opaque,",",1)) return false; - } + if (!wf(opaque,",",1)) return false; if (CFN_PLAY_REPEAT(cfn) == 0) { // "1x" if (!wf(opaque,"1x",2)) return false; @@ -1646,8 +1637,6 @@ static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, str = yaml_unsigned2str(CFN_PLAY_REPEAT(cfn) * CFN_PLAY_REPEAT_MUL); if (!wf(opaque, str, strlen(str))) return false; } - // "," - if (!wf(opaque, ",", 1)) return false; } if (!wf(opaque, "\"", 1)) return false; From 115f3ce1db930e23c6f4fc99a98233ef9e333998 Mon Sep 17 00:00:00 2001 From: eshifri Date: Sat, 21 Jan 2023 12:08:14 -0800 Subject: [PATCH 08/14] Cleanup based on review --- companion/src/firmwares/customfunctiondata.cpp | 2 +- companion/src/modeledit/customfunctions.cpp | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/companion/src/firmwares/customfunctiondata.cpp b/companion/src/firmwares/customfunctiondata.cpp index 876b69894e0..51a952a24c8 100644 --- a/companion/src/firmwares/customfunctiondata.cpp +++ b/companion/src/firmwares/customfunctiondata.cpp @@ -202,7 +202,7 @@ QString CustomFunctionData::enabledToString() const if (!enabled) { return tr("DISABLED"); } - return "ENABLED"; + return ""; } // static diff --git a/companion/src/modeledit/customfunctions.cpp b/companion/src/modeledit/customfunctions.cpp index 7135bdda78d..8b3ae3b1c26 100644 --- a/companion/src/modeledit/customfunctions.cpp +++ b/companion/src/modeledit/customfunctions.cpp @@ -363,7 +363,7 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) cfn.param = fswtchParam[i]->value(); } fswtchParam[i]->setValue(cfn.param); - widgetsMask |= CUSTOM_FUNCTION_NUMERIC_PARAM | CUSTOM_FUNCTION_ENABLE; + widgetsMask |= CUSTOM_FUNCTION_NUMERIC_PARAM; } } else if (func == FuncLogs) { @@ -381,7 +381,7 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) if (modified) cfn.adjustMode = fswtchGVmode[i]->currentData().toInt(); fswtchGVmode[i]->setCurrentIndex(fswtchGVmode[i]->findData(cfn.adjustMode)); - widgetsMask |= CUSTOM_FUNCTION_GV_MODE | CUSTOM_FUNCTION_ENABLE; + widgetsMask |= CUSTOM_FUNCTION_GV_MODE; if (cfn.adjustMode == FUNC_ADJUST_GVAR_CONSTANT || cfn.adjustMode == FUNC_ADJUST_GVAR_INCDEC) { if (modified) cfn.param = fswtchParam[i]->value() * model->gvarData[gvidx].multiplierSet(); @@ -412,7 +412,7 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) if (modified) cfn.param = fswtchParamT[i]->currentData().toInt(); populateFuncParamCB(fswtchParamT[i], func, cfn.param); - widgetsMask |= CUSTOM_FUNCTION_SOURCE_PARAM | CUSTOM_FUNCTION_ENABLE; + widgetsMask |= CUSTOM_FUNCTION_SOURCE_PARAM; } else if (func >= FuncSetTimer1 && func <= FuncSetTimer3) { if (modified) @@ -420,16 +420,13 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) RawSourceRange range = RawSource(SOURCE_TYPE_SPECIAL, func - FuncSetTimer1 + 2).getRange(model, generalSettings); fswtchParamTime[i]->setTimeRange((int)range.min, (int)range.max); fswtchParamTime[i]->setTime(cfn.param); - widgetsMask |= CUSTOM_FUNCTION_TIME_PARAM | CUSTOM_FUNCTION_ENABLE; - } - else if (func >= FuncSetFailsafe && func <= FuncBindExternalModule) { - widgetsMask |= CUSTOM_FUNCTION_ENABLE; + widgetsMask |= CUSTOM_FUNCTION_TIME_PARAM; } else if (func == FuncVolume || func == FuncBacklight) { if (modified) cfn.param = fswtchParamT[i]->currentData().toInt(); populateFuncParamCB(fswtchParamT[i], func, cfn.param); - widgetsMask |= CUSTOM_FUNCTION_SOURCE_PARAM | CUSTOM_FUNCTION_ENABLE; + widgetsMask |= CUSTOM_FUNCTION_SOURCE_PARAM; } else if (func == FuncPlaySound || func == FuncPlayHaptic || func == FuncPlayValue || func == FuncPlayPrompt || func == FuncPlayBoth || func == FuncBackgroundMusic || func == FuncSetScreen) { if (func != FuncBackgroundMusic) { @@ -536,10 +533,7 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) fswtchParam[i]->setDecimals(0); fswtchParam[i]->setSingleStep(1); fswtchParam[i]->setValue(cfn.param); - //if (func <= FuncInstantTrim) - { - widgetsMask |= CUSTOM_FUNCTION_ENABLE; - } + widgetsMask |= CUSTOM_FUNCTION_ENABLE; } } From 755afee450275481dfac72d52f11f4542ce59a76 Mon Sep 17 00:00:00 2001 From: eshifri Date: Sun, 22 Jan 2023 10:41:01 -0800 Subject: [PATCH 09/14] Change the fields order to match YAML --- radio/src/datastructs_private.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index b6b96156cce..1fa7a0bc23b 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -172,8 +172,8 @@ PACK(struct CustomFunctionData { NOBACKUP(CFN_SPARE_TYPE val2); }) clear); }) NAME(fp) SKIP; - int8_t repeat:7 SKIP; - uint8_t active:1 SKIP; + uint8_t active : 1 SKIP; + uint8_t active SKIP; bool isEmpty() const { From 2bde13b44b2b2802c7897176521b4448687d23cd Mon Sep 17 00:00:00 2001 From: eshifri Date: Sun, 22 Jan 2023 10:42:56 -0800 Subject: [PATCH 10/14] change the fields order to match the YAML --- radio/src/datastructs_private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index 1fa7a0bc23b..c403ffeaa84 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -173,7 +173,7 @@ PACK(struct CustomFunctionData { }) clear); }) NAME(fp) SKIP; uint8_t active : 1 SKIP; - uint8_t active SKIP; + int8_t repeat:7 SKIP; bool isEmpty() const { From dde85441d65e199b21554f653df23b61437fc84c Mon Sep 17 00:00:00 2001 From: eshifri Date: Sat, 28 Jan 2023 11:08:14 -0800 Subject: [PATCH 11/14] Add headers, set new CF to enabled --- companion/src/modeledit/customfunctions.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/companion/src/modeledit/customfunctions.cpp b/companion/src/modeledit/customfunctions.cpp index 8b3ae3b1c26..24ec87bb96f 100644 --- a/companion/src/modeledit/customfunctions.cpp +++ b/companion/src/modeledit/customfunctions.cpp @@ -99,7 +99,7 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, playIcon.addImage("stop.png", QIcon::Normal, QIcon::On); QStringList headerLabels; - headerLabels << "#" << tr("Switch") << tr("Action") << tr("Parameters") << ""; + headerLabels << "#" << tr("Switch") << tr("Action") << tr("Parameters") << tr("Repeat") << tr("Enable") << ""; TableLayout * tableLayout = new TableLayout(this, fswCapability, headerLabels); for (int i = 0; i < fswCapability; i++) { @@ -536,6 +536,9 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) widgetsMask |= CUSTOM_FUNCTION_ENABLE; } } + else { + cfn.enabled = true; + } fswtchFunc[i]->setVisible(widgetsMask & CUSTOM_FUNCTION_SHOW_FUNC); fswtchParam[i]->setVisible(widgetsMask & CUSTOM_FUNCTION_NUMERIC_PARAM); From cfc8b73bd5b54993df0b94f4c7357ae1dd778564 Mon Sep 17 00:00:00 2001 From: eshifri Date: Sun, 29 Jan 2023 14:26:40 -0800 Subject: [PATCH 12/14] Remove "ON" label from "Enabled" field. Fix "disabled" when swicthing functions. --- companion/src/modeledit/customfunctions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion/src/modeledit/customfunctions.cpp b/companion/src/modeledit/customfunctions.cpp index 24ec87bb96f..bbbc1d7ef01 100644 --- a/companion/src/modeledit/customfunctions.cpp +++ b/companion/src/modeledit/customfunctions.cpp @@ -203,7 +203,6 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, tableLayout->addLayout(i, 5, enableLayout); fswtchEnable[i] = new QCheckBox(this); fswtchEnable[i]->setProperty("index", i); - fswtchEnable[i]->setText(tr("ON")); fswtchEnable[i]->setFixedWidth(200); enableLayout->addWidget(fswtchEnable[i], i + 1); connect(fswtchEnable[i], SIGNAL(stateChanged(int)), this, SLOT(customFunctionEdited())); @@ -326,6 +325,7 @@ void CustomFunctionsPanel::functionEdited() functions[index].clear(); functions[index].swtch = swtch; functions[index].func = (AssignFunc)fswtchFunc[index]->currentData().toInt(); + functions[index].enabled = true; refreshCustomFunction(index); emit modified(); lock = false; From ff52d970e3e3dcf1f825a76421825a6791dfb445 Mon Sep 17 00:00:00 2001 From: eshifri Date: Sun, 5 Feb 2023 17:00:10 -0800 Subject: [PATCH 13/14] Set enable==1 when changing the function. --- radio/src/gui/colorlcd/special_functions.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/radio/src/gui/colorlcd/special_functions.cpp b/radio/src/gui/colorlcd/special_functions.cpp index a43f73630f7..7e2631e3db2 100644 --- a/radio/src/gui/colorlcd/special_functions.cpp +++ b/radio/src/gui/colorlcd/special_functions.cpp @@ -424,10 +424,9 @@ class SpecialFunctionEditPage : public Page 0, FUNC_MAX - 1, GET_DEFAULT(CFN_FUNC(cfn))); functionChoice->setSetValueHandler([=](int32_t newValue) { - int enableState = CFN_ACTIVE(cfn); CFN_FUNC(cfn) = newValue; CFN_RESET(cfn); - CFN_ACTIVE(cfn) = enableState; + CFN_ACTIVE(cfn) = 1; SET_DIRTY(); updateSpecialFunctionOneWindow(); }); From 8482d00e6e6f70d6570afcb46b5b3648f9f00ab3 Mon Sep 17 00:00:00 2001 From: eshifri Date: Mon, 6 Feb 2023 11:26:35 -0800 Subject: [PATCH 14/14] Set "enabled" if function is changed on B/W radios --- radio/src/gui/128x64/model_special_functions.cpp | 1 + radio/src/gui/212x64/model_special_functions.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/radio/src/gui/128x64/model_special_functions.cpp b/radio/src/gui/128x64/model_special_functions.cpp index 89a2f5e2218..ff9d3023093 100644 --- a/radio/src/gui/128x64/model_special_functions.cpp +++ b/radio/src/gui/128x64/model_special_functions.cpp @@ -207,6 +207,7 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF if (active) { CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); if (checkIncDec_Ret) CFN_RESET(cfn); + CFN_ACTIVE(cfn) = 1; // Enable if function is being changed } } else { diff --git a/radio/src/gui/212x64/model_special_functions.cpp b/radio/src/gui/212x64/model_special_functions.cpp index 0478af57707..7eeb69fa081 100644 --- a/radio/src/gui/212x64/model_special_functions.cpp +++ b/radio/src/gui/212x64/model_special_functions.cpp @@ -183,7 +183,7 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF uint8_t active = (attr && s_editMode>0); switch (j) { case ITEM_CUSTOM_FUNCTIONS_SWITCH: - if(CFN_SWITCH(cfn) == SWSRC_NONE) CFN_ACTIVE(cfn) = 1; + if(CFN_SWITCH(cfn) == SWSRC_NONE) CFN_ACTIVE(cfn) = 1; // Enable new function by default drawSwitch(MODEL_SPECIAL_FUNC_1ST_COLUMN, y, CFN_SWITCH(cfn), attr | ((functionsContext->activeSwitches & ((MASK_CFN_TYPE)1 << k)) ? BOLD : 0)); if (active || AUTOSWITCH_ENTER_LONG()) CHECK_INCDEC_SWITCH(event, CFN_SWITCH(cfn), SWSRC_FIRST, SWSRC_LAST, eeFlags, isSwitchAvailableInCustomFunctions); if (func == FUNC_OVERRIDE_CHANNEL && functions != g_model.customFn) { @@ -197,6 +197,7 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF if (active) { func = CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); if (checkIncDec_Ret) CFN_RESET(cfn); + CFN_ACTIVE(cfn) = 1; // Enable if function is being changed } } else {