Skip to content

Gestures

Native-backed gesture recognition, attached to any view-like element via the gestures= prop. Descriptors are frozen dataclasses; their numeric configuration crosses the bridge while callbacks are routed through the tag-based event channel. See the Gestures guide for usage patterns.

Native-backed gesture system with composition and arbitration.

Attach gestures to any view-like element via the gestures= prop:

import pythonnative as pn
from pythonnative import gestures


@pn.component
def Draggable():
    tx = pn.use_animated_value(0.0)
    ty = pn.use_animated_value(0.0)

    def on_end(event):
        pn.Animated.spring(tx, to=0.0).start()
        pn.Animated.spring(ty, to=0.0).start()

    return pn.Animated.View(
        pn.Text("Drag me"),
        style={"transform": [{"translate_x": tx}, {"translate_y": ty}], "padding": 24},
        gestures=[
            gestures.Pan(
                on_change=pn.Animated.event(translation_x=tx, translation_y=ty),
                on_end=on_end,
            )
        ],
    )

Each gesture descriptor is a frozen dataclass holding numeric configuration plus user callbacks. The reconciler serializes the configuration into plain dicts for the native handler (so prop diffing never compares closures) and routes the callbacks through the tag-based event channel. Recognition itself is native:

  • iOS attaches real UIGestureRecognizer instances (PythonNativeKit).
  • Android runs an equivalent recognizer set in Kotlin (the pythonnative Gradle module) on top of MotionEvent streams.
  • The browser preview feeds DOM pointer events into the pure-Python GestureArbiter below, which doubles as the executable specification for the native ports.

Composition

Gestures in a plain gestures=[...] list all recognize simultaneously (a press ripple plus a pan plus a pinch can all run at once). To control how gestures interact, wrap them in composition nodes, which nest arbitrarily:

  • Simultaneous: members may all activate together (the flat-list default, useful inside other nodes).
  • Race: the first member to activate wins; the rest fail for the remainder of the interaction.
  • Exclusive: priority order. A member may only activate after every member listed before it has failed. Exclusive(double_tap, single_tap) is the classic double-tap-wins arrangement: the single tap fires only after the double-tap window expires.
gestures=[
    gestures.Race(
        gestures.Pan(on_change=drag),
        gestures.LongPress(on_long_press=show_menu),
    ),
]

Every callback receives a GestureEvent with position, translation, velocity, scale, and rotation populated as appropriate for the gesture kind.

Classes:

Name Description
GestureState

Lifecycle states reported on GestureEvent.state.

GestureEvent

Snapshot delivered to gesture callbacks.

Tap

Recognize n_taps quick taps.

LongPress

Recognize a sustained press.

Pan

Track a drag with translation and velocity.

Swipe

Recognize a quick directional flick.

Fling

Recognize a quick multi-pointer directional flick.

Pinch

Track a two-finger pinch; event.scale is relative to activation.

Rotation

Track a two-finger rotation; event.rotation is in radians.

GestureGroup

A composition node relating child gestures (or nested groups).

GestureArbiter

Turn a raw pointer-event stream into arbitrated gesture payloads.

Functions:

Name Description
Simultaneous

Compose gestures that may all be active at the same time.

Race

Compose gestures where only the first to activate wins.

Exclusive

Compose gestures by priority: earlier members outrank later ones.

serialize_gestures

Flatten gesture descriptors into native config dicts and event routers.

GestureCallback module-attribute

GestureCallback = Callable[['GestureEvent'], Any]

SwipeDirection module-attribute

SwipeDirection = Literal[
    "any", "left", "right", "up", "down"
]

GestureSpec module-attribute

GestureSpec = _BaseGesture

Any gesture descriptor accepted by the gestures= prop.

EmitFn module-attribute

EmitFn = Callable[[int, Dict[str, Any]], None]

emit(gesture_index, payload): the arbiter's output channel.

GestureState

Bases: str, Enum

Lifecycle states reported on GestureEvent.state.

A str enum, so members compare equal to their wire value (GestureState.ENDED == "ended") and serialize as plain strings across the native bridge, while callers get exhaustive match support and autocomplete.

Attributes:

Name Type Description
BEGAN

The gesture activated (first callback).

CHANGED

A continuous gesture updated (pan, pinch, rotation).

ENDED

The gesture completed successfully.

CANCELLED

The gesture was interrupted (lost arbitration, view unmounted, pointer left the window).

BEGAN class-attribute instance-attribute

BEGAN = 'began'

CHANGED class-attribute instance-attribute

CHANGED = 'changed'

ENDED class-attribute instance-attribute

ENDED = 'ended'

CANCELLED class-attribute instance-attribute

