## Summary * **What is the goal of this PR?** Fixes #2402. On the X3, the "Time to Sleep" picker's 5-minute side buttons were inverted (left increased, right decreased) and the on-screen legend didn't match the physical buttons. * **What changes are included?** * Flip the large-step (±5 min) direction on X3 so the left side button decreases and the right increases, matching the layout. X4 is unchanged. * Add a device-specific step-hint string (`STR_SLEEP_TIMER_STEP_HINT_X3`) selected via `gpio.deviceIsX3()`. X3 shows `+/-: 1 min Side buttons: 5 min`; X4 keeps the original `Left/Right: 1 min Up/Down: 5 min`. * Same fix applied to both slider pickers that read the raw up/down side buttons: the Time-to-Sleep picker and the "Go to %" picker (`EpubReaderPercentSelectionActivity`), each with its own X3 hint string (`STR_SLEEP_TIMER_STEP_HINT_X3`, `STR_PERCENT_STEP_HINT_X3`). ## Additional Context * **Root cause:** the X3's side buttons sit one on each edge of the screen (power on top), whereas the X4 has a vertical up/down rocker on the right edge. So `BTN_UP` is physically the *left* button on X3 but the *top* button on X4. The picker mapped up→+5 / down→−5 unconditionally, which reads naturally on the X4 rocker but inverted on the X3's left/right buttons. The static legend ("Up/Down: 5 min") had the same X3-only mismatch. * The codebase already detects the device at runtime and handles this layout difference elsewhere (e.g. `LyraTheme::drawSideButtonHints` draws "Up on left, Down on right" for X3), so this reuses the same `gpio.deviceIsX3()` signal. * Swept the codebase for other side-button sliders: `ClockOffsetActivity` already uses the `Next/Previous` (`ButtonNavigator`) abstraction, which resolves to left=decrement / right=increment on X3, so it needs no change. List/page navigation (Wi-Fi, KOReader sync, BMP viewer) and the keyboard cursor are also unaffected. The two slider pickers above were the only ones using raw up/down. * **X4 is untouched** — same direction, same legend wording as before. * **Tested on X3 hardware:** left side button now decreases, right increases, and the legend matches. X4 not yet tested on device (no unit on hand); its behaviour and strings are unchanged from master. * **Translations:** the new `STR_SLEEP_TIMER_STEP_HINT_X3` and `STR_PERCENT_STEP_HINT_X3` were added to all 26 languages, but the non-English ones are AI-generated and would benefit from a native-speaker pass — particularly Hebrew (RTL ordering with a leading `+/-`), Kazakh, and Vietnamese. --- ### AI Usage Did you use AI tools to help write this code? _**YES**_ --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c9d27826f9
commit
e4d3981469
@@ -1,8 +1,11 @@
|
||||
#include "EpubReaderPercentSelectionActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalGPIO.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
@@ -51,8 +54,14 @@ void EpubReaderPercentSelectionActivity::loop() {
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] { adjustPercent(-kSmallStep); });
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] { adjustPercent(kSmallStep); });
|
||||
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this] { adjustPercent(kLargeStep); });
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down}, [this] { adjustPercent(-kLargeStep); });
|
||||
// On X3 the side buttons sit on the left/right edges of the screen rather than as a vertical up/down
|
||||
// rocker (X4), so BTN_UP is physically the left button and BTN_DOWN the right one. Flip the large-step
|
||||
// direction there so the left button decreases and the right button increases, matching the layout.
|
||||
const int upDelta = gpio.deviceIsX3() ? -kLargeStep : kLargeStep;
|
||||
const int downDelta = gpio.deviceIsX3() ? kLargeStep : -kLargeStep;
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this, upDelta] { adjustPercent(upDelta); });
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down},
|
||||
[this, downDelta] { adjustPercent(downDelta); });
|
||||
}
|
||||
|
||||
void EpubReaderPercentSelectionActivity::render(RenderLock&&) {
|
||||
@@ -89,8 +98,13 @@ void EpubReaderPercentSelectionActivity::render(RenderLock&&) {
|
||||
const int knobX = barX + 2 + fillWidth - 2;
|
||||
renderer.fillRect(knobX, barY - 4, 4, barHeight + 8, true);
|
||||
|
||||
// Hint text for step sizes.
|
||||
UITheme::drawCenteredText(renderer, screen, SMALL_FONT_ID, barY + 30, tr(STR_PERCENT_STEP_HINT), true);
|
||||
// Two-line step hint built from separate label + value strings (front buttons = fine step, side
|
||||
// buttons = coarse step), so the layout doesn't depend on a separator hidden in translated text.
|
||||
char line[64];
|
||||
snprintf(line, sizeof(line), "%s %d%%", I18N.get(StrId::STR_STEP_HINT_FRONT), kSmallStep);
|
||||
UITheme::drawCenteredText(renderer, screen, SMALL_FONT_ID, barY + 30, line, true);
|
||||
snprintf(line, sizeof(line), "%s %d%%", I18N.get(StrId::STR_STEP_HINT_SIDE), kLargeStep);
|
||||
UITheme::drawCenteredText(renderer, screen, SMALL_FONT_ID, barY + 52, line, true);
|
||||
|
||||
// Button hints follow the current front button layout.
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "-", "+");
|
||||
|
||||
@@ -336,10 +336,9 @@ void SettingsActivity::syncQuickResumeTimeoutForSleepScreen(bool sleepScreenChan
|
||||
void SettingsActivity::openSleepTimeoutPicker() {
|
||||
startActivityForResult(
|
||||
std::make_unique<IntervalSelectionActivity>(
|
||||
renderer, mappedInput, "SleepTimeoutInterval", StrId::STR_TIME_TO_SLEEP, StrId::STR_SLEEP_TIMER_STEP_HINT,
|
||||
SETTINGS.sleepTimeoutMinutes, CrossPointSettings::MIN_SLEEP_TIMEOUT_MINUTES,
|
||||
CrossPointSettings::MAX_SLEEP_TIMEOUT_MINUTES, 1, 5, StrId::STR_SLEEP_TIMER_VALUE_FORMAT, false, true,
|
||||
StrId::STR_SLEEP_NEVER),
|
||||
renderer, mappedInput, "SleepTimeoutInterval", StrId::STR_TIME_TO_SLEEP, SETTINGS.sleepTimeoutMinutes,
|
||||
CrossPointSettings::MIN_SLEEP_TIMEOUT_MINUTES, CrossPointSettings::MAX_SLEEP_TIMEOUT_MINUTES, 1, 5,
|
||||
StrId::STR_SLEEP_TIMER_VALUE_FORMAT, false, true, StrId::STR_SLEEP_NEVER),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
SETTINGS.sleepTimeoutMinutes = static_cast<uint8_t>(std::get<IntervalResult>(result.data).value);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "IntervalSelectionActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalGPIO.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -25,6 +26,18 @@ void IntervalSelectionActivity::adjustValue(const int delta) {
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void IntervalSelectionActivity::drawStepHintLine(const int y, const StrId labelId, const int step) {
|
||||
char stepText[24];
|
||||
if (valueFormatId != StrId::STR_NONE_OPT) {
|
||||
snprintf(stepText, sizeof(stepText), I18N.get(valueFormatId), static_cast<unsigned int>(step));
|
||||
} else {
|
||||
snprintf(stepText, sizeof(stepText), "%d", step);
|
||||
}
|
||||
char line[64];
|
||||
snprintf(line, sizeof(line), "%s %s", I18N.get(labelId), stepText);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, line, true);
|
||||
}
|
||||
|
||||
void IntervalSelectionActivity::loop() {
|
||||
if (ignoreConfirmRelease) {
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
@@ -52,8 +65,15 @@ void IntervalSelectionActivity::loop() {
|
||||
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] { adjustValue(-smallStep); });
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] { adjustValue(smallStep); });
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this] { adjustValue(largeStep); });
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down}, [this] { adjustValue(-largeStep); });
|
||||
|
||||
// On X3 the side buttons sit on the left/right edges of the screen rather than as a vertical up/down
|
||||
// rocker (X4), so BTN_UP is physically the left button and BTN_DOWN the right one. Flip the large-step
|
||||
// direction there so the left button decreases and the right button increases, matching the layout.
|
||||
const int upDelta = gpio.deviceIsX3() ? -largeStep : largeStep;
|
||||
const int downDelta = gpio.deviceIsX3() ? largeStep : -largeStep;
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this, upDelta] { adjustValue(upDelta); });
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down},
|
||||
[this, downDelta] { adjustValue(downDelta); });
|
||||
}
|
||||
|
||||
void IntervalSelectionActivity::render(RenderLock&&) {
|
||||
@@ -88,7 +108,11 @@ void IntervalSelectionActivity::render(RenderLock&&) {
|
||||
const int knobX = std::max(barX + 2, barX + 2 + fillWidth - 2);
|
||||
renderer.fillRect(knobX, barY - 4, 4, barHeight + 8, true);
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, barY + 30, I18N.get(stepHintId), true);
|
||||
// Two-line step hint: front buttons do the small step, side buttons the large step. Built from
|
||||
// separate label + value strings (rather than splitting one localized sentence) so the layout
|
||||
// doesn't depend on translators preserving a hidden separator.
|
||||
drawStepHintLine(barY + 30, StrId::STR_STEP_HINT_FRONT, smallStep);
|
||||
drawStepHintLine(barY + 52, StrId::STR_STEP_HINT_SIDE, largeStep);
|
||||
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "-", "+");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
@@ -11,13 +11,12 @@ class GfxRenderer;
|
||||
class IntervalSelectionActivity final : public Activity {
|
||||
public:
|
||||
explicit IntervalSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const char* activityName,
|
||||
StrId titleId, StrId stepHintId, int initialValue, int minValue, int maxValue,
|
||||
int smallStep, int largeStep, StrId valueFormatId = StrId::STR_NONE_OPT,
|
||||
StrId titleId, int initialValue, int minValue, int maxValue, int smallStep,
|
||||
int largeStep, StrId valueFormatId = StrId::STR_NONE_OPT,
|
||||
bool readerActivity = false, bool ignoreInitialConfirmRelease = false,
|
||||
StrId maxBoundaryLabelId = StrId::STR_NONE_OPT)
|
||||
: Activity(activityName, renderer, mappedInput),
|
||||
titleId(titleId),
|
||||
stepHintId(stepHintId),
|
||||
valueFormatId(valueFormatId),
|
||||
maxBoundaryLabelId(maxBoundaryLabelId),
|
||||
value(initialValue),
|
||||
@@ -35,7 +34,6 @@ class IntervalSelectionActivity final : public Activity {
|
||||
|
||||
private:
|
||||
StrId titleId;
|
||||
StrId stepHintId;
|
||||
StrId valueFormatId;
|
||||
StrId maxBoundaryLabelId;
|
||||
int value;
|
||||
@@ -49,4 +47,5 @@ class IntervalSelectionActivity final : public Activity {
|
||||
|
||||
void adjustValue(int delta);
|
||||
int clampedValue(int candidate) const;
|
||||
void drawStepHintLine(int y, StrId labelId, int step);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user