Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/source/overview/rendering/graphical-display.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ Menu integration
through ``GraphicalRendererContext``. This lets the renderer recompute visible
rows and value-column widths when fonts differ per item.

Item capabilities
-----------------

Built-in items now expose graphical capabilities through
``GraphicalMenuItem`` without RTTI:

- ``ITEM_BASIC`` and ``ITEM_LABEL`` opt into graphical-item capabilities.
- ``ITEM_BOOL`` and ``ITEM_TOGGLE`` expose toggle state for checkbox drawing.
- Widget-based items (for example ``ITEM_LIST`` and ``ITEM_RANGE``) expose
list-indicator support.
- ``ITEM_VALUE`` reports the rendered value width for right-aligned layout.

Renderer-specific enhancements remain optional through ``queryExtension()``.
For example, indicators use ``GraphicalIndicatorRenderer`` and value selection
highlighting can be added with ``GraphicalValueSelectionRenderer``.

Basic usage
-----------

Expand Down
96 changes: 89 additions & 7 deletions src/BaseItemManyWidgets.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

#include "LcdMenu.h"
#include "MenuItem.h"
#include "display/GraphicalDisplayInterface.h"
#include "renderer/GraphicalIndicatorRenderer.h"
#include "renderer/GraphicalMenuItem.h"
#include "renderer/GraphicalValueSelectionRenderer.h"
#include "utils/lcd_menu_utils.h"
#include "utils/std.h"
#include "widget/BaseWidget.h"
#include <vector>

