Touch gesture recognition demo¶
tap, long press, swipe, pinch, pan.
▶ Run in browserTags: ui
Demonstrates:
GestureRecognizer detecting tap, long_press, swipe, pinch, rotate, pan
Visual feedback for each gesture type
A draggable square that responds to pan gestures
A scalable circle that responds to pinch gestures
Rolling log of recent gesture events
Run: uv run python examples/features/ui/gestures.py
Controls: Mouse/Touch - Perform gestures Escape - Quit
Source¶
1"""Touch gesture recognition demo -- tap, long press, swipe, pinch, pan.
2
3Demonstrates:
4 - GestureRecognizer detecting tap, long_press, swipe, pinch, rotate, pan
5 - Visual feedback for each gesture type
6 - A draggable square that responds to pan gestures
7 - A scalable circle that responds to pinch gestures
8 - Rolling log of recent gesture events
9
10Run: uv run python examples/features/ui/gestures.py
11
12Controls:
13 Mouse/Touch - Perform gestures
14 Escape - Quit
15"""
16
17
18import math
19import time
20
21from simvx.core import GestureRecognizer, Input, Node2D, Vec2
22from simvx.graphics import App
23
24WIDTH, HEIGHT = 1024, 768
25MAX_LOG = 10
26
27
28class GestureDemo(Node2D):
29 """Root scene demonstrating gesture recognition with visual feedback."""
30
31 def on_ready(self):
32 # Enable mouse-to-touch emulation so gestures work on desktop
33 Input.set_touch_emulation(True)
34
35 # Gesture recognizer
36 self._gesture = self.add_child(GestureRecognizer(name="Gestures"))
37 self._gesture.tap_timeout = 0.3
38 self._gesture.long_press_timeout = 0.5
39 self._gesture.swipe_min_velocity = 500.0
40 self._gesture.tap_max_distance = 20.0
41
42 # Connect gesture signals
43 self._gesture.tap.connect(self._on_tap)
44 self._gesture.long_press.connect(self._on_long_press)
45 self._gesture.swipe.connect(self._on_swipe)
46 self._gesture.pinch.connect(self._on_pinch)
47 self._gesture.rotate.connect(self._on_rotate)
48 self._gesture.pan.connect(self._on_pan)
49
50 # Draggable square state
51 self._square_pos = Vec2(WIDTH * 0.3, HEIGHT * 0.5)
52 self._square_size = 80.0
53
54 # Scalable circle state
55 self._circle_pos = Vec2(WIDTH * 0.7, HEIGHT * 0.5)
56 self._circle_radius = 60.0
57
58 # Visual feedback
59 self._last_gesture = ""
60 self._last_gesture_time = 0.0
61 self._log: list[str] = []
62
63 # Tap flash
64 self._tap_pos: Vec2 | None = None
65 self._tap_flash = 0.0
66
67 def _log_event(self, msg: str):
68 self._log.append(msg)
69 if len(self._log) > MAX_LOG:
70 self._log.pop(0)
71 self._last_gesture = msg
72 self._last_gesture_time = time.monotonic()
73
74 def _on_tap(self, x: float, y: float):
75 self._tap_pos = Vec2(x, y)
76 self._tap_flash = 0.3
77 self._log_event(f"tap ({x:.0f}, {y:.0f})")
78
79 def _on_long_press(self, x: float, y: float):
80 self._log_event(f"long_press ({x:.0f}, {y:.0f})")
81
82 def _on_swipe(self, direction: str):
83 self._log_event(f"swipe {direction}")
84
85 def _on_pinch(self, scale: float, cx: float, cy: float):
86 # Scale the circle if pinch is near it
87 dx = cx - self._circle_pos.x
88 dy = cy - self._circle_pos.y
89 if math.sqrt(dx * dx + dy * dy) < self._circle_radius + 100:
90 self._circle_radius = max(20.0, min(200.0, self._circle_radius * scale))
91 self._log_event(f"pinch scale={scale:.2f}")
92
93 def _on_rotate(self, angle_delta: float, cx: float, cy: float):
94 self._log_event(f"rotate {math.degrees(angle_delta):.1f} deg")
95
96 def _on_pan(self, dx: float, dy: float):
97 self._square_pos = Vec2(
98 max(0, min(WIDTH, self._square_pos.x + dx)),
99 max(0, min(HEIGHT, self._square_pos.y + dy)),
100 )
101 self._log_event(f"pan ({dx:.0f}, {dy:.0f})")
102
103 def on_process(self, dt: float):
104 if self._tap_flash > 0:
105 self._tap_flash -= dt
106
107 def on_draw(self, renderer):
108 # Background
109 renderer.draw_rect((0, 0), (WIDTH, HEIGHT), colour=(0.08, 0.08, 0.12, 1.0))
110
111 # Draggable square
112 half = self._square_size / 2
113 sx, sy = self._square_pos.x - half, self._square_pos.y - half
114 renderer.draw_rect((sx, sy), (self._square_size, self._square_size), colour=(0.24, 0.47, 0.78, 1.0))
115 renderer.draw_text("drag me", (sx + 8, sy + 35), colour=(0.78, 0.86, 1.0), scale=2)
116
117 # Scalable circle
118 r = int(self._circle_radius)
119 renderer.draw_circle(self._circle_pos, r, colour=(0.78, 0.31, 0.47, 1.0), segments=32)
120 renderer.draw_text(
121 "pinch", (self._circle_pos.x - 18, self._circle_pos.y - 6), colour=(1.0, 0.78, 0.86), scale=2
122 )
123
124 # Tap flash indicator
125 if self._tap_flash > 0 and self._tap_pos is not None:
126 a = self._tap_flash / 0.3
127 renderer.draw_circle(self._tap_pos, 20, colour=(1.0, 1.0, 0.4, a), segments=16)
128
129 # HUD
130 renderer.draw_text("GESTURE RECOGNITION DEMO", (10, 10), colour=(0.78, 0.78, 0.78), scale=2)
131 renderer.draw_text(
132 "Mouse: tap, drag, long-press | Multitouch: pinch, rotate", (10, 35), colour=(0.59, 0.59, 0.59)
133 )
134
135 # Current gesture
136 age = time.monotonic() - self._last_gesture_time if self._last_gesture else 999
137 if age < 2.0:
138 renderer.draw_text(f">> {self._last_gesture}", (10, 60), scale=2, colour=(0.39, 1.0, 0.39))
139
140 # Event log
141 renderer.draw_text("Recent gestures:", (10, HEIGHT - 230), scale=2, colour=(0.71, 0.71, 0.71))
142 for i, entry in enumerate(reversed(self._log)):
143 y = HEIGHT - 210 + i * 18
144 fade = max(0.31, 0.86 - i * 0.06)
145 renderer.draw_text(entry, (20, y), scale=2, colour=(fade, fade, fade))
146
147
148def main():
149 app = App(width=WIDTH, height=HEIGHT, title="Gesture Recognition Demo")
150 app.run(GestureDemo())
151
152
153if __name__ == "__main__":
154 main()