fix: restore preferred orientation settings and long-press sub-menu
Re-add DynamicEnum entries for preferredPortrait/preferredLandscape in Settings with JSON persistence. Restore long-press Confirm on the reader menu's orientation toggle to open an inline sub-menu with all 4 orientation options. Made-with: Cursor
This commit is contained in:
@@ -124,6 +124,10 @@ bool JsonSettingsIO::saveSettings(const CrossPointSettings& s, const char* path)
|
|||||||
// Mod: timezone offset is int8_t, not uint8_t — handled separately
|
// Mod: timezone offset is int8_t, not uint8_t — handled separately
|
||||||
doc["timezoneOffsetHours"] = s.timezoneOffsetHours;
|
doc["timezoneOffsetHours"] = s.timezoneOffsetHours;
|
||||||
|
|
||||||
|
// Preferred orientations for portrait/landscape toggle (DynamicEnum, not in generic loop)
|
||||||
|
doc["preferredPortrait"] = s.preferredPortrait;
|
||||||
|
doc["preferredLandscape"] = s.preferredLandscape;
|
||||||
|
|
||||||
String json;
|
String json;
|
||||||
serializeJson(doc, json);
|
serializeJson(doc, json);
|
||||||
return Storage.writeFile(path, json);
|
return Storage.writeFile(path, json);
|
||||||
@@ -208,6 +212,14 @@ bool JsonSettingsIO::loadSettings(CrossPointSettings& s, const char* json, bool*
|
|||||||
if (s.timezoneOffsetHours < -12) s.timezoneOffsetHours = -12;
|
if (s.timezoneOffsetHours < -12) s.timezoneOffsetHours = -12;
|
||||||
if (s.timezoneOffsetHours > 14) s.timezoneOffsetHours = 14;
|
if (s.timezoneOffsetHours > 14) s.timezoneOffsetHours = 14;
|
||||||
|
|
||||||
|
// Preferred orientations for portrait/landscape toggle (DynamicEnum, not in generic loop)
|
||||||
|
auto isValidPortrait = [](uint8_t v) { return v == S::PORTRAIT || v == S::INVERTED; };
|
||||||
|
auto isValidLandscape = [](uint8_t v) { return v == S::LANDSCAPE_CW || v == S::LANDSCAPE_CCW; };
|
||||||
|
uint8_t pp = doc["preferredPortrait"] | s.preferredPortrait;
|
||||||
|
s.preferredPortrait = isValidPortrait(pp) ? pp : S::PORTRAIT;
|
||||||
|
uint8_t pl = doc["preferredLandscape"] | s.preferredLandscape;
|
||||||
|
s.preferredLandscape = isValidLandscape(pl) ? pl : S::LANDSCAPE_CW;
|
||||||
|
|
||||||
LOG_DBG("CPS", "Settings loaded from file");
|
LOG_DBG("CPS", "Settings loaded from file");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -64,6 +64,23 @@ inline const std::vector<SettingInfo>& getSettingsList() {
|
|||||||
SettingInfo::Enum(StrId::STR_ORIENTATION, &CrossPointSettings::orientation,
|
SettingInfo::Enum(StrId::STR_ORIENTATION, &CrossPointSettings::orientation,
|
||||||
{StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, StrId::STR_LANDSCAPE_CCW},
|
{StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, StrId::STR_LANDSCAPE_CCW},
|
||||||
"orientation", StrId::STR_CAT_READER),
|
"orientation", StrId::STR_CAT_READER),
|
||||||
|
SettingInfo::DynamicEnum(
|
||||||
|
StrId::STR_PREFERRED_PORTRAIT, {StrId::STR_PORTRAIT, StrId::STR_INVERTED},
|
||||||
|
[] { return static_cast<uint8_t>(SETTINGS.preferredPortrait == CrossPointSettings::INVERTED ? 1 : 0); },
|
||||||
|
[](uint8_t v) {
|
||||||
|
SETTINGS.preferredPortrait = (v == 1) ? CrossPointSettings::INVERTED : CrossPointSettings::PORTRAIT;
|
||||||
|
SETTINGS.saveToFile();
|
||||||
|
},
|
||||||
|
nullptr, StrId::STR_CAT_READER),
|
||||||
|
SettingInfo::DynamicEnum(
|
||||||
|
StrId::STR_PREFERRED_LANDSCAPE, {StrId::STR_LANDSCAPE_CW, StrId::STR_LANDSCAPE_CCW},
|
||||||
|
[] { return static_cast<uint8_t>(SETTINGS.preferredLandscape == CrossPointSettings::LANDSCAPE_CCW ? 1 : 0); },
|
||||||
|
[](uint8_t v) {
|
||||||
|
SETTINGS.preferredLandscape =
|
||||||
|
(v == 1) ? CrossPointSettings::LANDSCAPE_CCW : CrossPointSettings::LANDSCAPE_CW;
|
||||||
|
SETTINGS.saveToFile();
|
||||||
|
},
|
||||||
|
nullptr, StrId::STR_CAT_READER),
|
||||||
SettingInfo::Toggle(StrId::STR_EXTRA_SPACING, &CrossPointSettings::extraParagraphSpacing, "extraParagraphSpacing",
|
SettingInfo::Toggle(StrId::STR_EXTRA_SPACING, &CrossPointSettings::extraParagraphSpacing, "extraParagraphSpacing",
|
||||||
StrId::STR_CAT_READER),
|
StrId::STR_CAT_READER),
|
||||||
SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing",
|
SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing",
|
||||||
|
|||||||
@@ -63,7 +63,50 @@ void EpubReaderMenuActivity::onEnter() {
|
|||||||
void EpubReaderMenuActivity::onExit() { Activity::onExit(); }
|
void EpubReaderMenuActivity::onExit() { Activity::onExit(); }
|
||||||
|
|
||||||
void EpubReaderMenuActivity::loop() {
|
void EpubReaderMenuActivity::loop() {
|
||||||
// Handle navigation
|
constexpr unsigned long longPressMs = 700;
|
||||||
|
constexpr int orientationCount = CrossPointSettings::ORIENTATION_COUNT;
|
||||||
|
|
||||||
|
// Long-press Confirm on orientation item opens the sub-menu
|
||||||
|
if (!orientationSubMenuOpen && mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
|
||||||
|
mappedInput.getHeldTime() >= longPressMs &&
|
||||||
|
menuItems[selectedIndex].action == MenuAction::TOGGLE_ORIENTATION) {
|
||||||
|
orientationSubMenuOpen = true;
|
||||||
|
ignoreNextConfirmRelease = true;
|
||||||
|
orientationSubMenuIndex = pendingOrientation;
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When orientation sub-menu is open, intercept all input
|
||||||
|
if (orientationSubMenuOpen) {
|
||||||
|
buttonNavigator.onNext([this] {
|
||||||
|
orientationSubMenuIndex = (orientationSubMenuIndex + 1) % orientationCount;
|
||||||
|
requestUpdate();
|
||||||
|
});
|
||||||
|
buttonNavigator.onPrevious([this] {
|
||||||
|
orientationSubMenuIndex = (orientationSubMenuIndex + orientationCount - 1) % orientationCount;
|
||||||
|
requestUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
|
if (ignoreNextConfirmRelease) {
|
||||||
|
ignoreNextConfirmRelease = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingOrientation = static_cast<uint8_t>(orientationSubMenuIndex);
|
||||||
|
orientationSubMenuOpen = false;
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
|
orientationSubMenuOpen = false;
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal menu navigation
|
||||||
buttonNavigator.onNext([this] {
|
buttonNavigator.onNext([this] {
|
||||||
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast<int>(menuItems.size()));
|
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast<int>(menuItems.size()));
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
@@ -75,9 +118,13 @@ void EpubReaderMenuActivity::loop() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
|
if (ignoreNextConfirmRelease) {
|
||||||
|
ignoreNextConfirmRelease = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto selectedAction = menuItems[selectedIndex].action;
|
const auto selectedAction = menuItems[selectedIndex].action;
|
||||||
if (selectedAction == MenuAction::TOGGLE_ORIENTATION) {
|
if (selectedAction == MenuAction::TOGGLE_ORIENTATION) {
|
||||||
// Toggle between preferred portrait and preferred landscape.
|
|
||||||
const bool isCurrentlyPortrait =
|
const bool isCurrentlyPortrait =
|
||||||
(pendingOrientation == CrossPointSettings::PORTRAIT || pendingOrientation == CrossPointSettings::INVERTED);
|
(pendingOrientation == CrossPointSettings::PORTRAIT || pendingOrientation == CrossPointSettings::INVERTED);
|
||||||
pendingOrientation = isCurrentlyPortrait ? SETTINGS.preferredLandscape : SETTINGS.preferredPortrait;
|
pendingOrientation = isCurrentlyPortrait ? SETTINGS.preferredLandscape : SETTINGS.preferredPortrait;
|
||||||
@@ -115,25 +162,52 @@ void EpubReaderMenuActivity::loop() {
|
|||||||
void EpubReaderMenuActivity::render(RenderLock&&) {
|
void EpubReaderMenuActivity::render(RenderLock&&) {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
const auto screenHeight = renderer.getScreenHeight();
|
||||||
const auto orientation = renderer.getOrientation();
|
const auto orientation = renderer.getOrientation();
|
||||||
// Landscape orientation: button hints are drawn along a vertical edge, so we
|
|
||||||
// reserve a horizontal gutter to prevent overlap with menu content.
|
|
||||||
const bool isLandscapeCw = orientation == GfxRenderer::Orientation::LandscapeClockwise;
|
const bool isLandscapeCw = orientation == GfxRenderer::Orientation::LandscapeClockwise;
|
||||||
const bool isLandscapeCcw = orientation == GfxRenderer::Orientation::LandscapeCounterClockwise;
|
const bool isLandscapeCcw = orientation == GfxRenderer::Orientation::LandscapeCounterClockwise;
|
||||||
// Inverted portrait: button hints appear near the logical top, so we reserve
|
|
||||||
// vertical space to keep the header and list clear.
|
|
||||||
const bool isPortraitInverted = orientation == GfxRenderer::Orientation::PortraitInverted;
|
const bool isPortraitInverted = orientation == GfxRenderer::Orientation::PortraitInverted;
|
||||||
const int hintGutterWidth = (isLandscapeCw || isLandscapeCcw) ? 30 : 0;
|
const int hintGutterWidth = (isLandscapeCw || isLandscapeCcw) ? 30 : 0;
|
||||||
// Landscape CW places hints on the left edge; CCW keeps them on the right.
|
|
||||||
const int contentX = isLandscapeCw ? hintGutterWidth : 0;
|
const int contentX = isLandscapeCw ? hintGutterWidth : 0;
|
||||||
const int contentWidth = pageWidth - hintGutterWidth;
|
const int contentWidth = pageWidth - hintGutterWidth;
|
||||||
const int hintGutterHeight = isPortraitInverted ? 50 : 0;
|
const int hintGutterHeight = isPortraitInverted ? 50 : 0;
|
||||||
const int contentY = hintGutterHeight;
|
const int contentY = hintGutterHeight;
|
||||||
|
|
||||||
|
if (orientationSubMenuOpen) {
|
||||||
|
const char* subTitle = tr(STR_TOGGLE_ORIENTATION);
|
||||||
|
const int subTitleX =
|
||||||
|
contentX +
|
||||||
|
(contentWidth - renderer.getTextWidth(UI_12_FONT_ID, subTitle, EpdFontFamily::BOLD)) / 2;
|
||||||
|
renderer.drawText(UI_12_FONT_ID, subTitleX, 15 + contentY, subTitle, true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
|
constexpr int lineHeight = 35;
|
||||||
|
const int listStartY = screenHeight / 2 - (CrossPointSettings::ORIENTATION_COUNT * lineHeight) / 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < CrossPointSettings::ORIENTATION_COUNT; ++i) {
|
||||||
|
const int displayY = listStartY + (i * lineHeight);
|
||||||
|
const bool isSelected = (i == orientationSubMenuIndex);
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
renderer.fillRect(contentX, displayY, contentWidth - 1, lineHeight, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* label = I18N.get(orientationLabels[i]);
|
||||||
|
renderer.drawText(UI_10_FONT_ID, contentX + 40, displayY + 4, label, !isSelected);
|
||||||
|
|
||||||
|
if (i == pendingOrientation) {
|
||||||
|
renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY + 4, "*", !isSelected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
const std::string truncTitle =
|
const std::string truncTitle =
|
||||||
renderer.truncatedText(UI_12_FONT_ID, title.c_str(), contentWidth - 40, EpdFontFamily::BOLD);
|
renderer.truncatedText(UI_12_FONT_ID, title.c_str(), contentWidth - 40, EpdFontFamily::BOLD);
|
||||||
// Manual centering so we can respect the content gutter.
|
|
||||||
const int titleX =
|
const int titleX =
|
||||||
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, truncTitle.c_str(), EpdFontFamily::BOLD)) / 2;
|
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, truncTitle.c_str(), EpdFontFamily::BOLD)) / 2;
|
||||||
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, truncTitle.c_str(), true, EpdFontFamily::BOLD);
|
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, truncTitle.c_str(), true, EpdFontFamily::BOLD);
|
||||||
@@ -156,7 +230,6 @@ void EpubReaderMenuActivity::render(RenderLock&&) {
|
|||||||
const bool isSelected = (static_cast<int>(i) == selectedIndex);
|
const bool isSelected = (static_cast<int>(i) == selectedIndex);
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
// Highlight only the content area so we don't paint over hint gutters.
|
|
||||||
renderer.fillRect(contentX, displayY, contentWidth - 1, lineHeight, true);
|
renderer.fillRect(contentX, displayY, contentWidth - 1, lineHeight, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ class EpubReaderMenuActivity final : public Activity {
|
|||||||
int totalPages = 0;
|
int totalPages = 0;
|
||||||
int bookProgressPercent = 0;
|
int bookProgressPercent = 0;
|
||||||
|
|
||||||
|
bool orientationSubMenuOpen = false;
|
||||||
|
int orientationSubMenuIndex = 0;
|
||||||
|
bool ignoreNextConfirmRelease = false;
|
||||||
|
|
||||||
std::string bookCachePath;
|
std::string bookCachePath;
|
||||||
uint8_t pendingLetterboxFill = BookSettings::USE_GLOBAL;
|
uint8_t pendingLetterboxFill = BookSettings::USE_GLOBAL;
|
||||||
static constexpr int LETTERBOX_FILL_OPTION_COUNT = 4;
|
static constexpr int LETTERBOX_FILL_OPTION_COUNT = 4;
|
||||||
|
|||||||
Reference in New Issue
Block a user