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.
This commit is contained in:
philmoz
2023-01-23 21:15:53 +10:00
committed by GitHub
parent 5503048e47
commit 4a23f04844
5 changed files with 284 additions and 152 deletions
+85 -17
View File
@@ -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<int(int)> function):
rect(rect),
function(std::move(function))
{
}
coord_t CurveRenderer::getPointY(int y) const
{
return dy + limit<coord_t>(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<int(int)> function, std::function<int()> 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<coord_t>(0,
dw / 2 + divRoundClosest(x * dw / 2, RESX),
dw - 1);
}
coord_t Curve::getPointY(int y) const
{
return dy + limit<coord_t>(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<coord_t>(0,
dw / 2 + divRoundClosest(x * dw / 2, RESX),
dw - 1);
}
coord_t Curve::getPointY(int y) const
{
return dy + limit<coord_t>(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) {
+18 -2
View File
@@ -28,10 +28,25 @@ struct CurvePoint {
LcdFlags flags;
};
class CurveRenderer
{
public:
CurveRenderer(const rect_t & rect, std::function<int(int)> 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<int(int)> 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<int(int)> function, std::function<int()> 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;
+5 -10
View File
@@ -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();
}
+171 -117
View File
@@ -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<void(void)> 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;
}
}
}
}
+5 -6
View File
@@ -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_