Builtin Library
README.md
gestures
Touch-gesture recognizers over the touch event stream + contact
snapshot; backs Zin.gestures. Discrete gestures (tap, double-tap,
long-press, swipe) fire subscriptions; continuous gestures (pinch, pan)
expose per-tick deltas computed from the two-finger contact state.
Advanced by the Zin.tick pipeline — dormant until ticked.
Discrete vs continuous
- Discrete (tap / double-tap / long-press / swipe) — recognized on
a release or hold-duration edge, delivered via
on*subscriptions on the tick they fire. - Continuous (pinch / pan) — recomputed every tick from the
two-finger contact positions; read via
pinchDelta()/panDelta(), reset to zero at the start of each tick. The pair is formed from the contacts no on-screen widget is working, the onesZin.touchControlssays its stick and buttons hold having been left out, so a thumb on a movement stick is no part of a pinch.
Config
Zin.gestures.configure overrides recognition thresholds (unspecified
fields keep their current values):
| Field | Default | Meaning |
|---|---|---|
tapMaxDuration | 0.25 s | press→release within this = tap candidate |
tapMaxMovement | 16 px | total movement below this = tap candidate |
doubleTapWindow | 0.3 s | two taps within this = double-tap |
longPressDuration | 0.5 s | held past this (still) = long-press |
swipeMinDistance | 60 px | release travel at/above this = swipe candidate |
swipeMinVelocity | 800 px/s | release velocity at/above this = swipe |
Exports
M.configure(opts: { [string]: number })— override recognition thresholds.M.getConfig() -> GestureConfig— current thresholds (a copy).M.onTap(fn) -> number— subscribe to taps;fn(ev)withev = { x, y, id, duration }.M.onDoubleTap(fn) -> number— subscribe to double-taps;ev = { x, y, id }.M.onLongPress(fn) -> number— subscribe to long-presses (fires once per contact);ev = { x, y, id }.M.onSwipe(fn) -> number— subscribe to swipes (fires on release);ev = { direction, dx, dy, velocity, id }.M.off(handle: number) -> boolean— unsubscribe a handle from anyon*function.M.pinchDelta() -> number— this tick's two-finger pinch delta in px (positive = spreading).M.panDelta() -> (number, number)— this tick's two-finger pan delta(dx, dy)in px.M._beginTick()— internal: clear per-tick continuous deltas; wired byZin.tick.M._observeEvent(ev: any)— internal: builds contact tracks and classifies discrete gestures on release; wired byZin.tick.M._advanceTime(dt: number)— internal: time-based recognition (long-press) + continuous deltas; wired byZin.tick.M._setWidgetContacts(ids: { [number]: boolean })— internal: the contacts an on-screen widget is working; published each tick byZin.touchControls.M._pairDown() -> boolean— internal: whether two contacts no widget is working are down, which is the pair a two-finger gesture reads.M._dispatchFires()— internal: deliver this tick's discrete gesture fires; wired byZin.tick.M._reset()— test-only: clear all tracks, fires, deltas, and subscriptions.
Types:
GestureConfig = { tapMaxDuration, tapMaxMovement, doubleTapWindow, longPressDuration, swipeMinDistance, swipeMinVelocity }(allnumber)
Usage
local Gestures = require("@builtin::modules.zinput.gestures")
Gestures.onTap(function(ev)
print("tap at", ev.x, ev.y)
end)
Gestures.onSwipe(function(ev)
if ev.direction == "left" then nextPage() end
end)
-- inside a per-frame update:
local zoom = Gestures.pinchDelta()
local dx, dy = Gestures.panDelta()
Notes
- Pinch and pan deltas are zero unless exactly two contacts are down this tick that no on-screen widget is working.
- Long-press fires once per contact — the contact must stay within
tapMaxMovementfor the fulllongPressDurationand is marked so it never also fires a tap on release. - Every qualifying release fires
tap; the second tap of a double-tap firesdoubleTapandtapon the same tick. - Handler errors during dispatch are caught and logged, not propagated.