mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
195 lines
7.4 KiB
C++
195 lines
7.4 KiB
C++
#include "EpubReaderPercentSelectionActivity.h"
|
|
|
|
#include <GfxRenderer.h>
|
|
#include <HalGPIO.h>
|
|
#include <I18n.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
|
|
#include "components/UITheme.h"
|
|
#include "components/UiSliderDialog.h"
|
|
#include "fontIds.h"
|
|
|
|
namespace fui = freeink::ui;
|
|
|
|
namespace {
|
|
constexpr fui::ActionId ACTION_SLIDER = 1;
|
|
constexpr fui::ActionId ACTION_STEP = 2;
|
|
constexpr fui::ActionId ACTION_CANCEL = 3;
|
|
constexpr fui::ActionId ACTION_OK = 4;
|
|
// Fine/coarse step sizes for percent adjustments (buttons and -/+ tap zones).
|
|
constexpr int kSmallStep = 1;
|
|
constexpr int kLargeStep = 10;
|
|
} // namespace
|
|
|
|
EpubReaderPercentSelectionActivity::EpubReaderPercentSelectionActivity(GfxRenderer& renderer,
|
|
MappedInputManager& mappedInput,
|
|
const int initialPercent)
|
|
: Activity("EpubReaderPercentSelection", renderer, mappedInput), UiAppHost(renderer), percent(initialPercent) {}
|
|
|
|
void EpubReaderPercentSelectionActivity::onEnter() {
|
|
Activity::onEnter();
|
|
resetUi();
|
|
app.on(ACTION_SLIDER, &EpubReaderPercentSelectionActivity::onSliderEvent, this);
|
|
app.on(ACTION_STEP, &EpubReaderPercentSelectionActivity::onStepEvent, this);
|
|
app.on(ACTION_CANCEL, &EpubReaderPercentSelectionActivity::onCancelEvent, this);
|
|
app.on(ACTION_OK, &EpubReaderPercentSelectionActivity::onOkEvent, this);
|
|
app.setScreen(&EpubReaderPercentSelectionActivity::percentScreen, this);
|
|
// Set up rendering task and mark first frame dirty.
|
|
requestUpdate();
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::onExit() { Activity::onExit(); }
|
|
|
|
void EpubReaderPercentSelectionActivity::adjustPercent(const int delta) {
|
|
// Wrap using a 100-value ring (0% and 100% are the same wrap point), but keep 100 as the
|
|
// natural landing value when reached without crossing the boundary (e.g. 90 + 10 = 100).
|
|
const int raw = percent + delta;
|
|
if (raw > 0 && raw % 100 == 0) {
|
|
percent = 100;
|
|
} else {
|
|
percent = ((raw % 100) + 100) % 100;
|
|
}
|
|
requestUpdate();
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::setPercent(const int value) {
|
|
const int clamped = std::clamp(value, 0, 100);
|
|
if (clamped == percent) return;
|
|
percent = clamped;
|
|
requestUpdate();
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::onSliderEvent(const fui::ActionEvent& event, void* user) {
|
|
auto* self = static_cast<EpubReaderPercentSelectionActivity*>(user);
|
|
if (event.dragPermille < 0) return;
|
|
self->setPercent((static_cast<int>(event.dragPermille) * 100 + 500) / 1000);
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::onStepEvent(const fui::ActionEvent& event, void* user) {
|
|
static_cast<EpubReaderPercentSelectionActivity*>(user)->adjustPercent(event.value * kSmallStep);
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::onCancelEvent(const fui::ActionEvent&, void* user) {
|
|
auto* self = static_cast<EpubReaderPercentSelectionActivity*>(user);
|
|
self->app.clearTapFlash(); // the tap leaves this screen
|
|
self->cancel();
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::onOkEvent(const fui::ActionEvent&, void* user) {
|
|
auto* self = static_cast<EpubReaderPercentSelectionActivity*>(user);
|
|
self->app.clearTapFlash(); // the tap leaves this screen
|
|
self->confirm();
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::cancel() {
|
|
ActivityResult result;
|
|
result.isCancelled = true;
|
|
setResult(std::move(result));
|
|
finish();
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::confirm() {
|
|
setResult(PercentResult{percent});
|
|
finish();
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::loop() {
|
|
// Touch goes through the FreeInkApp: render() registered the slider and -/+ hit
|
|
// rects; the slider follows the finger via InputDrag (dragPermille per held frame).
|
|
// Runs before the Back handler because the release of a drag can also register as a
|
|
// swipe (e.g. the left-edge rightward back gesture) — the drag must consume it so it
|
|
// can't cancel the dialog or step the percent.
|
|
const auto route = routeTouch(mappedInput, false, /*routeHeld=*/true);
|
|
if (route.routed && app.invalidated()) requestUpdate();
|
|
if (route) {
|
|
if (route.event.dragPermille >= 0) draggingSlider = true;
|
|
return;
|
|
}
|
|
if (routingReady() && draggingSlider) {
|
|
// Drag ended (possibly off the slider): swallow the tap/swipe events it produced.
|
|
if (!route.snap.touchHeld) draggingSlider = false;
|
|
return;
|
|
}
|
|
|
|
// Back cancels, confirm selects, arrows adjust the percent.
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
|
cancel();
|
|
return;
|
|
}
|
|
|
|
const auto swipe = mappedInput.wasSwipe();
|
|
if (swipe == MappedInputManager::SwipeDir::Right) {
|
|
adjustPercent(kLargeStep);
|
|
return;
|
|
}
|
|
if (swipe == MappedInputManager::SwipeDir::Left) {
|
|
adjustPercent(-kLargeStep);
|
|
return;
|
|
}
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
|
confirm();
|
|
return;
|
|
}
|
|
|
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] { adjustPercent(-kSmallStep); });
|
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] { adjustPercent(kSmallStep); });
|
|
|
|
// On edge-button boards (X3, X4 Pro) 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.
|
|
const int upDelta = gpio.hasEdgeSideButtons() ? -kLargeStep : kLargeStep;
|
|
const int downDelta = gpio.hasEdgeSideButtons() ? kLargeStep : -kLargeStep;
|
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this, upDelta] { adjustPercent(upDelta); });
|
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down},
|
|
[this, downDelta] { adjustPercent(downDelta); });
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::percentScreen(UiScreen& screen, void* user) {
|
|
static_cast<EpubReaderPercentSelectionActivity*>(user)->buildPercentScreen(screen);
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::buildPercentScreen(UiScreen& screen) {
|
|
char readout[16];
|
|
snprintf(readout, sizeof(readout), "%d%%", percent);
|
|
char hint1[64];
|
|
snprintf(hint1, sizeof(hint1), "%s %d%%", I18N.get(StrId::STR_STEP_HINT_FRONT), kSmallStep);
|
|
char hint2[64];
|
|
snprintf(hint2, sizeof(hint2), "%s %d%%", I18N.get(StrId::STR_STEP_HINT_SIDE), kLargeStep);
|
|
|
|
UiSliderDialogSpec spec;
|
|
spec.readout = readout;
|
|
spec.value = percent;
|
|
spec.max = 100;
|
|
spec.sliderAction = ACTION_SLIDER;
|
|
spec.stepAction = ACTION_STEP;
|
|
spec.cancelAction = ACTION_CANCEL;
|
|
spec.okAction = ACTION_OK;
|
|
spec.hintLine1 = hint1;
|
|
spec.hintLine2 = hint2;
|
|
buildSliderDialogScreen(screen, renderer, mappedInput, spec);
|
|
}
|
|
|
|
void EpubReaderPercentSelectionActivity::render(RenderLock&&) {
|
|
renderer.clearScreen();
|
|
|
|
auto& theme = UITheme::getInstance();
|
|
auto metrics = theme.getMetrics();
|
|
Rect screen = theme.getScreenSafeArea(renderer, true, false);
|
|
|
|
GUI.drawHeader(renderer, Rect{screen.x, screen.y + metrics.topPadding, screen.width, metrics.headerHeight},
|
|
tr(STR_GO_TO_PERCENT));
|
|
|
|
// Percent readout, slider, and hints render through the app so the slider and its
|
|
// -/+ zones register touch hit rects.
|
|
renderUi();
|
|
|
|
// Button hints follow the current front button layout.
|
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "-", "+");
|
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
renderer.displayBuffer();
|
|
}
|