CANCELLED = 'cancelled'

GestureEvent dataclass

GestureEvent(
    kind: str,
    state: GestureState,
    x: float = 0.0,
    y: float = 0.0,
    translation_x: float = 0.0,
    translation_y: float = 0.0,
    velocity_x: float = 0.0,
    velocity_y: float = 0.0,
    scale: float = 1.0,
    rotation: float = 0.0,
    pointer_count: int = 1,
    direction: Optional[str] = None,
)

Snapshot delivered to gesture callbacks.

Attributes:

Name Type Description
kind str

Gesture kind ("tap", "long_press", "pan", "swipe", "fling", "pinch", "rotation").

state GestureState

One of GestureState.

x float

Pointer x-position in the view's coordinate space (points).

y float

Pointer y-position in the view's coordinate space (points).

translation_x float

Horizontal displacement since the gesture activated (pan only).

translation_y float

Vertical displacement since the gesture activated (pan only).

velocity_x float

Horizontal pointer velocity in points/second (pan, swipe, and fling).

velocity_y float

Vertical pointer velocity in points/second (pan, swipe, and fling).

scale float

Pinch scale factor relative to activation (pinch only).

rotation float

Rotation in radians relative to activation (rotation only).

pointer_count int

Number of pointers currently down.

direction Optional[str]

Resolved swipe/fling direction.

kind instance-attribute

kind: str

state instance-attribute

state: GestureState

x class-attribute instance-attribute

x: float = 0.0

y class-attribute instance-attribute

y: float = 0.0

translation_x class-attribute instance-attribute

translation_x: float = 0.0

translation_y class-attribute instance-attribute

translation_y: float = 0.0

velocity_x class-attribute instance-attribute

velocity_x: float = 0.0

velocity_y class-attribute instance-attribute

velocity_y: float = 0.0

scale class-attribute instance-attribute

scale: float = 1.0

rotation class-attribute instance-attribute

rotation: float = 0.0

pointer_count class-attribute instance-attribute

pointer_count: int = 1

direction class-attribute instance-attribute

direction: Optional[str] = None

Tap dataclass

Tap(
    on_begin: Optional[GestureCallback] = None,
    on_change: Optional[GestureCallback] = None,
    on_end: Optional[GestureCallback] = None,
    kind: str = "tap",
    on_tap: Optional[GestureCallback] = None,
    n_taps: int = 1,
    max_distance: float = 12.0,
)

Bases: _BaseGesture

Recognize n_taps quick taps.

Attributes:

Name Type Description
on_tap Optional[GestureCallback]

Called once the tap (or multi-tap) completes.

n_taps int

Number of consecutive taps required (2 for double-tap).

max_distance float

Maximum pointer travel (points) for a touch to still count as a tap.

on_tap class-attribute instance-attribute

on_tap: Optional[GestureCallback] = None

n_taps class-attribute instance-attribute

n_taps: int = 1

max_distance class-attribute instance-attribute

max_distance: float = 12.0

kind class-attribute instance-attribute

kind: str = 'tap'

LongPress dataclass

LongPress(
    on_begin: Optional[GestureCallback] = None,
    on_change: Optional[GestureCallback] = None,
    on_end: Optional[GestureCallback] = None,
    kind: str = "long_press",
    on_long_press: Optional[GestureCallback] = None,
    min_duration_ms: float = 500.0,
    max_distance: float = 12.0,
)

Bases: _BaseGesture

Recognize a sustained press.

on_long_press fires as soon as the press has been held for min_duration_ms (matching UILongPressGestureRecognizer); on_end fires when the finger lifts.

Attributes:

Name Type Description
on_long_press Optional[GestureCallback]

Called at activation time.

min_duration_ms float

Hold duration required to activate.

max_distance float

Maximum pointer travel before the press fails.

on_long_press class-attribute instance-attribute

on_long_press: Optional[GestureCallback] = None

min_duration_ms class-attribute instance-attribute

min_duration_ms: float = 500.0

max_distance class-attribute instance-attribute

max_distance: float = 12.0

kind class-attribute instance-attribute

kind: str = 'long_press'

Pan dataclass

Pan(
    on_begin: Optional[GestureCallback] = None,
    on_change: Optional[GestureCallback] = None,
    on_end: Optional[GestureCallback] = None,
    kind: str = "pan",
    min_distance: float = 10.0,
    min_pointers: int = 1,
)

Bases: _BaseGesture

Track a drag with translation and velocity.

Activates once the pointer travels min_distance points, then reports on_change for every movement with translation measured from the activation point, and on_end with release velocity.

Attributes:

Name Type Description
min_distance float

Travel (points) required before the pan activates.

min_pointers int

