Files
Crosspoint/src/activities/reader/EndOfBookOptions.cpp
T

116 lines
5.2 KiB
C++

#include "EndOfBookOptions.h"
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <I18n.h>
#include "CrossPointSettings.h"
#include "ReaderUtils.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/ButtonNavigator.h"
#include "util/NextBookFinder.h"
namespace {
// Display name without the file extension, mirroring the file browser rows
std::string displayName(const std::string& filename) {
const auto pos = filename.rfind('.');
return filename.substr(0, pos);
}
} // namespace
void EndOfBookOptions::loadOnce(const std::string& currentBookPath) {
if (isLoaded.load(std::memory_order_acquire)) {
return;
}
folder = FsHelpers::extractFolderPath(currentBookPath);
names = NextBookFinder::findNextBooks(currentBookPath, MAX_SUGGESTIONS);
selector = 0;
// Release-publish so the main task, which gates all access on isLoaded, never
// observes a partially built list
isLoaded.store(true, std::memory_order_release);
}
bool EndOfBookOptions::menuActive() const { return isLoaded.load(std::memory_order_acquire) && !names.empty(); }
std::string EndOfBookOptions::fullPath(const size_t index) const {
if (index >= names.size()) {
return {};
}
return folder == "/" ? "/" + names[index] : folder + "/" + names[index];
}
EndOfBookOptions::Action EndOfBookOptions::handleMenuInput(const MappedInputManager& input, std::string* openPath) {
if (input.wasReleased(MappedInputManager::Button::Confirm)) {
if (selector < static_cast<int>(names.size())) {
if (openPath) {
*openPath = fullPath(selector);
}
return Action::OpenBook;
}
return Action::GoHome; // "Home" entry selected
}
// Short-press Back returns to the last page; a long press falls through to the
// reader's own handler (file browser). Home is reached through the list's Home entry.
if (input.wasReleased(MappedInputManager::Button::Back) && input.getHeldTime() < ReaderUtils::GO_HOME_MS) {
return Action::LastPage;
}
// Selection movement on the standard list navigation buttons (side Up/Down plus front
// Left/Right, orientation swap included). It follows the reader's page-turn semantics
// (press-triggered by default, release-triggered when a long-press behavior is
// configured, same rule as ReaderUtils::detectPageTurn). This matters on entry: with
// press-triggered turns, the press that turned the final page already fired in the
// reader, and its release must not double-fire into this menu.
const bool usePress = SETTINGS.longPressButtonBehavior == CrossPointSettings::OFF;
const auto triggered = [&](const MappedInputManager::Button button) {
return usePress ? input.wasPressed(button) : input.wasReleased(button);
};
const int itemCount = static_cast<int>(names.size()) + 1; // + "Home" entry
if (triggered(MappedInputManager::Button::NavPrevious)) {
selector = ButtonNavigator::previousIndex(selector, itemCount); // wraps to the bottom
return Action::Redraw;
}
if (triggered(MappedInputManager::Button::NavNext)) {
selector = ButtonNavigator::nextIndex(selector, itemCount); // wraps to the top
return Action::Redraw;
}
return Action::None;
}
void EndOfBookOptions::render(GfxRenderer& renderer, const MappedInputManager& input) const {
const auto& metrics = UITheme::getInstance().getMetrics();
if (!menuActive()) {
// No suggestions: the historical plain end screen. 3/8 of the screen height matches
// the previous fixed position on the 480x800 panel and scales to other resolutions.
renderer.drawCenteredText(UI_12_FONT_ID, renderer.getScreenHeight() * 3 / 8, tr(STR_END_OF_BOOK), true,
EpdFontFamily::BOLD);
return;
}
// Suggestion menu: title, list (+ Home entry) and button hints. The hints are drawn at
// the physical front buttons, which is a logical side/top edge in the rotated
// orientations — lay out inside the safe area so nothing hides behind them. Vertical
// positions derive from the safe-area height and font line heights so other panel
// resolutions scale (review request on #2532).
const Rect safe = UITheme::getInstance().getScreenSafeArea(renderer, true, false);
const int titleY = safe.y + safe.height / 8;
const int subtitleY = titleY + renderer.getLineHeight(UI_12_FONT_ID) + metrics.verticalSpacing;
const int listTop = subtitleY + renderer.getLineHeight(UI_10_FONT_ID) + metrics.verticalSpacing * 2;
UITheme::drawCenteredText(renderer, safe, UI_12_FONT_ID, titleY, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD);
UITheme::drawCenteredText(renderer, safe, UI_10_FONT_ID, subtitleY, tr(STR_EOB_CONTINUE_WITH));
const int listHeight = safe.y + safe.height - listTop - metrics.verticalSpacing;
GUI.drawList(renderer, Rect{safe.x, listTop, safe.width, listHeight}, static_cast<int>(names.size()) + 1, selector,
[this](const int index) {
return index < static_cast<int>(names.size()) ? displayName(names[index])
: std::string(tr(STR_EOB_HOME));
});
const auto labels = input.mapLabels(tr(STR_BACK), tr(STR_OPEN), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
}