#include "EpubReaderBookmarkSelectionActivity.h" #include #include "ActivityResult.h" #include "MappedInputManager.h" #include "components/UITheme.h" #include "fontIds.h" int EpubReaderBookmarkSelectionActivity::getTotalItems() const { return static_cast(bookmarks.size()); } int EpubReaderBookmarkSelectionActivity::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 EpubReaderBookmarkSelectionActivity::getBookmarkPrefix(const Bookmark& bookmark) const { std::string label; if (epub) { const int tocIndex = epub->getTocIndexForSpineIndex(bookmark.spineIndex); if (tocIndex >= 0 && tocIndex < epub->getTocItemsCount()) { label = epub->getTocItem(tocIndex).title; } else { label = "Chapter " + std::to_string(bookmark.spineIndex + 1); } } else { label = "Chapter " + std::to_string(bookmark.spineIndex + 1); } if (!bookmark.snippet.empty()) { label += " - " + bookmark.snippet; } return label; } std::string EpubReaderBookmarkSelectionActivity::getPageSuffix(const Bookmark& bookmark) { return " - Page " + std::to_string(bookmark.pageNumber + 1); } void EpubReaderBookmarkSelectionActivity::onEnter() { Activity::onEnter(); requestUpdate(); } void EpubReaderBookmarkSelectionActivity::onExit() { Activity::onExit(); } void EpubReaderBookmarkSelectionActivity::loop() { const int totalItems = getTotalItems(); if (totalItems == 0) { if (mappedInput.wasReleased(MappedInputManager::Button::Back) || mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { ActivityResult r; r.isCancelled = true; setResult(std::move(r)); finish(); } return; } if (deleteConfirmMode) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (ignoreNextConfirmRelease) { ignoreNextConfirmRelease = false; } else { BookmarkStore::removeBookmark(cachePath, bookmarks[pendingDeleteIndex].spineIndex, bookmarks[pendingDeleteIndex].pageNumber); bookmarks.erase(bookmarks.begin() + pendingDeleteIndex); if (selectorIndex >= static_cast(bookmarks.size())) { selectorIndex = std::max(0, static_cast(bookmarks.size()) - 1); } deleteConfirmMode = false; requestUpdate(); } } if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { deleteConfirmMode = false; ignoreNextConfirmRelease = false; requestUpdate(); } return; } constexpr unsigned long DELETE_HOLD_MS = 700; if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= DELETE_HOLD_MS) { if (totalItems > 0 && selectorIndex >= 0 && selectorIndex < totalItems) { deleteConfirmMode = true; ignoreNextConfirmRelease = true; pendingDeleteIndex = selectorIndex; requestUpdate(); } return; } const int pageItems = getPageItems(); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (selectorIndex >= 0 && selectorIndex < totalItems) { const auto& b = bookmarks[selectorIndex]; setResult(SyncResult{.spineIndex = b.spineIndex, .page = b.pageNumber}); finish(); } else { ActivityResult r; r.isCancelled = true; setResult(std::move(r)); finish(); } } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { ActivityResult r; r.isCancelled = true; setResult(std::move(r)); finish(); } 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 EpubReaderBookmarkSelectionActivity::render(RenderLock&&) { renderer.clearScreen(); 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(); const int totalItems = getTotalItems(); const int titleX = contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, "Go to Bookmark", EpdFontFamily::BOLD)) / 2; renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, "Go to Bookmark", true, EpdFontFamily::BOLD); if (totalItems == 0) { renderer.drawCenteredText(UI_10_FONT_ID, 100 + contentY, "No bookmarks", true); } else { const auto pageStartIndex = selectorIndex / pageItems * pageItems; renderer.fillRect(contentX, 60 + contentY + (selectorIndex % pageItems) * 30 - 2, contentWidth - 1, 30); const int maxLabelWidth = contentWidth - 40 - contentX - 20; 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 suffix = getPageSuffix(bookmarks[itemIndex]); const int suffixWidth = renderer.getTextWidth(UI_10_FONT_ID, suffix.c_str()); const std::string prefix = getBookmarkPrefix(bookmarks[itemIndex]); const std::string truncatedPrefix = renderer.truncatedText(UI_10_FONT_ID, prefix.c_str(), maxLabelWidth - suffixWidth); const std::string label = truncatedPrefix + suffix; renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, label.c_str(), !isSelected); } } if (deleteConfirmMode && pendingDeleteIndex < static_cast(bookmarks.size())) { const std::string suffix = getPageSuffix(bookmarks[pendingDeleteIndex]); std::string msg = "Delete bookmark" + suffix + "?"; constexpr int margin = 15; constexpr int popupY = 200; const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, msg.c_str(), EpdFontFamily::BOLD); const int textHeight = renderer.getLineHeight(UI_12_FONT_ID); const int w = textWidth + margin * 2; const int h = textHeight + margin * 2; const int x = (renderer.getScreenWidth() - w) / 2; renderer.fillRect(x - 2, popupY - 2, w + 4, h + 4, true); renderer.fillRect(x, popupY, w, h, false); const int textX = x + (w - textWidth) / 2; const int textY = popupY + margin - 2; renderer.drawText(UI_12_FONT_ID, textX, textY, msg.c_str(), true, EpdFontFamily::BOLD); const auto labels = mappedInput.mapLabels("Cancel", "Delete", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } else { if (!bookmarks.empty()) { const char* deleteHint = "Hold select to delete"; const int hintWidth = renderer.getTextWidth(SMALL_FONT_ID, deleteHint); renderer.drawText(SMALL_FONT_ID, (renderer.getScreenWidth() - hintWidth) / 2, renderer.getScreenHeight() - 70, deleteHint); } const auto labels = mappedInput.mapLabels("\xC2\xAB Back", "Select", "Up", "Down"); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } renderer.displayBuffer(); }