diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 474511b..f078972 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -250,6 +250,55 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int } } +// Use Bayer matrix 4x4 dithering to fill the rectangle with a grey level - 0 white to 15 black +void GfxRenderer::fillRectGrey(const int x, const int y, const int width, const int height, const int greyLevel) const { + static constexpr uint8_t bayer4x4[4][4] = { + {0, 8, 2, 10}, + {12, 4, 14, 6}, + {3, 11, 1, 9}, + {15, 7, 13, 5}, + }; + static constexpr int matrixSize = 4; + static constexpr int matrixLevels = matrixSize * matrixSize; + + const int normalizedGrey = (greyLevel * 255) / (matrixLevels - 1); + const int clampedGrey = std::max(0, std::min(normalizedGrey, 255)); + const int threshold = (clampedGrey * (matrixLevels + 1)) / 256; + + for (int dy = 0; dy < height; ++dy) { + const int screenY = y + dy; + const int matrixY = screenY & (matrixSize - 1); + for (int dx = 0; dx < width; ++dx) { + const int screenX = x + dx; + const int matrixX = screenX & (matrixSize - 1); + const uint8_t patternValue = bayer4x4[matrixY][matrixX]; + const bool black = patternValue < threshold; + drawPixel(screenX, screenY, black); + } + } +} + +// Color -1 white, 0 clear, 1 black +void GfxRenderer::fillArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir, const int insideColor, const int outsideColor) const { + const int radiusSq = maxRadius * maxRadius; + for (int dy = 0; dy <= maxRadius; ++dy) { + for (int dx = 0; dx <= maxRadius; ++dx) { + const int distSq = dx * dx + dy * dy; + const int px = cx + xDir * dx; + const int py = cy + yDir * dy; + if (distSq > radiusSq) { + if (outsideColor != 0) { + drawPixel(px, py, outsideColor == 1); + } + } else { + if (insideColor != 0) { + drawPixel(px, py, insideColor == 1); + } + } + } + } +}; + void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const { // Flip X and Y for portrait mode einkDisplay.drawImage(bitmap, y, x, height, width); diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 7c36533..32ba525 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -51,6 +51,8 @@ class GfxRenderer { void drawArc(int maxRadius, int cx, int cy, int xDir, int yDir, int lineWidth, bool state) const; void drawRoundedRect(int x, int y, int width, int height, int lineWidth, int cornerRadius, bool state) const; void fillRect(int x, int y, int width, int height, bool state = true) const; + void fillRectGrey(int x, int y, int width, int height, int greyLevel) const; + void fillArc(int maxRadius, int cx, int cy, int xDir, int yDir, int insideColor, int outsideColor) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; void drawIcon(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) const; diff --git a/src/activities/home/GridBrowserActivity.cpp b/src/activities/home/GridBrowserActivity.cpp index cd642f5..8823dc5 100644 --- a/src/activities/home/GridBrowserActivity.cpp +++ b/src/activities/home/GridBrowserActivity.cpp @@ -17,7 +17,7 @@ constexpr int TILE_PADDING = 5; constexpr int THUMB_W = 90; constexpr int THUMB_H = 120; constexpr int TILE_TEXT_H = 60; -constexpr int gridLeftOffset = 45; +constexpr int gridLeftOffset = 37; constexpr int gridTopOffset = 125; } // namespace @@ -61,7 +61,7 @@ void GridBrowserActivity::loadFiles() { std::string ext = filename.substr(dot); basename = filename.substr(0, dot); // lowercase ext for case-insensitive compare - for (char &c : ext) c = (char)tolower(c); + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); if (ext == ".epub") { type = F_EPUB; } else if (ext == ".thumb.bmp") { @@ -136,7 +136,7 @@ void GridBrowserActivity::loop() { } } else if (inputManager.wasPressed(InputManager::BTN_BACK)) { if (basepath != "/") { - basepath = basepath.substr(0, basepath.rfind('/')); + basepath.resize(basepath.rfind('/')); if (basepath.empty()) basepath = "/"; loadFiles(); renderRequired = true; @@ -197,9 +197,9 @@ void GridBrowserActivity::render(bool clear) const { auto folderName = basepath == "/" ? "SD card" : basepath.substr(basepath.rfind('/') + 1).c_str(); drawFullscreenWindowFrame(renderer, folderName); } - bool hasGeyscaleBitmaps = false; if (!files.empty()) { + bool hasGeyscaleBitmaps = false; for (int pass = 0; pass < 3; pass++) { if (pass > 0) { renderer.clearScreen(0x00); @@ -255,7 +255,7 @@ void GridBrowserActivity::render(bool clear) const { } } else if (pass == 1) { renderer.copyGrayscaleLsbBuffers(); - } else if (pass == 2) { + } else { renderer.copyGrayscaleMsbBuffers(); renderer.displayGrayBuffer(); renderer.setRenderMode(GfxRenderer::BW); diff --git a/src/activities/home/GridBrowserActivity.h b/src/activities/home/GridBrowserActivity.h index c3a0cd9..f950cf4 100644 --- a/src/activities/home/GridBrowserActivity.h +++ b/src/activities/home/GridBrowserActivity.h @@ -30,7 +30,7 @@ class GridBrowserActivity final : public Activity { std::vector files; int selectorIndex = 0; int previousSelectorIndex = -1; - int page; + int page = 0; bool updateRequired = false; bool renderRequired = false; const std::function onSelect; diff --git a/src/activities/util/Window.cpp b/src/activities/util/Window.cpp index d3774d6..c701d4d 100644 --- a/src/activities/util/Window.cpp +++ b/src/activities/util/Window.cpp @@ -14,6 +14,13 @@ constexpr int batteryHeight = 10; void drawWindowFrame(GfxRenderer& renderer, int xMargin, int y, int height, bool hasShadow, const char* title) { const int windowWidth = GfxRenderer::getScreenWidth() - 2 * xMargin; + + if (title) { // Header background + renderer.fillRectGrey(xMargin, y, windowWidth, windowHeaderHeight, 5); + renderer.fillArc(windowCornerRadius, xMargin + windowCornerRadius, y + windowCornerRadius, -1, -1, 0, -1); // TL + renderer.fillArc(windowCornerRadius, windowWidth + xMargin - windowCornerRadius, y + windowCornerRadius, 1, -1, 0, -1); // TR + } + renderer.drawRoundedRect(xMargin, y, windowWidth, height, windowBorderWidth, windowCornerRadius, true); if (hasShadow) { @@ -43,7 +50,6 @@ void drawStatusBar(GfxRenderer& renderer) { // Left aligned battery icon and percentage const uint16_t percentage = battery.readPercentage(); const auto percentageText = std::to_string(percentage) + "%"; - const auto percentageTextWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); renderer.drawText(SMALL_FONT_ID, fullscreenWindowMargin + batteryWidth + 5, textY, percentageText.c_str()); // 1 column on left, 2 columns on right, 5 columns of battery body