-
-
Notifications
You must be signed in to change notification settings - Fork 48
feat: expose graphical value selection renderer hook #412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,30 @@ | |
|
|
||
| namespace { | ||
| const uint8_t listGlyph[] = {0x08, 0x1C, 0x3E, 0x00, 0x3E, 0x1C, 0x08, 0x00}; | ||
| const uint8_t textBufferSize = ITEM_DRAW_BUFFER_SIZE; | ||
|
|
||
| uint8_t safeLength(const char* text) { | ||
| if (text == NULL) { | ||
| return 0; | ||
| } | ||
| size_t len = strlen(text); | ||
| return len > 255 ? 255 : static_cast<uint8_t>(len); | ||
| } | ||
|
|
||
| void copyTextRange(const char* text, uint8_t start, uint8_t count, char* out) { | ||
| if (text == NULL || count == 0) { | ||
| out[0] = '\0'; | ||
| return; | ||
| } | ||
|
|
||
| uint8_t i = 0; | ||
| while (text[start] != '\0' && i < count && i < textBufferSize - 1) { | ||
| out[i] = text[start]; | ||
| i++; | ||
| start++; | ||
| } | ||
| out[i] = '\0'; | ||
| } | ||
|
|
||
| const GraphicalMenuItem* asGraphical(const MenuItem* item) { | ||
| if (item == NULL) { | ||
|
|
@@ -100,20 +124,36 @@ void GraphicalDisplayRenderer::setValueAreaWidth(uint8_t width) { | |
|
|
||
| void GraphicalDisplayRenderer::setActiveItem(const MenuItem* item) { | ||
| activeItem = item; | ||
| clearValueSelection(); | ||
| applyItemFont(item); | ||
| } | ||
|
|
||
| GraphicalDisplayInterface* GraphicalDisplayRenderer::getGraphicalDisplay() { | ||
| return gDisplay; | ||
| } | ||
|
|
||
| void GraphicalDisplayRenderer::setValueSelection(uint8_t start, uint8_t length) { | ||
| valueSelectionStart = start; | ||
| valueSelectionLength = length; | ||
| hasValueSelection = length > 0; | ||
| } | ||
|
|
||
| void GraphicalDisplayRenderer::clearValueSelection() { | ||
| valueSelectionStart = 0; | ||
| valueSelectionLength = 0; | ||
| hasValueSelection = false; | ||
| } | ||
|
|
||
| void* GraphicalDisplayRenderer::queryExtension(uint8_t extensionId) { | ||
| if (extensionId == FrameLifecycleRenderer::extensionId()) { | ||
| return static_cast<FrameLifecycleRenderer*>(this); | ||
| } | ||
| if (extensionId == GraphicalIndicatorRenderer::extensionId()) { | ||
| return static_cast<GraphicalIndicatorRenderer*>(this); | ||
| } | ||
| if (extensionId == GraphicalValueSelectionRenderer::extensionId()) { | ||
| return static_cast<GraphicalValueSelectionRenderer*>(this); | ||
| } | ||
| if (extensionId == GraphicalRendererContext::extensionId()) { | ||
| return static_cast<GraphicalRendererContext*>(this); | ||
| } | ||
|
|
@@ -127,6 +167,9 @@ const void* GraphicalDisplayRenderer::queryExtension(uint8_t extensionId) const | |
| if (extensionId == GraphicalIndicatorRenderer::extensionId()) { | ||
| return static_cast<const GraphicalIndicatorRenderer*>(this); | ||
| } | ||
| if (extensionId == GraphicalValueSelectionRenderer::extensionId()) { | ||
| return static_cast<const GraphicalValueSelectionRenderer*>(this); | ||
| } | ||
| if (extensionId == GraphicalRendererContext::extensionId()) { | ||
| return static_cast<const GraphicalRendererContext*>(this); | ||
| } | ||
|
|
@@ -174,6 +217,9 @@ void GraphicalDisplayRenderer::drawItem(const char* text, const char* value, boo | |
| gDisplay->draw(label); | ||
| x += measureText(label) + 1; | ||
|
|
||
| const GraphicalMenuItem* graphicalItem = asGraphical(activeItem); | ||
| bool tightSelection = graphicalItem != NULL && graphicalItem->useTightGraphicalSelectionBox(); | ||
|
|
||
| if (value != NULL && value[0] != '\0') { | ||
| uint8_t valueWidth = valueAreaWidth; | ||
| if (valueWidth == 0) { | ||
|
|
@@ -183,8 +229,51 @@ void GraphicalDisplayRenderer::drawItem(const char* text, const char* value, boo | |
| uint8_t valueX = valueRight > valueWidth ? valueRight - valueWidth : x; | ||
| gDisplay->setCursor(valueX, baseline); | ||
| gDisplay->draw(value); | ||
|
|
||
| if (hasFocus && MenuItem::isEditing() && hasValueSelection) { | ||
| uint8_t valueLen = safeLength(value); | ||
| uint8_t selectionStart = valueSelectionStart > valueLen ? valueLen : valueSelectionStart; | ||
| uint16_t rawEnd = static_cast<uint16_t>(valueSelectionStart) + valueSelectionLength; | ||
| uint8_t selectionEnd = rawEnd > valueLen ? valueLen : static_cast<uint8_t>(rawEnd); | ||
|
|
||
| if (selectionEnd <= selectionStart && selectionStart < valueLen) { | ||
| selectionEnd = selectionStart + 1; | ||
| } | ||
|
Comment on lines
+233
to
+241
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Defensive Given the call site invariants (
Either the expansion block can be dropped, or the intent (e.g., guarding against future API changes that allow 🤖 Prompt for AI Agents |
||
|
|
||
| if (selectionEnd > selectionStart) { | ||
| char prefixBuf[textBufferSize]; | ||
| char selectedBuf[textBufferSize]; | ||
| copyTextRange(value, 0, selectionStart, prefixBuf); | ||
| copyTextRange(value, selectionStart, static_cast<uint8_t>(selectionEnd - selectionStart), selectedBuf); | ||
|
|
||
| uint8_t prefixWidth = measureText(prefixBuf); | ||
| uint8_t selectedWidth = measureText(selectedBuf); | ||
| if (selectedWidth == 0) { | ||
| selectedWidth = gDisplay->getFontWidth() == 0 ? 1 : gDisplay->getFontWidth(); | ||
| } | ||
|
|
||
| uint8_t selectedX = valueX + prefixWidth; | ||
| uint8_t pad = tightSelection ? 0 : 1; | ||
| uint8_t highlightX = selectedX > pad ? static_cast<uint8_t>(selectedX - pad) : 0; | ||
| uint16_t highlightRight = static_cast<uint16_t>(selectedX) + selectedWidth + pad; | ||
| if (highlightRight > valueRight) { | ||
| highlightRight = valueRight; | ||
| } | ||
|
|
||
| uint8_t highlightWidth = highlightRight > highlightX | ||
| ? static_cast<uint8_t>(highlightRight - highlightX) | ||
| : 0; | ||
| if (highlightWidth > 0) { | ||
| gDisplay->setDrawColor(1); | ||
| gDisplay->drawBox(highlightX, yTop, highlightWidth, h); | ||
| gDisplay->setDrawColor(0); | ||
| gDisplay->setCursor(selectedX, baseline); | ||
| gDisplay->draw(selectedBuf); | ||
| gDisplay->setDrawColor(1); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| const GraphicalMenuItem* graphicalItem = asGraphical(activeItem); | ||
| if (graphicalItem != NULL && graphicalItem->hasGraphicalToggle()) { | ||
| uint8_t box = toggleIndicatorWidth(); | ||
| uint8_t xBox = contentRight > rightPadding + box ? contentRight - rightPadding - box : x; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,52 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <ArduinoUnitTests.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <display/GraphicalDisplayInterface.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <renderer/GraphicalDisplayRenderer.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <renderer/GraphicalValueSelectionRenderer.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class StubGraphicalDisplay : public GraphicalDisplayInterface { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void begin() override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void clear() override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void show() override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void hide() override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void draw(uint8_t) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void draw(const char*) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void setCursor(uint8_t, uint8_t) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void setBacklight(bool) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void setFont(const uint8_t*) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t getDisplayWidth() const override { return 128; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t getDisplayHeight() const override { return 64; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t getFontWidth() const override { return 6; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t getFontHeight() const override { return 8; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t getTextWidth(const char* text) override { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (text == NULL) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t len = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (text[len] != '\0') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| len++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return static_cast<uint8_t>(len * 6); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void setDrawColor(uint8_t) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void clearBuffer() override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void sendBuffer() override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void drawBox(uint8_t, uint8_t, uint8_t, uint8_t) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void drawFrame(uint8_t, uint8_t, uint8_t, uint8_t) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void drawXbm(uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*) override {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unittest(graphical_renderer_exposes_value_selection_extension) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StubGraphicalDisplay display; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GraphicalDisplayRenderer renderer(&display); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void* extension = renderer.queryExtension(GraphicalValueSelectionRenderer::extensionId()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assertTrue(extension != NULL); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GraphicalValueSelectionRenderer* selection = static_cast<GraphicalValueSelectionRenderer*>(extension); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| selection->setValueSelection(1, 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| selection->clearValueSelection(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+40
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Optional: broaden coverage to const overload and negative case. The test confirms the non-const
♻️ Suggested addition GraphicalValueSelectionRenderer* selection = static_cast<GraphicalValueSelectionRenderer*>(extension);
selection->setValueSelection(1, 2);
selection->clearValueSelection();
+
+ const GraphicalDisplayRenderer& constRenderer = renderer;
+ const void* constExtension = constRenderer.queryExtension(GraphicalValueSelectionRenderer::extensionId());
+ assertTrue(constExtension != NULL);
+
+ assertTrue(renderer.queryExtension(0xFF) == NULL);
}📝 Committable suggestion
Suggested change
🧰 Tools🪛 Cppcheck (2.20.0)[error] 40-40: syntax error (syntaxError) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unittest_main() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Optional: scope
tightSelectionto where it's used.graphicalItemandtightSelectionare computed on everydrawItemcall but only consumed inside the selection-highlight block at Line 256. Moving the lookup insideif (hasFocus && MenuItem::isEditing() && hasValueSelection)avoids queryinguseTightGraphicalSelectionBox()for every rendered row.♻️ Suggested change
…and inside the selection block:
if (hasFocus && MenuItem::isEditing() && hasValueSelection) { + const GraphicalMenuItem* graphicalItem = asGraphical(activeItem); + bool tightSelection = graphicalItem != NULL && graphicalItem->useTightGraphicalSelectionBox(); uint8_t valueLen = safeLength(value);Note:
graphicalItemis also used for the toggle branch at Line 277, so you'd need to keep the lookup for that path (or split the variable).🤖 Prompt for AI Agents