diff --git a/radio/src/gui/colorlcd/model_flightmodes.cpp b/radio/src/gui/colorlcd/model_flightmodes.cpp index 859de4974..28a4b3037 100644 --- a/radio/src/gui/colorlcd/model_flightmodes.cpp +++ b/radio/src/gui/colorlcd/model_flightmodes.cpp @@ -27,206 +27,286 @@ #define LABEL_RIGHT_MARGIN 5 -ModelFlightModesPage::ModelFlightModesPage(): - PageTab(STR_MENUFLIGHTMODES, ICON_MODEL_FLIGHT_MODES) -{ -} - bool isTrimModeAvailable(int mode) { return (mode < 0 || (mode%2) == 0 || (mode/2) != 0); //ToDo menuVerticalPosition } -class FlightModeGroup: public FormGroup -{ - public: - FlightModeGroup(Window * parent, uint8_t index, const rect_t & rect) : - FormGroup(parent, rect), - index(index) - { - setFocusHandler([=] (bool focus) { - setSubWindowColors(focus); - }); - } - - void checkEvents() override - { - FormGroup::checkEvents(); - bool newActive = (getFlightMode() == index); - if (newActive != active) { - active = newActive; - invalidate(); - } - } - - void paint(BitmapBuffer * dc) override - { - uint32_t fillColor = getFocus() != this ? COLOR_THEME_SECONDARY2 : COLOR_THEME_FOCUS; - dc->drawSolidFilledRect(0, 0, width(), height(), fillColor); - - FormGroup::paint(dc); - } - - void setSubWindowColors(bool focus) - { - - int32_t color = focus ? COLOR_THEME_PRIMARY2 : COLOR_THEME_PRIMARY1; - for (auto window: getChildren()) { - if (dynamic_cast(window) != nullptr) { - LcdFlags flags = dynamic_cast(window) == nullptr ? RIGHT : 0; - window->setTextFlags(flags | color); - } - } - } - - protected: - uint8_t index; - bool active = false; +struct FlightModeEdit : public Page { + FlightModeEdit(uint8_t index); }; -rect_t getRightAlignedLabelSlot(FormGridLayout &grid) +static const lv_coord_t line_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), + LV_GRID_TEMPLATE_LAST}; + +static const lv_coord_t line_row_dsc[] = {LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; + +static const lv_coord_t trims_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2), LV_GRID_FR(2), + LV_GRID_TEMPLATE_LAST}; + +static const lv_coord_t trims_row_dsc[] = {LV_GRID_CONTENT, + LV_GRID_CONTENT, + LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; + +static void btn_changed(lv_event_t* e) { - auto rect = grid.getLabelSlot(); - rect.w -= LABEL_RIGHT_MARGIN; - return rect; + auto target = lv_event_get_target(e); + auto btn = (Button*)lv_obj_get_user_data(target); + auto obj = (lv_obj_t*)lv_event_get_user_data(e); + + if (!btn->checked()) { + lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_clear_flag(obj, LV_OBJ_FLAG_HIDDEN); + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, nullptr); + } } -rect_t getRightAlignedFieldSlot(FormGridLayout &grid, uint8_t count, uint8_t index) +static void make_conditional(Window* w, Button* btn) { - auto rect = grid.getFieldSlot(count, index); - rect.w -= LABEL_RIGHT_MARGIN; - return rect; + lv_obj_t* w_obj = w->getLvObj(); + if (!btn->checked()) { lv_obj_add_flag(w_obj, LV_OBJ_FLAG_HIDDEN); } + + lv_obj_t* btn_obj = btn->getLvObj(); + lv_obj_add_event_cb(btn_obj, btn_changed, LV_EVENT_CLICKED, w_obj); } +static std::string getFMTrimStr(uint8_t mode) +{ + char str[4]; + + mode &= 0x1F; + + if (mode & 1) str[0] = '+'; + else str[0] = '='; + str[1] = ' '; + + mode >>= 1; + if (mode > MAX_FLIGHT_MODES - 1) mode = MAX_FLIGHT_MODES - 1; + str[2] = '0' + mode; + str[3] = '\0'; + + return str; +} + +FlightModeEdit::FlightModeEdit(uint8_t index) : + Page(ICON_MODEL_FLIGHT_MODES) +{ + std::string title = + std::string(TR_MENUFLIGHTMODE) + " " + std::to_string(index); + new StaticText(&header, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, + PAGE_LINE_HEIGHT}, + title, 0, COLOR_THEME_PRIMARY2); + + auto form = new FormWindow(&body, rect_t{0, 0, body.width(), body.height()}, + NO_FOCUS | FORM_FORWARD_FOCUS); + + form->setFlexLayout(); + FlexGridLayout grid(line_col_dsc, line_row_dsc, 2); + + FlightModeData* p_fm = &g_model.flightModeData[index]; + + // Flight mode name + auto line = form->newLine(&grid); + new StaticText(line, rect_t{}, STR_NAME, 0, COLOR_THEME_PRIMARY1); + new ModelTextEdit(line, rect_t{}, p_fm->name, LEN_FLIGHT_MODE_NAME); + + if (index > 0) { + // Switch + line = form->newLine(&grid); + new StaticText(line, rect_t{}, STR_SWITCH, 0, COLOR_THEME_PRIMARY1); + new SwitchChoice(line, rect_t{}, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, + GET_SET_DEFAULT(p_fm->swtch)); + } + + FlexGridLayout trim_grid(trims_col_dsc, trims_row_dsc); + line = form->newLine(&trim_grid); + new StaticText(line, rect_t{}, STR_TRIMS, 0, COLOR_THEME_PRIMARY1); + + for (int t = 0; t < NUM_TRIMS; t++) { + + auto trim_obj = window_create(line->getLvObj()); + auto trim = new FormGroup::Line(line, trim_obj, nullptr, form); + lv_obj_set_layout(trim_obj, LV_LAYOUT_FLEX); + lv_obj_set_style_pad_column(trim_obj, 4, 0); + lv_obj_set_style_pad_bottom(trim_obj, 8, 0); + lv_obj_set_style_flex_cross_place(trim_obj, LV_FLEX_ALIGN_CENTER, 0); + + trim_t* tr = &p_fm->trim[t]; + auto btn = new TextButton( + trim, rect_t{}, getSourceString(MIXSRC_FIRST_TRIM + t), + [=]() { + if (tr->mode == TRIM_MODE_NONE) { + tr->mode = 0; + SET_DIRTY(); + return 1; + } else { + tr->mode = TRIM_MODE_NONE; + SET_DIRTY(); + return 0; + } + }, + 0, COLOR_THEME_PRIMARY1); + + if (tr->mode != TRIM_MODE_NONE) btn->check(); + btn->setWidth(LV_DPI_DEF / 2); + btn->setHeight(LV_DPI_DEF / 4); + + auto tr_mode = new Choice(trim, rect_t{}, 0, 2*MAX_FLIGHT_MODES - 1, + GET_SET_DEFAULT(tr->mode)); + tr_mode->setTextHandler(getFMTrimStr); + + // show trim value choice iff btn->checked() + make_conditional(tr_mode, btn); + + if (t % 2 == 1) { + trim_grid.nextCell(); + } + } + + // Fade in + line = form->newLine(&grid); + new StaticText(line, rect_t{}, STR_FADEIN, 0, COLOR_THEME_PRIMARY1); + new NumberEdit(line, rect_t{}, 0, DELAY_MAX, GET_DEFAULT(p_fm->fadeIn), + SET_VALUE(p_fm->fadeIn, newValue), 0, PREC1); + + // Fade out + line = form->newLine(&grid); + new StaticText(line, rect_t{}, STR_FADEOUT, 0, COLOR_THEME_PRIMARY1); + new NumberEdit(line, rect_t{}, 0, DELAY_MAX, GET_DEFAULT(p_fm->fadeOut), + SET_VALUE(p_fm->fadeOut, newValue), 0, PREC1); +} + +class FlightModeBtn: public Button +{ + uint8_t index; + bool active = false; + +public: + FlightModeBtn(Window * parent, uint8_t index); + void checkEvents() override; + void paint(BitmapBuffer* dc) override; + + bool isActive() { return active; } + void onPress() override; +}; + +static void fm_draw_begin(lv_event_t *e) +{ + auto fmg = (FlightModeBtn*)lv_event_get_user_data(e); + if (!fmg) return; + + lv_obj_draw_part_dsc_t* dsc = lv_event_get_draw_part_dsc(e); + if (fmg->isActive()) { + dsc->rect_dsc->bg_color = makeLvColor(COLOR_THEME_ACTIVE); + } else { + dsc->rect_dsc->bg_color = makeLvColor(COLOR_THEME_PRIMARY2); + } +} + +FlightModeBtn::FlightModeBtn(Window * parent, uint8_t index) : + Button(parent, rect_t{}, nullptr, 0, 0, lv_btn_create), + index(index) +{ + lv_obj_add_flag(lvobj, LV_OBJ_FLAG_CLICKABLE); + lv_obj_set_style_bg_opa(lvobj, LV_OPA_100, 0); + + lv_obj_set_style_grid_cell_x_align(lvobj, LV_GRID_ALIGN_STRETCH, 0); + lv_obj_set_width(lvobj, lv_pct(100)); + lv_obj_set_height(lvobj, LV_DPI_DEF / 3); + + lv_obj_add_event_cb(lvobj, fm_draw_begin, LV_EVENT_DRAW_PART_BEGIN, this); +} + +void FlightModeBtn::checkEvents() +{ + bool newActive = (getFlightMode() == index); + if (newActive != active) { + active = newActive; + invalidate(); + } +} + +void FlightModeBtn::paint(BitmapBuffer* dc) +{ + coord_t x=8, y=0; + + LcdFlags txt_flags; + if (active) { + txt_flags = FONT(BOLD) | COLOR_THEME_PRIMARY2; + } else { + txt_flags = FONT(STD) | COLOR_THEME_SECONDARY1; + } + + auto h = lv_obj_get_height(lvobj); + y = (h - getFontHeight(txt_flags)) / 2; + + const auto& fm = g_model.flightModeData[index]; + if (fm.name[0] != '\0') { + dc->drawSizedText(x, y, fm.name, sizeof(fm.name), txt_flags); + } else { + char label[8]; + getFlightModeString(label, index + 1); + dc->drawSizedText(x, y, label, sizeof(label), txt_flags); + } + + if ((index > 0) && (fm.swtch != SWSRC_NONE)) { + char sw[8]; + getSwitchPositionName(sw, fm.swtch); + + x = lv_obj_get_width(lvobj) - 8; + dc->drawSizedText(x, y, sw, sizeof(sw), txt_flags | RIGHT); + } +} + +void FlightModeBtn::onPress() +{ + new FlightModeEdit(index); +} + +ModelFlightModesPage::ModelFlightModesPage(): + PageTab(STR_MENUFLIGHTMODES, ICON_MODEL_FLIGHT_MODES) +{ +} + +// FM table: 3 x 3 + +static const lv_coord_t fmt_col_dsc[] = {LV_GRID_FR(1), + LV_GRID_FR(1), + LV_GRID_FR(1), + LV_GRID_TEMPLATE_LAST}; + +static const lv_coord_t fmt_row_dsc[] = {LV_GRID_CONTENT, + LV_GRID_CONTENT, + LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; void ModelFlightModesPage::build(FormWindow * window) { - coord_t y = 2; + window->setFlexLayout(); + lv_obj_t* obj = window->getLvObj(); + lv_obj_set_style_flex_cross_place(obj, LV_FLEX_ALIGN_CENTER, 0); + FlexGridLayout grid(fmt_col_dsc, fmt_row_dsc, 0); + auto fm_box = window->newLine(&grid); + + obj = fm_box->getLvObj(); + lv_obj_set_style_pad_all(obj, 8, 0); + lv_obj_set_style_pad_row(obj, 2, 0); + lv_obj_set_style_pad_column(obj, 2, 0); + for (int i = 0; i < MAX_FLIGHT_MODES; i++) { - auto group = new FlightModeGroup(window, i, { 2, y, LCD_W - 10, 0 }); - - FormGridLayout grid; - grid.setMarginRight(15); -#if LCD_W > LCD_H - grid.setLabelWidth(140); -#else - grid.setLabelWidth(110); -#endif - grid.spacer(); - - char label[16]; - getFlightModeString(label, i + 1); - new Subtitle(group, grid.getLabelSlot(), label, 0, COLOR_THEME_PRIMARY1); - - FormField *a = nullptr; - std::function backgroundHandler = [=](FormField *field) { - if (! field->isEditMode() && !field->hasFocus()) - return i == getFlightMode() ? COLOR_THEME_ACTIVE : COLOR_THEME_PRIMARY2; - else if (field->hasFocus() && ! field->isEditMode()) - return COLOR_THEME_FOCUS; - else - return COLOR_THEME_EDIT; - }; - - bool bNarrowScreen = LCD_W < LCD_H; - - // Flight mode name - new StaticText(group, getRightAlignedLabelSlot(grid), STR_NAME, 0, RIGHT | COLOR_THEME_PRIMARY1); - - rect_t r = grid.getFieldSlot();; - if (! bNarrowScreen) { - r = grid.getFieldSlot(3,0); - r.w += 53; - } - a = new ModelTextEdit(group, r, g_model.flightModeData[i].name, LEN_FLIGHT_MODE_NAME); - a->setBackgroundHandler(backgroundHandler); - - // Flight mode switch - if (i > 0) { - if (! bNarrowScreen) { - new StaticText(group, getRightAlignedFieldSlot(grid, 3, 1), STR_SWITCH, 0, RIGHT | COLOR_THEME_PRIMARY1); - a = new SwitchChoice(group, grid.getFieldSlot(3, 2), SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, GET_SET_DEFAULT(g_model.flightModeData[i].swtch)); - } - else { - grid.nextLine(); - new StaticText(group, getRightAlignedLabelSlot(grid), STR_SWITCH, 0, RIGHT | COLOR_THEME_PRIMARY1); - a = new SwitchChoice(group, grid.getFieldSlot(), SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, GET_SET_DEFAULT(g_model.flightModeData[i].swtch)); - } - a->setBackgroundHandler(backgroundHandler); - grid.nextLine(); - } - else { - grid.nextLine(); - } - - // Flight mode trims - new StaticText(group, getRightAlignedLabelSlot(grid), STR_TRIMS, 0, RIGHT | COLOR_THEME_PRIMARY1); - - const char * STR_VTRIMS_MODES = "\002" - "--" - "=0" - "+0" - "=1" - "+1" - "=2" - "+2" - "=3" - "+3" - "=4" - "+4" - "=5" - "+5" - "=6" - "+6" - "=7" - "+7" - "=8" - "+8"; - for (int t = 0; t < NUM_TRIMS; t++) { - // TODO isTrimModeAvailable to avoid +{{CURRENT}} - a = new Choice(group, grid.getFieldSlot(NUM_TRIMS, t), STR_VTRIMS_MODES, -1, 2*MAX_FLIGHT_MODES-1, - GET_DEFAULT(g_model.flightModeData[i].trim[t].mode==TRIM_MODE_NONE ? -1 : g_model.flightModeData[i].trim[t].mode), - SET_DEFAULT(g_model.flightModeData[i].trim[t].mode)); - a->setBackgroundHandler(backgroundHandler); - } - grid.nextLine(); - - // Flight mode fade in / out - new StaticText(group, getRightAlignedLabelSlot(grid), "Fade in/out", 0, RIGHT | COLOR_THEME_PRIMARY1); - a = new NumberEdit(group, grid.getFieldSlot(2, 0), 0, DELAY_MAX, - GET_DEFAULT(g_model.flightModeData[i].fadeIn), - SET_VALUE(g_model.flightModeData[i].fadeIn, newValue), - 0, PREC1); - a->setBackgroundHandler(backgroundHandler); - a = new NumberEdit(group, grid.getFieldSlot(2, 1), 0, DELAY_MAX, - GET_DEFAULT(g_model.flightModeData[i].fadeOut), - SET_VALUE(g_model.flightModeData[i].fadeOut, newValue), - 0, PREC1); - a->setBackgroundHandler(backgroundHandler); - - grid.nextLine(); - - grid.spacer(); - coord_t height = grid.getWindowHeight(); - group->setHeight(height); - y += height + 2; - - if (i == 0) - group->setSubWindowColors(true); + new FlightModeBtn(fm_box, i); } - char label[32]; - sprintf(label, "Check FM Trims"); - // TODO rather use a centered slot? - // TODO dynamic text with FM index for the button text - new TextButton(window, { 60, y + 5, LCD_W - 120, PAGE_LINE_HEIGHT }, label, - [&]() -> uint8_t { - if (trimsCheckTimer) - trimsCheckTimer = 0; - else - trimsCheckTimer = 200; // 2 seconds trims cancelled - return trimsCheckTimer; - }); - + new TextButton(window, rect_t{}, "Check FM Trims", [&]() -> uint8_t { + if (trimsCheckTimer) + trimsCheckTimer = 0; + else + trimsCheckTimer = 200; // 2 seconds trims cancelled + return trimsCheckTimer; + }); }