328 lines
15 KiB
C++
328 lines
15 KiB
C++
#include "EpubReaderMenuActivity.h"
|
|
|
|
#include <GfxRenderer.h>
|
|
#include <I18n.h>
|
|
|
|
#include "KOReaderCredentialStore.h"
|
|
#include "MappedInputManager.h"
|
|
#include "components/UITheme.h"
|
|
#include "fontIds.h"
|
|
|
|
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
|
const std::string& title, const int currentPage, const int totalPages,
|
|
const int bookProgressPercent, const uint8_t currentOrientation,
|
|
const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride,
|
|
const int8_t initialImageRenderingOverride,
|
|
const int8_t initialFontFamilyOverride,
|
|
const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness,
|
|
const bool hasStarredPages, const bool isCurrentPageStarred)
|
|
: MenuListActivity("EpubReaderMenu", renderer, mappedInput),
|
|
currentPageStarred(isCurrentPageStarred),
|
|
pendingOrientation(currentOrientation),
|
|
pendingEmbeddedStyleOverride(initialEmbeddedStyleOverride),
|
|
pendingImageRenderingOverride(initialImageRenderingOverride),
|
|
pendingFontFamilyOverride(initialFontFamilyOverride),
|
|
pendingFontSizeOverride(initialFontSizeOverride),
|
|
pendingTextDarkness(initialTextDarkness),
|
|
title(title),
|
|
currentPage(currentPage),
|
|
totalPages(totalPages),
|
|
bookProgressPercent(bookProgressPercent) {
|
|
buildMenuItems(hasFootnotes, hasStarredPages);
|
|
}
|
|
|
|
void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPages) {
|
|
menuItems.reserve(19);
|
|
|
|
// --- Navigation ---
|
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_NAVIGATION));
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_SELECT_CHAPTER, SettingAction::None));
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_GO_TO_PERCENT, SettingAction::None));
|
|
|
|
// Bookmarks, footnotes
|
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_BOOKMARKS));
|
|
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_STAR_PAGE, SettingAction::None));
|
|
if (hasStarredPages) {
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_STARRED_PAGES, SettingAction::None));
|
|
}
|
|
if (hasFootnotes) {
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_FOOTNOTES, SettingAction::None));
|
|
}
|
|
|
|
// --- Appearance ---
|
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_APPEARANCE));
|
|
|
|
auto* self = this;
|
|
// Orientation: straightforward 0-3 cycle
|
|
menuItems.push_back(SettingInfo::DynamicEnumCtx(
|
|
StrId::STR_ORIENTATION,
|
|
{StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, StrId::STR_LANDSCAPE_CCW}, self,
|
|
[](const void* ctx) -> uint8_t { return static_cast<const EpubReaderMenuActivity*>(ctx)->pendingOrientation; },
|
|
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingOrientation = v; }));
|
|
|
|
// Embedded style: cycles default(-1) -> ON(1) -> OFF(0) via DynamicEnum indices 0/1/2
|
|
menuItems.push_back(SettingInfo::DynamicEnumCtx(
|
|
StrId::STR_EMBEDDED_STYLE,
|
|
{StrId::STR_DEFAULT_VALUE, StrId::STR_STATE_ON, StrId::STR_STATE_OFF}, self,
|
|
[](const void* ctx) -> uint8_t {
|
|
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
|
|
if (s->pendingEmbeddedStyleOverride < 0) return 0;
|
|
if (s->pendingEmbeddedStyleOverride > 0) return 1;
|
|
return 2;
|
|
},
|
|
[](void* ctx, uint8_t v) {
|
|
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
|
|
if (v == 0)
|
|
s->pendingEmbeddedStyleOverride = -1;
|
|
else if (v == 1)
|
|
s->pendingEmbeddedStyleOverride = 1;
|
|
else
|
|
s->pendingEmbeddedStyleOverride = 0;
|
|
})
|
|
.withSubmenu(StrId::STR_READER_OVERRIDES));
|
|
|
|
// Image rendering: cycles default(-1) -> display(0) -> placeholder(1) -> suppress(2)
|
|
menuItems.push_back(SettingInfo::DynamicEnumCtx(
|
|
StrId::STR_IMAGES,
|
|
{StrId::STR_DEFAULT_VALUE, StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER,
|
|
StrId::STR_IMAGES_SUPPRESS},
|
|
self,
|
|
[](const void* ctx) -> uint8_t {
|
|
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
|
|
return (s->pendingImageRenderingOverride < 0) ? 0 : (s->pendingImageRenderingOverride + 1);
|
|
},
|
|
[](void* ctx, uint8_t v) {
|
|
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
|
|
s->pendingImageRenderingOverride = (v == 0) ? -1 : static_cast<int8_t>(v - 1);
|
|
})
|
|
.withSubmenu(StrId::STR_READER_OVERRIDES));
|
|
|
|
// Reader font family: cycles default(-1) -> Bookerly(0) -> Noto Sans(1) -> Open Dyslexic(2)
|
|
menuItems.push_back(
|
|
SettingInfo::DynamicEnumCtx(
|
|
StrId::STR_FONT_FAMILY,
|
|
{StrId::STR_DEFAULT_VALUE, StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC}, self,
|
|
[](const void* ctx) -> uint8_t {
|
|
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
|
|
return (s->pendingFontFamilyOverride < 0) ? 0 : static_cast<uint8_t>(s->pendingFontFamilyOverride + 1);
|
|
},
|
|
[](void* ctx, uint8_t v) {
|
|
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
|
|
s->pendingFontFamilyOverride = (v == 0) ? -1 : static_cast<int8_t>(v - 1);
|
|
})
|
|
.withSubmenu(StrId::STR_READER_OVERRIDES));
|
|
|
|
// Reader font size: cycles default(-1) -> Small(0) -> Medium(1) -> Large(2) -> X Large(3)
|
|
menuItems.push_back(
|
|
SettingInfo::DynamicEnumCtx(
|
|
StrId::STR_FONT_SIZE,
|
|
{StrId::STR_DEFAULT_VALUE, StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE}, self,
|
|
[](const void* ctx) -> uint8_t {
|
|
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
|
|
return (s->pendingFontSizeOverride < 0) ? 0 : static_cast<uint8_t>(s->pendingFontSizeOverride + 1);
|
|
},
|
|
[](void* ctx, uint8_t v) {
|
|
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
|
|
s->pendingFontSizeOverride = (v == 0) ? -1 : static_cast<int8_t>(v - 1);
|
|
})
|
|
.withSubmenu(StrId::STR_READER_OVERRIDES));
|
|
|
|
// Text darkness: straightforward 0-3 cycle
|
|
menuItems.push_back(
|
|
SettingInfo::DynamicEnumCtx(
|
|
StrId::STR_TEXT_DARKNESS, {StrId::STR_NORMAL, StrId::STR_DARK, StrId::STR_EXTRA_DARK, StrId::STR_MAX_DARK},
|
|
self,
|
|
[](const void* ctx) -> uint8_t {
|
|
return static_cast<const EpubReaderMenuActivity*>(ctx)->pendingTextDarkness;
|
|
},
|
|
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingTextDarkness = v; })
|
|
.withSubmenu(StrId::STR_READER_OVERRIDES));
|
|
|
|
// Helper functions, reading ruler, auto page turn, orientation
|
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS));
|
|
// Auto page turn: ACTION type with custom cycling in onActionSelected
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_AUTO_TURN_PAGES_PER_MIN, SettingAction::None));
|
|
|
|
// --- Synchronisation (only if credentials are set) ---
|
|
if (KOREADER_STORE.hasCredentials()) {
|
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_KOREADER_SYNC));
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_PULL_PROGRESS_FROM_OTHER_DEVICES, SettingAction::None));
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_PUSH_PROGRESS_FROM_THIS_DEVICE, SettingAction::None));
|
|
}
|
|
|
|
// --- Tools ---
|
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_TOOLS));
|
|
menuItems.push_back(
|
|
SettingInfo::Action(StrId::STR_SCREENSHOT_BUTTON, SettingAction::None).withSubmenu(StrId::STR_READER_TOOLS));
|
|
menuItems.push_back(
|
|
SettingInfo::Action(StrId::STR_DISPLAY_QR, SettingAction::None).withSubmenu(StrId::STR_READER_TOOLS));
|
|
menuItems.push_back(
|
|
SettingInfo::Action(StrId::STR_DELETE_CACHE, SettingAction::None).withSubmenu(StrId::STR_READER_TOOLS));
|
|
menuItems.push_back(SettingInfo::Action(StrId::STR_GO_HOME_BUTTON, SettingAction::None));
|
|
}
|
|
|
|
EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForNameId(StrId nameId) {
|
|
switch (nameId) {
|
|
case StrId::STR_SELECT_CHAPTER:
|
|
return MenuAction::SELECT_CHAPTER;
|
|
case StrId::STR_GO_TO_PERCENT:
|
|
return MenuAction::GO_TO_PERCENT;
|
|
case StrId::STR_STARRED_PAGES:
|
|
return MenuAction::STARRED_PAGES;
|
|
case StrId::STR_STAR_PAGE:
|
|
return MenuAction::STAR_PAGE;
|
|
case StrId::STR_FOOTNOTES:
|
|
return MenuAction::FOOTNOTES;
|
|
case StrId::STR_AUTO_TURN_PAGES_PER_MIN:
|
|
return MenuAction::AUTO_PAGE_TURN;
|
|
case StrId::STR_EMBEDDED_STYLE:
|
|
return MenuAction::EMBEDDED_STYLE;
|
|
case StrId::STR_IMAGES:
|
|
return MenuAction::IMAGE_RENDERING;
|
|
case StrId::STR_TEXT_DARKNESS:
|
|
return MenuAction::TEXT_DARKNESS;
|
|
case StrId::STR_ORIENTATION:
|
|
return MenuAction::ROTATE_SCREEN;
|
|
case StrId::STR_PULL_PROGRESS_FROM_OTHER_DEVICES:
|
|
return MenuAction::PULL_REMOTE;
|
|
case StrId::STR_PUSH_PROGRESS_FROM_THIS_DEVICE:
|
|
return MenuAction::PUSH_LOCAL;
|
|
case StrId::STR_SCREENSHOT_BUTTON:
|
|
return MenuAction::SCREENSHOT;
|
|
case StrId::STR_DISPLAY_QR:
|
|
return MenuAction::DISPLAY_QR;
|
|
case StrId::STR_DELETE_CACHE:
|
|
return MenuAction::DELETE_CACHE;
|
|
case StrId::STR_GO_HOME_BUTTON:
|
|
return MenuAction::GO_HOME;
|
|
default:
|
|
return MenuAction::NONE;
|
|
}
|
|
}
|
|
|
|
void EpubReaderMenuActivity::finishWithAction(MenuAction action) {
|
|
setResult(MenuResult{static_cast<int>(action), pendingOrientation, selectedPageTurnOption,
|
|
pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride,
|
|
pendingFontSizeOverride, pendingTextDarkness});
|
|
finish();
|
|
}
|
|
|
|
void EpubReaderMenuActivity::onActionSelected(int index) {
|
|
const auto& item = menuItems[index];
|
|
|
|
// Auto page turn cycles locally (not a DynamicEnum because labels are raw strings)
|
|
if (item.nameId == StrId::STR_AUTO_TURN_PAGES_PER_MIN) {
|
|
selectedPageTurnOption = (selectedPageTurnOption + 1) % 5;
|
|
requestUpdate();
|
|
return;
|
|
}
|
|
|
|
// All other ACTION items finish with a result
|
|
finishWithAction(actionForNameId(item.nameId));
|
|
}
|
|
|
|
void EpubReaderMenuActivity::onSettingToggled(int /*index*/) {
|
|
// DynamicEnum items update pending state via their setters — no persistence needed.
|
|
}
|
|
|
|
void EpubReaderMenuActivity::onBackPressed() {
|
|
ActivityResult result;
|
|
result.isCancelled = true;
|
|
result.data = MenuResult{-1,
|
|
pendingOrientation,
|
|
selectedPageTurnOption,
|
|
pendingEmbeddedStyleOverride,
|
|
pendingImageRenderingOverride,
|
|
pendingFontFamilyOverride,
|
|
pendingFontSizeOverride,
|
|
pendingTextDarkness};
|
|
setResult(std::move(result));
|
|
finish();
|
|
}
|
|
|
|
std::string EpubReaderMenuActivity::getItemValueString(int index) const {
|
|
const auto& item = menuItems[index];
|
|
|
|
// Auto page turn: custom labels
|
|
if (item.nameId == StrId::STR_AUTO_TURN_PAGES_PER_MIN) {
|
|
if (selectedPageTurnOption == 0) return std::string(tr(STR_STATE_OFF));
|
|
return std::string(pageTurnLabels[selectedPageTurnOption]);
|
|
}
|
|
|
|
// Star page: reflect current page's star state
|
|
if (item.nameId == StrId::STR_STAR_PAGE) {
|
|
return currentPageStarred ? std::string(tr(STR_STATE_ON)) : std::string(tr(STR_STATE_OFF));
|
|
}
|
|
|
|
// Plain ACTION items (select chapter, screenshot, etc.) show no value.
|
|
// Submenu placeholders should still show the standard submenu indicator.
|
|
if (item.type == SettingType::ACTION) {
|
|
if (item.action == SettingAction::Submenu) return MenuListActivity::getItemValueString(index);
|
|
return {};
|
|
}
|
|
|
|
if (item.type == SettingType::ENUM) {
|
|
if (item.nameId == StrId::STR_EMBEDDED_STYLE && pendingEmbeddedStyleOverride < 0) {
|
|
const auto defaultEffective = (SETTINGS.embeddedStyle != 0) ? tr(STR_STATE_ON) : tr(STR_STATE_OFF);
|
|
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + defaultEffective + ")";
|
|
}
|
|
if (item.nameId == StrId::STR_IMAGES && pendingImageRenderingOverride < 0) {
|
|
const auto index = static_cast<size_t>(SETTINGS.imageRendering + 1);
|
|
if (index < item.enumValues.size()) {
|
|
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[index]) + ")";
|
|
}
|
|
}
|
|
if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0) {
|
|
const auto index = static_cast<size_t>(SETTINGS.fontFamily + 1);
|
|
if (index < item.enumValues.size()) {
|
|
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[index]) + ")";
|
|
}
|
|
}
|
|
if (item.nameId == StrId::STR_FONT_SIZE && pendingFontSizeOverride < 0) {
|
|
const auto index = static_cast<size_t>(SETTINGS.fontSize + 1);
|
|
if (index < item.enumValues.size()) {
|
|
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[index]) + ")";
|
|
}
|
|
}
|
|
}
|
|
|
|
// DynamicEnum items use the standard display
|
|
return MenuListActivity::getItemValueString(index);
|
|
}
|
|
|
|
void EpubReaderMenuActivity::onEnter() { MenuListActivity::onEnter(); }
|
|
|
|
void EpubReaderMenuActivity::render(RenderLock&&) {
|
|
renderer.clearScreen();
|
|
const Rect contentRect = UITheme::getContentRect(renderer, true, false);
|
|
|
|
// Title
|
|
const std::string truncTitle =
|
|
renderer.truncatedText(UI_12_FONT_ID, title.c_str(), contentRect.width - 40, EpdFontFamily::BOLD);
|
|
const int titleX =
|
|
contentRect.x +
|
|
(contentRect.width - renderer.getTextWidth(UI_12_FONT_ID, truncTitle.c_str(), EpdFontFamily::BOLD)) / 2;
|
|
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentRect.y, truncTitle.c_str(), true, EpdFontFamily::BOLD);
|
|
|
|
// Progress summary
|
|
std::string progressLine;
|
|
if (totalPages > 0) {
|
|
progressLine = std::string(tr(STR_CHAPTER_PREFIX)) + std::to_string(currentPage) + "/" +
|
|
std::to_string(totalPages) + std::string(tr(STR_PAGES_SEPARATOR));
|
|
}
|
|
progressLine += std::string(tr(STR_BOOK_PREFIX)) + std::to_string(bookProgressPercent) + "%";
|
|
renderer.drawCenteredText(UI_10_FONT_ID, 45, progressLine.c_str());
|
|
|
|
// Menu Items
|
|
const int startY = 75 + contentRect.y;
|
|
const int listHeight = contentRect.height - (startY - contentRect.y);
|
|
drawMenuList(Rect{contentRect.x, startY, contentRect.width, listHeight});
|
|
|
|
// Footer / Hints
|
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
renderer.displayBuffer();
|
|
}
|