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