Integrate and extend pr 1372 by andreaturchet

This commit is contained in:
jpirnay
2026-04-14 18:22:54 +02:00
parent 9d3f98eeb1
commit 4aabcc934c
15 changed files with 443 additions and 20 deletions
+6 -1
View File
@@ -57,8 +57,13 @@ struct FootnoteResult {
std::string href;
};
struct StarredPageResult {
int spineIndex = 0;
int pageNumber = 0;
};
using ResultVariant = std::variant<std::monostate, WifiResult, KeyboardResult, MenuResult, ChapterResult, PercentResult,
PageResult, SyncResult, NetworkModeResult, FootnoteResult>;
PageResult, SyncResult, NetworkModeResult, FootnoteResult, StarredPageResult>;
struct ActivityResult {
bool isCancelled = false;
+41 -2
View File
@@ -23,6 +23,7 @@
#include "QrDisplayActivity.h"
#include "ReaderUtils.h"
#include "RecentBooksStore.h"
#include "StarredPagesActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/ScreenshotUtil.h"
@@ -118,6 +119,9 @@ void EpubReaderActivity::onEnter() {
}
}
// Load bookmarks for this book
bookmarkStore.load(epub->getCachePath());
// Save current epub as last opened epub and add to recent books
APP_STATE.openEpubPath = epub->getPath();
APP_STATE.saveToFile();
@@ -139,6 +143,9 @@ void EpubReaderActivity::onExit() {
Activity::onExit();
logReaderMemSnapshot("onExit_before_release");
// Save bookmarks before exit
bookmarkStore.save();
// Reset orientation back to portrait for the rest of the UI
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
@@ -212,7 +219,7 @@ void EpubReaderActivity::loop() {
startActivityForResult(std::make_unique<EpubReaderMenuActivity>(
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
SETTINGS.orientation, !currentPageFootnotes.empty(), bookEmbeddedStyleOverride,
bookImageRenderingOverride, SETTINGS.textDarkness),
bookImageRenderingOverride, SETTINGS.textDarkness, !bookmarkStore.isEmpty()),
[this](const ActivityResult& result) {
// Always apply orientation/darkness change even if the menu was cancelled
const auto& menu = std::get<MenuResult>(result.data);
@@ -265,6 +272,16 @@ void EpubReaderActivity::loop() {
return;
}
// Star page toggle via short power button press
if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::STAR_PAGE &&
mappedInput.wasReleased(MappedInputManager::Button::Power)) {
if (section && section->currentPage >= 0 && section->currentPage < section->pageCount) {
bookmarkStore.toggle(static_cast<uint16_t>(currentSpineIndex), static_cast<uint16_t>(section->currentPage));
requestUpdate();
}
return;
}
auto [prevTriggered, nextTriggered] = ReaderUtils::detectPageTurn(mappedInput);
if (!prevTriggered && !nextTriggered) {
return;
@@ -499,6 +516,22 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
requestUpdate();
break;
}
case EpubReaderMenuActivity::MenuAction::STARRED_PAGES: {
startActivityForResult(
std::make_unique<StarredPagesActivity>(renderer, mappedInput, bookmarkStore.getAll(), epub),
[this](const ActivityResult& result) {
if (!result.isCancelled) {
const auto& starred = std::get<StarredPageResult>(result.data);
if (currentSpineIndex != starred.spineIndex || !section || section->currentPage != starred.pageNumber) {
RenderLock lock(*this);
currentSpineIndex = starred.spineIndex;
nextPageNumber = starred.pageNumber;
section.reset();
}
}
});
break;
}
case EpubReaderMenuActivity::MenuAction::GO_HOME: {
onGoHome();
return;
@@ -514,6 +547,10 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
epub->clearCache();
epub->setupCacheDir();
saveProgress(backupSpine, backupPage, backupPageCount);
if (!bookmarkStore.isEmpty()) {
bookmarkStore.markDirty();
bookmarkStore.save();
}
}
}
onGoHome();
@@ -1144,7 +1181,9 @@ void EpubReaderActivity::renderStatusBar() const {
title = epub->getTitle();
}
GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title, 0, textYOffset);
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);
}
void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool savePosition) {
@@ -5,6 +5,7 @@
#include <optional>
#include "BookmarkStore.h"
#include "EpubReaderMenuActivity.h"
#include "ReaderUtils.h"
#include "activities/Activity.h"
@@ -52,6 +53,9 @@ class EpubReaderActivity final : public Activity {
int8_t bookEmbeddedStyleOverride = -1;
int8_t bookImageRenderingOverride = -1;
// Bookmarks (starred pages)
BookmarkStore bookmarkStore;
// Footnote support
std::vector<FootnoteEntry> currentPageFootnotes;
struct SavedPosition {
@@ -13,7 +13,7 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu
const int bookProgressPercent, const uint8_t currentOrientation,
const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride,
const int8_t initialImageRenderingOverride,
const uint8_t initialTextDarkness)
const uint8_t initialTextDarkness, const bool hasStarredPages)
: MenuListActivity("EpubReaderMenu", renderer, mappedInput),
pendingOrientation(currentOrientation),
pendingEmbeddedStyleOverride(initialEmbeddedStyleOverride),
@@ -23,16 +23,19 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu
currentPage(currentPage),
totalPages(totalPages),
bookProgressPercent(bookProgressPercent) {
buildMenuItems(hasFootnotes);
buildMenuItems(hasFootnotes, hasStarredPages);
}
void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes) {
menuItems.reserve(18);
void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPages) {
menuItems.reserve(19);
// --- Navigation ---
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_NAVIGATION));
menuItems.push_back(SettingInfo::Action(StrId::STR_SELECT_CHAPTER, SettingAction::None));
menuItems.push_back(SettingInfo::Action(StrId::STR_GO_TO_PERCENT, SettingAction::None));
if (hasStarredPages) {
menuItems.push_back(SettingInfo::Action(StrId::STR_STARRED_PAGES, SettingAction::None));
}
if (hasFootnotes) {
menuItems.push_back(SettingInfo::Action(StrId::STR_FOOTNOTES, SettingAction::None));
}
@@ -98,6 +101,8 @@ EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForNameId(StrId
return MenuAction::SELECT_CHAPTER;
case StrId::STR_GO_TO_PERCENT:
return MenuAction::GO_TO_PERCENT;
case StrId::STR_STARRED_PAGES:
return MenuAction::STARRED_PAGES;
case StrId::STR_FOOTNOTES:
return MenuAction::FOOTNOTES;
case StrId::STR_AUTO_TURN_PAGES_PER_MIN:
@@ -26,6 +26,8 @@ class EpubReaderMenuActivity final : public MenuListActivity {
GO_HOME,
PULL_REMOTE,
PUSH_LOCAL,
SYNC,
STARRED_PAGES,
DELETE_CACHE
};
@@ -33,13 +35,14 @@ class EpubReaderMenuActivity final : public MenuListActivity {
const int currentPage, const int totalPages, const int bookProgressPercent,
const uint8_t currentOrientation, const bool hasFootnotes,
const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride,
const uint8_t initialTextDarkness);
const uint8_t initialTextDarkness,
const bool hasStarredPages);
void onEnter() override;
void render(RenderLock&&) override;
private:
void buildMenuItems(bool hasFootnotes);
void buildMenuItems(bool hasFootnotes, bool hasStarredPages);
void finishWithAction(MenuAction action);
// MenuListActivity overrides
@@ -0,0 +1,137 @@
#include "StarredPagesActivity.h"
#include <GfxRenderer.h>
#include <I18n.h>
#include "MappedInputManager.h"
#include "components/UITheme.h"
#include "fontIds.h"
int StarredPagesActivity::getPageItems() const {
constexpr int lineHeight = 30;
const int screenHeight = renderer.getScreenHeight();
const auto orientation = renderer.getOrientation();
const bool isPortraitInverted = orientation == GfxRenderer::Orientation::PortraitInverted;
const int hintGutterHeight = isPortraitInverted ? 50 : 0;
const int startY = 60 + hintGutterHeight;
const int availableHeight = screenHeight - startY - lineHeight;
return std::max(1, availableHeight / lineHeight);
}
std::string StarredPagesActivity::getItemLabel(int index) const {
const auto& bm = bookmarks[index];
char buf[64];
if (epub) {
// Try to get chapter title from TOC
const int tocIndex = epub->getTocIndexForSpineIndex(bm.spineIndex);
if (tocIndex != -1) {
const auto tocItem = epub->getTocItem(tocIndex);
snprintf(buf, sizeof(buf), "%d. ", index + 1);
return std::string(buf) + tocItem.title + " - " + tr(STR_PAGE_PREFIX) + std::to_string(bm.pageNumber + 1);
}
snprintf(buf, sizeof(buf), "%d. %s%d, %s%d", index + 1, tr(STR_SECTION_PREFIX), bm.spineIndex + 1,
tr(STR_PAGE_PREFIX), bm.pageNumber + 1);
} else {
// TXT file: just page number (spineIndex is always 0)
snprintf(buf, sizeof(buf), "%d. %s%d", index + 1, tr(STR_PAGE_PREFIX), bm.pageNumber + 1);
}
return std::string(buf);
}
void StarredPagesActivity::onEnter() {
Activity::onEnter();
requestUpdate();
}
void StarredPagesActivity::onExit() { Activity::onExit(); }
void StarredPagesActivity::loop() {
const int totalItems = static_cast<int>(bookmarks.size());
const int pageItems = getPageItems();
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (!bookmarks.empty()) {
const auto& bm = bookmarks[selectorIndex];
setResult(StarredPageResult{bm.spineIndex, bm.pageNumber});
finish();
}
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
ActivityResult result;
result.isCancelled = true;
setResult(std::move(result));
finish();
return;
}
buttonNavigator.onNextRelease([this, totalItems] {
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems);
requestUpdate();
});
buttonNavigator.onPreviousRelease([this, totalItems] {
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems);
requestUpdate();
});
buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems);
requestUpdate();
});
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems);
requestUpdate();
});
}
void StarredPagesActivity::render(RenderLock&&) {
renderer.clearScreen();
const int totalItems = static_cast<int>(bookmarks.size());
if (totalItems == 0) {
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_NO_STARRED_PAGES), true, EpdFontFamily::BOLD);
renderer.displayBuffer();
return;
}
const auto pageWidth = renderer.getScreenWidth();
const auto orientation = renderer.getOrientation();
const bool isLandscapeCw = orientation == GfxRenderer::Orientation::LandscapeClockwise;
const bool isLandscapeCcw = orientation == GfxRenderer::Orientation::LandscapeCounterClockwise;
const bool isPortraitInverted = orientation == GfxRenderer::Orientation::PortraitInverted;
const int hintGutterWidth = (isLandscapeCw || isLandscapeCcw) ? 30 : 0;
const int contentX = isLandscapeCw ? hintGutterWidth : 0;
const int contentWidth = pageWidth - hintGutterWidth;
const int hintGutterHeight = isPortraitInverted ? 50 : 0;
const int contentY = hintGutterHeight;
const int pageItems = getPageItems();
// Title
const int titleX =
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, tr(STR_STARRED_PAGES), EpdFontFamily::BOLD)) / 2;
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, tr(STR_STARRED_PAGES), true, EpdFontFamily::BOLD);
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
// Highlight selection
renderer.fillRect(contentX, 60 + contentY + (selectorIndex % pageItems) * 30 - 2, contentWidth - 1, 30);
for (int i = 0; i < pageItems; i++) {
int itemIndex = pageStartIndex + i;
if (itemIndex >= totalItems) break;
const int displayY = 60 + contentY + i * 30;
const bool isSelected = (itemIndex == selectorIndex);
const std::string label = renderer.truncatedText(UI_10_FONT_ID, getItemLabel(itemIndex).c_str(), contentWidth - 40);
renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, label.c_str(), !isSelected);
}
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}
@@ -0,0 +1,30 @@
#pragma once
#include <Epub.h>
#include <memory>
#include <vector>
#include "../Activity.h"
#include "BookmarkStore.h"
#include "util/ButtonNavigator.h"
class StarredPagesActivity final : public Activity {
std::shared_ptr<Epub> epub; // nullptr for TXT files
const std::vector<BookmarkStore::Bookmark> bookmarks;
ButtonNavigator buttonNavigator;
int selectorIndex = 0;
int getPageItems() const;
std::string getItemLabel(int index) const;
public:
explicit StarredPagesActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::vector<BookmarkStore::Bookmark>& bookmarks,
std::shared_ptr<Epub> epub = nullptr)
: Activity("StarredPages", renderer, mappedInput), epub(std::move(epub)), bookmarks(bookmarks) {}
void onEnter() override;
void onExit() override;
void loop() override;
void render(RenderLock&&) override;
};
+32 -1
View File
@@ -12,6 +12,7 @@
#include "MappedInputManager.h"
#include "ReaderUtils.h"
#include "RecentBooksStore.h"
#include "StarredPagesActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -99,6 +100,9 @@ void TxtReaderActivity::onEnter() {
txt->setupCacheDir();
// Load bookmarks for this file
bookmarkStore.load(txt->getCachePath());
// Save current txt as last opened file and add to recent books
auto filePath = txt->getPath();
auto fileName = filePath.substr(filePath.rfind('/') + 1);
@@ -113,6 +117,9 @@ void TxtReaderActivity::onEnter() {
void TxtReaderActivity::onExit() {
Activity::onExit();
// Save bookmarks before exit
bookmarkStore.save();
// Reset orientation back to portrait for the rest of the UI
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
@@ -143,6 +150,29 @@ void TxtReaderActivity::loop() {
return;
}
// Star page toggle via short power button press
if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::STAR_PAGE &&
mappedInput.wasReleased(MappedInputManager::Button::Power)) {
bookmarkStore.toggle(0, static_cast<uint16_t>(currentPage));
requestUpdate();
return;
}
// Open starred pages list via Confirm button
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) && !bookmarkStore.isEmpty()) {
startActivityForResult(std::make_unique<StarredPagesActivity>(renderer, mappedInput, bookmarkStore.getAll()),
[this](const ActivityResult& result) {
if (!result.isCancelled) {
const auto& starred = std::get<StarredPageResult>(result.data);
currentPage = starred.pageNumber;
if (currentPage >= totalPages) currentPage = totalPages - 1;
if (currentPage < 0) currentPage = 0;
}
requestUpdate();
});
return;
}
auto [prevTriggered, nextTriggered] = ReaderUtils::detectPageTurn(mappedInput);
if (!prevTriggered && !nextTriggered) {
return;
@@ -373,7 +403,8 @@ void TxtReaderActivity::renderStatusBar() const {
if (SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE) {
title = txt->getTitle();
}
GUI.drawStatusBar(renderer, progress, currentPage + 1, totalPages, title);
const bool isStarred = bookmarkStore.has(0, static_cast<uint16_t>(currentPage));
GUI.drawStatusBar(renderer, progress, currentPage + 1, totalPages, title, 0, 0, isStarred);
}
void TxtReaderActivity::saveProgress() const {
@@ -4,6 +4,7 @@
#include <vector>
#include "BookmarkStore.h"
#include "CrossPointSettings.h"
#include "ReaderUtils.h"
#include "activities/Activity.h"
@@ -16,6 +17,9 @@ class TxtReaderActivity final : public Activity {
int pagesUntilFullRefresh = 0;
ReaderUtils::InputDrainGuard inputDrainGuard;
// Bookmarks (starred pages)
BookmarkStore bookmarkStore;
// Streaming text reader - stores file offsets for each page
std::vector<size_t> pageOffsets; // File offset for start of each page
std::vector<std::string> currentPageLines;