From 1facf55fbda2d765d84c79bb79a0459075183de1 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Fri, 23 Jan 2026 08:23:19 +0500 Subject: [PATCH] refactor: Update popup dimensions and styles; add rounded rectangle drawing functions --- .../Epub/parsers/ChapterHtmlSlimParser.cpp | 5 +- lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h | 6 + lib/GfxRenderer/GfxRenderer.cpp | 165 ++++++++++++++++++ lib/GfxRenderer/GfxRenderer.h | 4 + src/ScreenComponents.cpp | 31 ++-- src/ScreenComponents.h | 7 +- src/activities/boot_sleep/SleepActivity.cpp | 5 +- src/activities/reader/EpubReaderActivity.cpp | 2 +- 8 files changed, 200 insertions(+), 25 deletions(-) diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 1d7e2ab3..e93b3a72 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -10,9 +10,6 @@ const char* HEADER_TAGS[] = {"h1", "h2", "h3", "h4", "h5", "h6"}; constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]); -// Minimum file size (in bytes) to show progress bar - smaller chapters don't benefit from it -constexpr size_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB - const char* BLOCK_TAGS[] = {"p", "li", "div", "br", "blockquote"}; constexpr int NUM_BLOCK_TAGS = sizeof(BLOCK_TAGS) / sizeof(BLOCK_TAGS[0]); @@ -305,7 +302,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() { // Update progress (call every 10% change to avoid too frequent updates) // Only show progress for larger chapters where rendering overhead is worth it bytesRead += len; - if (progressFn && totalSize >= MIN_SIZE_FOR_PROGRESS) { + if (progressFn && totalSize >= ChapterHtmlSlimParser::MIN_SIZE_FOR_PROGRESS) { const int progress = static_cast((bytesRead * 100) / totalSize); if (lastProgress / 10 != progress / 10) { lastProgress = progress; diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h index 5355211a..89355e50 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -15,6 +16,11 @@ class GfxRenderer; #define MAX_WORD_SIZE 200 class ChapterHtmlSlimParser { + public: + // Minimum file size (in bytes) to show progress bar - smaller chapters don't benefit from it. + static constexpr size_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB + + private: const std::string& filepath; GfxRenderer& renderer; std::function)> completePageFn; diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 08420bf9..7a343ad2 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -2,6 +2,19 @@ #include +namespace { +int clampRoundedRadius(const int width, const int height, int radius) { + if (radius <= 0) { + return 0; + } + const int maxRadius = (std::min(width, height) - 1) / 2; + if (maxRadius <= 0) { + return 0; + } + return radius > maxRadius ? maxRadius : radius; +} +} // namespace + void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); } void GfxRenderer::rotateCoordinates(const int x, const int y, int* rotatedX, int* rotatedY) const { @@ -138,6 +151,158 @@ void GfxRenderer::drawRect(const int x, const int y, const int width, const int drawLine(x, y, x, y + height - 1, state); } +void GfxRenderer::drawRoundedRect(const int x, const int y, const int width, const int height, int radius, + const bool state) const { + if (width <= 0 || height <= 0) { + return; + } + + radius = clampRoundedRadius(width, height, radius); + if (radius <= 0) { + drawRect(x, y, width, height, state); + return; + } + + const int right = x + width - 1; + const int bottom = y + height - 1; + const int x0 = x + radius; + const int x1 = right - radius; + const int y0 = y + radius; + const int y1 = bottom - radius; + + if (x0 <= x1) { + drawLine(x0, y, x1, y, state); + drawLine(x0, bottom, x1, bottom, state); + } + if (y0 <= y1) { + drawLine(x, y0, x, y1, state); + drawLine(right, y0, right, y1, state); + } + + const int cxLeft = x + radius; + const int cxRight = right - radius; + const int cyTop = y + radius; + const int cyBottom = bottom - radius; + + auto plotCornerPoints = [&](const int offsetX, const int offsetY) { + drawPixel(cxLeft - offsetX, cyTop - offsetY, state); + drawPixel(cxRight + offsetX, cyTop - offsetY, state); + drawPixel(cxRight + offsetX, cyBottom + offsetY, state); + drawPixel(cxLeft - offsetX, cyBottom + offsetY, state); + + if (offsetX == offsetY) { + return; + } + + drawPixel(cxLeft - offsetY, cyTop - offsetX, state); + drawPixel(cxRight + offsetY, cyTop - offsetX, state); + drawPixel(cxRight + offsetY, cyBottom + offsetX, state); + drawPixel(cxLeft - offsetY, cyBottom + offsetX, state); + }; + + int f = 1 - radius; + int ddF_x = 1; + int ddF_y = -2 * radius; + int offsetX = 0; + int offsetY = radius; + + while (offsetX <= offsetY) { + plotCornerPoints(offsetX, offsetY); + if (f >= 0) { + offsetY--; + ddF_y += 2; + f += ddF_y; + } + offsetX++; + ddF_x += 2; + f += ddF_x; + plotCornerPoints(offsetX, offsetY); + } +} + +void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, const int height, int radius, + const bool state) const { + if (width <= 0 || height <= 0) { + return; + } + + radius = clampRoundedRadius(width, height, radius); + if (radius <= 0) { + fillRect(x, y, width, height, state); + return; + } + + const int right = x + width - 1; + const int bottom = y + height - 1; + const int innerTop = y + radius; + const int innerBottom = bottom - radius; + + if (innerBottom >= innerTop) { + fillRect(x, innerTop, width, innerBottom - innerTop + 1, state); + } + + int f = 1 - radius; + int ddF_x = 1; + int ddF_y = -2 * radius; + int offsetX = 0; + int offsetY = radius; + + while (offsetX <= offsetY) { + int left = x + radius - offsetX; + int rightSpan = right - radius + offsetX; + int topY = innerTop - offsetY; + int bottomY = innerBottom + offsetY; + drawLine(left, topY, rightSpan, topY, state); + drawLine(left, bottomY, rightSpan, bottomY, state); + + if (offsetX != offsetY) { + left = x + radius - offsetY; + rightSpan = right - radius + offsetY; + topY = innerTop - offsetX; + bottomY = innerBottom + offsetX; + drawLine(left, topY, rightSpan, topY, state); + drawLine(left, bottomY, rightSpan, bottomY, state); + } + + if (f >= 0) { + offsetY--; + ddF_y += 2; + f += ddF_y; + } + offsetX++; + ddF_x += 2; + f += ddF_x; + } +} + +void GfxRenderer::drawRoundedRectFrame(const int x, const int y, const int width, const int height, int radius, + int thickness, const bool frameState, const bool fillState) const { + if (width <= 0 || height <= 0) { + return; + } + + const int maxThickness = (std::min(width, height) - 1) / 2; + thickness = std::min(thickness, maxThickness); + if (thickness <= 0) { + fillRoundedRect(x, y, width, height, radius, fillState); + return; + } + + fillRoundedRect(x, y, width, height, radius, frameState); + + const int inset = thickness * 2; + const int innerX = x + thickness; + const int innerY = y + thickness; + const int innerW = width - inset; + const int innerH = height - inset; + if (innerW <= 0 || innerH <= 0) { + return; + } + + const int innerRadius = radius - thickness; + fillRoundedRect(innerX, innerY, innerW, innerH, innerRadius, fillState); +} + void GfxRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const { for (int fillY = y; fillY < y + height; fillY++) { drawLine(x, fillY, x + width - 1, fillY, state); diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index dc0dfa03..1c6fc4ea 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -64,6 +64,10 @@ class GfxRenderer { void drawPixel(int x, int y, bool state = true) const; void drawLine(int x1, int y1, int x2, int y2, bool state = true) const; void drawRect(int x, int y, int width, int height, bool state = true) const; + void drawRoundedRect(int x, int y, int width, int height, int radius, bool state = true) const; + void fillRoundedRect(int x, int y, int width, int height, int radius, bool state = true) const; + void drawRoundedRectFrame(int x, int y, int width, int height, int radius, int thickness = 1, + bool frameState = true, bool fillState = false) const; void fillRect(int x, int y, int width, int height, bool state = true) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, diff --git a/src/ScreenComponents.cpp b/src/ScreenComponents.cpp index 042ff735..e3eed0d6 100644 --- a/src/ScreenComponents.cpp +++ b/src/ScreenComponents.cpp @@ -42,22 +42,23 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4); } -ScreenComponents::PopupLayout ScreenComponents::drawPopup(const GfxRenderer& renderer, const char* message, const int y, - const int minWidth, const int minHeight) { - constexpr int margin = 15; +ScreenComponents::PopupLayout ScreenComponents::drawPopup(const GfxRenderer& renderer, const char* message) { + constexpr int margin = 12; + constexpr int frameThickness = 4; + constexpr int frameInset = frameThickness / 2; + constexpr int frameRadius = 8; + constexpr int bottomOffset = 20; const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, message, EpdFontFamily::BOLD); - const int contentWidth = std::max(textWidth, minWidth); - const int contentHeight = renderer.getLineHeight(UI_12_FONT_ID) + margin * 2; - const int w = contentWidth + margin * 2 + 50; - // const int x = (renderer.getScreenWidth() - w) / 2; - const int x = renderer.getScreenWidth() - w - margin; - const int h = std::max(contentHeight, minHeight); - renderer.fillRect(x - 2, y - 2, w + 4, h + 4, true); - renderer.fillRect(x + 2, y + 2, w - 4, h - 4, false); + const int w = std::max(textWidth, POPUP_DEFAULT_MIN_WIDTH) + margin * 2; + const int h = std::max(renderer.getLineHeight(UI_12_FONT_ID) + margin * 2, POPUP_DEFAULT_MIN_HEIGHT); + const int x = std::max(0, (renderer.getScreenWidth() - w) / 2); + const int y = std::max(0, renderer.getScreenHeight() - h - margin - bottomOffset); - const int textX = x + margin + (contentWidth - textWidth) / 2; - // renderer.drawText(UI_12_FONT_ID, textX, y + margin + 4, message, true, EpdFontFamily::BOLD); - renderer.drawText(NOTOSANS_18_FONT_ID, textX, y + margin + 4, message, true, EpdFontFamily::BOLD); + renderer.drawRoundedRectFrame(x - frameInset, y - frameInset, w + frameInset * 2, h + frameInset * 2, + frameRadius, frameThickness, true, false); + + const int textX = x + (w - textWidth) / 2; + renderer.drawText(UI_12_FONT_ID, textX, y + margin - 2, message, true, EpdFontFamily::BOLD); renderer.displayBuffer(); return {x, y, w, h}; } @@ -66,7 +67,7 @@ void ScreenComponents::fillPopupProgress(const GfxRenderer& renderer, const Popu constexpr int barWidth = POPUP_DEFAULT_MIN_WIDTH; constexpr int barHeight = POPUP_DEFAULT_BAR_HEIGHT; const int barX = layout.x + (layout.width - barWidth) / 2; - const int barY = layout.y + layout.height - 15; + const int barY = layout.y + layout.height - 13; int fillWidth = barWidth * progress / 100; if (fillWidth < 0) { diff --git a/src/ScreenComponents.h b/src/ScreenComponents.h index fba62251..00f3ee81 100644 --- a/src/ScreenComponents.h +++ b/src/ScreenComponents.h @@ -13,8 +13,8 @@ struct TabInfo { class ScreenComponents { public: - static constexpr int POPUP_DEFAULT_MIN_WIDTH = 200; - static constexpr int POPUP_DEFAULT_MIN_HEIGHT = 72; + static constexpr int POPUP_DEFAULT_MIN_WIDTH = 130; + static constexpr int POPUP_DEFAULT_MIN_HEIGHT = 50; static constexpr int POPUP_DEFAULT_BAR_HEIGHT = 4; struct PopupLayout { @@ -26,8 +26,7 @@ class ScreenComponents { static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true); - static PopupLayout drawPopup(const GfxRenderer& renderer, const char* message, int y = 125, - int minWidth = POPUP_DEFAULT_MIN_WIDTH, int minHeight = POPUP_DEFAULT_MIN_HEIGHT); + static PopupLayout drawPopup(const GfxRenderer& renderer, const char* message); static void fillPopupProgress(const GfxRenderer& renderer, const PopupLayout& layout, int progress); diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 7da55d8d..738c1c0c 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -16,7 +16,10 @@ void SleepActivity::onEnter() { Activity::onEnter(); - ScreenComponents::drawPopup(renderer, "Entering Sleep..."); + ScreenComponents::drawPopup(renderer, "Sleeping"); + + // debug delay to see sleep screen + delay(5000); if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::BLANK) { return renderBlankSleepScreen(); diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index e77e559b..196a7455 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -286,7 +286,7 @@ void EpubReaderActivity::renderScreen() { Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis()); pagesUntilFullRefresh = 0; - const auto popupLayout = ScreenComponents::drawPopup(renderer, "Indexing..."); + const auto popupLayout = ScreenComponents::drawPopup(renderer, "Rendering"); const auto progressCallback = [this, popupLayout](int progress) { ScreenComponents::fillPopupProgress(renderer, popupLayout, progress); };