#pragma once #include #include #include #include #include #include "MappedInputManager.h" namespace ReaderUtils { constexpr unsigned long GO_HOME_MS = 1000; constexpr unsigned long SKIP_HOLD_MS = 700; constexpr unsigned long BOOKMARK_HOLD_MS = 400; constexpr unsigned long BOOKMARK_MESSAGE_DURATION_MS = 2500; // Press-and-hold in the center touch zone (see detectTouchPageTurn) opens the // reader menu, the touch analogue of releasing the Confirm button. constexpr unsigned long TOUCH_MENU_HOLD_MS = 400; 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 = SETTINGS.frontButtonFollowOrientation && (SETTINGS.orientation == CrossPointSettings::INVERTED || SETTINGS.orientation == CrossPointSettings::LANDSCAPE_CCW); 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}; } // Touch reader controls: left third = page back, right third = forward, center = // open menu on press-and-hold (see isTouchMenuGesture). heldMs is the contact // duration, for the same long-press behavior as the buttons (chapter skip). // All-false on Xteink (no touch) or when the setting is off. struct TouchPageTurn { bool prev; bool next; bool center; unsigned long heldMs; }; inline TouchPageTurn detectTouchPageTurn(GfxRenderer& renderer) { TouchPageTurn result{false, false, false, 0}; if (gpio.isXteinkDevice() || !SETTINGS.touchReaderControls) { return result; } float nx = 0.0f, ny = 0.0f; if (!gpio.wasTouchTap(nx, ny)) { return result; } int lx = 0, ly = 0; renderer.tapToLogical(nx, ny, lx, ly); const int third = renderer.getScreenWidth() / 3; if (lx < third) { result.prev = true; } else if (lx >= 2 * third) { result.next = true; } else { result.center = true; } result.heldMs = gpio.lastTouchHeldMs(); return result; } // True when the center zone was pressed and held long enough to open the reader // menu (touch analogue of a Confirm release). inline bool isTouchMenuGesture(const TouchPageTurn& touch) { return touch.center && touch.heldMs >= TOUCH_MENU_HOLD_MS; } inline void displayWithRefreshCycle(const GfxRenderer& renderer, int& pagesUntilFullRefresh) { if (pagesUntilFullRefresh <= 1) { renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else { renderer.displayBuffer(); 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 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(); } } // namespace ReaderUtils