224 lines
8.3 KiB
C++
224 lines
8.3 KiB
C++
|
|
#include "EpubReaderBookmarkSelectionActivity.h"
|
||
|
|
|
||
|
|
#include <GfxRenderer.h>
|
||
|
|
|
||
|
|
#include "ActivityResult.h"
|
||
|
|
#include "MappedInputManager.h"
|
||
|
|
#include "components/UITheme.h"
|
||
|
|
#include "fontIds.h"
|
||
|
|
|
||
|
|
int EpubReaderBookmarkSelectionActivity::getTotalItems() const { return static_cast<int>(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<int>(bookmarks.size())) {
|
||
|
|
selectorIndex = std::max(0, static_cast<int>(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<int>(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();
|
||
|
|
}
|