Movable status bar and dual-edge progress bars

This commit is contained in:
jpirnay
2026-05-06 23:09:26 +02:00
parent 464846fd75
commit 4f140afd10
14 changed files with 475 additions and 168 deletions
+64 -35
View File
@@ -16,6 +16,7 @@
#include <esp_heap_caps.h>
#include <esp_system.h>
#include <algorithm>
#include <memory>
#include "CrossPointSettings.h"
@@ -111,6 +112,27 @@ const char* orientationToString(const GfxRenderer::Orientation orientation) {
return "Unknown";
}
int getImageOnlyPageYOffset(const Page& page, const int viewportHeight) {
if (viewportHeight <= 0 || page.elements.empty()) {
return 0;
}
const bool imageOnlyPage = std::all_of(
page.elements.begin(), page.elements.end(),
[](const std::shared_ptr<PageElement>& element) { return element && element->getTag() == TAG_PageImage; });
if (!imageOnlyPage) {
return 0;
}
int16_t imgX, imgY, imgW, imgH;
if (!page.getImageBoundingBox(imgX, imgY, imgW, imgH) || imgH >= viewportHeight) {
return 0;
}
const int centeredTop = (viewportHeight - imgH) / 2;
return std::max(0, centeredTop - static_cast<int>(imgY));
}
} // namespace
void EpubReaderActivity::onEnter() {
@@ -241,7 +263,7 @@ void EpubReaderActivity::loop() {
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) ||
mappedInput.wasReleased(MappedInputManager::Button::Back)) {
buttonEvents.drain();
automaticPageTurnActive = false;
stopAutomaticPageTurn();
// updates chapter title space to indicate page turn disabled
requestUpdate();
return;
@@ -1009,9 +1031,30 @@ void EpubReaderActivity::applyTextDarkness(const uint8_t textDarkness) {
requestUpdate();
}
void EpubReaderActivity::stopAutomaticPageTurn() {
if (!automaticPageTurnActive) {
return;
}
automaticPageTurnActive = false;
if (UITheme::getStatusBarHeight(true) == UITheme::getStatusBarHeight()) {
return;
}
// Preserve current reading position so we can restore after reflow.
RenderLock lock(*this);
if (section) {
cachedSpineIndex = currentSpineIndex;
cachedChapterTotalPageCount = section->pageCount;
nextPageNumber = section->currentPage;
}
section.reset();
}
void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption) {
if (selectedPageTurnOption == 0 || selectedPageTurnOption >= std::size(PAGE_TURN_LABELS)) {
automaticPageTurnActive = false;
stopAutomaticPageTurn();
return;
}
@@ -1020,9 +1063,8 @@ void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption
pageTurnDuration = (1UL * 60 * 1000) / PAGE_TURN_LABELS[selectedPageTurnOption];
automaticPageTurnActive = true;
const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight();
// resets cached section so that space is reserved for auto page turn indicator when None or progress bar only
if (statusBarHeight == 0 || statusBarHeight == UITheme::getInstance().getProgressBarHeight()) {
// Reset cached section when automatic page turn adds a forced status item band.
if (UITheme::getStatusBarHeight(true) != UITheme::getStatusBarHeight()) {
// Preserve current reading position so we can restore after reflow.
RenderLock lock(*this);
if (section) {
@@ -1223,21 +1265,13 @@ void EpubReaderActivity::render(RenderLock&& lock) {
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom,
&orientedMarginLeft);
orientedMarginTop += SETTINGS.screenMargin;
const int statusBarTopHeight = UITheme::getStatusBarTopHeight(automaticPageTurnActive);
const int statusBarBottomHeight = UITheme::getStatusBarBottomHeight(automaticPageTurnActive);
orientedMarginTop += std::max(static_cast<int>(SETTINGS.screenMargin), statusBarTopHeight);
orientedMarginLeft += SETTINGS.screenMargin;
orientedMarginRight += SETTINGS.screenMargin;
const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight();
// reserves space for automatic page turn indicator when no status bar or progress bar only
if (automaticPageTurnActive &&
(statusBarHeight == 0 || statusBarHeight == UITheme::getInstance().getProgressBarHeight())) {
orientedMarginBottom +=
std::max(SETTINGS.screenMargin,
static_cast<uint8_t>(statusBarHeight + UITheme::getInstance().getMetrics().statusBarVerticalMargin));
} else {
orientedMarginBottom += std::max(SETTINGS.screenMargin, statusBarHeight);
}
orientedMarginBottom += std::max(static_cast<int>(SETTINGS.screenMargin), statusBarBottomHeight);
const uint16_t viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight;
const uint16_t viewportHeight = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom;
@@ -1485,10 +1519,13 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
fcm->resetStats();
logReaderMemSnapshot("prewarm_begin");
const int viewportHeight = std::max(0, renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom);
const int contentTop = orientedMarginTop + getImageOnlyPageYOffset(*page, viewportHeight);
// Font prewarm: scan pass accumulates text, then prewarm, then real render
const uint32_t heapBefore = esp_get_free_heap_size();
auto scope = fcm->createPrewarmScope();
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); // scan pass
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop); // scan pass
scope.endScanAndPrewarm();
const uint32_t heapAfter = esp_get_free_heap_size();
fcm->logStats("prewarm");
@@ -1506,7 +1543,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
lastRenderStats.forcedHalfRefresh = forceHalfRefreshThisPage;
logReaderMemSnapshot("before_bw_render");
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
renderStatusBar();
fcm->logStats("bw_render");
const auto tBwRender = millis();
@@ -1520,12 +1557,12 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
// Step 2: Re-render with images and display again (images appear clean)
int16_t imgX, imgY, imgW, imgH;
if (page->getImageBoundingBox(imgX, imgY, imgW, imgH)) {
renderer.fillRect(imgX + orientedMarginLeft, imgY + orientedMarginTop, imgW, imgH, false);
renderer.fillRect(imgX + orientedMarginLeft, imgY + contentTop, imgW, imgH, false);
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
// Re-render page content to restore images into the blanked area
// Status bar is not re-rendered here to avoid reading stale dynamic values (e.g. battery %)
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
} else {
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
@@ -1558,7 +1595,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
logReaderMemSnapshot("gray_lsb_begin");
renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
renderer.copyGrayscaleLsbBuffers();
const auto tGrayLsb = millis();
logReaderMemSnapshot("gray_lsb_end");
@@ -1567,7 +1604,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
logReaderMemSnapshot("gray_msb_begin");
renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
renderer.copyGrayscaleMsbBuffers();
const auto tGrayMsb = millis();
logReaderMemSnapshot("gray_msb_end");
@@ -1638,16 +1675,9 @@ void EpubReaderActivity::renderStatusBar() const {
const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100;
std::string title;
int textYOffset = 0;
if (automaticPageTurnActive) {
title = tr(STR_AUTO_TURN_ENABLED) + std::to_string(60 * 1000 / pageTurnDuration);
const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight();
if (statusBarHeight == 0 || statusBarHeight == UITheme::getInstance().getProgressBarHeight()) {
textYOffset += UITheme::getInstance().getMetrics().statusBarVerticalMargin;
}
} else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) {
const int tocIndex =
section ? section->getTocIndexForPage(section->currentPage) : epub->getTocIndexForSpineIndex(currentSpineIndex);
@@ -1663,7 +1693,7 @@ void EpubReaderActivity::renderStatusBar() const {
const bool isStarred = section && bookmarkStore.has(static_cast<uint16_t>(currentSpineIndex),
static_cast<uint16_t>(section->currentPage));
GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title, 0, textYOffset, isStarred);
GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title, 0, isStarred);
}
void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool savePosition) {
@@ -1754,11 +1784,10 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf
// Compute margins exactly as render() does
int marginTop, marginRight, marginBottom, marginLeft;
renderer.getOrientedViewableTRBL(&marginTop, &marginRight, &marginBottom, &marginLeft);
marginTop += SETTINGS.screenMargin;
marginTop += std::max(static_cast<int>(SETTINGS.screenMargin), UITheme::getStatusBarTopHeight());
marginLeft += SETTINGS.screenMargin;
marginRight += SETTINGS.screenMargin;
const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight();
marginBottom += std::max(SETTINGS.screenMargin, statusBarHeight);
marginBottom += std::max(static_cast<int>(SETTINGS.screenMargin), UITheme::getStatusBarBottomHeight());
const uint16_t viewportWidth = renderer.getScreenWidth() - marginLeft - marginRight;
const uint16_t viewportHeight = renderer.getScreenHeight() - marginTop - marginBottom;