feat: Add touch coordinate mapping and RTOS task yielding (#2481)
Co-authored-by: Julia Nguyen <julia@uxj.io>
This commit is contained in:
co-authored by
Julia Nguyen
parent
c9188a7347
commit
f42fab1c66
+214
-3
@@ -2,7 +2,11 @@
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "components/UITheme.h"
|
||||
|
||||
bool MappedInputManager::isNavDirectionSwapped() const {
|
||||
// Key the swap on the orientation the screen is *actually* rendered at, not the persisted reader
|
||||
@@ -74,9 +78,209 @@ bool MappedInputManager::mapButton(const Button button, bool (HalGPIO::*fn)(uint
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasPressed(const Button button) const { return mapButton(button, &HalGPIO::wasPressed); }
|
||||
namespace {
|
||||
constexpr float LEFT_EDGE_BACK_GESTURE_FRAC_X = 0.25f;
|
||||
constexpr float BOTTOM_EDGE_BACK_GESTURE_FRAC_Y = 0.14f;
|
||||
constexpr float TOP_EDGE_MENU_GESTURE_FRAC_Y = 0.14f;
|
||||
constexpr unsigned long TOUCH_DOWN_SELECT_DELAY_MS = 90;
|
||||
constexpr unsigned long TOUCH_HELD_OVERRIDE_WINDOW_MS = 250;
|
||||
} // namespace
|
||||
|
||||
bool MappedInputManager::wasReleased(const Button button) const { return mapButton(button, &HalGPIO::wasReleased); }
|
||||
bool MappedInputManager::hasTouch() const { return gpio.hasTouch(); }
|
||||
|
||||
void MappedInputManager::rememberTouchHeldTime() const {
|
||||
touchHeldOverrideValid = true;
|
||||
touchHeldOverrideMs = gpio.lastTouchHeldMs();
|
||||
touchHeldOverrideAt = millis();
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasScreenTapped(int& x, int& y) const {
|
||||
float nx = 0.0f;
|
||||
float ny = 0.0f;
|
||||
if (!gpio.wasTouchTap(nx, ny)) return false;
|
||||
renderer.tapToLogical(nx, ny, x, y);
|
||||
rememberTouchHeldTime();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasScreenTouchDown(int& x, int& y) const {
|
||||
float nx = 0.0f;
|
||||
float ny = 0.0f;
|
||||
unsigned long heldMs = 0;
|
||||
if (!gpio.isTouchTapCandidate(nx, ny, heldMs)) return false;
|
||||
if (heldMs < TOUCH_DOWN_SELECT_DELAY_MS) return false;
|
||||
renderer.tapToLogical(nx, ny, x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MappedInputManager::isScreenTouchHeld(int& x, int& y) const {
|
||||
// Live contact position while the finger is down (no tap-slop gate) — drag tracking.
|
||||
float nx = 0.0f;
|
||||
float ny = 0.0f;
|
||||
if (!gpio.isTouchHeldAt(nx, ny)) return false;
|
||||
renderer.tapToLogical(nx, ny, x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasTapInRect(const int x, const int y, const int width, const int height) const {
|
||||
int tx = 0;
|
||||
int ty = 0;
|
||||
return wasScreenTapped(tx, ty) && tx >= x && tx < x + width && ty >= y && ty < y + height;
|
||||
}
|
||||
|
||||
bool MappedInputManager::listItemFromPoint(const int x, const int y, int& index, const int itemCount,
|
||||
const int selectedIndex, const int listTop, const int listHeight,
|
||||
const bool hasSubtitle) const {
|
||||
(void)x;
|
||||
if (itemCount <= 0) return false;
|
||||
if (y < listTop || y >= listTop + listHeight) return false;
|
||||
|
||||
const auto& theme = UITheme::getInstance().getTheme();
|
||||
const int rowStep = theme.getListRowStep(hasSubtitle);
|
||||
if (rowStep <= 0) return false;
|
||||
|
||||
const int pageItems = theme.getListPageItems(listHeight, hasSubtitle);
|
||||
if (pageItems <= 0) return false;
|
||||
const int pageStart = std::max(0, selectedIndex / pageItems) * pageItems;
|
||||
const int row = (y - listTop) / rowStep;
|
||||
const int tapped = pageStart + row;
|
||||
if (row < 0 || row >= pageItems || tapped >= itemCount) return false;
|
||||
index = tapped;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasListItemTapped(int& index, const int itemCount, const int selectedIndex, const int listTop,
|
||||
const int listHeight, const bool hasSubtitle) const {
|
||||
int tx = 0;
|
||||
int ty = 0;
|
||||
return wasScreenTapped(tx, ty) &&
|
||||
listItemFromPoint(tx, ty, index, itemCount, selectedIndex, listTop, listHeight, hasSubtitle);
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasListItemTouchedDown(int& index, const int itemCount, const int selectedIndex,
|
||||
const int listTop, const int listHeight, const bool hasSubtitle) const {
|
||||
int tx = 0;
|
||||
int ty = 0;
|
||||
return wasScreenTouchDown(tx, ty) &&
|
||||
listItemFromPoint(tx, ty, index, itemCount, selectedIndex, listTop, listHeight, hasSubtitle);
|
||||
}
|
||||
|
||||
MappedInputManager::RowTouch MappedInputManager::rowTouch(int& row, const int top, const int rowStep,
|
||||
const int rowCount, const int xStart, const int xEnd,
|
||||
const int rowHeight) const {
|
||||
if (rowStep <= 0 || rowCount <= 0) return RowTouch::None;
|
||||
const auto hit = [&](const int x, const int y) {
|
||||
if (x < xStart || x >= xEnd || y < top) return false;
|
||||
const int r = (y - top) / rowStep;
|
||||
if (r >= rowCount) return false;
|
||||
if (rowHeight > 0 && (y - top) % rowStep >= rowHeight) return false;
|
||||
row = r;
|
||||
return true;
|
||||
};
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
if (wasScreenTouchDown(x, y) && hit(x, y)) return RowTouch::Down;
|
||||
if (wasScreenTapped(x, y) && hit(x, y)) return RowTouch::Tap;
|
||||
return RowTouch::None;
|
||||
}
|
||||
|
||||
MappedInputManager::RowTouch MappedInputManager::colTouch(int& col, const int left, const int colStep,
|
||||
const int colCount, const int yStart, const int yEnd,
|
||||
const int colWidth) const {
|
||||
if (colStep <= 0 || colCount <= 0) return RowTouch::None;
|
||||
const auto hit = [&](const int x, const int y) {
|
||||
if (y < yStart || y >= yEnd || x < left) return false;
|
||||
const int c = (x - left) / colStep;
|
||||
if (c >= colCount) return false;
|
||||
if (colWidth > 0 && (x - left) % colStep >= colWidth) return false;
|
||||
col = c;
|
||||
return true;
|
||||
};
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
if (wasScreenTouchDown(x, y) && hit(x, y)) return RowTouch::Down;
|
||||
if (wasScreenTapped(x, y) && hit(x, y)) return RowTouch::Tap;
|
||||
return RowTouch::None;
|
||||
}
|
||||
|
||||
bool MappedInputManager::decodeSwipe(int& sx, int& sy, int& ex, int& ey) const {
|
||||
float nxs = 0.0f;
|
||||
float nys = 0.0f;
|
||||
float nxe = 0.0f;
|
||||
float nye = 0.0f;
|
||||
if (!gpio.wasSwipe(nxs, nys, nxe, nye)) return false;
|
||||
renderer.tapToLogical(nxs, nys, sx, sy);
|
||||
renderer.tapToLogical(nxe, nye, ex, ey);
|
||||
return true;
|
||||
}
|
||||
|
||||
MappedInputManager::SwipeDir MappedInputManager::wasSwipe() const {
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (!decodeSwipe(sx, sy, ex, ey)) return SwipeDir::None;
|
||||
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::wasBackGesture() const {
|
||||
// Back = left-to-right swipe starting near the left edge. Edge-anchored so that
|
||||
// mid-screen horizontal swipes stay available to activities that consume
|
||||
// SwipeDir::Left/Right (e.g. percent selection, image viewer).
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (!decodeSwipe(sx, sy, ex, ey)) return false;
|
||||
const bool hit = sx <= renderer.getScreenWidth() * LEFT_EDGE_BACK_GESTURE_FRAC_X && ex > sx &&
|
||||
std::abs(ex - sx) > std::abs(ey - sy);
|
||||
if (hit) rememberTouchHeldTime();
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasMenuGesture() const {
|
||||
// Downward swipe starting at the top edge (mirror of the bottom-edge home gesture).
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (!decodeSwipe(sx, sy, ex, ey)) return false;
|
||||
const int topEdgeBottom = static_cast<int>(renderer.getScreenHeight() * TOP_EDGE_MENU_GESTURE_FRAC_Y);
|
||||
const bool hit = sy <= topEdgeBottom && ey > sy && std::abs(ey - sy) > std::abs(ex - sx);
|
||||
if (hit) rememberTouchHeldTime();
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasHomeGesture() const {
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int ex = 0;
|
||||
int ey = 0;
|
||||
if (decodeSwipe(sx, sy, ex, ey)) {
|
||||
const int bottomEdgeTop =
|
||||
renderer.getScreenHeight() - static_cast<int>(renderer.getScreenHeight() * BOTTOM_EDGE_BACK_GESTURE_FRAC_Y);
|
||||
if (sy >= bottomEdgeTop && ey < sy && std::abs(ey - sy) > std::abs(ex - sx)) {
|
||||
rememberTouchHeldTime();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MappedInputManager::wasPressed(const Button button) const {
|
||||
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); }
|
||||
|
||||
@@ -84,7 +288,14 @@ bool MappedInputManager::wasAnyPressed() const { return gpio.wasAnyPressed(); }
|
||||
|
||||
bool MappedInputManager::wasAnyReleased() const { return gpio.wasAnyReleased(); }
|
||||
|
||||
unsigned long MappedInputManager::getHeldTime() const { return gpio.getHeldTime(); }
|
||||
unsigned long MappedInputManager::getHeldTime() const {
|
||||
if (!gpio.wasAnyPressed() && !gpio.wasAnyReleased() && touchHeldOverrideValid &&
|
||||
millis() - touchHeldOverrideAt <= TOUCH_HELD_OVERRIDE_WINDOW_MS) {
|
||||
return touchHeldOverrideMs;
|
||||
}
|
||||
touchHeldOverrideValid = false;
|
||||
return gpio.getHeldTime();
|
||||
}
|
||||
|
||||
MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous,
|
||||
const char* next) const {
|
||||
|
||||
Reference in New Issue
Block a user