From 5503048e472c0325275c6754cf85066f1beaa1cb Mon Sep 17 00:00:00 2001 From: philmoz Date: Mon, 23 Jan 2023 21:48:27 +1100 Subject: [PATCH] chore(color): LVGLify Special/Global Functions tabs (#2992) * Convert Special / Global function page to flex layout. Move function name text into row button so entire row acts as a button. Display active functions first, then inactive functions as smaller buttons in a grid. * Merge duplicate cases in switch, fix indentation. * Keep selected button focussed after editing a function. Add long press handler to jump between free and used functions. * Revert to numerical order for functions list. Use context menu for empty functions all the time. * Fix parameter type and remove redundant lines. * Fix incorrect function being selected when changing tabs. * Fix selection of focussed button when using 'Clear', 'Delete' and switching tabs. * Change button style to match input/mixes/outputs pages. * Fix checked state display for buttons. * Fix scroll position when adding new entries at the bottom. Center selected button after editing or changes to list. * Remove logic that tries to center scroll around selected button (not working). * Convert overview button to lvgl grid. Display function as more readable expression instead of discrete values. Single line display for overview button on landscape screen. Fix display when editing GVAR INC/DEC value (was '--' instead of '-='). Fix incorrect module displayed for 'Set Failsafe' (swapped). Show all parameter and values for each function type in overview. * Fix 'Enabled' indicator so it is not selectable with the scroll wheel and looks less like a radio button. * Remove empty buttons and add a single '+' button at the bottom to match inputs and mixes. * Add long press handler for quick access to the '+' button functions. * Ensure addButton is initialised if not needed (list is full). * Use formatNumberAsString helper function. * Adjust grid spacing. * fix: `STR_VALUE` not `STR_VOLUME` --- radio/src/gui/colorlcd/special_functions.cpp | 628 ++++++++++++------- radio/src/gui/colorlcd/special_functions.h | 18 +- 2 files changed, 396 insertions(+), 250 deletions(-) diff --git a/radio/src/gui/colorlcd/special_functions.cpp b/radio/src/gui/colorlcd/special_functions.cpp index 7c09a0942..225db5981 100644 --- a/radio/src/gui/colorlcd/special_functions.cpp +++ b/radio/src/gui/colorlcd/special_functions.cpp @@ -23,12 +23,13 @@ #include "opentx.h" #include "libopenui.h" #include "view_main.h" +#include "lvgl_widgets/input_mix_line.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), +static const lv_coord_t col_dsc[] = {LV_GRID_FR(2), LV_GRID_FR(3), LV_GRID_TEMPLATE_LAST}; -static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_CONTENT, +static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST}; class SpecialFunctionEditPage : public Page @@ -49,7 +50,7 @@ class SpecialFunctionEditPage : public Page protected: CustomFunctionData *functions; uint8_t index; - FormGroup *specialFunctionOneWindow = nullptr; + FormWindow *specialFunctionOneWindow = nullptr; StaticText *headerSF = nullptr; bool active = false; @@ -91,6 +92,18 @@ class SpecialFunctionEditPage : public Page lv_obj_set_style_text_font(headerSF->getLvObj(), getFont(FONT(BOLD)), LV_STATE_USER_1); } + void addSourceChoice(FormGroup::Line* line, const char* title, CustomFunctionData* cfn, int16_t vmax) + { + new StaticText(line, rect_t{}, title, 0, COLOR_THEME_PRIMARY1); + new SourceChoice(line, rect_t{}, 0, vmax, GET_SET_DEFAULT(CFN_PARAM(cfn))); + } + + NumberEdit* addNumberEdit(FormGroup::Line* line, const char* title, CustomFunctionData* cfn, int16_t vmin, int16_t vmax) + { + new StaticText(line, rect_t{}, title, 0, COLOR_THEME_PRIMARY1); + return new NumberEdit(line, rect_t{}, vmin, vmax, GET_SET_DEFAULT(CFN_PARAM(cfn))); + } + void updateSpecialFunctionOneWindow() { specialFunctionOneWindow->clear(); @@ -110,13 +123,10 @@ class SpecialFunctionEditPage : public Page 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); + addNumberEdit(line, STR_VALUE, cfn, -limit, limit); break; - } + } case FUNC_TRAINER: { new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); @@ -132,7 +142,6 @@ class SpecialFunctionEditPage : public Page return TEXT_AT_INDEX(STR_VSRCRAW, value); ; }); - line = specialFunctionOneWindow->newLine(&grid); break; } @@ -153,22 +162,15 @@ class SpecialFunctionEditPage : public Page .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); + addSourceChoice(line, STR_VOLUME, cfn, MIXSRC_LAST_CH); 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); + addSourceChoice(line, STR_VALUE, cfn, MIXSRC_LAST_CH); break; case FUNC_PLAY_SOUND: @@ -177,7 +179,6 @@ class SpecialFunctionEditPage : public Page 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: @@ -199,7 +200,6 @@ class SpecialFunctionEditPage : public Page LUA_LOAD_MODEL_SCRIPTS(); }, true); // strip extension - line = specialFunctionOneWindow->newLine(&grid); break; case FUNC_SET_TIMER: { @@ -215,7 +215,6 @@ class SpecialFunctionEditPage : public Page 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; } @@ -224,28 +223,18 @@ class SpecialFunctionEditPage : public Page 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); + addSourceChoice(line, STR_VALUE, cfn, MIXSRC_LAST_TELEM); 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); + addNumberEdit(line, STR_VALUE, cfn, 0, 3); 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))); + auto edit = addNumberEdit(line, STR_VALUE, cfn, 0, 255); edit->setDisplayHandler( [=](int32_t value) { return formatNumberAsString(CFN_PARAM(cfn), PREC1, sizeof(CFN_PARAM(cfn)), nullptr, "s"); @@ -254,15 +243,9 @@ class SpecialFunctionEditPage : public Page } 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); + CFN_PARAM(cfn) = (int16_t)min(CFN_PARAM(cfn), (int16_t)ViewMain::instance()->getMainViewsCount()); + addNumberEdit(line, STR_VALUE, cfn, 1, ViewMain::instance()->getMainViewsCount()); break; case FUNC_ADJUST_GVAR: { @@ -305,18 +288,13 @@ class SpecialFunctionEditPage : public Page 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))); + getMixSrcRange(CFN_GVAR_INDEX(cfn) + MIXSRC_FIRST_GVAR, val_min, val_max); + addNumberEdit(line, STR_CONSTANT, cfn, val_min, val_max); 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))); + addSourceChoice(line, STR_MIXSOURCE, cfn, MIXSRC_LAST_CH); break; case FUNC_ADJUST_GVAR_GVAR: { new StaticText(line, rect_t{}, STR_GLOBALVAR, 0, COLOR_THEME_PRIMARY1); @@ -332,29 +310,27 @@ class SpecialFunctionEditPage : public Page 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))); + auto numedit = addNumberEdit(line, STR_INCDEC, cfn, val_min, val_max); numedit->setDisplayHandler( [](int value) { - return formatNumberAsString(abs(value), 0, 0, value >= 0 ? "+=" : "--", nullptr); + return formatNumberAsString(abs(value), 0, 0, value >= 0 ? "+=" : "-=", nullptr); }); break; } } - line = specialFunctionOneWindow->newLine(&grid); } } if (HAS_ENABLE_PARAM(func)) { + 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); } else if (HAS_REPEAT_PARAM(func)) { // !1x 1x 1s 2s 3s ... + line = specialFunctionOneWindow->newLine(&grid); new StaticText(line, rect_t{}, STR_REPEAT, 0, COLOR_THEME_PRIMARY1); auto repeat = new NumberEdit( @@ -371,7 +347,6 @@ class SpecialFunctionEditPage : public Page return formatNumberAsString(value * CFN_PLAY_REPEAT_MUL, 0, 0, nullptr, "s"); } }); - line = specialFunctionOneWindow->newLine(&grid); } } @@ -394,7 +369,6 @@ class SpecialFunctionEditPage : public Page ? isSwitchAvailable(value, ModelCustomFunctionsContext) : isSwitchAvailable(value, GeneralCustomFunctionsContext)); }); - line = window->newLine(&grid); // Patch function in case not available if (!isAssignableFunctionAvailable(CFN_FUNC(cfn), functions)) { @@ -409,6 +383,7 @@ class SpecialFunctionEditPage : public Page } // Function + line = window->newLine(&grid); new StaticText(line, rect_t{}, STR_FUNC, 0, COLOR_THEME_PRIMARY1); auto functionChoice = new Choice(line, rect_t{}, STR_VFSWFUNC, @@ -423,44 +398,122 @@ class SpecialFunctionEditPage : public Page functionChoice->setAvailableHandler([=](int value) { return isAssignableFunctionAvailable(value, functions); }); - line = window->newLine(&grid); - specialFunctionOneWindow = new FormGroup(window, rect_t{}); + specialFunctionOneWindow = new FormWindow(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; +#if LCD_W > LCD_H + +static const lv_coord_t b_col_dsc[] = { + 43, 70, LV_GRID_FR(1), 40, 30, + LV_GRID_TEMPLATE_LAST +}; + +static const lv_coord_t b_row_dsc[] = { + LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; + +#define NM_ROW_CNT 1 +#define FUNC_COL 2 +#define FUNC_ROW 0 + +#else + +static const lv_coord_t b_col_dsc[] = { + 40, LV_GRID_FR(1), 34, 24, + LV_GRID_TEMPLATE_LAST +}; + +static const lv_coord_t b_row_dsc[] = { + LV_GRID_CONTENT, LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; + +#define NM_ROW_CNT 2 +#define FUNC_COL 1 +#define FUNC_ROW 1 + +#endif static const char* _failsafe_module[] = { - "Ext.", "Int.", + "Int.", "Ext.", }; class SpecialFunctionButton : public Button { public: - SpecialFunctionButton(FormWindow *parent, const rect_t &rect, + SpecialFunctionButton(Window *parent, const rect_t &rect, CustomFunctionData *functions, uint8_t index) : - Button(parent, rect), functions(functions), index(index) + Button(parent, rect, nullptr, 0, 0, input_mix_line_create), 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 LCD_H > LCD_W + padTop(0); +#endif + padLeft(4); + lv_obj_set_layout(lvobj, LV_LAYOUT_GRID); + lv_obj_set_grid_dsc_array(lvobj, b_col_dsc, b_row_dsc); + lv_obj_set_style_pad_row(lvobj, 0, 0); + lv_obj_set_style_pad_column(lvobj, 4, 0); + + check(isActive()); + + lv_obj_add_event_cb(lvobj, SpecialFunctionButton::on_draw, LV_EVENT_DRAW_MAIN_BEGIN, nullptr); } #if defined(DEBUG_WINDOWS) std::string getName() const override { return "SpecialFunctionButton"; } #endif + static void on_draw(lv_event_t * e) + { + lv_obj_t* target = lv_event_get_target(e); + auto line = (SpecialFunctionButton*)lv_obj_get_user_data(target); + if (line) { + if (!line->init) + line->delayed_init(e); + else + line->refresh(); + } + } + + void delayed_init(lv_event_t* e) + { + sfName = lv_label_create(lvobj); + lv_obj_set_style_text_align(sfName, LV_TEXT_ALIGN_LEFT, 0); + lv_obj_set_grid_cell(sfName, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_CENTER, 0, NM_ROW_CNT); + + sfSwitch = lv_label_create(lvobj); + lv_obj_set_style_text_align(sfSwitch, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_grid_cell(sfSwitch, LV_GRID_ALIGN_START, 1, 1, LV_GRID_ALIGN_CENTER, 0, 1); + + sfFunc = lv_label_create(lvobj); + lv_obj_set_style_text_align(sfFunc, LV_TEXT_ALIGN_LEFT, 0); + lv_obj_set_grid_cell(sfFunc, LV_GRID_ALIGN_START, FUNC_COL, 1, LV_GRID_ALIGN_CENTER, FUNC_ROW, 1); + + sfRepeat = lv_label_create(lvobj); + lv_obj_set_style_text_align(sfRepeat, LV_TEXT_ALIGN_LEFT, 0); + lv_obj_set_grid_cell(sfRepeat, LV_GRID_ALIGN_CENTER, FUNC_COL+1, 1, LV_GRID_ALIGN_CENTER, 0, NM_ROW_CNT); + + sfEnable = lv_obj_create(lvobj); + lv_obj_set_size(sfEnable, 16, 16); + lv_obj_add_flag(sfEnable, LV_OBJ_FLAG_HIDDEN); + lv_obj_set_style_border_width(sfEnable, 2, 0); + lv_obj_set_style_border_color(sfEnable, makeLvColor(COLOR_THEME_SECONDARY1), 0); + lv_obj_set_style_border_opa(sfEnable, LV_OPA_100, 0); + lv_obj_set_style_bg_color(sfEnable, makeLvColor(COLOR_THEME_ACTIVE), LV_STATE_CHECKED); + lv_obj_set_style_bg_opa(sfEnable, LV_OPA_100, 0); + lv_obj_set_grid_cell(sfEnable, LV_GRID_ALIGN_CENTER, FUNC_COL+2, 1, LV_GRID_ALIGN_CENTER, 0, NM_ROW_CNT); + + init = true; + refresh(); + + if(e) { + auto param = lv_event_get_param(e); + lv_event_send(lvobj, LV_EVENT_DRAW_MAIN, param); + } + } + bool isActive() const { return ((functions == g_model.customFn @@ -474,158 +527,157 @@ class SpecialFunctionButton : public Button void checkEvents() override { Button::checkEvents(); - if (active != isActive()) { - invalidate(); - active = !active; - } + check(isActive()); } - void paintSpecialFunctionLine(BitmapBuffer *dc) + void refresh() { + if (!init) return; const CustomFunctionData *cfn = &functions[index]; + // TODO: Is this necessary? 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; + char s[32] = "SFxx"; + if (functions == g_eeGeneral.customFn) s[0] = 'G'; + strAppendUnsigned(&s[2], index+1); - dc->drawTextAtIndex(col2, line1, STR_VFSWFUNC, func, COLOR_THEME_SECONDARY1); - int16_t val_min = 0; - int16_t val_max = 255; + lv_label_set_text(sfName, s); + lv_label_set_text(sfSwitch, getSwitchPositionName(CFN_SWITCH(cfn))); + + strcpy(s, STR_VFSWFUNC[func]); + strcat(s, " - "); 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); + sprintf(s+strlen(s), "%s = %s", getSourceString(MIXSRC_CH1 + CFN_CH_INDEX(cfn)), formatNumberAsString(CFN_PARAM(cfn)).c_str()); break; case FUNC_TRAINER: { - std::string text; int16_t value = CFN_CH_INDEX(cfn); if (value == 0) - text = std::string(STR_STICKS); + strcat(s, STR_STICKS); else if (value == NUM_STICKS + 1) - text = std::string(STR_CHANS); + strcat(s, STR_CHANS); else - text = TEXT_AT_INDEX(STR_VSRCRAW, value); - dc->drawText(col1, line2, text.c_str(), COLOR_THEME_SECONDARY1); + strcat(s, STR_VSRCRAW[value]); 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); + strcat(s, STR_VFSWRESET[CFN_PARAM(cfn)]); } 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); + TelemetrySensor *sensor = &g_model.telemetrySensors[CFN_PARAM(cfn) - FUNC_RESET_PARAM_FIRST_TELEM]; + strAppend(s+strlen(s), sensor->label, TELEM_LABEL_LEN); } 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); + case FUNC_PLAY_VALUE: + strcat(s, getSourceString(CFN_PARAM(cfn))); break; case FUNC_PLAY_SOUND: - dc->drawTextAtIndex(col1, line2, STR_FUNCSOUNDS, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + strcat(s, STR_FUNCSOUNDS[CFN_PARAM(cfn)]); 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); + if (ZEXIST(cfn->play.name)) { + strAppend(s+strlen(s), cfn->play.name, LEN_FUNCTION_NAME); + } else { + strcat(s, STR_VCSWFUNC[0]); + } break; case FUNC_SET_TIMER: - drawStringWithIndex(dc, col1, line2, STR_TIMER, CFN_TIMER_INDEX(cfn) + 1, COLOR_THEME_SECONDARY1); + sprintf(s+strlen(s), "%s%d = %s", STR_TIMER, CFN_TIMER_INDEX(cfn) + 1, getTimerString(CFN_PARAM(cfn), {.options = SHOW_TIME})); 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); + strcat(s, _failsafe_module[CFN_PARAM(cfn)]); break; case FUNC_HAPTIC: - dc->drawNumber(col1, line2, CFN_PARAM(cfn), COLOR_THEME_SECONDARY1); + case FUNC_SET_SCREEN: + strcat(s, formatNumberAsString(CFN_PARAM(cfn)).c_str()); 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); + strcat(s, formatNumberAsString(CFN_PARAM(cfn), PREC1, 0, nullptr, "s").c_str()); break; case FUNC_ADJUST_GVAR: + strcat(s, getSourceString(CFN_GVAR_INDEX(cfn) + MIXSRC_FIRST_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; - } + case FUNC_ADJUST_GVAR_CONSTANT: + sprintf(s+strlen(s), " = %s", formatNumberAsString(CFN_PARAM(cfn)).c_str()); + break; + case FUNC_ADJUST_GVAR_SOURCE: + sprintf(s+strlen(s), " = %s", getSourceString(CFN_PARAM(cfn))); + break; + case FUNC_ADJUST_GVAR_GVAR: + sprintf(s+strlen(s), " = %s", getSourceString(CFN_PARAM(cfn) + MIXSRC_FIRST_GVAR)); + break; + case FUNC_ADJUST_GVAR_INCDEC: { + int16_t value = CFN_PARAM(cfn); + sprintf(s+strlen(s), " %s= %d", (value >= 0) ? "+" : "-", abs(value)); + break; + } } + break; + + default: + strcpy(s, STR_VFSWFUNC[func]); + break; } + + lv_label_set_text(sfFunc, s); + + lv_obj_add_flag(sfEnable, LV_OBJ_FLAG_HIDDEN); + s[0] = 0; + if (HAS_ENABLE_PARAM(func)) { - theme->drawCheckBox(dc, CFN_ACTIVE(cfn), col3, line2); + if (CFN_ACTIVE(cfn)) + lv_obj_add_state(sfEnable, LV_STATE_CHECKED); + else + lv_obj_clear_state(sfEnable, LV_STATE_CHECKED); + lv_obj_clear_flag(sfEnable, LV_OBJ_FLAG_HIDDEN); } else 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) { - 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"); - } + sprintf(s, "(%s)", + (CFN_PLAY_REPEAT(cfn) == 0) ? "1x" : + (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) ? "!1x" : + formatNumberAsString(CFN_PLAY_REPEAT(cfn) * CFN_PLAY_REPEAT_MUL, 0, 0, nullptr, "s").c_str() + ); } - } - 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); - - 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); + lv_label_set_text(sfRepeat, s); } protected: + bool init = false; CustomFunctionData *functions; uint8_t index; - bool active = false; + + lv_obj_t* sfName = nullptr; + lv_obj_t* sfSwitch = nullptr; + lv_obj_t* sfFunc = nullptr; + lv_obj_t* sfRepeat = nullptr; + lv_obj_t* sfEnable = nullptr; }; +#if LCD_W > LCD_H +#define SF_BUTTON_H 36 +#else +#define SF_BUTTON_H 45 +#endif + SpecialFunctionsPage::SpecialFunctionsPage(CustomFunctionData *functions) : PageTab(functions == g_model.customFn ? STR_MENUCUSTOMFUNC : STR_MENUSPECIALFUNCS, @@ -635,30 +687,114 @@ SpecialFunctionsPage::SpecialFunctionsPage(CustomFunctionData *functions) : { } -void SpecialFunctionsPage::rebuild(FormWindow *window, - int8_t focusSpecialFunctionIndex) +void SpecialFunctionsPage::rebuild(FormWindow *window) { - auto scroll_y = lv_obj_get_scroll_y(window->getLvObj()); + // When window.clear() is called the last button on screen is given focus (???) + // This causes the page to jump to the end when rebuilt. + // Set flag to bypass the button focus handler and reset focusIndex when rebuilding + isRebuilding = true; window->clear(); - build(window, focusSpecialFunctionIndex); - lv_obj_scroll_to_y(window->getLvObj(), scroll_y, LV_ANIM_OFF); + build(window); + isRebuilding = false; } -void SpecialFunctionsPage::editSpecialFunction(FormWindow *window, - uint8_t index) +void SpecialFunctionsPage::newSF(FormWindow * window, bool pasteSF) +{ + Menu* menu = new Menu(Layer::back()); + menu->setTitle(functions == g_model.customFn ? STR_MENUCUSTOMFUNC : STR_MENUSPECIALFUNCS); + char s[6] = "SFxx"; + if (functions == g_eeGeneral.customFn) s[0] = 'G'; + + // search for unused switches + for (uint8_t i = 0; i < MAX_SPECIAL_FUNCTIONS; i++) { + CustomFunctionData *cfn = &functions[i]; + if (cfn->swtch == 0) { + strAppendUnsigned(&s[2], i+1); + menu->addLineBuffered(s, [=]() { + if (pasteSF) { + pasteSpecialFunction(window, i, nullptr); + } else { + editSpecialFunction(window, i, nullptr); + } + }); + } + } + menu->updateLines(); +} + +void SpecialFunctionsPage::pasteSpecialFunction(FormWindow * window, uint8_t index, Button* button) +{ + CustomFunctionData *cfn = &functions[index]; + 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(); + storageDirty(EE_MODEL); + focusIndex = index; + if (button) + lv_event_send(button->getLvObj(), LV_EVENT_VALUE_CHANGED, nullptr); + else + rebuild(window); +} + +void SpecialFunctionsPage::editSpecialFunction(FormWindow * window, uint8_t index, Button* button) { auto editPage = new SpecialFunctionEditPage(functions, index); - editPage->setCloseHandler([=]() { rebuild(window, index); }); + editPage->setCloseHandler([=]() { + CustomFunctionData *cfn = &functions[index]; + if (cfn->swtch != 0) { + focusIndex = index; + if (button) + lv_event_send(button->getLvObj(), LV_EVENT_VALUE_CHANGED, nullptr); + else + rebuild(window); + } + }); } -void SpecialFunctionsPage::build(FormWindow *window, int8_t focusIndex) +void SpecialFunctionsPage::plusPopup(FormWindow * window) { - FormGridLayout grid; - grid.spacer(PAGE_PADDING); - grid.setLabelWidth(66); - window->padAll(0); + if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) { + Menu* menu = new Menu(window); + menu->addLine(STR_NEW, [=]() { + newSF(window, false); + }); + menu->addLine(STR_PASTE, [=]() { + newSF(window, true); + }); + } else { + newSF(window, false); + } +} - // Window::clearFocus(); +void SpecialFunctionsPage::build(FormWindow *window) +{ +#if LCD_W > LCD_H + #define PER_ROW 6 + static const lv_coord_t l_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), + LV_GRID_TEMPLATE_LAST}; +#else + #define PER_ROW 4 + static const lv_coord_t l_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), + LV_GRID_TEMPLATE_LAST}; +#endif + + window->padAll(4); + + auto form = new FormWindow(window, rect_t{}); + form->setFlexLayout(); + form->padAll(0); + + FlexGridLayout grid(l_col_dsc, row_dsc, 2); + + FormWindow::Line* line; + bool hasEmptyFunction = false; + Button* button; + + // Reset focusIndex after switching tabs + if (!isRebuilding) + focusIndex = prevFocusIndex; char s[] = "SFxx"; if (functions == g_eeGeneral.customFn) s[0] = 'G'; @@ -667,66 +803,65 @@ void SpecialFunctionsPage::build(FormWindow *window, int8_t focusIndex) 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); + bool isActive = (cfn->swtch != 0); + + if (isActive) { + line = form->newLine(&grid); + + button = new SpecialFunctionButton(line, rect_t{0, 0, window->width() - 12, SF_BUTTON_H}, functions, i); + + lv_obj_set_grid_cell(button->getLvObj(), LV_GRID_ALIGN_CENTER, 0, PER_ROW, LV_GRID_ALIGN_CENTER, 0, 1); + + if (focusIndex == i) { + lv_group_focus_obj(button->getLvObj()); + } + + button->setFocusHandler([=](bool hasFocus) { + if (hasFocus && !isRebuilding) { + prevFocusIndex = focusIndex; + focusIndex = 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; + menu->addLine(STR_EDIT, [=]() { + editSpecialFunction(window, i, button); }); + if (isActive) { + 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); + pasteSpecialFunction(window, i, button); }); } if (functions[MAX_SPECIAL_FUNCTIONS - 1].isEmpty()) { - menu->addLine(STR_INSERT, [=]() { - memmove( - cfn + 1, cfn, - (MAX_SPECIAL_FUNCTIONS - i - 1) * sizeof(CustomFunctionData)); + for (int j = i; j < MAX_SPECIAL_FUNCTIONS; j++) { + if (!functions[j].isEmpty()) { + menu->addLine(STR_INSERT, [=]() { + memmove( + cfn + 1, cfn, + (MAX_SPECIAL_FUNCTIONS - i - 1) * sizeof(CustomFunctionData)); + memset(cfn, 0, sizeof(CustomFunctionData)); + editSpecialFunction(window, i, nullptr); + }); + break; + } + } + } + if (isActive) { + 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); + rebuild(window); }); } - 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, [=]() { @@ -738,40 +873,47 @@ void SpecialFunctionsPage::build(FormWindow *window, int8_t focusIndex) memset(&functions[MAX_SPECIAL_FUNCTIONS - 1], 0, sizeof(CustomFunctionData)); SET_DIRTY(); - rebuild(window, i); + rebuild(window); }); 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); + + button->setLongPressHandler([=]() -> uint8_t { + if (addButton) { + lv_group_focus_obj(addButton->getLvObj()); + plusPopup(window); } - txt->invalidate(); + return 0; }); - - // 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); + } else { + hasEmptyFunction = true; } - - // if (focusIndex == i) { // fix focus #303 - // button->setFocus(SET_FOCUS_DEFAULT); - // } } - grid.nextLine(); + if (hasEmptyFunction) + { + line = form->newLine(&grid); + addButton = new TextButton(line, rect_t{0, 0, window->width() - 12, SF_BUTTON_H}, LV_SYMBOL_PLUS, [=]() { + plusPopup(window); + return 0; + }); - // window->setLastField(); + addButton->setLongPressHandler([=]() -> uint8_t { + plusPopup(window); + return 0; + }); + + addButton->setFocusHandler([=](bool hasFocus) { + if (hasFocus && !isRebuilding) { + prevFocusIndex = focusIndex; + } + }); + } + else + { + addButton = nullptr; + } } diff --git a/radio/src/gui/colorlcd/special_functions.h b/radio/src/gui/colorlcd/special_functions.h index 10b50fc8f..5d144024b 100644 --- a/radio/src/gui/colorlcd/special_functions.h +++ b/radio/src/gui/colorlcd/special_functions.h @@ -30,16 +30,20 @@ class SpecialFunctionsPage: public PageTab { public: SpecialFunctionsPage(CustomFunctionData * functions); - void build(FormWindow * window) override - { - build(window, 0); - } + void build(FormWindow * window) override; protected: + int8_t focusIndex = -1; + int8_t prevFocusIndex = -1; + bool isRebuilding = false; CustomFunctionData * functions; - void build(FormWindow * window, int8_t focusSpecialFunctionIndex); - void rebuild(FormWindow * window, int8_t focusSpecialFunctionIndex); - void editSpecialFunction(FormWindow * window, uint8_t index); + Button* addButton = nullptr; + + void rebuild(FormWindow * window); + void newSF(FormWindow* window, bool pasteSF); + void editSpecialFunction(FormWindow * window, uint8_t index, Button* button); + void pasteSpecialFunction(FormWindow * window, uint8_t index, Button* button); + void plusPopup(FormWindow * window); }; #endif //_SPECIAL_FUNCTIONS_H