class BaseItemManyWidgets : public MenuItem {
class BaseItemManyWidgets : public MenuItem, public GraphicalMenuItem {
protected:
std::vector<BaseWidget*> widgets;
uint8_t activeWidget = 0;
Expand Down Expand Up @@ -71,6 +75,38 @@ class BaseItemManyWidgets : public MenuItem {
}
}

uint8_t measureGraphicalValueWidth(GraphicalDisplayInterface* display) const override {
if (display == NULL) {
return 0;
}

char buf[ITEM_DRAW_BUFFER_SIZE];
uint8_t index = 0;
for (size_t i = 0; i < widgets.size() && index < ITEM_DRAW_BUFFER_SIZE - 1; i++) {
uint8_t written = widgets[i]->draw(buf, index);
uint8_t maxWritable = static_cast<uint8_t>(ITEM_DRAW_BUFFER_SIZE - 1 - index);
index += written > maxWritable ? maxWritable : written;
}
buf[index] = '\0';
return display->getTextWidth(buf);
}

bool hasGraphicalListIndicator() const override {
for (size_t i = 0; i < widgets.size(); i++) {
if (widgets[i] != NULL && widgets[i]->isList()) {
return true;
}
}
return false;
}

const void* queryCapability(uint8_t capabilityId) const override {
if (capabilityId == GraphicalMenuItem::capabilityId()) {
return static_cast<const GraphicalMenuItem*>(this);
}
return MenuItem::queryCapability(capabilityId);
}

virtual ~BaseItemManyWidgets() {
for (auto widget : widgets) {
delete widget;
Expand Down Expand Up @@ -111,22 +147,68 @@ class BaseItemManyWidgets : public MenuItem {

uint8_t index = 0;
uint8_t cursorCol = 0;
bool hasListWidget = false;
uint8_t activeSegmentStart = 0;
uint8_t activeSegmentLength = 0;

GraphicalValueSelectionRenderer* valueSelectionRenderer =
static_cast<GraphicalValueSelectionRenderer*>(renderer->queryExtension(GraphicalValueSelectionRenderer::extensionId()));

for (uint8_t i = 0; i < widgets.size(); i++) {
index += widgets[i]->draw(buf, index);
uint8_t widgetStart = index;
uint8_t written = widgets[i]->draw(buf, index);
uint8_t maxWritable = index < ITEM_DRAW_BUFFER_SIZE - 1 ? static_cast<uint8_t>(ITEM_DRAW_BUFFER_SIZE - 1 - index) : 0;
index += written > maxWritable ? maxWritable : written;
hasListWidget = hasListWidget || widgets[i]->isList();
if (i == activeWidget && MenuItem::isEditing()) {
// Calculate the available space for the widgets after the text
size_t v_size = renderer->getEffectiveCols() - strlen(text) - 1;
// Adjust the view shift to ensure the active widget is visible
renderer->viewShift = index > v_size ? index - v_size : 0;
activeSegmentStart = widgetStart;
activeSegmentLength = index > widgetStart ? static_cast<uint8_t>(index - widgetStart) : 0;

const char* label = text == NULL ? "" : text;
int16_t valueArea = static_cast<int16_t>(renderer->getEffectiveCols()) - static_cast<int16_t>(strlen(label)) - 1;
int16_t shift = static_cast<int16_t>(index) - valueArea;
renderer->viewShift = shift > 0 ? static_cast<uint8_t>(shift) : 0;

if (valueSelectionRenderer != NULL) {
if (activeSegmentLength > 0) {
valueSelectionRenderer->setValueSelection(activeSegmentStart, activeSegmentLength);
} else {
valueSelectionRenderer->clearValueSelection();
}
}

// Draw the item with the renderer, indicating if it's the last widget
renderer->drawItem(text, buf, i == widgets.size() - 1);
// Calculate the cursor column position for the active widget
cursorCol = renderer->getCursorCol() - 1 - widgets[i]->cursorOffset;
uint8_t endCol = renderer->getCursorCol();
uint8_t offset = static_cast<uint8_t>(1 + widgets[i]->cursorOffset);
cursorCol = endCol > offset ? endCol - offset : 0;
}
}
buf[index < ITEM_DRAW_BUFFER_SIZE ? index : ITEM_DRAW_BUFFER_SIZE - 1] = '\0';

if (valueSelectionRenderer != NULL) {
if (MenuItem::isEditing() && activeSegmentLength > 0) {
valueSelectionRenderer->setValueSelection(activeSegmentStart, activeSegmentLength);
} else {
valueSelectionRenderer->clearValueSelection();
}
}

renderer->drawItem(text, buf);

if (valueSelectionRenderer != NULL) {
valueSelectionRenderer->clearValueSelection();
}

if (hasListWidget) {
GraphicalIndicatorRenderer* indicatorRenderer =
static_cast<GraphicalIndicatorRenderer*>(renderer->queryExtension(GraphicalIndicatorRenderer::extensionId()));
if (indicatorRenderer != NULL) {
indicatorRenderer->drawListIndicator();
}
}

if (MenuItem::isEditing()) {
renderer->moveCursor(cursorCol, renderer->getCursorRow());
}
Expand Down
12 changes: 10 additions & 2 deletions src/BaseItemZeroWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#define BASE_ITEM_ZERO_WIDGET_H

#include "MenuItem.h"
#include "renderer/GraphicalMenuItem.h"

/**
* @class BaseItemZeroWidget
Expand All @@ -15,7 +16,7 @@
* @note This class is intended to be used as a base class for other menu items.
* It should not be instantiated directly.
*/
class BaseItemZeroWidget : public MenuItem {
class BaseItemZeroWidget : public MenuItem, public GraphicalMenuItem {
public:
virtual ~BaseItemZeroWidget() = default;
/**
Expand All @@ -25,6 +26,13 @@ class BaseItemZeroWidget : public MenuItem {
*/
explicit BaseItemZeroWidget(const char* text) : MenuItem(text) {}

const void* queryCapability(uint8_t capabilityId) const override {
if (capabilityId == GraphicalMenuItem::capabilityId()) {
return static_cast<const GraphicalMenuItem*>(this);
}
return MenuItem::queryCapability(capabilityId);
}

protected:
virtual void handleCommit(LcdMenu* menu) = 0;

Expand All @@ -42,4 +50,4 @@ class BaseItemZeroWidget : public MenuItem {
}
};

#endif
#endif
42 changes: 40 additions & 2 deletions src/ItemBool.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <ItemWidget.h>
#include <display/GraphicalDisplayInterface.h>
#include <widget/WidgetBool.h>

/**
Expand All @@ -15,6 +16,10 @@
*/
template <typename V = bool>
class ItemBool : public ItemWidget<V> {
private:
const char* textOn = NULL;
const char* textOff = NULL;

public:
virtual ~ItemBool() = default;

Expand All @@ -26,7 +31,40 @@ class ItemBool : public ItemWidget<V> {
const char* format,
const uint8_t cursorOffset,
typename ItemWidget<V>::CallbackType callback)
: ItemWidget<V>(text, new WidgetBool<V>(value, textOn, textOff, format, cursorOffset), callback) {}
: ItemWidget<V>(text, new WidgetBool<V>(value, textOn, textOff, format, cursorOffset), callback),
textOn(textOn),
textOff(textOff) {}

uint8_t measureGraphicalValueWidth(GraphicalDisplayInterface* display) const override {
if (display == NULL) {
return 0;
}
uint8_t toggleWidth = display->getFontHeight();
if (toggleWidth > 4) {
toggleWidth -= 4;
}

if (toggleWidth < 3) {
toggleWidth = 3;
}

uint8_t onWidth = display->getTextWidth(textOn == NULL ? "" : textOn);
uint8_t offWidth = display->getTextWidth(textOff == NULL ? "" : textOff);
uint8_t textWidth = onWidth > offWidth ? onWidth : offWidth;

return toggleWidth > textWidth ? toggleWidth : textWidth;
}

bool hasGraphicalToggle() const override { return true; }

bool graphicalToggleState() const override {
BaseWidget* widget = this->getWidgetAt(0);
if (widget == nullptr) {
return false;
}
BaseWidgetValue<V>* boolWidget = static_cast<BaseWidgetValue<V>*>(widget);
return static_cast<bool>(boolWidget->getValue());
}
};

/**
Expand Down Expand Up @@ -76,4 +114,4 @@ inline ItemBool<Ref<bool>>* ITEM_BOOL_REF(
const char* format = "%s",
const uint8_t cursorOffset = 0) {
return new ItemBool<Ref<bool>>(text, Ref<bool>(value), textOn, textOff, format, cursorOffset, callback);
}
}
4 changes: 2 additions & 2 deletions src/ItemLabel.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* @class ItemLabel
* @brief An unselectable menu item used for titles or separators.
*/
class ItemLabel final : public MenuItem {
class ItemLabel final : public BasicItem {
public:
explicit ItemLabel(const char* text) : MenuItem(text) {}
explicit ItemLabel(const char* text) : BasicItem(text) {}

bool isSelectable() const override { return false; }
};
Expand Down
10 changes: 10 additions & 0 deletions src/ItemSubMenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "BaseItemZeroWidget.h"
#include "LcdMenu.h"
#include "MenuScreen.h"
#include "renderer/GraphicalIndicatorRenderer.h"

/**
* @class ItemSubMenu
Expand Down Expand Up @@ -34,6 +35,15 @@ class ItemSubMenu : public BaseItemZeroWidget {
}

protected:
void draw(MenuRenderer* renderer) override {
renderer->drawItem(text, nullptr);
GraphicalIndicatorRenderer* indicatorRenderer =
static_cast<GraphicalIndicatorRenderer*>(renderer->queryExtension(GraphicalIndicatorRenderer::extensionId()));
if (indicatorRenderer != NULL) {
indicatorRenderer->drawSubMenuIndicator();
}
}

void handleCommit(LcdMenu* menu) override {
LOG(F("ItemSubMenu::changeScreen"), text);
screen->setParent(menu->getScreen());
Expand Down
32 changes: 31 additions & 1 deletion src/ItemToggle.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "LcdMenu.h"
#include "MenuItem.h"
#include "display/GraphicalDisplayInterface.h"
#include "renderer/GraphicalMenuItem.h"
#include <utils/lcd_menu_utils.h>

/**
Expand All @@ -16,7 +18,7 @@
*
* Additionally to `text` this item has ON/OFF `enabled` state.
*/
class ItemToggle : public MenuItem {
class ItemToggle : public MenuItem, public GraphicalMenuItem {
private:
bool enabled = false;
const char* textOn = NULL;
Expand Down Expand Up @@ -82,6 +84,34 @@ class ItemToggle : public MenuItem {

const char* getTextOff() { return this->textOff; }

uint8_t measureGraphicalValueWidth(GraphicalDisplayInterface* display) const override {
if (display == NULL) {
return 0;
}
uint8_t toggleWidth = display->getFontHeight();
if (toggleWidth > 4) {
toggleWidth -= 4;
}
if (toggleWidth < 3) {
toggleWidth = 3;
}
uint8_t onWidth = display->getTextWidth(textOn == NULL ? "" : textOn);
uint8_t offWidth = display->getTextWidth(textOff == NULL ? "" : textOff);
uint8_t textWidth = onWidth > offWidth ? onWidth : offWidth;
return toggleWidth > textWidth ? toggleWidth : textWidth;
}

bool hasGraphicalToggle() const override { return true; }

bool graphicalToggleState() const override { return enabled; }

const void* queryCapability(uint8_t capabilityId) const override {
if (capabilityId == GraphicalMenuItem::capabilityId()) {
return static_cast<const GraphicalMenuItem*>(this);
}
return MenuItem::queryCapability(capabilityId);
}

void draw(MenuRenderer* renderer) override {
renderer->drawItem(text, enabled ? textOn : textOff);
};
Expand Down
12 changes: 11 additions & 1 deletion src/ItemValue.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "display/GraphicalDisplayInterface.h"
#include "utils/custom_printf.h"

#include "BaseItemZeroWidget.h"
Expand Down Expand Up @@ -31,6 +32,15 @@ class ItemValue : public BaseItemZeroWidget {
snprintf(buffer, ITEM_DRAW_BUFFER_SIZE, format, value);
renderer->drawItem(text, buffer);
}

uint8_t measureGraphicalValueWidth(GraphicalDisplayInterface* display) const override {
if (display == NULL) {
return 0;
}
char buffer[ITEM_DRAW_BUFFER_SIZE];
snprintf(buffer, ITEM_DRAW_BUFFER_SIZE, format, value);
return display->getTextWidth(buffer);
}
};

/**
Expand All @@ -54,4 +64,4 @@ inline MenuItem* ITEM_VALUE(
T& value,
const char* format = "%s") {
return new ItemValue<T>(text, value, format);
}
}
Loading
Loading