feat: implement screen rotation feature in Epub reader menu

This commit is contained in:
Arthur Tazhitdinov 2026-02-03 17:38:31 +03:00
parent 465cc029ff
commit a85ec57018
4 changed files with 69 additions and 60 deletions

View File

@ -20,22 +20,8 @@ constexpr unsigned long goHomeMs = 1000;
constexpr int statusBarMargin = 19;
constexpr int progressBarMarginTop = 1;
} // namespace
void EpubReaderActivity::taskTrampoline(void* param) {
auto* self = static_cast<EpubReaderActivity*>(param);
self->displayTaskLoop();
}
void EpubReaderActivity::onEnter() {
ActivityWithSubactivity::onEnter();
if (!epub) {
return;
}
// Configure screen orientation based on settings
switch (SETTINGS.orientation) {
void applyReaderOrientation(GfxRenderer& renderer, const uint8_t orientation) {
switch (orientation) {
case CrossPointSettings::ORIENTATION::PORTRAIT:
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
break;
@ -51,6 +37,24 @@ void EpubReaderActivity::onEnter() {
default:
break;
}
}
} // namespace
void EpubReaderActivity::taskTrampoline(void* param) {
auto* self = static_cast<EpubReaderActivity*>(param);
self->displayTaskLoop();
}
void EpubReaderActivity::onEnter() {
ActivityWithSubactivity::onEnter();
if (!epub) {
return;
}
// Configure screen orientation based on settings
applyReaderOrientation(renderer, SETTINGS.orientation);
renderingMutex = xSemaphoreCreateMutex();
@ -127,11 +131,10 @@ void EpubReaderActivity::loop() {
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
// Don't start activity transition while rendering
xSemaphoreTake(renderingMutex, portMAX_DELAY);
const int currentPage = section ? section->currentPage : 0;
const int totalPages = section ? section->pageCount : 0;
exitActivity();
enterNewActivity(new EpubReaderMenuActivity(
this->renderer, this->mappedInput, epub->getTitle(), [this]() { onReaderMenuBack(); },
this->renderer, this->mappedInput, epub->getTitle(), SETTINGS.orientation,
[this](const uint8_t orientation) { onReaderMenuBack(orientation); },
[this](EpubReaderMenuActivity::MenuAction action) { onReaderMenuConfirm(action); }));
xSemaphoreGive(renderingMutex);
}
@ -220,8 +223,9 @@ void EpubReaderActivity::loop() {
}
}
void EpubReaderActivity::onReaderMenuBack() {
void EpubReaderActivity::onReaderMenuBack(const uint8_t orientation) {
exitActivity();
applyOrientation(orientation);
updateRequired = true;
}
@ -268,40 +272,6 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
xSemaphoreGive(renderingMutex);
break;
}
case EpubReaderMenuActivity::MenuAction::ROTATE_SCREEN: {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (section) {
cachedSpineIndex = currentSpineIndex;
cachedChapterTotalPageCount = section->pageCount;
nextPageNumber = section->currentPage;
}
SETTINGS.orientation = (SETTINGS.orientation + 1) % CrossPointSettings::ORIENTATION_COUNT;
SETTINGS.saveToFile();
switch (SETTINGS.orientation) {
case CrossPointSettings::ORIENTATION::PORTRAIT:
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CW:
renderer.setOrientation(GfxRenderer::Orientation::LandscapeClockwise);
break;
case CrossPointSettings::ORIENTATION::INVERTED:
renderer.setOrientation(GfxRenderer::Orientation::PortraitInverted);
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CCW:
renderer.setOrientation(GfxRenderer::Orientation::LandscapeCounterClockwise);
break;
default:
break;
}
section.reset();
exitActivity();
updateRequired = true;
xSemaphoreGive(renderingMutex);
break;
}
case EpubReaderMenuActivity::MenuAction::GO_HOME: {
// 2. Trigger the reader's "Go Home" callback
if (onGoHome) {
@ -337,6 +307,27 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
}
}
void EpubReaderActivity::applyOrientation(const uint8_t orientation) {
if (SETTINGS.orientation == orientation) {
return;
}
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (section) {
cachedSpineIndex = currentSpineIndex;
cachedChapterTotalPageCount = section->pageCount;
nextPageNumber = section->currentPage;
}
SETTINGS.orientation = orientation;
SETTINGS.saveToFile();
applyReaderOrientation(renderer, SETTINGS.orientation);
section.reset();
xSemaphoreGive(renderingMutex);
}
void EpubReaderActivity::displayTaskLoop() {
while (true) {
if (updateRequired) {

View File

@ -29,8 +29,9 @@ 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);
void onReaderMenuBack();
void onReaderMenuBack(uint8_t orientation);
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
void applyOrientation(uint8_t orientation);
public:
explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Epub> epub,

View File

@ -56,9 +56,15 @@ void EpubReaderMenuActivity::loop() {
selectedIndex = (selectedIndex + 1) % menuItems.size();
updateRequired = true;
} else if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
const auto selectedAction = menuItems[selectedIndex].action;
if (selectedAction == MenuAction::ROTATE_SCREEN) {
pendingOrientation = (pendingOrientation + 1) % orientationLabels.size();
updateRequired = true;
return;
}
// 1. Capture the callback and action locally
auto actionCallback = onAction;
auto selectedAction = menuItems[selectedIndex].action;
// 2. Execute the callback
actionCallback(selectedAction);
@ -66,7 +72,7 @@ void EpubReaderMenuActivity::loop() {
// 3. CRITICAL: Return immediately. 'this' is likely deleted now.
return;
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
onBack();
onBack(pendingOrientation);
return; // Also return here just in case
}
}
@ -93,6 +99,12 @@ void EpubReaderMenuActivity::renderScreen() {
}
renderer.drawText(UI_10_FONT_ID, 20, displayY, menuItems[i].label.c_str(), !isSelected);
if (menuItems[i].action == MenuAction::ROTATE_SCREEN) {
const auto value = orientationLabels[pendingOrientation];
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, displayY, value, !isSelected);
}
}
// Footer / Hints

View File

@ -16,9 +16,12 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
enum class MenuAction { SELECT_CHAPTER, ROTATE_SCREEN, GO_HOME, DELETE_CACHE };
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
const std::function<void()>& onBack, const std::function<void(MenuAction)>& onAction)
const uint8_t currentOrientation,
const std::function<void(uint8_t)>& onBack,
const std::function<void(MenuAction)>& onAction)
: ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput),
title(title),
pendingOrientation(currentOrientation),
onBack(onBack),
onAction(onAction) {}
@ -33,7 +36,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
};
const std::vector<MenuItem> menuItems = {{MenuAction::SELECT_CHAPTER, "Go to Chapter"},
{MenuAction::ROTATE_SCREEN, "Rotate Screen"},
{MenuAction::ROTATE_SCREEN, "Reading Orientation"},
{MenuAction::GO_HOME, "Go Home"},
{MenuAction::DELETE_CACHE, "Delete Book Cache"}};
@ -42,8 +45,10 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
std::string title = "Reader Menu";
uint8_t pendingOrientation = 0;
const std::vector<const char*> orientationLabels = {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"};
const std::function<void()> onBack;
const std::function<void(uint8_t)> onBack;
const std::function<void(MenuAction)> onAction;
static void taskTrampoline(void* param);