feat: Auto Page Turn for Epub Reader (#1219)
## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) - Implements auto page turn feature for epub reader in the reader submenu * **What changes are included?** - added auto page turn feature in epub reader in the submenu - currently there are 5 settings, `OFF, 1, 3, 6, 12` pages per minute ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). - Replacement PR for #723 - when auto turn is enabled, space reserved for chapter title will be used to indicate auto page turn being active - Back and Confirm button is used to disable it --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**Partially (mainly code reviews)**_
This commit is contained in:
@@ -334,3 +334,5 @@ STR_FOOTNOTES: "Footnotes"
|
|||||||
STR_NO_FOOTNOTES: "No footnotes on this page"
|
STR_NO_FOOTNOTES: "No footnotes on this page"
|
||||||
STR_LINK: "[link]"
|
STR_LINK: "[link]"
|
||||||
STR_SCREENSHOT_BUTTON: "Take screenshot"
|
STR_SCREENSHOT_BUTTON: "Take screenshot"
|
||||||
|
STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: "
|
||||||
|
STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)"
|
||||||
|
|||||||
@@ -250,3 +250,12 @@ void RenderLock::unlock() {
|
|||||||
isLocked = false;
|
isLocked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Checks if renderingMutex is busy.
|
||||||
|
*
|
||||||
|
* @return true if renderingMutex is busy, otherwise false.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool RenderLock::peek() { return xQueuePeek(activityManager.renderingMutex, NULL, 0) != pdTRUE; };
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ struct KeyboardResult {
|
|||||||
struct MenuResult {
|
struct MenuResult {
|
||||||
int action = -1;
|
int action = -1;
|
||||||
uint8_t orientation = 0;
|
uint8_t orientation = 0;
|
||||||
|
uint8_t pageTurnOption = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ChapterResult {
|
struct ChapterResult {
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ class RenderLock {
|
|||||||
RenderLock& operator=(const RenderLock&) = delete;
|
RenderLock& operator=(const RenderLock&) = delete;
|
||||||
~RenderLock();
|
~RenderLock();
|
||||||
void unlock();
|
void unlock();
|
||||||
|
static bool peek();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ namespace {
|
|||||||
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
|
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
|
||||||
constexpr unsigned long skipChapterMs = 700;
|
constexpr unsigned long skipChapterMs = 700;
|
||||||
constexpr unsigned long goHomeMs = 1000;
|
constexpr unsigned long goHomeMs = 1000;
|
||||||
|
// pages per minute, first item is 1 to prevent division by zero if accessed
|
||||||
|
const std::vector<int> PAGE_TURN_LABELS = {1, 1, 3, 6, 12};
|
||||||
|
|
||||||
int clampPercent(int percent) {
|
int clampPercent(int percent) {
|
||||||
if (percent < 0) {
|
if (percent < 0) {
|
||||||
@@ -126,6 +128,32 @@ void EpubReaderActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (automaticPageTurnActive) {
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) ||
|
||||||
|
mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
|
automaticPageTurnActive = false;
|
||||||
|
// updates chapter title space to indicate page turn disabled
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!section) {
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skips page turn if renderingMutex is busy
|
||||||
|
if (RenderLock::peek()) {
|
||||||
|
lastPageTurnTime = millis();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((millis() - lastPageTurnTime) >= pageTurnDuration) {
|
||||||
|
pageTurn(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enter reader menu activity.
|
// Enter reader menu activity.
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
const int currentPage = section ? section->currentPage + 1 : 0;
|
const int currentPage = section ? section->currentPage + 1 : 0;
|
||||||
@@ -143,6 +171,7 @@ void EpubReaderActivity::loop() {
|
|||||||
// Always apply orientation change even if the menu was cancelled
|
// Always apply orientation change even if the menu was cancelled
|
||||||
const auto& menu = std::get<MenuResult>(result.data);
|
const auto& menu = std::get<MenuResult>(result.data);
|
||||||
applyOrientation(menu.orientation);
|
applyOrientation(menu.orientation);
|
||||||
|
toggleAutoPageTurn(menu.pageTurnOption);
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
||||||
}
|
}
|
||||||
@@ -194,6 +223,7 @@ void EpubReaderActivity::loop() {
|
|||||||
const bool skipChapter = SETTINGS.longPressChapterSkip && mappedInput.getHeldTime() > skipChapterMs;
|
const bool skipChapter = SETTINGS.longPressChapterSkip && mappedInput.getHeldTime() > skipChapterMs;
|
||||||
|
|
||||||
if (skipChapter) {
|
if (skipChapter) {
|
||||||
|
lastPageTurnTime = millis();
|
||||||
// We don't want to delete the section mid-render, so grab the semaphore
|
// We don't want to delete the section mid-render, so grab the semaphore
|
||||||
{
|
{
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
@@ -212,31 +242,9 @@ void EpubReaderActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (prevTriggered) {
|
if (prevTriggered) {
|
||||||
if (section->currentPage > 0) {
|
pageTurn(false);
|
||||||
section->currentPage--;
|
|
||||||
} else if (currentSpineIndex > 0) {
|
|
||||||
// We don't want to delete the section mid-render, so grab the semaphore
|
|
||||||
{
|
|
||||||
RenderLock lock(*this);
|
|
||||||
nextPageNumber = UINT16_MAX;
|
|
||||||
currentSpineIndex--;
|
|
||||||
section.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestUpdate();
|
|
||||||
} else {
|
} else {
|
||||||
if (section->currentPage < section->pageCount - 1) {
|
pageTurn(true);
|
||||||
section->currentPage++;
|
|
||||||
} else {
|
|
||||||
// We don't want to delete the section mid-render, so grab the semaphore
|
|
||||||
{
|
|
||||||
RenderLock lock(*this);
|
|
||||||
nextPageNumber = 0;
|
|
||||||
currentSpineIndex++;
|
|
||||||
section.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestUpdate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,6 +460,61 @@ void EpubReaderActivity::applyOrientation(const uint8_t orientation) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption) {
|
||||||
|
if (selectedPageTurnOption == 0 || selectedPageTurnOption >= PAGE_TURN_LABELS.size()) {
|
||||||
|
automaticPageTurnActive = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPageTurnTime = millis();
|
||||||
|
// calculates page turn duration by dividing by number of pages
|
||||||
|
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()) {
|
||||||
|
// 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::pageTurn(bool isForwardTurn) {
|
||||||
|
if (isForwardTurn) {
|
||||||
|
if (section->currentPage < section->pageCount - 1) {
|
||||||
|
section->currentPage++;
|
||||||
|
} else {
|
||||||
|
// We don't want to delete the section mid-render, so grab the semaphore
|
||||||
|
{
|
||||||
|
RenderLock lock(*this);
|
||||||
|
nextPageNumber = 0;
|
||||||
|
currentSpineIndex++;
|
||||||
|
section.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (section->currentPage > 0) {
|
||||||
|
section->currentPage--;
|
||||||
|
} else if (currentSpineIndex > 0) {
|
||||||
|
// We don't want to delete the section mid-render, so grab the semaphore
|
||||||
|
{
|
||||||
|
RenderLock lock(*this);
|
||||||
|
nextPageNumber = UINT16_MAX;
|
||||||
|
currentSpineIndex--;
|
||||||
|
section.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastPageTurnTime = millis();
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Failure handling
|
// TODO: Failure handling
|
||||||
void EpubReaderActivity::render(RenderLock&& lock) {
|
void EpubReaderActivity::render(RenderLock&& lock) {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
@@ -472,6 +535,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
automaticPageTurnActive = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,8 +546,18 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
orientedMarginTop += SETTINGS.screenMargin;
|
orientedMarginTop += SETTINGS.screenMargin;
|
||||||
orientedMarginLeft += SETTINGS.screenMargin;
|
orientedMarginLeft += SETTINGS.screenMargin;
|
||||||
orientedMarginRight += SETTINGS.screenMargin;
|
orientedMarginRight += SETTINGS.screenMargin;
|
||||||
orientedMarginBottom +=
|
|
||||||
std::max(SETTINGS.screenMargin, static_cast<uint8_t>(UITheme::getInstance().getStatusBarHeight()));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
if (!section) {
|
if (!section) {
|
||||||
const auto filepath = epub->getSpineItem(currentSpineIndex).href;
|
const auto filepath = epub->getSpineItem(currentSpineIndex).href;
|
||||||
@@ -546,6 +620,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_EMPTY_CHAPTER), true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_EMPTY_CHAPTER), true, EpdFontFamily::BOLD);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
automaticPageTurnActive = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,6 +629,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_OUT_OF_BOUNDS), true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_OUT_OF_BOUNDS), true, EpdFontFamily::BOLD);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
automaticPageTurnActive = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +640,8 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
section->clearCache();
|
section->clearCache();
|
||||||
section.reset();
|
section.reset();
|
||||||
requestUpdate(); // Try again after clearing cache
|
requestUpdate(); // Try again after clearing cache
|
||||||
// TODO: prevent infinite loop if the page keeps failing to load for some reason
|
// TODO: prevent infinite loop if the page keeps failing to load for some reason
|
||||||
|
automaticPageTurnActive = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,23 +746,34 @@ void EpubReaderActivity::renderStatusBar() const {
|
|||||||
const float sectionChapterProg = (pageCount > 0) ? (static_cast<float>(currentPage) / pageCount) : 0;
|
const float sectionChapterProg = (pageCount > 0) ? (static_cast<float>(currentPage) / pageCount) : 0;
|
||||||
const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100;
|
const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100;
|
||||||
|
|
||||||
const int tocIndex = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
|
||||||
std::string title;
|
std::string title;
|
||||||
|
|
||||||
if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) {
|
int textYOffset = 0;
|
||||||
if (tocIndex == -1) {
|
|
||||||
title = tr(STR_UNNAMED);
|
if (automaticPageTurnActive) {
|
||||||
} else {
|
title = tr(STR_AUTO_TURN_ENABLED) + std::to_string(60 * 1000 / pageTurnDuration);
|
||||||
|
|
||||||
|
// calculates textYOffset when rendering title in status bar
|
||||||
|
const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight();
|
||||||
|
|
||||||
|
// offsets text if no status bar or progress bar only
|
||||||
|
if (statusBarHeight == 0 || statusBarHeight == UITheme::getInstance().getProgressBarHeight()) {
|
||||||
|
textYOffset += UITheme::getInstance().getMetrics().statusBarVerticalMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) {
|
||||||
|
title = tr(STR_UNNAMED);
|
||||||
|
const int tocIndex = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||||
|
if (tocIndex != -1) {
|
||||||
const auto tocItem = epub->getTocItem(tocIndex);
|
const auto tocItem = epub->getTocItem(tocIndex);
|
||||||
title = tocItem.title;
|
title = tocItem.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE) {
|
} else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE) {
|
||||||
title = epub->getTitle();
|
title = epub->getTitle();
|
||||||
} else {
|
|
||||||
title = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title);
|
GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title, 0, textYOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool savePosition) {
|
void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool savePosition) {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ class EpubReaderActivity final : public Activity {
|
|||||||
int pagesUntilFullRefresh = 0;
|
int pagesUntilFullRefresh = 0;
|
||||||
int cachedSpineIndex = 0;
|
int cachedSpineIndex = 0;
|
||||||
int cachedChapterTotalPageCount = 0;
|
int cachedChapterTotalPageCount = 0;
|
||||||
|
unsigned long lastPageTurnTime = 0UL;
|
||||||
|
unsigned long pageTurnDuration = 0UL;
|
||||||
// Signals that the next render should reposition within the newly loaded section
|
// Signals that the next render should reposition within the newly loaded section
|
||||||
// based on a cross-book percentage jump.
|
// based on a cross-book percentage jump.
|
||||||
bool pendingPercentJump = false;
|
bool pendingPercentJump = false;
|
||||||
@@ -21,6 +23,7 @@ class EpubReaderActivity final : public Activity {
|
|||||||
float pendingSpineProgress = 0.0f;
|
float pendingSpineProgress = 0.0f;
|
||||||
bool pendingScreenshot = false;
|
bool pendingScreenshot = false;
|
||||||
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
|
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
|
||||||
|
bool automaticPageTurnActive = false;
|
||||||
|
|
||||||
// Footnote support
|
// Footnote support
|
||||||
std::vector<FootnoteEntry> currentPageFootnotes;
|
std::vector<FootnoteEntry> currentPageFootnotes;
|
||||||
@@ -40,6 +43,8 @@ class EpubReaderActivity final : public Activity {
|
|||||||
void jumpToPercent(int percent);
|
void jumpToPercent(int percent);
|
||||||
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
|
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
|
||||||
void applyOrientation(uint8_t orientation);
|
void applyOrientation(uint8_t orientation);
|
||||||
|
void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
|
||||||
|
void pageTurn(bool isForwardTurn);
|
||||||
|
|
||||||
// Footnote navigation
|
// Footnote navigation
|
||||||
void navigateToHref(const std::string& href, bool savePosition = false);
|
void navigateToHref(const std::string& href, bool savePosition = false);
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu
|
|||||||
|
|
||||||
std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes) {
|
std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes) {
|
||||||
std::vector<MenuItem> items;
|
std::vector<MenuItem> items;
|
||||||
items.reserve(9);
|
items.reserve(10);
|
||||||
items.push_back({MenuAction::SELECT_CHAPTER, StrId::STR_SELECT_CHAPTER});
|
items.push_back({MenuAction::SELECT_CHAPTER, StrId::STR_SELECT_CHAPTER});
|
||||||
if (hasFootnotes) {
|
if (hasFootnotes) {
|
||||||
items.push_back({MenuAction::FOOTNOTES, StrId::STR_FOOTNOTES});
|
items.push_back({MenuAction::FOOTNOTES, StrId::STR_FOOTNOTES});
|
||||||
}
|
}
|
||||||
items.push_back({MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION});
|
items.push_back({MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION});
|
||||||
|
items.push_back({MenuAction::AUTO_PAGE_TURN, StrId::STR_AUTO_TURN_PAGES_PER_MIN});
|
||||||
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
|
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
|
||||||
items.push_back({MenuAction::SCREENSHOT, StrId::STR_SCREENSHOT_BUTTON});
|
items.push_back({MenuAction::SCREENSHOT, StrId::STR_SCREENSHOT_BUTTON});
|
||||||
items.push_back({MenuAction::DISPLAY_QR, StrId::STR_DISPLAY_QR});
|
items.push_back({MenuAction::DISPLAY_QR, StrId::STR_DISPLAY_QR});
|
||||||
@@ -64,13 +65,19 @@ void EpubReaderMenuActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setResult(MenuResult{static_cast<int>(selectedAction), pendingOrientation});
|
if (selectedAction == MenuAction::AUTO_PAGE_TURN) {
|
||||||
|
selectedPageTurnOption = (selectedPageTurnOption + 1) % pageTurnLabels.size();
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setResult(MenuResult{static_cast<int>(selectedAction), pendingOrientation, selectedPageTurnOption});
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
ActivityResult result;
|
ActivityResult result;
|
||||||
result.isCancelled = true;
|
result.isCancelled = true;
|
||||||
result.data = MenuResult{-1, pendingOrientation};
|
result.data = MenuResult{-1, pendingOrientation, selectedPageTurnOption};
|
||||||
setResult(std::move(result));
|
setResult(std::move(result));
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
@@ -133,6 +140,13 @@ void EpubReaderMenuActivity::render(RenderLock&&) {
|
|||||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
||||||
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (menuItems[i].action == MenuAction::AUTO_PAGE_TURN) {
|
||||||
|
// Render current page turn value on the right edge of the content area.
|
||||||
|
const auto value = pageTurnLabels[selectedPageTurnOption];
|
||||||
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
||||||
|
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer / Hints
|
// Footer / Hints
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class EpubReaderMenuActivity final : public Activity {
|
|||||||
SELECT_CHAPTER,
|
SELECT_CHAPTER,
|
||||||
FOOTNOTES,
|
FOOTNOTES,
|
||||||
GO_TO_PERCENT,
|
GO_TO_PERCENT,
|
||||||
|
AUTO_PAGE_TURN,
|
||||||
ROTATE_SCREEN,
|
ROTATE_SCREEN,
|
||||||
SCREENSHOT,
|
SCREENSHOT,
|
||||||
DISPLAY_QR,
|
DISPLAY_QR,
|
||||||
@@ -48,8 +49,10 @@ class EpubReaderMenuActivity final : public Activity {
|
|||||||
ButtonNavigator buttonNavigator;
|
ButtonNavigator buttonNavigator;
|
||||||
std::string title = "Reader Menu";
|
std::string title = "Reader Menu";
|
||||||
uint8_t pendingOrientation = 0;
|
uint8_t pendingOrientation = 0;
|
||||||
|
uint8_t selectedPageTurnOption = 0;
|
||||||
const std::vector<StrId> orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED,
|
const std::vector<StrId> orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED,
|
||||||
StrId::STR_LANDSCAPE_CCW};
|
StrId::STR_LANDSCAPE_CCW};
|
||||||
|
const std::vector<const char*> pageTurnLabels = {I18N.get(StrId::STR_STATE_OFF), "1", "3", "6", "12"};
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
int totalPages = 0;
|
int totalPages = 0;
|
||||||
int bookProgressPercent = 0;
|
int bookProgressPercent = 0;
|
||||||
|
|||||||
@@ -431,8 +431,10 @@ void TxtReaderActivity::renderPage() {
|
|||||||
|
|
||||||
void TxtReaderActivity::renderStatusBar() const {
|
void TxtReaderActivity::renderStatusBar() const {
|
||||||
const float progress = totalPages > 0 ? (currentPage + 1) * 100.0f / totalPages : 0;
|
const float progress = totalPages > 0 ? (currentPage + 1) * 100.0f / totalPages : 0;
|
||||||
std::string title = txt->getTitle();
|
std::string title;
|
||||||
|
if (SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE) {
|
||||||
|
title = txt->getTitle();
|
||||||
|
}
|
||||||
GUI.drawStatusBar(renderer, progress, currentPage + 1, totalPages, title);
|
GUI.drawStatusBar(renderer, progress, currentPage + 1, totalPages, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ void StatusBarSettingsActivity::render(RenderLock&&) {
|
|||||||
std::string title;
|
std::string title;
|
||||||
if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE) {
|
if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE) {
|
||||||
title = tr(STR_EXAMPLE_BOOK);
|
title = tr(STR_EXAMPLE_BOOK);
|
||||||
} else {
|
} else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) {
|
||||||
title = tr(STR_EXAMPLE_CHAPTER);
|
title = tr(STR_EXAMPLE_CHAPTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,3 +103,10 @@ int UITheme::getStatusBarHeight() {
|
|||||||
return (showStatusBar ? (metrics.statusBarVerticalMargin) : 0) +
|
return (showStatusBar ? (metrics.statusBarVerticalMargin) : 0) +
|
||||||
(showProgressBar ? (((SETTINGS.statusBarProgressBarThickness + 1) * 2) + metrics.progressBarMarginTop) : 0);
|
(showProgressBar ? (((SETTINGS.statusBarProgressBarThickness + 1) * 2) + metrics.progressBarMarginTop) : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int UITheme::getProgressBarHeight() {
|
||||||
|
const ThemeMetrics& metrics = UITheme::getInstance().getMetrics();
|
||||||
|
const bool showProgressBar =
|
||||||
|
SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS;
|
||||||
|
return (showProgressBar ? (((SETTINGS.statusBarProgressBarThickness + 1) * 2) + metrics.progressBarMarginTop) : 0);
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class UITheme {
|
|||||||
static std::string getCoverThumbPath(std::string coverBmpPath, int coverHeight);
|
static std::string getCoverThumbPath(std::string coverBmpPath, int coverHeight);
|
||||||
static UIIcon getFileIcon(std::string filename);
|
static UIIcon getFileIcon(std::string filename);
|
||||||
static int getStatusBarHeight();
|
static int getStatusBarHeight();
|
||||||
|
static int getProgressBarHeight();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const ThemeMetrics* currentMetrics;
|
const ThemeMetrics* currentMetrics;
|
||||||
|
|||||||
@@ -629,7 +629,8 @@ void BaseTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layou
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage,
|
void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage,
|
||||||
const int pageCount, std::string title, const int paddingBottom) const {
|
const int pageCount, std::string title, const int paddingBottom,
|
||||||
|
const int textYOffset) const {
|
||||||
auto metrics = UITheme::getInstance().getMetrics();
|
auto metrics = UITheme::getInstance().getMetrics();
|
||||||
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
|
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
|
||||||
renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom,
|
renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom,
|
||||||
@@ -637,8 +638,7 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c
|
|||||||
|
|
||||||
// Draw Progress Text
|
// Draw Progress Text
|
||||||
const auto screenHeight = renderer.getScreenHeight();
|
const auto screenHeight = renderer.getScreenHeight();
|
||||||
const auto textY =
|
auto textY = screenHeight - UITheme::getInstance().getStatusBarHeight() - orientedMarginBottom - paddingBottom - 4;
|
||||||
screenHeight - UITheme::getInstance().getStatusBarHeight() - orientedMarginBottom - paddingBottom - 4;
|
|
||||||
int progressTextWidth = 0;
|
int progressTextWidth = 0;
|
||||||
|
|
||||||
if (SETTINGS.statusBarBookProgressPercentage || SETTINGS.statusBarChapterPageCount) {
|
if (SETTINGS.statusBarBookProgressPercentage || SETTINGS.statusBarChapterPageCount) {
|
||||||
@@ -688,7 +688,8 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw Title
|
// Draw Title
|
||||||
if (SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE) {
|
if (!title.empty()) {
|
||||||
|
textY -= textYOffset;
|
||||||
// Centered chapter title text
|
// Centered chapter title text
|
||||||
// Page width minus existing content with 30px padding on each side
|
// Page width minus existing content with 30px padding on each side
|
||||||
const int rendererableScreenWidth =
|
const int rendererableScreenWidth =
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ class BaseTheme {
|
|||||||
virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) 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 fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const;
|
||||||
virtual void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage,
|
virtual void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage,
|
||||||
const int pageCount, std::string title, const int paddingBottom = 0) const;
|
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;
|
virtual void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const;
|
||||||
virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const;
|
virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const;
|
||||||
virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected) const;
|
virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected) const;
|
||||||
|
|||||||
Reference in New Issue
Block a user