From 606b20d9d05a7925c684d966c41033f84fc26d75 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 2 Feb 2026 18:06:41 +0300 Subject: [PATCH 1/8] feat: Add percent selection feature for jumping within the book --- src/activities/reader/EpubReaderActivity.cpp | 141 +++++++++++++++++- src/activities/reader/EpubReaderActivity.h | 11 ++ .../reader/EpubReaderMenuActivity.h | 5 +- .../EpubReaderPercentSelectionActivity.cpp | 138 +++++++++++++++++ .../EpubReaderPercentSelectionActivity.h | 46 ++++++ 5 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 src/activities/reader/EpubReaderPercentSelectionActivity.cpp create mode 100644 src/activities/reader/EpubReaderPercentSelectionActivity.h diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 5ccfb4fe..4c3ac400 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -5,12 +5,14 @@ #include #include + #include "CrossPointSettings.h" #include "CrossPointState.h" #include "EpubReaderChapterSelectionActivity.h" #include "MappedInputManager.h" #include "RecentBooksStore.h" #include "ScreenComponents.h" +#include "activities/reader/EpubReaderPercentSelectionActivity.h" #include "fontIds.h" namespace { @@ -22,6 +24,17 @@ constexpr int progressBarMarginTop = 1; } // namespace +// Clamp any percent-like value into the valid 0-100 range. +static int clampPercent(const int value) { + if (value < 0) { + return 0; + } + if (value > 100) { + return 100; + } + return value; +} + void EpubReaderActivity::taskTrampoline(void* param) { auto* self = static_cast(param); self->displayTaskLoop(); @@ -123,7 +136,19 @@ void EpubReaderActivity::loop() { return; } - // Enter chapter selection activity + // Enter reader menu activity (suppressed after slider confirm/cancel). + if (suppressMenuOpenOnce) { + // If we're seeing the confirm release that closed the slider, consume it and return. + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + suppressMenuOpenOnce = false; + return; + } + // If confirm is no longer pressed and no release is pending, clear suppression. + if (!mappedInput.isPressed(MappedInputManager::Button::Confirm)) { + suppressMenuOpenOnce = false; + } + } + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { // Don't start activity transition while rendering xSemaphoreTake(renderingMutex, portMAX_DELAY); @@ -225,6 +250,85 @@ void EpubReaderActivity::onReaderMenuBack() { updateRequired = true; } +// Translate an absolute percent into a spine index plus a normalized position +// within that spine so we can jump after the section is loaded. +void EpubReaderActivity::jumpToPercent(int percent) { + if (!epub) { + return; + } + + const size_t bookSize = epub->getBookSize(); + if (bookSize == 0) { + return; + } + + // Normalize input to 0-100 to avoid invalid jumps. + percent = clampPercent(percent); + + // Convert percent into a byte-like absolute position across the spine sizes. + size_t targetSize = (bookSize * static_cast(percent)) / 100; + if (percent >= 100 && bookSize > 0) { + // Ensure the final percent lands inside the last spine item. + targetSize = bookSize - 1; + } + + const int spineCount = epub->getSpineItemsCount(); + if (spineCount == 0) { + return; + } + + int targetSpineIndex = spineCount - 1; + size_t prevCumulative = 0; + + for (int i = 0; i < spineCount; i++) { + const size_t cumulative = epub->getCumulativeSpineItemSize(i); + if (targetSize <= cumulative) { + // Found the spine item containing the absolute position. + targetSpineIndex = i; + prevCumulative = (i > 0) ? epub->getCumulativeSpineItemSize(i - 1) : 0; + break; + } + } + + const size_t cumulative = epub->getCumulativeSpineItemSize(targetSpineIndex); + const size_t spineSize = (cumulative > prevCumulative) ? (cumulative - prevCumulative) : 0; + // Store a normalized position within the spine so it can be applied once loaded. + pendingSpineProgress = (spineSize == 0) ? 0.0f + : static_cast(targetSize - prevCumulative) / + static_cast(spineSize); + if (pendingSpineProgress < 0.0f) { + pendingSpineProgress = 0.0f; + } else if (pendingSpineProgress > 1.0f) { + pendingSpineProgress = 1.0f; + } + + // Reset state so renderScreen() reloads and repositions on the target spine. + xSemaphoreTake(renderingMutex, portMAX_DELAY); + currentSpineIndex = targetSpineIndex; + nextPageNumber = 0; + pendingPercentJump = true; + section.reset(); + xSemaphoreGive(renderingMutex); +} + +// Compute the overall reading position as a percent of the book. +int EpubReaderActivity::getCurrentPercent() const { + if (!epub || epub->getBookSize() == 0) { + return 0; + } + + // Estimate within-spine progress based on the current page. + float chapterProgress = 0.0f; + if (section && section->pageCount > 0) { + chapterProgress = static_cast(section->currentPage) / static_cast(section->pageCount); + } + + // Convert to overall progress using cumulative spine sizes. + const float progress = epub->calculateProgress(currentSpineIndex, chapterProgress); + const int percent = static_cast(progress * 100.0f + 0.5f); + return clampPercent(percent); +} + void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) { switch (action) { case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: { @@ -268,6 +372,29 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction xSemaphoreGive(renderingMutex); break; } + case EpubReaderMenuActivity::MenuAction::GO_TO_PERCENT: { + // Launch the slider-based percent selector and return here on confirm/cancel. + const int initialPercent = getCurrentPercent(); + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new EpubReaderPercentSelectionActivity( + renderer, mappedInput, initialPercent, + [this](const int percent) { + // Apply the new position and exit back to the reader. + jumpToPercent(percent); + suppressMenuOpenOnce = true; + exitActivity(); + updateRequired = true; + }, + [this]() { + // Cancel selection and return to the reader. + suppressMenuOpenOnce = true; + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + break; + } case EpubReaderMenuActivity::MenuAction::GO_HOME: { // 2. Trigger the reader's "Go Home" callback if (onGoHome) { @@ -398,6 +525,18 @@ void EpubReaderActivity::renderScreen() { } cachedChapterTotalPageCount = 0; // resets to 0 to prevent reading cached progress again } + + if (pendingPercentJump && section->pageCount > 0) { + // Apply the pending percent jump now that we know the new section's page count. + int newPage = static_cast(pendingSpineProgress * static_cast(section->pageCount)); + if (newPage < 0) { + newPage = 0; + } else if (newPage >= section->pageCount) { + newPage = section->pageCount - 1; + } + section->currentPage = newPage; + pendingPercentJump = false; + } } renderer.clearScreen(); diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index ca7c0dc9..fa3218dd 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -18,6 +18,13 @@ class EpubReaderActivity final : public ActivityWithSubactivity { int pagesUntilFullRefresh = 0; int cachedSpineIndex = 0; int cachedChapterTotalPageCount = 0; + // Signals that the next render should reposition within the newly loaded section + // based on a cross-book percentage jump. + bool pendingPercentJump = false; + // Normalized 0.0-1.0 progress within the target spine item, computed from book percentage. + float pendingSpineProgress = 0.0f; + // Prevents the reader menu from reopening due to the confirm button used to exit the slider. + bool suppressMenuOpenOnce = false; bool updateRequired = false; const std::function onGoBack; const std::function onGoHome; @@ -29,6 +36,10 @@ class EpubReaderActivity final : public ActivityWithSubactivity { int orientedMarginBottom, int orientedMarginLeft); void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; void saveProgress(int spineIndex, int currentPage, int pageCount); + // Jump to a percentage of the book (0-100), mapping it to spine and page. + void jumpToPercent(int percent); + // Compute the current reading position as an integer percent (0-100). + int getCurrentPercent() const; void onReaderMenuBack(); void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index bd253f81..c44bd60e 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -13,7 +13,8 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { public: - enum class MenuAction { SELECT_CHAPTER, GO_HOME, DELETE_CACHE }; + // Menu actions available from the reader menu. + enum class MenuAction { SELECT_CHAPTER, GO_TO_PERCENT, GO_HOME, DELETE_CACHE }; explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, const std::function& onBack, const std::function& onAction) @@ -32,7 +33,9 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { std::string label; }; + // Fixed menu layout (order matters for up/down navigation). const std::vector menuItems = {{MenuAction::SELECT_CHAPTER, "Go to Chapter"}, + {MenuAction::GO_TO_PERCENT, "Go to %"}, {MenuAction::GO_HOME, "Go Home"}, {MenuAction::DELETE_CACHE, "Delete Book Cache"}}; diff --git a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp new file mode 100644 index 00000000..906cb445 --- /dev/null +++ b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp @@ -0,0 +1,138 @@ +#include "EpubReaderPercentSelectionActivity.h" + +#include + +#include "MappedInputManager.h" +#include "fontIds.h" + +namespace { +// Fine/coarse slider step sizes for percent adjustments. +constexpr int kSmallStep = 1; +constexpr int kLargeStep = 10; +} + +void EpubReaderPercentSelectionActivity::onEnter() { + ActivityWithSubactivity::onEnter(); + // Set up rendering task and mark first frame dirty. + renderingMutex = xSemaphoreCreateMutex(); + updateRequired = true; + xTaskCreate(&EpubReaderPercentSelectionActivity::taskTrampoline, "EpubPercentSlider", 4096, this, 1, + &displayTaskHandle); +} + +void EpubReaderPercentSelectionActivity::onExit() { + ActivityWithSubactivity::onExit(); + // Ensure the render task is stopped before freeing the mutex. + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void EpubReaderPercentSelectionActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void EpubReaderPercentSelectionActivity::displayTaskLoop() { + while (true) { + // Render only when the view is dirty and no subactivity is running. + if (updateRequired && !subActivity) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + renderScreen(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void EpubReaderPercentSelectionActivity::adjustPercent(const int delta) { + // Apply delta and clamp within 0-100. + percent += delta; + if (percent < 0) { + percent = 0; + } else if (percent > 100) { + percent = 100; + } + updateRequired = true; +} + +void EpubReaderPercentSelectionActivity::loop() { + if (subActivity) { + subActivity->loop(); + return; + } + + // Back cancels, confirm selects, arrows adjust the percent. + if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + onCancel(); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + onSelect(percent); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Left)) { + adjustPercent(-kSmallStep); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Right)) { + adjustPercent(kSmallStep); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Up)) { + adjustPercent(kLargeStep); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Down)) { + adjustPercent(-kLargeStep); + return; + } +} + +void EpubReaderPercentSelectionActivity::renderScreen() { + renderer.clearScreen(); + + // Title and numeric percent value. + renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Position", true, EpdFontFamily::BOLD); + + const std::string percentText = std::to_string(percent) + "%"; + renderer.drawCenteredText(UI_12_FONT_ID, 70, percentText.c_str(), true, EpdFontFamily::BOLD); + + // Draw slider track. + const int screenWidth = renderer.getScreenWidth(); + constexpr int barWidth = 360; + constexpr int barHeight = 16; + const int barX = (screenWidth - barWidth) / 2; + const int barY = 120; + + renderer.drawRect(barX, barY, barWidth, barHeight); + + // Fill slider based on percent. + const int fillWidth = (barWidth - 4) * percent / 100; + if (fillWidth > 0) { + renderer.fillRect(barX + 2, barY + 2, fillWidth, barHeight - 4); + } + + // Draw a simple knob centered at the current percent. + const int knobX = barX + 2 + fillWidth - 2; + renderer.fillRect(knobX, barY - 4, 4, barHeight + 8, true); + + // Hint text for step sizes. + renderer.drawCenteredText(SMALL_FONT_ID, barY + 30, "Left/Right: 1% Up/Down: 10%", true); + + // Button hints follow the current front button layout. + const auto labels = mappedInput.mapLabels("« Back", "Select", "-", "+"); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(); +} diff --git a/src/activities/reader/EpubReaderPercentSelectionActivity.h b/src/activities/reader/EpubReaderPercentSelectionActivity.h new file mode 100644 index 00000000..74a51c1f --- /dev/null +++ b/src/activities/reader/EpubReaderPercentSelectionActivity.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include + +#include + +#include "activities/ActivityWithSubactivity.h" +#include "MappedInputManager.h" + +class EpubReaderPercentSelectionActivity final : public ActivityWithSubactivity { + public: + // Slider-style percent selector for jumping within a book. + explicit EpubReaderPercentSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const int initialPercent, const std::function& onSelect, + const std::function& onCancel) + : ActivityWithSubactivity("EpubReaderPercentSelection", renderer, mappedInput), + percent(initialPercent), + onSelect(onSelect), + onCancel(onCancel) {} + + void onEnter() override; + void onExit() override; + void loop() override; + + private: + // Current percent value (0-100) shown on the slider. + int percent = 0; + // Render dirty flag for the task loop. + bool updateRequired = false; + // FreeRTOS task and mutex for rendering. + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + + // Callback invoked when the user confirms a percent. + const std::function onSelect; + // Callback invoked when the user cancels the slider. + const std::function onCancel; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + // Render the slider UI. + void renderScreen(); + // Change the current percent by a delta and clamp within bounds. + void adjustPercent(int delta); +}; From 78e9c7c733f1faf5a89b9d706f58c215d3d28dbb Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 2 Feb 2026 18:25:29 +0300 Subject: [PATCH 2/8] refactor: Simplify reader menu activity handling by removing suppression logic --- src/activities/reader/EpubReaderActivity.cpp | 16 +--------------- src/activities/reader/EpubReaderActivity.h | 2 -- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 4c3ac400..18600780 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -136,19 +136,7 @@ void EpubReaderActivity::loop() { return; } - // Enter reader menu activity (suppressed after slider confirm/cancel). - if (suppressMenuOpenOnce) { - // If we're seeing the confirm release that closed the slider, consume it and return. - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - suppressMenuOpenOnce = false; - return; - } - // If confirm is no longer pressed and no release is pending, clear suppression. - if (!mappedInput.isPressed(MappedInputManager::Button::Confirm)) { - suppressMenuOpenOnce = false; - } - } - + // Enter reader menu activity. if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { // Don't start activity transition while rendering xSemaphoreTake(renderingMutex, portMAX_DELAY); @@ -382,13 +370,11 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction [this](const int percent) { // Apply the new position and exit back to the reader. jumpToPercent(percent); - suppressMenuOpenOnce = true; exitActivity(); updateRequired = true; }, [this]() { // Cancel selection and return to the reader. - suppressMenuOpenOnce = true; exitActivity(); updateRequired = true; })); diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index fa3218dd..76ba95f9 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -23,8 +23,6 @@ class EpubReaderActivity final : public ActivityWithSubactivity { bool pendingPercentJump = false; // Normalized 0.0-1.0 progress within the target spine item, computed from book percentage. float pendingSpineProgress = 0.0f; - // Prevents the reader menu from reopening due to the confirm button used to exit the slider. - bool suppressMenuOpenOnce = false; bool updateRequired = false; const std::function onGoBack; const std::function onGoHome; From f7a0c8ac659f4592d08117bf63d35b7b96422494 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 2 Feb 2026 18:44:03 +0300 Subject: [PATCH 3/8] refactor: Rename getCurrentPercent to getBookProgressPercent and update its logic for improved accuracy --- src/activities/reader/EpubReaderActivity.cpp | 24 +++++++------------- src/activities/reader/EpubReaderActivity.h | 4 ++-- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 18600780..49a352a0 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -300,21 +300,16 @@ void EpubReaderActivity::jumpToPercent(int percent) { } // Compute the overall reading position as a percent of the book. -int EpubReaderActivity::getCurrentPercent() const { - if (!epub || epub->getBookSize() == 0) { - return 0; +float EpubReaderActivity::getBookProgressPercent() const { + if (!epub || epub->getBookSize() == 0 || !section || section->pageCount == 0) { + return 0.0f; } // Estimate within-spine progress based on the current page. - float chapterProgress = 0.0f; - if (section && section->pageCount > 0) { - chapterProgress = static_cast(section->currentPage) / static_cast(section->pageCount); - } + const float chapterProgress = static_cast(section->currentPage) / static_cast(section->pageCount); // Convert to overall progress using cumulative spine sizes. - const float progress = epub->calculateProgress(currentSpineIndex, chapterProgress); - const int percent = static_cast(progress * 100.0f + 0.5f); - return clampPercent(percent); + return epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f; } void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) { @@ -362,7 +357,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction } case EpubReaderMenuActivity::MenuAction::GO_TO_PERCENT: { // Launch the slider-based percent selector and return here on confirm/cancel. - const int initialPercent = getCurrentPercent(); + const int initialPercent = clampPercent(static_cast(getBookProgressPercent() + 0.5f)); xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); enterNewActivity(new EpubReaderPercentSelectionActivity( @@ -515,9 +510,7 @@ void EpubReaderActivity::renderScreen() { if (pendingPercentJump && section->pageCount > 0) { // Apply the pending percent jump now that we know the new section's page count. int newPage = static_cast(pendingSpineProgress * static_cast(section->pageCount)); - if (newPage < 0) { - newPage = 0; - } else if (newPage >= section->pageCount) { + if (newPage >= section->pageCount) { newPage = section->pageCount - 1; } section->currentPage = newPage; @@ -637,8 +630,7 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in int progressTextWidth = 0; // Calculate progress in book - const float sectionChapterProg = static_cast(section->currentPage) / section->pageCount; - const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100; + const float bookProgress = getBookProgressPercent(); if (showProgressText || showProgressPercentage) { // Right aligned text for progress counter diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 76ba95f9..ba615cde 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -33,11 +33,11 @@ class EpubReaderActivity final : public ActivityWithSubactivity { void renderContents(std::unique_ptr page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft); void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; + // Compute overall book progress as a percentage in the range 0-100. + float getBookProgressPercent() const; void saveProgress(int spineIndex, int currentPage, int pageCount); // Jump to a percentage of the book (0-100), mapping it to spine and page. void jumpToPercent(int percent); - // Compute the current reading position as an integer percent (0-100). - int getCurrentPercent() const; void onReaderMenuBack(); void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); From fff39db2dc4dfbb7785a6495c4062f6a92fc7ea5 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 2 Feb 2026 18:55:57 +0300 Subject: [PATCH 4/8] format fix --- src/activities/reader/EpubReaderActivity.cpp | 6 ++---- .../reader/EpubReaderPercentSelectionActivity.cpp | 2 +- .../reader/EpubReaderPercentSelectionActivity.h | 12 ++++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 49a352a0..fffc524a 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -5,7 +5,6 @@ #include #include - #include "CrossPointSettings.h" #include "CrossPointState.h" #include "EpubReaderChapterSelectionActivity.h" @@ -281,9 +280,8 @@ void EpubReaderActivity::jumpToPercent(int percent) { const size_t cumulative = epub->getCumulativeSpineItemSize(targetSpineIndex); const size_t spineSize = (cumulative > prevCumulative) ? (cumulative - prevCumulative) : 0; // Store a normalized position within the spine so it can be applied once loaded. - pendingSpineProgress = (spineSize == 0) ? 0.0f - : static_cast(targetSize - prevCumulative) / - static_cast(spineSize); + pendingSpineProgress = + (spineSize == 0) ? 0.0f : static_cast(targetSize - prevCumulative) / static_cast(spineSize); if (pendingSpineProgress < 0.0f) { pendingSpineProgress = 0.0f; } else if (pendingSpineProgress > 1.0f) { diff --git a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp index 906cb445..db67a956 100644 --- a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp @@ -9,7 +9,7 @@ namespace { // Fine/coarse slider step sizes for percent adjustments. constexpr int kSmallStep = 1; constexpr int kLargeStep = 10; -} +} // namespace void EpubReaderPercentSelectionActivity::onEnter() { ActivityWithSubactivity::onEnter(); diff --git a/src/activities/reader/EpubReaderPercentSelectionActivity.h b/src/activities/reader/EpubReaderPercentSelectionActivity.h index 74a51c1f..56238935 100644 --- a/src/activities/reader/EpubReaderPercentSelectionActivity.h +++ b/src/activities/reader/EpubReaderPercentSelectionActivity.h @@ -5,8 +5,8 @@ #include -#include "activities/ActivityWithSubactivity.h" #include "MappedInputManager.h" +#include "activities/ActivityWithSubactivity.h" class EpubReaderPercentSelectionActivity final : public ActivityWithSubactivity { public: @@ -24,17 +24,17 @@ class EpubReaderPercentSelectionActivity final : public ActivityWithSubactivity void loop() override; private: - // Current percent value (0-100) shown on the slider. + // Current percent value (0-100) shown on the slider. int percent = 0; - // Render dirty flag for the task loop. + // Render dirty flag for the task loop. bool updateRequired = false; - // FreeRTOS task and mutex for rendering. + // FreeRTOS task and mutex for rendering. TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; - // Callback invoked when the user confirms a percent. + // Callback invoked when the user confirms a percent. const std::function onSelect; - // Callback invoked when the user cancels the slider. + // Callback invoked when the user cancels the slider. const std::function onCancel; static void taskTrampoline(void* param); From a6fd503f946905501f3f44b3a7f20e6d3f218c50 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 2 Feb 2026 19:05:22 +0300 Subject: [PATCH 5/8] format fix --- src/activities/reader/EpubReaderActivity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index fffc524a..64aca030 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -8,10 +8,10 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" #include "EpubReaderChapterSelectionActivity.h" +#include "EpubReaderPercentSelectionActivity.h" #include "MappedInputManager.h" #include "RecentBooksStore.h" #include "ScreenComponents.h" -#include "activities/reader/EpubReaderPercentSelectionActivity.h" #include "fontIds.h" namespace { From 17c20db930eb242fa2aebdb6a49dd2fba9414541 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 2 Feb 2026 19:42:22 +0300 Subject: [PATCH 6/8] Update src/activities/reader/EpubReaderActivity.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/activities/reader/EpubReaderActivity.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 64aca030..eb8f6ae2 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -253,7 +253,10 @@ void EpubReaderActivity::jumpToPercent(int percent) { percent = clampPercent(percent); // Convert percent into a byte-like absolute position across the spine sizes. - size_t targetSize = (bookSize * static_cast(percent)) / 100; + // Use an overflow-safe computation: (bookSize / 100) * percent + (bookSize % 100) * percent / 100 + size_t targetSize = + (bookSize / 100) * static_cast(percent) + + (bookSize % 100) * static_cast(percent) / 100; if (percent >= 100 && bookSize > 0) { // Ensure the final percent lands inside the last spine item. targetSize = bookSize - 1; From 2a9464882823be608f4619d9afbe4d5beeead1f8 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Tue, 3 Feb 2026 12:13:39 +0300 Subject: [PATCH 7/8] cppcheck fix --- src/activities/reader/EpubReaderActivity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index eb8f6ae2..0fde714a 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -257,7 +257,7 @@ void EpubReaderActivity::jumpToPercent(int percent) { size_t targetSize = (bookSize / 100) * static_cast(percent) + (bookSize % 100) * static_cast(percent) / 100; - if (percent >= 100 && bookSize > 0) { + if (percent >= 100) { // Ensure the final percent lands inside the last spine item. targetSize = bookSize - 1; } From 2b6927f0834b5405735cdb567ecc5efc3744d30f Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Tue, 3 Feb 2026 12:20:35 +0300 Subject: [PATCH 8/8] clang format fix --- src/activities/reader/EpubReaderActivity.cpp | 3 +-- src/activities/reader/EpubReaderPercentSelectionActivity.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 0fde714a..79e350bd 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -255,8 +255,7 @@ void EpubReaderActivity::jumpToPercent(int percent) { // Convert percent into a byte-like absolute position across the spine sizes. // Use an overflow-safe computation: (bookSize / 100) * percent + (bookSize % 100) * percent / 100 size_t targetSize = - (bookSize / 100) * static_cast(percent) + - (bookSize % 100) * static_cast(percent) / 100; + (bookSize / 100) * static_cast(percent) + (bookSize % 100) * static_cast(percent) / 100; if (percent >= 100) { // Ensure the final percent lands inside the last spine item. targetSize = bookSize - 1; diff --git a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp index db67a956..ffc69e9f 100644 --- a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp @@ -106,14 +106,14 @@ void EpubReaderPercentSelectionActivity::renderScreen() { renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Position", true, EpdFontFamily::BOLD); const std::string percentText = std::to_string(percent) + "%"; - renderer.drawCenteredText(UI_12_FONT_ID, 70, percentText.c_str(), true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 90, percentText.c_str(), true, EpdFontFamily::BOLD); // Draw slider track. const int screenWidth = renderer.getScreenWidth(); constexpr int barWidth = 360; constexpr int barHeight = 16; const int barX = (screenWidth - barWidth) / 2; - const int barY = 120; + const int barY = 140; renderer.drawRect(barX, barY, barWidth, barHeight);