Minimum pointers that must be down.

min_distance class-attribute instance-attribute

min_distance: float = 10.0

min_pointers class-attribute instance-attribute

min_pointers: int = 1

kind class-attribute instance-attribute

kind: str = 'pan'

Swipe dataclass

Swipe(
    on_begin: Optional[GestureCallback] = None,
    on_change: Optional[GestureCallback] = None,
    on_end: Optional[GestureCallback] = None,
    kind: str = "swipe",
    on_swipe: Optional[GestureCallback] = None,
    direction: SwipeDirection = "any",
    min_velocity: float = 300.0,
)

Bases: _BaseGesture

Recognize a quick directional flick.

Attributes:

Name Type Description
on_swipe Optional[GestureCallback]

Called once on release with the resolved direction and release velocity.

direction SwipeDirection

Required direction, or "any".

min_velocity float

Minimum release speed in points/second.

on_swipe class-attribute instance-attribute

on_swipe: Optional[GestureCallback] = None

direction class-attribute instance-attribute

direction: SwipeDirection = 'any'

min_velocity class-attribute instance-attribute

min_velocity: float = 300.0

kind class-attribute instance-attribute

kind: str = 'swipe'

Fling dataclass

Fling(
    on_begin: Optional[GestureCallback] = None,
    on_change: Optional[GestureCallback] = None,
    on_end: Optional[GestureCallback] = None,
    kind: str = "fling",
    on_fling: Optional[GestureCallback] = None,
    direction: SwipeDirection = "any",
    n_pointers: int = 1,
    min_velocity: float = 300.0,
)

Bases: _BaseGesture

Recognize a quick multi-pointer directional flick.

Like Swipe but with a pointer-count requirement, mirroring React Native Gesture Handler's Fling (and iOS UISwipeGestureRecognizer with numberOfTouchesRequired). A two-finger downward fling is a common dismiss gesture:

gestures.Fling(direction="down", n_pointers=2, on_fling=dismiss)

Attributes:

Name Type Description
on_fling Optional[GestureCallback]

Called once on release with the resolved direction and release velocity.

direction SwipeDirection

Required direction, or "any".

n_pointers int

Number of pointers that must participate.

min_velocity float

Minimum release speed in points/second.

on_fling class-attribute instance-attribute

on_fling: Optional[GestureCallback] = None

direction class-attribute instance-attribute

direction: SwipeDirection = 'any'

n_pointers class-attribute instance-attribute

n_pointers: int = 1

min_velocity class-attribute instance-attribute

min_velocity: float = 300.0

kind class-attribute instance-attribute

kind: str = 'fling'

Pinch dataclass

Pinch(
    on_begin: Optional[GestureCallback] = None,
    on_change: Optional[GestureCallback] = None,
    on_end: Optional[GestureCallback] = None,
    kind: str = "pinch",
)

Bases: _BaseGesture

Track a two-finger pinch; event.scale is relative to activation.

Attributes:

Name Type Description
kind str

kind class-attribute instance-attribute

kind: str = 'pinch'

Rotation dataclass

Rotation(
    on_begin: Optional[GestureCallback] = None,
    on_change: Optional[GestureCallback] = None,
    on_end: Optional[GestureCallback] = None,
    kind: str = "rotation",
)

Bases: _BaseGesture

Track a two-finger rotation; event.rotation is in radians.

Attributes:

Name Type Description
kind str

kind class-attribute instance-attribute

kind: str = 'rotation'

GestureGroup dataclass

GestureGroup(
    mode: Literal["simultaneous", "race", "exclusive"],
    children: Tuple[Any, ...] = tuple(),
)

A composition node relating child gestures (or nested groups).

Build instances with Simultaneous, Race, or Exclusive rather than directly.

Attributes:

Name Type Description
mode Literal['simultaneous', 'race', 'exclusive']
children Tuple[Any, ...]

mode instance-attribute

mode: Literal['simultaneous', 'race', 'exclusive']

children class-attribute instance-attribute

children: Tuple[Any, ...] = field(default_factory=tuple)

GestureArbiter

GestureArbiter(
    specs: Sequence[Dict[str, Any]], emit: EmitFn
)

Turn a raw pointer-event stream into arbitrated gesture payloads.

