From 4a23f04844e3894c3fd887a8fd11ec5152c73630 Mon Sep 17 00:00:00 2001 From: philmoz Date: Mon, 23 Jan 2023 22:15:53 +1100 Subject: [PATCH] feat(color): New model curves page layout (#3041) * New curve page layout. * Set scroll to selected curve. * Cleanup. * Add #define's for sizing. Show both curve number and name in title bar (if name is set). * Remove unnecessary call to CurveEdit::SetCurrentSource. Use runtime size for layout where possible. * Add comments * Fix base layout position. * Fix issue with button selection not working properly with scroll wheel. --- radio/src/gui/colorlcd/curve.cpp | 102 +++++++-- radio/src/gui/colorlcd/curve.h | 20 +- radio/src/gui/colorlcd/curveedit.cpp | 15 +- radio/src/gui/colorlcd/model_curves.cpp | 288 ++++++++++++++---------- radio/src/gui/colorlcd/model_curves.h | 11 +- 5 files changed, 284 insertions(+), 152 deletions(-) diff --git a/radio/src/gui/colorlcd/curve.cpp b/radio/src/gui/colorlcd/curve.cpp index e4de41e40..813cb10ff 100644 --- a/radio/src/gui/colorlcd/curve.cpp +++ b/radio/src/gui/colorlcd/curve.cpp @@ -42,13 +42,93 @@ IMPL_LZ4_BITMAP(LBM_CURVE_POINT); IMPL_LZ4_BITMAP(LBM_CURVE_POINT_CENTER); IMPL_LZ4_BITMAP(LBM_CURVE_COORD_SHADOW); +// Base curve rendering class to 'paint' the background and curve. +CurveRenderer::CurveRenderer(const rect_t & rect, std::function function): + rect(rect), + function(std::move(function)) +{ +} + +coord_t CurveRenderer::getPointY(int y) const +{ + return dy + limit(0, + dh / 2 - divRoundClosest(y * dh / 2, RESX), + dh - 1); +} + +void CurveRenderer::drawBackground(BitmapBuffer * dc) +{ + dc->drawSolidFilledRect(rect.x, rect.y, rect.w, rect.h, COLOR_THEME_PRIMARY2); + + // Axis + dc->drawSolidHorizontalLine(dx, dy+dh/2, dw, COLOR_THEME_SECONDARY2); + dc->drawSolidVerticalLine(dx+dw/2, dy, dh, COLOR_THEME_SECONDARY2); + + // Extra lines + dc->drawVerticalLine(dx+dw/4, dy, dh, STASHED, COLOR_THEME_SECONDARY2); + dc->drawVerticalLine(dx+dw*3/4, dy, dh, STASHED, COLOR_THEME_SECONDARY2); + dc->drawHorizontalLine(dx, dy+dh/4, dw, STASHED, COLOR_THEME_SECONDARY2); + dc->drawHorizontalLine(dx, dy+dh*3/4, dw, STASHED, COLOR_THEME_SECONDARY2); + + // Outside border + dc->drawSolidRect(dx, dy, dw, dh, 1, COLOR_THEME_SECONDARY2); +} + +void CurveRenderer::drawCurve(BitmapBuffer * dc) +{ + auto prev = (coord_t) -1; + + for (int x = 0; x < dw; x++) { + coord_t y = getPointY(function(divRoundClosest((x - dw / 2) * RESX, dw / 2))); + if (prev >= 0) { + if (prev < y) { + for (int tmp = prev; tmp <= y; tmp++) { + dc->drawBitmapPattern(dx + x - 2, tmp - 2, LBM_POINT, COLOR_THEME_SECONDARY1); + } + } + else { + for (int tmp = y; tmp <= prev; tmp++) { + dc->drawBitmapPattern(dx + x - 2, tmp - 2, LBM_POINT, COLOR_THEME_SECONDARY1); + } + } + } + prev = y; + } +} + +void CurveRenderer::paint(BitmapBuffer * dc, uint8_t ofst) +{ + dx = rect.x + 2 + ofst; + dy = rect.y + 2 + ofst; + dw = rect.w - 4 - ofst * 2; + dh = rect.h - 4 - ofst * 2; + + drawBackground(dc); + drawCurve(dc); +} + Curve::Curve(Window * parent, const rect_t & rect, std::function function, std::function position): Window(parent, rect, OPAQUE), function(std::move(function)), - position(std::move(position)) + position(std::move(position)), + base(rect_t{0, 0, rect.w, rect.h}, function) { } +coord_t Curve::getPointX(int x) const +{ + return dx + limit(0, + dw / 2 + divRoundClosest(x * dw / 2, RESX), + dw - 1); +} + +coord_t Curve::getPointY(int y) const +{ + return dy + limit(0, + dh / 2 - divRoundClosest(y * dh / 2, RESX), + dh - 1); +} + void Curve::drawBackground(BitmapBuffer * dc) { dc->clear(COLOR_THEME_PRIMARY2); @@ -67,20 +147,6 @@ void Curve::drawBackground(BitmapBuffer * dc) dc->drawSolidRect(dx, dy, dw, dh, 1, COLOR_THEME_SECONDARY2); } -coord_t Curve::getPointX(int x) const -{ - return dx + limit(0, - dw / 2 + divRoundClosest(x * dw / 2, RESX), - dw - 1); -} - -coord_t Curve::getPointY(int y) const -{ - return dy + limit(0, - dh / 2 - divRoundClosest(y * dh / 2, RESX), - dh - 1); -} - void Curve::drawCurve(BitmapBuffer * dc) { auto prev = (coord_t) -1; @@ -136,6 +202,7 @@ void Curve::drawPoint(BitmapBuffer * dc, const CurvePoint & point) void Curve::paint(BitmapBuffer * dc) { + // Adjust border - if drawing points leave more space to prevent clipping of end points. if (points.size() > 0) { dx = 4; dy = 4; @@ -146,8 +213,8 @@ void Curve::paint(BitmapBuffer * dc) dw = width() - dx * 2; dh = height() - dy * 2; - drawBackground(dc); - drawCurve(dc); + base.paint(dc, dx-2); + for (auto point: points) { drawPoint(dc, point); } @@ -170,6 +237,7 @@ void Curve::clearPoints() void Curve::checkEvents() { + // Redraw if crosshair position has changed if (position) { int pos = position(); if (pos != lastPos) { diff --git a/radio/src/gui/colorlcd/curve.h b/radio/src/gui/colorlcd/curve.h index 2c10bc4be..405a8cb35 100644 --- a/radio/src/gui/colorlcd/curve.h +++ b/radio/src/gui/colorlcd/curve.h @@ -28,10 +28,25 @@ struct CurvePoint { LcdFlags flags; }; +class CurveRenderer +{ + public: + CurveRenderer(const rect_t & rect, std::function function); + + void paint(BitmapBuffer * dc, uint8_t ofst = 0); + + protected: + // Drawing rectangle position & size + uint8_t dx, dy, dw, dh; + rect_t rect; + std::function function; + void drawBackground(BitmapBuffer * dc); + void drawCurve(BitmapBuffer * dc); + coord_t getPointY(int y) const; +}; + class Curve: public Window { - friend class CurveEdit; - public: Curve(Window * parent, const rect_t & rect, std::function function, std::function position=nullptr); @@ -51,6 +66,7 @@ class Curve: public Window void paint(BitmapBuffer * dc) override; protected: + CurveRenderer base; // Drawing rectangle position & size uint8_t dx, dy, dw, dh; int lastPos = 0; diff --git a/radio/src/gui/colorlcd/curveedit.cpp b/radio/src/gui/colorlcd/curveedit.cpp index 7772f77fb..b276100d2 100644 --- a/radio/src/gui/colorlcd/curveedit.cpp +++ b/radio/src/gui/colorlcd/curveedit.cpp @@ -205,14 +205,7 @@ void CurveEdit::updatePreview() preview.clearPoints(); CurveHeader & curve = g_model.curves[index]; for (uint8_t i = 0; i < 5 + curve.points; i++) { - if (hasFocus() && current == i) { - preview.position = [=] () -> int { - return getPoint(index, i).x; - }; - } - else { - preview.addPoint(getPoint(index, i), COLOR_THEME_SECONDARY1); - } + preview.addPoint(getPoint(index, i), COLOR_THEME_SECONDARY1); } invalidate(); } @@ -249,7 +242,8 @@ void CurveEdit::updatePreview() void CurveEdit::next() { - if (current++ == preview.points.size()) { + CurveHeader & curve = g_model.curves[index]; + if (current++ == 5 + curve.points - 1) { current = 0; } updatePreview(); @@ -257,8 +251,9 @@ void CurveEdit::next() void CurveEdit::previous() { + CurveHeader & curve = g_model.curves[index]; if (current-- == 0) { - current = preview.points.size(); + current = 5 + curve.points - 1; } updatePreview(); } diff --git a/radio/src/gui/colorlcd/model_curves.cpp b/radio/src/gui/colorlcd/model_curves.cpp index d488ebd18..4d91c13ae 100644 --- a/radio/src/gui/colorlcd/model_curves.cpp +++ b/radio/src/gui/colorlcd/model_curves.cpp @@ -25,6 +25,70 @@ #define SET_DIRTY() storageDirty(EE_MODEL) +#define PREVIEW_PAD 9 +#define TITLE_H 20 +#define INFO_H 27 +#define CURVE_BTN_W 142 +#define CURVE_BTH_H CURVE_BTN_W + TITLE_H + INFO_H - PREVIEW_PAD + +#if LCD_W > LCD_H + #define PER_ROW 3 +#else + #define PER_ROW 2 +#endif + +DEFINE_LZ4_BITMAP(LBM_DOT); + +class CurveButton : public Button { + public: + CurveButton(Window * parent, const rect_t &rect, uint8_t index) : + Button(parent, rect, nullptr, 0, 0, lv_btn_create), + index(index) + { + padAll(0); + preview = new CurveRenderer({PREVIEW_PAD, PREVIEW_PAD+TITLE_H, width() - PREVIEW_PAD*2, width() - PREVIEW_PAD*2}, + [=](int x) -> int { + return applyCustomCurve(x, index); + }); + } + + void paint(BitmapBuffer * dc) override + { + char buf[32]; + LcdFlags bg_color = hasFocus() ? COLOR_THEME_FOCUS : COLOR_THEME_SECONDARY2; + LcdFlags txt_color = hasFocus() ? COLOR_THEME_PRIMARY2 : COLOR_THEME_SECONDARY1; + + int w = width(); + + // Title bar background + dc->drawSolidFilledRect(8, 0, w - 16, 8, bg_color); + dc->drawSolidFilledRect(0, 8, w, TITLE_H-6, bg_color); + dc->drawBitmapPattern(0, 0, LBM_DOT, bg_color); + dc->drawBitmapPattern(w - 13, 0, LBM_DOT, bg_color); + + // Title + char *s = strAppendStringWithIndex(buf, STR_CV, index + 1); + if (g_model.curves[index].name[0]) { + s = strAppend(s, ":"); + strAppend(s, g_model.curves[index].name, LEN_CURVE_NAME); + } + + dc->drawText(w / 2, 1, buf, txt_color|CENTERED|FONT(BOLD)); + + // Curve preview + preview->paint(dc); + + // Curve characteristics + CurveHeader &curve = g_model.curves[index]; + snprintf(buf, 32, "%s %d %s", STR_CURVE_TYPES[curve.type], 5 + curve.points, STR_PTS); + dc->drawText(w / 2, height() - INFO_H + 1, buf, COLOR_THEME_SECONDARY1|CENTERED|FONT(BOLD)); + } + + protected: + uint8_t index; + CurveRenderer* preview; +}; + // initialize a new curves points to the default for a 5 point // curve. void initPoints(const CurveHeader &curve, int8_t *points) @@ -36,46 +100,6 @@ void initPoints(const CurveHeader &curve, int8_t *points) } } -class CurveButton : public Button { - public: - CurveButton(FormGroup * parent, const rect_t &rect, uint8_t index) : - Button(parent, rect), - index(index) - { - if (isCurveUsed(index)) { - setHeight(130); - new Curve(this, {5, 5, 120, 120}, - [=](int x) -> int { - return applyCustomCurve(x, index); - }); - } - } - - void paint(BitmapBuffer * dc) override - { - dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR_THEME_PRIMARY2); - - // 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); - } - - // curve characteristics - if (isCurveUsed(index)) { - CurveHeader &curve = g_model.curves[index]; - dc->drawNumber(130, 5, 5 + curve.points, LEFT | COLOR_THEME_SECONDARY1, 0, nullptr, STR_PTS); - dc->drawTextAtIndex(130, 25, STR_CURVE_TYPES, curve.type, COLOR_THEME_SECONDARY1); - if (curve.smooth) - dc->drawText(130, 45, STR_SMOOTH, COLOR_THEME_SECONDARY1); - } - } - - protected: - uint8_t index; -}; - ModelCurvesPage::ModelCurvesPage() : PageTab(STR_MENUCURVES, ICON_MODEL_CURVES) { @@ -94,71 +118,86 @@ void ModelCurvesPage::pushEditCurve(int index) new CurveEditWindow(index); } -void ModelCurvesPage::rebuild(FormWindow * window, int8_t focusIndex) +void ModelCurvesPage::rebuild(FormWindow * window) { - auto scroll_y = lv_obj_get_scroll_y(window->getLvObj()); window->clear(); - build(window, focusIndex); - lv_obj_scroll_to_y(window->getLvObj(), scroll_y, LV_ANIM_OFF); + build(window); } void ModelCurvesPage::editCurve(FormWindow * window, uint8_t curve) { Window * editWindow = new CurveEditWindow(curve); editWindow->setCloseHandler([=]() { - rebuild(window, curve); + rebuild(window); }); } -void ModelCurvesPage::build(FormWindow * window, int8_t focusIndex) +void ModelCurvesPage::presetMenu(FormWindow * window, uint8_t index) { - FormGridLayout grid; - grid.spacer(PAGE_PADDING); - grid.setLabelWidth(66); - window->padAll(0); + Menu *menu = new Menu(window); + for (int angle = -45; angle <= 45; angle += 15) { + char label[16]; + strAppend(strAppendSigned(label, angle), "°"); + menu->addLineBuffered(label, [=]() { + CurveHeader &curve = g_model.curves[index]; + int8_t * points = curveAddress(index); - CurveEdit::SetCurrentSource(0); + int dx = 2000 / (5 + curve.points - 1); + for (uint8_t i = 0; i < 5 + curve.points; i++) { + int x = -1000 + i * dx; + points[i] = divRoundClosest(angle * x, 450); + } + if (curve.type == CURVE_TYPE_CUSTOM) { + resetCustomCurveX(points, 5 + curve.points); + } + + storageDirty(EE_MODEL); + rebuild(window); + }); + } + menu->updateLines(); +} + +void ModelCurvesPage::build(FormWindow * window) +{ +#if LCD_W > LCD_H + static const lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST}; +#else + static const lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST}; +#endif + static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST}; + + auto form = new FormWindow(window, rect_t{}); + form->setFlexLayout(); + + FlexGridLayout grid(col_dsc, row_dsc); + + FormWindow::Line* line; + + bool hasFocusButton = false; + + uint8_t curveIndex = 0; + uint8_t firstCurveIndex; + CurveButton* firstCurveButton = nullptr; for (uint8_t index = 0; index < MAX_CURVES; index++) { - - CurveHeader &curve = g_model.curves[index]; - int8_t * points = curveAddress(index); - - std::function presetCurveFct = [=]() { - Menu *menu = new Menu(window); - for (int angle = -45; angle <= 45; angle += 15) { - char label[16]; - strAppend(strAppendSigned(label, angle), "°"); - menu->addLineBuffered(label, [=]() { - int dx = 2000 / (5 + curve.points - 1); - for (uint8_t i = 0; i < 5 + curve.points; i++) { - int x = -1000 + i * dx; - points[i] = divRoundClosest(angle * x, 450); - } - if (curve.type == CURVE_TYPE_CUSTOM) { - resetCustomCurveX(points, 5 + curve.points); - } - storageDirty(EE_MODEL); - rebuild(window, index); - }); - } - menu->updateLines(); - }; - if (isCurveUsed(index)) { - // Curve label - auto txt = - new StaticText(window, grid.getLabelSlot(), getCurveString(1 + index), - BUTTON_BACKGROUND, COLOR_THEME_PRIMARY1 | CENTERED); + if ((curveIndex % PER_ROW) == 0) { + line = form->newLine(&grid); + lv_obj_set_grid_align(line->getLvObj(), LV_GRID_ALIGN_SPACE_BETWEEN, LV_GRID_ALIGN_SPACE_BETWEEN); + } + + CurveHeader &curve = g_model.curves[index]; + int8_t * points = curveAddress(index); // Curve drawing - Button * button = new CurveButton(window, grid.getFieldSlot(), index); + auto button = new CurveButton(line, rect_t{0, 0, CURVE_BTN_W, CURVE_BTH_H}, index); button->setPressHandler([=]() -> uint8_t { Menu * menu = new Menu(window); menu->addLine(STR_EDIT, [=]() { editCurve(window, index); }); - menu->addLine(STR_CURVE_PRESET, presetCurveFct); + menu->addLine(STR_CURVE_PRESET, [=]() { presetMenu(window, index); }); menu->addLine(STR_MIRROR, [=]() { curveMirror(index); storageDirty(EE_MODEL); @@ -167,49 +206,64 @@ void ModelCurvesPage::build(FormWindow * window, int8_t focusIndex) menu->addLine(STR_CLEAR, [=]() { curveClear(index); storageDirty(EE_MODEL); - rebuild(window, index); + rebuild(window); }); return 0; }); - button->setFocusHandler([=](bool focus) { - if (focus) { - txt->setBackgroundColor(COLOR_THEME_FOCUS); - txt->setTextFlags(COLOR_THEME_PRIMARY2 | CENTERED); - } else { - txt->setBackgroundColor(COLOR_THEME_SECONDARY2); - txt->setTextFlags(COLOR_THEME_PRIMARY1 | CENTERED); - } - txt->invalidate(); + button->setFocusHandler([=](bool hasFocus) { + if (hasFocus) + focusIndex = index; }); - // if (focusIndex == index) { - // button->setFocus(SET_FOCUS_DEFAULT); - // txt->setBackgroundColor(COLOR_THEME_FOCUS); - // txt->setTextFlags(COLOR_THEME_PRIMARY2 | CENTERED); - // txt->invalidate(); - // } + if (!firstCurveButton) { + firstCurveIndex = index; + firstCurveButton = button; + } - txt->setHeight(button->height()); - grid.spacer(button->height() + 5); - } else { - auto button = new TextButton(window, grid.getLabelSlot(), - getCurveString(1 + index)); - button->setPressHandler([=]() { - Menu *menu = new Menu(window); - menu->addLine(STR_EDIT, [=]() { - initPoints(curve, points); - editCurve(window, index); - }); - menu->addLine(STR_CURVE_PRESET, presetCurveFct); - return 0; - }); - grid.spacer(button->height() + 5); + if (index == focusIndex) { + hasFocusButton = true; + lv_group_focus_obj(button->getLvObj()); + } + + lv_obj_set_grid_cell(button->getLvObj(), LV_GRID_ALIGN_CENTER, curveIndex % PER_ROW, 1, LV_GRID_ALIGN_CENTER, 0, 1); + + curveIndex += 1; } } -// extra bottom padding if touchscreen -#if defined HARDWARE_TOUCH - grid.nextLine(); -#endif + if (!hasFocusButton && firstCurveButton) { + lv_group_focus_obj(firstCurveButton->getLvObj()); + } + if (curveIndex < MAX_CURVES) { + for (uint8_t n = 0; n < MAX_CURVES; n += 1) { + if (!isCurveUsed(n)) { + if ((curveIndex % PER_ROW) == 0) { + line = form->newLine(&grid); + lv_obj_set_grid_align(line->getLvObj(), LV_GRID_ALIGN_SPACE_BETWEEN, LV_GRID_ALIGN_SPACE_BETWEEN); + } + + CurveHeader &curve = g_model.curves[n]; + int8_t * points = curveAddress(n); + + auto button = new TextButton(line, rect_t{0, 0, CURVE_BTN_W, CURVE_BTH_H}, LV_SYMBOL_PLUS, [=]() { + Menu *menu = new Menu(window); + menu->addLine(STR_EDIT, [=]() { + focusIndex = n; + initPoints(curve, points); + editCurve(window, n); + }); + menu->addLine(STR_CURVE_PRESET, [=]() { + focusIndex = n; + presetMenu(window, n); + }); + return 0; + }); + + lv_obj_set_grid_cell(button->getLvObj(), LV_GRID_ALIGN_CENTER, curveIndex % PER_ROW, 1, LV_GRID_ALIGN_CENTER, 0, 1); + + break; + } + } + } } diff --git a/radio/src/gui/colorlcd/model_curves.h b/radio/src/gui/colorlcd/model_curves.h index fdbc91afc..53c063e8f 100644 --- a/radio/src/gui/colorlcd/model_curves.h +++ b/radio/src/gui/colorlcd/model_curves.h @@ -29,15 +29,14 @@ class ModelCurvesPage: public PageTab { ModelCurvesPage(); static void pushEditCurve(int index); - virtual void build(FormWindow * window) override - { - build(window, 0); - } + virtual void build(FormWindow * window) override; protected: - void build(FormWindow * window, int8_t focusIndex); - void rebuild(FormWindow * window, int8_t focusIndex); + uint8_t focusIndex = -1; + + void rebuild(FormWindow * window); void editCurve(FormWindow * window, uint8_t curve); + void presetMenu(FormWindow * window, uint8_t index); }; #endif // _MODEL_CURVES_H_