#pragma once #include #include #include #include #include #include #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(renderer.getScreenWidth()); const int16_t height = static_cast(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(width - previousZoneWidth), height}, READER_TOUCH_NEXT}, }; for (const auto& zone : zones) { if (!zone.enabled || !zone.rect.contains(static_cast(x), static_cast(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 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