From 1bd7a1de67f7e3c96242ecef8ee6bcac61278531 Mon Sep 17 00:00:00 2001 From: Zach Nelson Date: Tue, 14 Apr 2026 17:05:06 -0500 Subject: [PATCH] refactor: Deduplicate battery drawing code and fix Lyra charging indicator (#1437) ## Summary **What is the goal of this PR?** Following up on #1427: - Extracted shared battery drawing logic, including lightning bolt, to reduce duplication. - In Lyra with segmented battery the lightning bolt was hard to see, so when charging Lyra now uses a solid battery fill. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ --- src/components/themes/BaseTheme.cpp | 60 ++++++++++++++---------- src/components/themes/BaseTheme.h | 5 ++ src/components/themes/lyra/LyraTheme.cpp | 57 ++++++++-------------- 3 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 6f7f84e4..0d6dcd33 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -16,24 +16,14 @@ // Internal constants namespace { -constexpr int batteryPercentSpacing = 4; constexpr int homeMenuMargin = 20; constexpr int homeMarginTop = 30; constexpr int subtitleY = 738; // Helper: draw battery icon at given position void drawBatteryIcon(const GfxRenderer& renderer, int x, int y, int battWidth, int rectHeight, uint16_t percentage) { - // Top line - renderer.drawLine(x + 1, y, x + battWidth - 3, y); - // Bottom line - renderer.drawLine(x + 1, y + rectHeight - 1, x + battWidth - 3, y + rectHeight - 1); - // Left line - renderer.drawLine(x, y + 1, x, y + rectHeight - 2); - // Battery end - renderer.drawLine(x + battWidth - 2, y + 1, x + battWidth - 2, y + rectHeight - 2); - renderer.drawPixel(x + battWidth - 1, y + 3); - renderer.drawPixel(x + battWidth - 1, y + rectHeight - 4); - renderer.drawLine(x + battWidth - 0, y + 4, x + battWidth - 0, y + rectHeight - 5); + // Draw battery outline (shared code) + BaseTheme::drawBatteryOutline(renderer, x, y, battWidth, rectHeight); const bool charging = gpio.isUsbConnected(); @@ -58,20 +48,37 @@ void drawBatteryIcon(const GfxRenderer& renderer, int x, int y, int battWidth, i // Draw lightning bolt when charging (white/inverted on black fill for visibility) if (charging) { - const int boltX = x + 4; - const int boltY = y + 2; - renderer.drawLine(boltX + 4, boltY + 0, boltX + 5, boltY + 0, false); - renderer.drawLine(boltX + 3, boltY + 1, boltX + 4, boltY + 1, false); - renderer.drawLine(boltX + 2, boltY + 2, boltX + 5, boltY + 2, false); - renderer.drawLine(boltX + 3, boltY + 3, boltX + 4, boltY + 3, false); - renderer.drawLine(boltX + 2, boltY + 4, boltX + 3, boltY + 4, false); - renderer.drawLine(boltX + 1, boltY + 5, boltX + 4, boltY + 5, false); - renderer.drawLine(boltX + 2, boltY + 6, boltX + 3, boltY + 6, false); - renderer.drawLine(boltX + 1, boltY + 7, boltX + 2, boltY + 7, false); + BaseTheme::drawBatteryLightningBolt(renderer, x + 4, y + 2); } } } // namespace +void BaseTheme::drawBatteryOutline(const GfxRenderer& renderer, int x, int y, int battWidth, int rectHeight) { + // Top line + renderer.drawLine(x + 1, y, x + battWidth - 3, y); + // Bottom line + renderer.drawLine(x + 1, y + rectHeight - 1, x + battWidth - 3, y + rectHeight - 1); + // Left line + renderer.drawLine(x, y + 1, x, y + rectHeight - 2); + // Battery end + renderer.drawLine(x + battWidth - 2, y + 1, x + battWidth - 2, y + rectHeight - 2); + renderer.drawPixel(x + battWidth - 1, y + 3); + renderer.drawPixel(x + battWidth - 1, y + rectHeight - 4); + renderer.drawLine(x + battWidth - 0, y + 4, x + battWidth - 0, y + rectHeight - 5); +} + +void BaseTheme::drawBatteryLightningBolt(const GfxRenderer& renderer, int boltX, int boltY) { + // Draw lightning bolt (white/inverted on black fill for visibility) + renderer.drawLine(boltX + 4, boltY + 0, boltX + 5, boltY + 0, false); + renderer.drawLine(boltX + 3, boltY + 1, boltX + 4, boltY + 1, false); + renderer.drawLine(boltX + 2, boltY + 2, boltX + 5, boltY + 2, false); + renderer.drawLine(boltX + 3, boltY + 3, boltX + 4, boltY + 3, false); + renderer.drawLine(boltX + 2, boltY + 4, boltX + 3, boltY + 4, false); + renderer.drawLine(boltX + 1, boltY + 5, boltX + 4, boltY + 5, false); + renderer.drawLine(boltX + 2, boltY + 6, boltX + 3, boltY + 6, false); + renderer.drawLine(boltX + 1, boltY + 7, boltX + 2, boltY + 7, false); +} + void BaseTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { // Left aligned: icon on left, percentage on right (reader mode) const uint16_t percentage = powerManager.getBatteryPercentage(); @@ -79,8 +86,8 @@ void BaseTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bo if (showPercentage) { const auto percentageText = std::to_string(percentage) + "%"; - renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + BaseMetrics::values.batteryWidth, rect.y, - percentageText.c_str()); + renderer.drawText(SMALL_FONT_ID, rect.x + BaseTheme::batteryPercentSpacing + BaseMetrics::values.batteryWidth, + rect.y, percentageText.c_str()); } drawBatteryIcon(renderer, rect.x, y, BaseMetrics::values.batteryWidth, rect.height, percentage); @@ -97,9 +104,10 @@ void BaseTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const b const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); // Clear the area where we're going to draw the text to prevent ghosting const auto textHeight = renderer.getTextHeight(SMALL_FONT_ID); - renderer.fillRect(rect.x - textWidth - batteryPercentSpacing, rect.y, textWidth, textHeight, false); + renderer.fillRect(rect.x - textWidth - BaseTheme::batteryPercentSpacing, rect.y, textWidth, textHeight, false); // Draw text to the left of the icon - renderer.drawText(SMALL_FONT_ID, rect.x - textWidth - batteryPercentSpacing, rect.y, percentageText.c_str()); + renderer.drawText(SMALL_FONT_ID, rect.x - textWidth - BaseTheme::batteryPercentSpacing, rect.y, + percentageText.c_str()); } // Icon is already at correct position from rect.x diff --git a/src/components/themes/BaseTheme.h b/src/components/themes/BaseTheme.h index 6878f555..5476c085 100644 --- a/src/components/themes/BaseTheme.h +++ b/src/components/themes/BaseTheme.h @@ -142,4 +142,9 @@ class BaseTheme { virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const; virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected) const; virtual bool showsFileIcons() const { return false; } + + // Shared constants and helpers for battery drawing (used by all themes) + static constexpr int batteryPercentSpacing = 4; + static void drawBatteryOutline(const GfxRenderer& renderer, int x, int y, int battWidth, int rectHeight); + static void drawBatteryLightningBolt(const GfxRenderer& renderer, int boltX, int boltY); }; diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 0eee84a2..e2451f68 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -30,7 +30,6 @@ // Internal constants namespace { -constexpr int batteryPercentSpacing = 4; constexpr int hPaddingInSelection = 8; constexpr int cornerRadius = 6; constexpr int topHintButtonY = 345; @@ -45,42 +44,25 @@ int coverWidth = 0; void drawLyraBatteryIcon(const GfxRenderer& renderer, int x, int y, int battWidth, int rectHeight, uint16_t percentage) { - // Top line - renderer.drawLine(x + 1, y, x + battWidth - 3, y); - // Bottom line - renderer.drawLine(x + 1, y + rectHeight - 1, x + battWidth - 3, y + rectHeight - 1); - // Left line - renderer.drawLine(x, y + 1, x, y + rectHeight - 2); - // Battery end - renderer.drawLine(x + battWidth - 2, y + 1, x + battWidth - 2, y + rectHeight - 2); - renderer.drawPixel(x + battWidth - 1, y + 3); - renderer.drawPixel(x + battWidth - 1, y + rectHeight - 4); - renderer.drawLine(x + battWidth - 0, y + 4, x + battWidth - 0, y + rectHeight - 5); + BaseTheme::drawBatteryOutline(renderer, x, y, battWidth, rectHeight); const bool charging = gpio.isUsbConnected(); - // Draw bars - if (percentage > 10 || charging) { - renderer.fillRect(x + 2, y + 2, 3, rectHeight - 4); - } - if (percentage > 40 || charging) { - renderer.fillRect(x + 6, y + 2, 3, rectHeight - 4); - } - if (percentage > 70) { - renderer.fillRect(x + 10, y + 2, 3, rectHeight - 4); - } - if (charging) { - const int boltX = x + 4; - const int boltY = y + 2; - renderer.drawLine(boltX + 4, boltY + 0, boltX + 5, boltY + 0, false); - renderer.drawLine(boltX + 3, boltY + 1, boltX + 4, boltY + 1, false); - renderer.drawLine(boltX + 2, boltY + 2, boltX + 5, boltY + 2, false); - renderer.drawLine(boltX + 3, boltY + 3, boltX + 4, boltY + 3, false); - renderer.drawLine(boltX + 2, boltY + 4, boltX + 3, boltY + 4, false); - renderer.drawLine(boltX + 1, boltY + 5, boltX + 4, boltY + 5, false); - renderer.drawLine(boltX + 2, boltY + 6, boltX + 3, boltY + 6, false); - renderer.drawLine(boltX + 1, boltY + 7, boltX + 2, boltY + 7, false); + // Draw solid fill when charging so lightning bolt is visible + renderer.fillRect(x + 2, y + 2, battWidth - 5, rectHeight - 4); + BaseTheme::drawBatteryLightningBolt(renderer, x + 4, y + 2); + } else { + // Draw bars when not charging + if (percentage > 10) { + renderer.fillRect(x + 2, y + 2, 3, rectHeight - 4); + } + if (percentage > 40) { + renderer.fillRect(x + 6, y + 2, 3, rectHeight - 4); + } + if (percentage > 70) { + renderer.fillRect(x + 10, y + 2, 3, rectHeight - 4); + } } } @@ -132,8 +114,8 @@ void LyraTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bo if (showPercentage) { const auto percentageText = std::to_string(percentage) + "%"; - renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + LyraMetrics::values.batteryWidth, rect.y, - percentageText.c_str()); + renderer.drawText(SMALL_FONT_ID, rect.x + BaseTheme::batteryPercentSpacing + LyraMetrics::values.batteryWidth, + rect.y, percentageText.c_str()); } drawLyraBatteryIcon(renderer, rect.x, rect.y + 6, LyraMetrics::values.batteryWidth, rect.height, percentage); @@ -148,9 +130,10 @@ void LyraTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const b const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); // Clear the area where we're going to draw the text to prevent ghosting const auto textHeight = renderer.getTextHeight(SMALL_FONT_ID); - renderer.fillRect(rect.x - textWidth - batteryPercentSpacing, rect.y, textWidth, textHeight, false); + renderer.fillRect(rect.x - textWidth - BaseTheme::batteryPercentSpacing, rect.y, textWidth, textHeight, false); // Draw text to the left of the icon - renderer.drawText(SMALL_FONT_ID, rect.x - textWidth - batteryPercentSpacing, rect.y, percentageText.c_str()); + renderer.drawText(SMALL_FONT_ID, rect.x - textWidth - BaseTheme::batteryPercentSpacing, rect.y, + percentageText.c_str()); } drawLyraBatteryIcon(renderer, rect.x, rect.y + 6, LyraMetrics::values.batteryWidth, rect.height, percentage);