Files
crosspoint-reader/src/activities/reader/EpubReaderPercentSelectionActivity.cpp

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();
}