One arbiter serves one view. The host backend feeds it normalized pointer events (positions in the view's coordinate space, times in seconds, any monotonic clock) and provides an emit callback that forwards (gesture_index, payload) pairs to dispatch_event.

Beyond running each gesture's state machine, the arbiter enforces the relationships computed by serialize_gestures:

  • Two gestures not in each other's "simultaneous" sets race; when one activates, the other is force-failed for the rest of the interaction.
  • A gesture with a "wait_for" set may only activate after all of those gestures have failed. Its output (including a buffered discrete completion, like a single tap waiting out a double-tap window) is held and either flushed on failure of the targets or discarded if a target succeeds.

Specs without relationship metadata (hand-built dicts) default to fully simultaneous, matching pre-composition behavior.

Timing: after each pointer event, hosts should check next_deadline and schedule a poll call for that time (long-press activation and multi-tap windows).

Methods:

Name Description
pointer_down

Record a pointer press and advance every recognizer.

pointer_move

Record pointer travel and advance every recognizer.

pointer_up

Record a pointer release and advance every recognizer.

cancel

Abort every in-flight gesture (e.g. touch stolen by a scroll parent).

poll

Advance time-based recognizers (long-press, multi-tap windows).

next_deadline

Earliest time poll should be called, or None.

has_active_pan

Whether a pan gesture is currently activated.

pointer_down

pointer_down(
    pointer_id: int, x: float, y: float, t: float
) -> None

Record a pointer press and advance every recognizer.

pointer_move

pointer_move(
    pointer_id: int, x: float, y: float, t: float
) -> None

Record pointer travel and advance every recognizer.

pointer_up

pointer_up(
    pointer_id: int, x: float, y: float, t: float
) -> None

Record a pointer release and advance every recognizer.

cancel

cancel(t: float) -> None

Abort every in-flight gesture (e.g. touch stolen by a scroll parent).

poll

poll(t: float) -> None

Advance time-based recognizers (long-press, multi-tap windows).

next_deadline

next_deadline() -> Optional[float]

Earliest time poll should be called, or None.

has_active_pan

has_active_pan() -> bool

Whether a pan gesture is currently activated.

Android handlers use this to call requestDisallowInterceptTouchEvent so an enclosing ScrollView doesn't steal the drag.

event_from_payload

event_from_payload(payload: Dict[str, Any]) -> GestureEvent

Build a GestureEvent from a payload dict.

Unknown keys are dropped so platform handlers can attach extra diagnostics without breaking the public dataclass.

Simultaneous

Simultaneous(*gestures: Any) -> GestureGroup

Compose gestures that may all be active at the same time.

This matches the flat-list default; it exists so simultaneity can be expressed inside Race or Exclusive nodes:

gestures.Race(
    gestures.Simultaneous(gestures.Pinch(...), gestures.Rotation(...)),
    gestures.Pan(...),
)

Race

Race(*gestures: Any) -> GestureGroup

Compose gestures where only the first to activate wins.

As soon as one member activates, every other member fails for the rest of the interaction (its in-progress recognition is abandoned without firing callbacks).

Exclusive

Exclusive(*gestures: Any) -> GestureGroup

Compose gestures by priority: earlier members outrank later ones.

A member may only activate once every member listed before it has failed. Exclusive(double_tap, single_tap) delays the single tap until the double-tap window has expired, then fires it; if the second tap lands in time, only the double tap fires.

serialize_gestures

serialize_gestures(
    specs: Sequence[Any],
) -> Tuple[
    List[Dict[str, Any]], Dict[str, Callable[..., Any]]
]

Flatten gesture descriptors into native config dicts and event routers.

Composition nodes (Simultaneous, Race, Exclusive) are flattened depth-first; each resulting spec dict carries the relationship metadata the recognizers need:

  • "simultaneous": indices this gesture may be active alongside.
  • "wait_for": indices that must fail before this gesture may activate.

Two gestures that are not in each other's simultaneous sets race: the first to activate causes the other to fail. Gestures in the top-level list (outside any composition node) are mutually simultaneous.

Parameters:

Name Type Description Default
specs Sequence[Any]

The value of an element's gestures prop. Plain dicts are passed through with relationship metadata attached (no callbacks to route).

required

Returns:

Type Description
List[Dict[str, Any]]

(clean_specs, events) where clean_specs is a list of

Dict[str, Callable[..., Any]]

JSON-ish config dicts (one per leaf gesture, depth-first) and

Tuple[List[Dict[str, Any]], Dict[str, Callable[..., Any]]]

events maps "gesture:<i>" to a router that unpacks the

Tuple[List[Dict[str, Any]], Dict[str, Callable[..., Any]]]

native payload into a GestureEvent and invokes the right

Tuple[List[Dict[str, Any]], Dict[str, Callable[..., Any]]]

user callback.

make_arbiter

make_arbiter(
    specs: Sequence[Dict[str, Any]], emit: EmitFn
) -> GestureArbiter

Build a GestureArbiter from serialized specs.

See also

  • The Gestures guide walks through taps, drags, and gesture-driven animations.
  • Animated pairs with Pan velocity for springs and decays.