Files
Crosspoint/src/activities/reader/ReaderUtils.h
T

193 lines
7.0 KiB
C++

#pragma once
#include <CrossPointSettings.h>
#include <GfxRenderer.h>
#include <HalGPIO.h>
#include <HalTiltSensor.h>
#include <Logging.h>
#include <components/bars/tap-zones.h>
#include "MappedInputManager.h"
#include "activities/ActivityManager.h"
namespace ReaderUtils {
constexpr unsigned long GO_HOME_MS = 1000;
constexpr unsigned long GO_BACK_OR_HOME_MS = GO_HOME_MS;
constexpr unsigned long SKIP_HOLD_MS = 700;
constexpr unsigned long BOOKMARK_HOLD_MS = 400;
constexpr unsigned long BOOKMARK_MESSAGE_DURATION_MS = 2500;
enum ReaderTouchAction : freeink::ui::ActionId {
READER_TOUCH_PREV = 1,
READER_TOUCH_NEXT = 3,
};
inline void applyOrientation(GfxRenderer& renderer, const uint8_t orientation) {
switch (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;
}
}
struct PageTurnResult {
bool prev;
bool next;
bool fromTilt;
};
inline PageTurnResult detectPageTurn(const MappedInputManager& input) {
const bool usePress = SETTINGS.longPressButtonBehavior == SETTINGS.OFF;
const bool tiltNext = SETTINGS.tiltPageTurn && halTiltSensor.wasTiltedForward();
const bool tiltPrev = SETTINGS.tiltPageTurn && halTiltSensor.wasTiltedBack();
const bool swapFront = input.isNavDirectionSwapped();
const auto prevButton = swapFront ? MappedInputManager::Button::Right : MappedInputManager::Button::Left;
const auto nextButton = swapFront ? MappedInputManager::Button::Left : MappedInputManager::Button::Right;
const bool prev =
tiltPrev ||
(usePress ? (input.wasPressed(MappedInputManager::Button::PageBack) || input.wasPressed(prevButton))
: (input.wasReleased(MappedInputManager::Button::PageBack) || input.wasReleased(prevButton)));
const bool powerTurn = SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN &&
input.wasReleased(MappedInputManager::Button::Power);
const bool next = tiltNext || (usePress ? (input.wasPressed(MappedInputManager::Button::PageForward) || powerTurn ||
input.wasPressed(nextButton))
: (input.wasReleased(MappedInputManager::Button::PageForward) || powerTurn ||
input.wasReleased(nextButton)));
return {prev, next, tiltPrev || tiltNext};
}
struct TouchPageTurn {
bool prev;
bool next;
unsigned long heldMs;
};
inline TouchPageTurn detectTouchPageTurn(GfxRenderer& renderer, const MappedInputManager& input) {
TouchPageTurn result{false, false, 0};
if (!SETTINGS.touchReaderControls || !input.hasTouch()) {
return result;
}
int x = 0;
int y = 0;
if (!input.wasScreenTapped(x, y)) {
return result;
}
const int16_t width = static_cast<int16_t>(renderer.getScreenWidth());
const int16_t height = static_cast<int16_t>(renderer.getScreenHeight());
const int16_t previousZoneWidth = width / 3;
const freeink::ui::TapZone zones[] = {
{freeink::ui::Rect{0, 0, previousZoneWidth, height}, READER_TOUCH_PREV},
{freeink::ui::Rect{previousZoneWidth, 0, static_cast<int16_t>(width - previousZoneWidth), height},
READER_TOUCH_NEXT},
};
for (const auto& zone : zones) {
if (!zone.enabled || !zone.rect.contains(static_cast<int16_t>(x), static_cast<int16_t>(y))) continue;
result.prev = zone.action == READER_TOUCH_PREV;
result.next = zone.action == READER_TOUCH_NEXT;
break;
}
result.heldMs = gpio.lastTouchHeldMs();
return result;
}
// Reader menu opens on a downward swipe from the top edge (replaces the old center tap-and-hold).
inline bool isTouchMenuGesture(const MappedInputManager& input) {
return SETTINGS.touchReaderControls && input.hasTouch() && input.wasMenuGesture();
}
// One helper, blocking or deferred: the async form starts the refresh and
// returns so the caller can overlap CPU work with the panel's refresh time.
// Async callers must not touch the framebuffer until
// renderer.waitRefreshComplete() and must rebuild the differential baseline
// before the next page turn (the tiled grayscale cleanup does).
inline void displayWithRefreshCycle(const GfxRenderer& renderer, int& pagesUntilFullRefresh, bool async = false) {
const auto mode = (pagesUntilFullRefresh <= 1) ? HalDisplay::HALF_REFRESH : HalDisplay::FAST_REFRESH;
if (async) {
renderer.displayBufferAsync(mode);
} else {
renderer.displayBuffer(mode);
}
if (pagesUntilFullRefresh <= 1) {
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
} else {
pagesUntilFullRefresh--;
}
}
// Grayscale anti-aliasing pass. Renders content twice (LSB + MSB) to build
// the grayscale buffer. Only the content callback is re-rendered — status bars
// and other overlays should be drawn before calling this.
// Kept as a template to avoid std::function overhead; instantiated once per reader type.
template <typename RenderFn>
void renderAntiAliased(GfxRenderer& renderer, RenderFn&& renderFn) {
if (!renderer.storeBwBuffer()) {
LOG_ERR("READER", "Failed to store BW buffer for anti-aliasing");
return;
}
renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
renderFn();
renderer.copyGrayscaleLsbBuffers();
renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
renderFn();
renderer.copyGrayscaleMsbBuffers();
renderer.displayGrayBuffer();
renderer.setRenderMode(GfxRenderer::BW);
renderer.restoreBwBuffer();
}
struct BackNavCallback {
void* ctx;
void (*fn)(void*);
};
// Returns true if the back button was consumed (caller should return).
// Long press (>= GO_BACK_OR_HOME_MS):
// - default: go to file browser
// - with backShortToFileBrowser: go home
// Short press (< GO_BACK_OR_HOME_MS):
// - default: go home
// - with backShortToFileBrowser: go to file browser.
inline bool handleBackNavigation(const MappedInputManager& mappedInput, ActivityManager& activityManager,
const char* filePath, BackNavCallback goHome) {
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_BACK_OR_HOME_MS) {
if (SETTINGS.backShortToFileBrowser) {
goHome.fn(goHome.ctx);
} else {
activityManager.goToFileBrowser(filePath);
}
return true;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back) && mappedInput.getHeldTime() < GO_BACK_OR_HOME_MS) {
if (SETTINGS.backShortToFileBrowser) {
activityManager.goToFileBrowser(filePath);
} else {
goHome.fn(goHome.ctx);
}
return true;
}
return false;
}
} // namespace ReaderUtils