#include "MappedInputManager.h" #include #include #include #include "CrossPointSettings.h" #include "components/TouchRegistry.h" bool MappedInputManager::mapButton(const Button button, bool (HalGPIO::*fn)(uint8_t) const) const { const auto sideLayout = SETTINGS.sideButtonLayout; switch (button) { case Button::Back: // Logical Back maps to user-configured front button. return (gpio.*fn)(SETTINGS.frontButtonBack); case Button::Confirm: // Logical Confirm maps to user-configured front button. return (gpio.*fn)(SETTINGS.frontButtonConfirm); case Button::Left: // Logical Left maps to user-configured front button. return (gpio.*fn)(SETTINGS.frontButtonLeft); case Button::Right: // Logical Right maps to user-configured front button. return (gpio.*fn)(SETTINGS.frontButtonRight); case Button::Up: // Side buttons remain fixed for Up/Down. return (gpio.*fn)(HalGPIO::BTN_UP); case Button::Down: // Side buttons remain fixed for Up/Down. return (gpio.*fn)(HalGPIO::BTN_DOWN); case Button::Power: // Power button bypasses remapping. return (gpio.*fn)(HalGPIO::BTN_POWER); case Button::PageBack: // Reader page navigation uses side buttons and can be swapped via settings. switch (sideLayout) { case CrossPointSettings::PREV_NEXT: return (gpio.*fn)(HalGPIO::BTN_UP); case CrossPointSettings::NEXT_PREV: return (gpio.*fn)(HalGPIO::BTN_DOWN); case CrossPointSettings::SIDE_BUTTONS_DISABLED: default: return false; } case Button::PageForward: // Reader page navigation uses side buttons and can be swapped via settings. switch (sideLayout) { case CrossPointSettings::PREV_NEXT: return (gpio.*fn)(HalGPIO::BTN_DOWN); case CrossPointSettings::NEXT_PREV: return (gpio.*fn)(HalGPIO::BTN_UP); case CrossPointSettings::SIDE_BUTTONS_DISABLED: default: return false; } } return false; } // Top-left corner fallback, as a fraction of the logical screen. Generous to hit. static constexpr float BACK_GESTURE_FRAC_X = 0.22f; static constexpr float BACK_GESTURE_FRAC_Y = 0.12f; static constexpr float BOTTOM_EDGE_BACK_GESTURE_FRAC_Y = 0.14f; static constexpr unsigned long TOUCH_DOWN_SELECT_DELAY_MS = 90; bool MappedInputManager::wasBottomEdgeSwipeUp() const { float nxs = 0.0f, nys = 0.0f, nxe = 0.0f, nye = 0.0f; if (!gpio.wasSwipe(nxs, nys, nxe, nye)) return false; int sx = 0, sy = 0, ex = 0, ey = 0; renderer.tapToLogical(nxs, nys, sx, sy); renderer.tapToLogical(nxe, nye, ex, ey); const int screenHeight = renderer.getScreenHeight(); const int bottomEdgeTop = screenHeight - static_cast(screenHeight * BOTTOM_EDGE_BACK_GESTURE_FRAC_Y); return sy >= bottomEdgeTop && ey < sy && std::abs(ey - sy) > std::abs(ex - sx); } bool MappedInputManager::wasBackGesture() const { if (wasBottomEdgeSwipeUp()) return true; float nx = 0.0f, ny = 0.0f; if (!gpio.wasTouchTap(nx, ny)) return false; int lx = 0, ly = 0; renderer.tapToLogical(nx, ny, lx, ly); // A tap on the theme's header Back target acts as Back. int id = 0; if (TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Back, id)) return true; // Else the top-left corner, for screens with no Back target (e.g. the reader). return lx <= renderer.getScreenWidth() * BACK_GESTURE_FRAC_X && ly <= renderer.getScreenHeight() * BACK_GESTURE_FRAC_Y; } bool MappedInputManager::wasItemTapped(int& id) const { float nx = 0.0f, ny = 0.0f; if (!gpio.wasTouchTap(nx, ny)) return false; int lx = 0, ly = 0; renderer.tapToLogical(nx, ny, lx, ly); return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id); } bool MappedInputManager::wasItemTouchedDown(int& id) const { float nx = 0.0f, ny = 0.0f; unsigned long heldMs = 0; if (!gpio.isTouchTapCandidate(nx, ny, heldMs)) { touchSelectTracking = false; touchSelectEmitted = false; touchSelectId = -1; return false; } if (!touchSelectTracking) { float downNx = 0.0f, downNy = 0.0f; if (!gpio.wasTouchDown(downNx, downNy)) return false; int lx = 0, ly = 0; renderer.tapToLogical(downNx, downNy, lx, ly); int candidateId = -1; if (!TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, candidateId)) { touchSelectTracking = false; touchSelectEmitted = false; touchSelectId = -1; return false; } touchSelectTracking = true; touchSelectEmitted = false; touchSelectId = candidateId; } if (touchSelectEmitted || heldMs < TOUCH_DOWN_SELECT_DELAY_MS) return false; touchSelectEmitted = true; id = touchSelectId; return true; } bool MappedInputManager::wasItemLongPressed(int& id) const { static constexpr unsigned long TOUCH_LONG_PRESS_MS = 500; float nx = 0.0f, ny = 0.0f; if (!gpio.wasTouchTap(nx, ny)) return false; // release frame if (gpio.lastTouchHeldMs() < TOUCH_LONG_PRESS_MS) return false; int lx = 0, ly = 0; renderer.tapToLogical(nx, ny, lx, ly); return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id); } bool MappedInputManager::wasTabTapped(int& id) const { float nx = 0.0f, ny = 0.0f; if (!gpio.wasTouchTap(nx, ny)) return false; int lx = 0, ly = 0; renderer.tapToLogical(nx, ny, lx, ly); return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Tab, id); } bool MappedInputManager::wasCoverTapped(int& id) const { float nx = 0.0f, ny = 0.0f; if (!gpio.wasTouchTap(nx, ny)) return false; int lx = 0, ly = 0; renderer.tapToLogical(nx, ny, lx, ly); return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Cover, id); } bool MappedInputManager::wasListScroll(int& index, int count, int pageItems) const { if (count <= 0) return false; if (pageItems < 1) pageItems = 1; if (wasBottomEdgeSwipeUp()) return false; const SwipeDir swipe = wasSwipe(); if (swipe == SwipeDir::Up) { index = std::min(index + pageItems, count - 1); return true; } if (swipe == SwipeDir::Down) { index = std::max(index - pageItems, 0); return true; } return false; } MappedInputManager::SwipeDir MappedInputManager::wasSwipe() const { if (wasBottomEdgeSwipeUp()) return SwipeDir::None; float nxs = 0.0f, nys = 0.0f, nxe = 0.0f, nye = 0.0f; if (!gpio.wasSwipe(nxs, nys, nxe, nye)) return SwipeDir::None; // Map both endpoints into the logical frame so the direction follows what the // user sees regardless of panel mount/orientation. int sx = 0, sy = 0, ex = 0, ey = 0; renderer.tapToLogical(nxs, nys, sx, sy); renderer.tapToLogical(nxe, nye, ex, ey); const int dx = ex - sx; const int dy = ey - sy; if (std::abs(dx) >= std::abs(dy)) { return dx < 0 ? SwipeDir::Left : SwipeDir::Right; } return dy < 0 ? SwipeDir::Up : SwipeDir::Down; } bool MappedInputManager::wasPressed(const Button button) const { // A top-left tap fires on the release frame; expose it on Back's press edge too // so menus that act on wasPressed(Back) also respond. Deliberately NOT folded // into isPressed, so a quick tap never satisfies the readers' long-press-home. if (button == Button::Back && wasBackGesture()) return true; return mapButton(button, &HalGPIO::wasPressed); } bool MappedInputManager::wasReleased(const Button button) const { if (button == Button::Back && wasBackGesture()) return true; return mapButton(button, &HalGPIO::wasReleased); } bool MappedInputManager::isPressed(const Button button) const { return mapButton(button, &HalGPIO::isPressed); } bool MappedInputManager::wasAnyPressed() const { return gpio.wasAnyPressed(); } bool MappedInputManager::wasAnyReleased() const { return gpio.wasAnyReleased(); } unsigned long MappedInputManager::getHeldTime() const { return gpio.getHeldTime(); } MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const { // Swap previous/next labels to match the page turn direction swap in INVERTED and LANDSCAPE_CCW. const bool swapLabels = SETTINGS.frontButtonFollowOrientation && (SETTINGS.orientation == CrossPointSettings::INVERTED || SETTINGS.orientation == CrossPointSettings::LANDSCAPE_CCW); const char* leftLabel = swapLabels ? next : previous; const char* rightLabel = swapLabels ? previous : next; // Build the label order based on the configured hardware mapping. auto labelForHardware = [&](uint8_t hw) -> const char* { // Compare against configured logical roles and return the matching label. if (hw == SETTINGS.frontButtonBack) { return back; } if (hw == SETTINGS.frontButtonConfirm) { return confirm; } if (hw == SETTINGS.frontButtonLeft) { return leftLabel; } if (hw == SETTINGS.frontButtonRight) { return rightLabel; } return ""; }; return {labelForHardware(HalGPIO::BTN_BACK), labelForHardware(HalGPIO::BTN_CONFIRM), labelForHardware(HalGPIO::BTN_LEFT), labelForHardware(HalGPIO::BTN_RIGHT)}; } int MappedInputManager::getPressedFrontButton() const { // Scan the raw front buttons in hardware order. // This bypasses remapping so the remap activity can capture physical presses. if (gpio.wasPressed(HalGPIO::BTN_BACK)) { return HalGPIO::BTN_BACK; } if (gpio.wasPressed(HalGPIO::BTN_CONFIRM)) { return HalGPIO::BTN_CONFIRM; } if (gpio.wasPressed(HalGPIO::BTN_LEFT)) { return HalGPIO::BTN_LEFT; } if (gpio.wasPressed(HalGPIO::BTN_RIGHT)) { return HalGPIO::BTN_RIGHT; } return -1; }