diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 49f5d2a7..1d173aaa 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -20,37 +20,6 @@ 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) { - // Draw battery outline (shared code) - BaseTheme::drawBatteryOutline(renderer, x, y, battWidth, rectHeight); - - const bool charging = gpio.isUsbConnected(); - - // The +1 is to round up, so that we always fill at least one pixel - const int maxFillWidth = battWidth - 5; - const int fillHeight = rectHeight - 4; - if (maxFillWidth <= 0 || fillHeight <= 0) { - return; - } - int filledWidth = percentage * maxFillWidth / 100 + 1; - if (filledWidth > maxFillWidth) { - filledWidth = maxFillWidth; - } - - // When charging, ensure minimum fill so lightning bolt is fully visible - constexpr int minFillForBolt = 8; - if (charging && filledWidth < minFillForBolt) { - filledWidth = std::min(minFillForBolt, maxFillWidth); - } - - renderer.fillRect(x + 2, y + 2, filledWidth, fillHeight); - - // Draw lightning bolt when charging (white/inverted on black fill for visibility) - if (charging) { - BaseTheme::drawBatteryLightningBolt(renderer, x + 4, y + 2); - } -} } // namespace void BaseTheme::drawBatteryOutline(const GfxRenderer& renderer, int x, int y, int battWidth, int rectHeight) { @@ -79,6 +48,33 @@ void BaseTheme::drawBatteryLightningBolt(const GfxRenderer& renderer, int boltX, renderer.drawLine(boltX + 1, boltY + 7, boltX + 2, boltY + 7, false); } +void BaseTheme::fillBatteryIcon(const GfxRenderer& renderer, Rect rect, uint16_t percentage) const { + const bool charging = gpio.isUsbConnected(); + + const int maxFillWidth = rect.width - 5; + const int fillHeight = rect.height - 4; + if (maxFillWidth <= 0 || fillHeight <= 0) { + return; + } + // +1 to round up so we always fill at least one pixel + int filledWidth = percentage * maxFillWidth / 100 + 1; + if (filledWidth > maxFillWidth) { + filledWidth = maxFillWidth; + } + + // When charging, ensure minimum fill so lightning bolt is fully visible + constexpr int minFillForBolt = 8; + if (charging && filledWidth < minFillForBolt) { + filledWidth = std::min(minFillForBolt, maxFillWidth); + } + + renderer.fillRect(rect.x + 2, rect.y + 2, filledWidth, fillHeight); + + if (charging) { + drawBatteryLightningBolt(renderer, rect.x + 4, rect.y + 2); + } +} + 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(); @@ -86,11 +82,12 @@ 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 + BaseTheme::batteryPercentSpacing + BaseMetrics::values.batteryWidth, - rect.y, percentageText.c_str()); + renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + rect.width, rect.y, percentageText.c_str()); } - drawBatteryIcon(renderer, rect.x, y, BaseMetrics::values.batteryWidth, rect.height, percentage); + const Rect iconRect{rect.x, y, rect.width, rect.height}; + drawBatteryOutline(renderer, rect.x, y, rect.width, rect.height); + fillBatteryIcon(renderer, iconRect, percentage); } void BaseTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { @@ -102,16 +99,12 @@ void BaseTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const b if (showPercentage) { const auto percentageText = std::to_string(percentage) + "%"; 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 - BaseTheme::batteryPercentSpacing, rect.y, textWidth, textHeight, false); - // Draw text to the left of the icon - renderer.drawText(SMALL_FONT_ID, rect.x - textWidth - BaseTheme::batteryPercentSpacing, rect.y, - percentageText.c_str()); + renderer.drawText(SMALL_FONT_ID, rect.x - textWidth - batteryPercentSpacing, rect.y, percentageText.c_str()); } - // Icon is already at correct position from rect.x - drawBatteryIcon(renderer, rect.x, y, BaseMetrics::values.batteryWidth, rect.height, percentage); + const Rect iconRect{rect.x, y, rect.width, rect.height}; + drawBatteryOutline(renderer, rect.x, y, rect.width, rect.height); + fillBatteryIcon(renderer, iconRect, percentage); } void BaseTheme::drawProgressBar(const GfxRenderer& renderer, Rect rect, const size_t current, diff --git a/src/components/themes/BaseTheme.h b/src/components/themes/BaseTheme.h index e24f8074..daf60a12 100644 --- a/src/components/themes/BaseTheme.h +++ b/src/components/themes/BaseTheme.h @@ -125,11 +125,12 @@ class BaseTheme { virtual ~BaseTheme() = default; // Component drawing methods - virtual void drawProgressBar(const GfxRenderer& renderer, Rect rect, size_t current, size_t total) const; - virtual void drawBatteryLeft(const GfxRenderer& renderer, Rect rect, - bool showPercentage = true) const; // Left aligned (reader mode) - virtual void drawBatteryRight(const GfxRenderer& renderer, Rect rect, - bool showPercentage = true) const; // Right aligned (UI headers) + void drawProgressBar(const GfxRenderer& renderer, Rect rect, size_t current, size_t total) const; + void drawBatteryLeft(const GfxRenderer& renderer, Rect rect, + bool showPercentage = true) const; // Left aligned (reader mode) + void drawBatteryRight(const GfxRenderer& renderer, Rect rect, + bool showPercentage = true) const; // Right aligned (UI headers) + virtual void fillBatteryIcon(const GfxRenderer& renderer, Rect rect, uint16_t percentage) const; virtual void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3, const char* btn4) const; virtual void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const; @@ -153,10 +154,9 @@ class BaseTheme { const std::function& rowIcon) const; virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) const; virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; - virtual void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, - const int pageCount, std::string title, const int paddingBottom = 0, - const int textYOffset = 0) const; - virtual void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const; + void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, const int pageCount, + std::string title, const int paddingBottom = 0, const int textYOffset = 0) const; + void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const; virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false, int contentStartX = 0, int contentWidth = 0) const; virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected, diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 994b047e..a1f687fd 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -41,30 +41,6 @@ constexpr int listIconSize = 24; constexpr int mainMenuColumns = 2; int coverWidth = 0; -void drawLyraBatteryIcon(const GfxRenderer& renderer, int x, int y, int battWidth, int rectHeight, - uint16_t percentage) { - BaseTheme::drawBatteryOutline(renderer, x, y, battWidth, rectHeight); - - const bool charging = gpio.isUsbConnected(); - - if (charging) { - // 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); - } - } -} - const uint8_t* iconForName(UIIcon icon, int size) { if (size == 24) { switch (icon) { @@ -107,35 +83,24 @@ const uint8_t* iconForName(UIIcon icon, int size) { } } // namespace -void LyraTheme::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(); +void LyraTheme::fillBatteryIcon(const GfxRenderer& renderer, Rect rect, uint16_t percentage) const { + const bool charging = gpio.isUsbConnected(); - if (showPercentage) { - const auto percentageText = std::to_string(percentage) + "%"; - renderer.drawText(SMALL_FONT_ID, rect.x + BaseTheme::batteryPercentSpacing + LyraMetrics::values.batteryWidth, - rect.y, percentageText.c_str()); + if (charging) { + // Solid fill when charging so lightning bolt is visible + renderer.fillRect(rect.x + 2, rect.y + 2, rect.width - 5, rect.height - 4); + drawBatteryLightningBolt(renderer, rect.x + 4, rect.y + 2); + } else { + if (percentage > 10) { + renderer.fillRect(rect.x + 2, rect.y + 2, 3, rect.height - 4); + } + if (percentage > 40) { + renderer.fillRect(rect.x + 6, rect.y + 2, 3, rect.height - 4); + } + if (percentage > 70) { + renderer.fillRect(rect.x + 10, rect.y + 2, 3, rect.height - 4); + } } - - drawLyraBatteryIcon(renderer, rect.x, rect.y + 6, LyraMetrics::values.batteryWidth, rect.height, percentage); -} - -void LyraTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { - // Right aligned: percentage on left, icon on right (UI headers) - const uint16_t percentage = powerManager.getBatteryPercentage(); - - if (showPercentage) { - const auto percentageText = std::to_string(percentage) + "%"; - 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 - BaseTheme::batteryPercentSpacing, rect.y, textWidth, textHeight, false); - // Draw text to the left of the icon - 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); } void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* title, const char* subtitle) const { diff --git a/src/components/themes/lyra/LyraTheme.h b/src/components/themes/lyra/LyraTheme.h index eec76b10..60ef3989 100644 --- a/src/components/themes/lyra/LyraTheme.h +++ b/src/components/themes/lyra/LyraTheme.h @@ -49,9 +49,7 @@ constexpr ThemeMetrics values = {.batteryWidth = 16, class LyraTheme : public BaseTheme { public: // Component drawing methods - // void drawProgressBar(const GfxRenderer& renderer, Rect rect, size_t current, size_t total) override; - void drawBatteryLeft(const GfxRenderer& renderer, Rect rect, bool showPercentage = true) const override; - void drawBatteryRight(const GfxRenderer& renderer, Rect rect, bool showPercentage = true) const override; + void fillBatteryIcon(const GfxRenderer& renderer, Rect rect, uint16_t percentage) const override; void drawHeader(const GfxRenderer& renderer, Rect rect, const char* title, const char* subtitle) const override; void drawSubHeader(const GfxRenderer& renderer, Rect rect, const char* label, const char* rightLabel = nullptr) const override; diff --git a/src/components/themes/roundedraff/RoundedRaffTheme.cpp b/src/components/themes/roundedraff/RoundedRaffTheme.cpp index 0307f3e4..29bb913e 100644 --- a/src/components/themes/roundedraff/RoundedRaffTheme.cpp +++ b/src/components/themes/roundedraff/RoundedRaffTheme.cpp @@ -1,7 +1,6 @@ #include "RoundedRaffTheme.h" #include -#include #include #include @@ -22,7 +21,6 @@ constexpr int kBottomRadius = 15; constexpr int kRowRadius = 20; constexpr int kInteractiveInsetX = 20; constexpr int kSelectableRowGap = 6; -constexpr int batteryPercentSpacing = 4; constexpr int kTitleFontId = UI_12_FONT_ID; // Requested main title size: 12px constexpr int kSubtitleFontId = SMALL_FONT_ID; // Requested subtitle size: 8px constexpr int kGuideFontId = SMALL_FONT_ID; // Closest available to requested 6px @@ -46,42 +44,6 @@ void drawScrollBar(const GfxRenderer& renderer, Rect rect, int itemCount, int pa renderer.fillRect(barX, thumbY, barW, thumbH); } -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); - - // The +1 is to round up, so that we always fill at least one pixel. - int filledWidth = percentage * (battWidth - 5) / 100 + 1; - if (filledWidth > battWidth - 5) { - filledWidth = battWidth - 5; // Ensure we don't overflow. - } - - renderer.fillRect(x + 2, y + 2, filledWidth, rectHeight - 4); -} - -void drawBatteryRightStable(const GfxRenderer& renderer, Rect iconRect, uint16_t percentage, bool showPercentage) { - // Match BaseTheme::drawBatteryRight layout, but use a stable percentage value for this render. - const int iconY = iconRect.y + 6; - - if (showPercentage) { - const auto percentageText = std::to_string(percentage) + "%"; - const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); - renderer.drawText(SMALL_FONT_ID, iconRect.x - textWidth - batteryPercentSpacing, iconRect.y, - percentageText.c_str()); - } - - drawBatteryIcon(renderer, iconRect.x, iconY, RoundedRaffMetrics::values.batteryWidth, iconRect.height, percentage); -} - std::string sanitizeButtonLabel(std::string label) { // Remove common directional prefixes/symbols (e.g. "<< Home", unsupported icon glyphs). while (!label.empty() && !std::isalnum(static_cast(label[0]))) { @@ -110,28 +72,27 @@ void RoundedRaffTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const const bool showBatteryPercentage = SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS; - const uint16_t percentage = powerManager.getBatteryPercentage(); const int batteryIconX = rect.x + rect.width - sidePadding - RoundedRaffMetrics::values.batteryWidth; + + // Reserve space for the widest possible percentage text to avoid title/battery overlap int batteryGroupLeftX = batteryIconX; if (showBatteryPercentage) { - const auto percentageText = std::to_string(percentage) + "%"; - batteryGroupLeftX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()) + batteryPercentSpacing; - - // Clear a fixed-width area for the battery percentage to avoid ghosting when digit count changes (e.g. 100% -> - // 99%). + // Clear a fixed-width area for the battery percentage to avoid ghosting when digit count changes (e.g. 100% -> 99%) const int maxTextWidth = renderer.getTextWidth(SMALL_FONT_ID, "100%"); + batteryGroupLeftX -= maxTextWidth + batteryPercentSpacing; + const int clearW = maxTextWidth + batteryPercentSpacing + RoundedRaffMetrics::values.batteryWidth; const int clearH = std::max(renderer.getTextHeight(SMALL_FONT_ID), RoundedRaffMetrics::values.batteryHeight + 8); renderer.fillRect(batteryIconX - maxTextWidth - batteryPercentSpacing, rect.y + 14, clearW, clearH, false); } - const int maxTextWidth = std::max(0, batteryGroupLeftX - 20 - titleX); - auto headerTitle = renderer.truncatedText(kTitleFontId, title, maxTextWidth, EpdFontFamily::BOLD); + const int maxTitleWidth = std::max(0, batteryGroupLeftX - 20 - titleX); + auto headerTitle = renderer.truncatedText(kTitleFontId, title, maxTitleWidth, EpdFontFamily::BOLD); renderer.drawText(kTitleFontId, titleX, titleY, headerTitle.c_str(), true, EpdFontFamily::BOLD); - drawBatteryRightStable(renderer, - Rect{batteryIconX, rect.y + 14, RoundedRaffMetrics::values.batteryWidth, - RoundedRaffMetrics::values.batteryHeight}, - percentage, showBatteryPercentage); + drawBatteryRight(renderer, + Rect{batteryIconX, rect.y + 14, RoundedRaffMetrics::values.batteryWidth, + RoundedRaffMetrics::values.batteryHeight}, + showBatteryPercentage); } void RoundedRaffTheme::drawTabBar(const GfxRenderer& renderer, Rect rect, const std::vector& tabs,