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`
This commit is contained in:
philmoz
2023-01-23 20:48:27 +10:00
committed by GitHub
parent 8e8e78671a
commit 5503048e47
2 changed files with 396 additions and 250 deletions
+385 -243
View File
@@ -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;
}
}
+11 -7
View File
@@ -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