yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
touch_input.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <cmath>
6
7#include "imgui/imgui.h"
8#include "util/log.h"
9
10#ifdef __EMSCRIPTEN__
11#include <emscripten.h>
12#include <emscripten/html5.h>
13#endif
14
15#ifndef M_PI
16#define M_PI 3.14159265358979323846
17#endif
18
19namespace yaze {
20namespace gui {
21
22namespace {
23
24// Static state for touch input system
26 bool initialized = false;
27 bool touch_mode = false; // true if last input was touch
28 bool pan_zoom_enabled = true;
29
30 // Touch points
31 std::array<TouchPoint, TouchInput::kMaxTouchPoints> touch_points;
32 int active_touch_count = 0;
33
34 // Gesture recognition state
37
38 // Tap detection state
39 ImVec2 last_tap_position = ImVec2(0, 0);
40 double last_tap_time = 0.0;
41 bool potential_tap = false;
42 bool potential_double_tap = false;
43
44 // Long press detection
45 bool long_press_detected = false;
46 double touch_start_time = 0.0;
47
48 // Canvas transform state
49 ImVec2 pan_offset = ImVec2(0, 0);
50 float zoom_level = 1.0f;
51 float rotation = 0.0f;
52 ImVec2 zoom_center = ImVec2(0, 0);
53
54 // Inertia state
55 ImVec2 inertia_velocity = ImVec2(0, 0);
56 bool inertia_active = false;
57
58 // Two-finger gesture tracking
59 float initial_pinch_distance = 0.0f;
60 float initial_rotation_angle = 0.0f;
61 ImVec2 initial_pan_center = ImVec2(0, 0);
62
63 // Configuration
65
66 // Callback
67 std::function<void(const GestureState&)> gesture_callback;
68};
69
71
72// Helper functions
73float Distance(ImVec2 a, ImVec2 b) {
74 float dx = b.x - a.x;
75 float dy = b.y - a.y;
76 return std::sqrt(dx * dx + dy * dy);
77}
78
79float Angle(ImVec2 a, ImVec2 b) {
80 return std::atan2(b.y - a.y, b.x - a.x);
81}
82
83ImVec2 Midpoint(ImVec2 a, ImVec2 b) {
84 return ImVec2((a.x + b.x) * 0.5f, (a.y + b.y) * 0.5f);
85}
86
87double GetTime() {
88#ifdef __EMSCRIPTEN__
89 return emscripten_get_now() / 1000.0; // Convert ms to seconds
90#else
91 auto now = std::chrono::steady_clock::now();
92 auto duration = now.time_since_epoch();
93 return std::chrono::duration<double>(duration).count();
94#endif
95}
96
98 for (auto& touch : g_touch_state.touch_points) {
99 if (touch.active && touch.id == id) {
100 return &touch;
101 }
102 }
103 return nullptr;
104}
105
107 for (auto& touch : g_touch_state.touch_points) {
108 if (!touch.active) {
109 return &touch;
110 }
111 }
112 return nullptr;
113}
114
117 for (const auto& touch : g_touch_state.touch_points) {
118 if (touch.active) {
120 }
121 }
122}
123
124// Get the first N active touch points
125std::array<TouchPoint*, 2> GetFirstTwoTouches() {
126 std::array<TouchPoint*, 2> result = {nullptr, nullptr};
127 int found = 0;
128 for (auto& touch : g_touch_state.touch_points) {
129 if (touch.active && found < 2) {
130 result[found++] = &touch;
131 }
132 }
133 return result;
134}
135
136} // namespace
137
138// === Public API Implementation ===
139
141 if (g_touch_state.initialized) {
142 return;
143 }
144
145 // Reset all state
146 g_touch_state = TouchInputState();
147 g_touch_state.initialized = true;
148
149 // Initialize touch points
150 for (auto& touch : g_touch_state.touch_points) {
151 touch = TouchPoint();
152 }
153
154 // Platform-specific initialization
156
157 LOG_INFO("TouchInput", "Touch input system initialized");
158}
159
161 if (!g_touch_state.initialized) {
162 return;
163 }
164
166 g_touch_state.initialized = false;
167
168 LOG_INFO("TouchInput", "Touch input system shut down");
169}
170
172 if (!g_touch_state.initialized) {
173 return;
174 }
175
176 // Store previous gesture for phase detection
177 g_touch_state.previous_gesture = g_touch_state.current_gesture;
178
179 // Update gesture recognition
181
182 // Process inertia if active
183 if (g_touch_state.inertia_active) {
185 }
186
187 // Translate to ImGui events
189
190 // Invoke callback if gesture state changed
191 if (g_touch_state.gesture_callback &&
192 (g_touch_state.current_gesture.gesture != TouchGesture::kNone ||
193 g_touch_state.previous_gesture.gesture != TouchGesture::kNone)) {
194 g_touch_state.gesture_callback(g_touch_state.current_gesture);
195 }
196}
197
199 return g_touch_state.active_touch_count > 0;
200}
201
203 return g_touch_state.touch_mode;
204}
205
209
211 if (index >= 0 && index < kMaxTouchPoints) {
212 return g_touch_state.touch_points[index];
213 }
214 return TouchPoint();
215}
216
218 return g_touch_state.active_touch_count;
219}
220
222 g_touch_state.pan_zoom_enabled = enabled;
223}
224
226 return g_touch_state.pan_zoom_enabled;
227}
228
230 return g_touch_state.pan_offset;
231}
232
234 return g_touch_state.zoom_level;
235}
236
238 return g_touch_state.rotation;
239}
240
242 return g_touch_state.zoom_center;
243}
244
245void TouchInput::ApplyPanOffset(ImVec2 offset) {
246 g_touch_state.pan_offset.x += offset.x;
247 g_touch_state.pan_offset.y += offset.y;
248}
249
250void TouchInput::SetZoomLevel(float zoom) {
251 auto& config = g_touch_state.config;
252 g_touch_state.zoom_level = std::clamp(zoom, config.min_zoom, config.max_zoom);
253}
254
255void TouchInput::SetPanOffset(ImVec2 offset) {
256 g_touch_state.pan_offset = offset;
257}
258
260 g_touch_state.pan_offset = ImVec2(0, 0);
261 g_touch_state.zoom_level = 1.0f;
262 g_touch_state.rotation = 0.0f;
263 g_touch_state.inertia_velocity = ImVec2(0, 0);
264 g_touch_state.inertia_active = false;
265}
266
268 return g_touch_state.config;
269}
270
272 std::function<void(const GestureState&)> callback) {
273 g_touch_state.gesture_callback = callback;
274}
275
276// === Internal Implementation ===
277
279 auto& state = g_touch_state;
280 auto& gesture = state.current_gesture;
281 auto& config = state.config;
282 double current_time = GetTime();
283
284 CountActiveTouches();
285
286 // Update touch count in gesture state
287 gesture.touch_count = state.active_touch_count;
288
289 if (state.active_touch_count == 0) {
290 // No touches - check for completed gestures
291
292 // Check if we had a tap
293 if (state.potential_tap && !state.long_press_detected) {
294 double tap_duration = current_time - state.touch_start_time;
295 if (tap_duration <= config.tap_max_duration) {
296 // Check for double tap
297 double time_since_last_tap = current_time - state.last_tap_time;
298 if (state.potential_double_tap &&
299 time_since_last_tap <= config.double_tap_max_delay) {
300 gesture.gesture = TouchGesture::kDoubleTap;
301 gesture.phase = TouchPhase::kEnded;
302 state.potential_double_tap = false;
303 } else {
304 gesture.gesture = TouchGesture::kTap;
305 gesture.phase = TouchPhase::kEnded;
306 state.last_tap_time = current_time;
307 state.last_tap_position = gesture.position;
308 state.potential_double_tap = true;
309 }
310 }
311 }
312
313 // Clear gesture if it was in progress
314 if (gesture.gesture != TouchGesture::kTap &&
315 gesture.gesture != TouchGesture::kDoubleTap &&
316 gesture.gesture != TouchGesture::kLongPress) {
317 // Start inertia for pan gestures
318 if (gesture.gesture == TouchGesture::kPan && config.enable_inertia) {
319 state.inertia_active = true;
320 // Velocity was already being tracked during pan
321 }
322
323 if (gesture.gesture != TouchGesture::kNone) {
324 gesture.phase = TouchPhase::kEnded;
325 }
326 }
327
328 state.potential_tap = false;
329 state.long_press_detected = false;
330
331 // Only clear gesture after it's been reported as ended
332 if (gesture.phase == TouchPhase::kEnded) {
333 // Keep gesture info for one frame so consumers can see it ended
334 // It will be cleared next frame
335 } else {
336 gesture.gesture = TouchGesture::kNone;
337 }
338
339 return;
340 }
341
342 // Clear potential double tap if too much time has passed
343 if (state.potential_double_tap) {
344 double time_since_last_tap = current_time - state.last_tap_time;
345 if (time_since_last_tap > config.double_tap_max_delay) {
346 state.potential_double_tap = false;
347 }
348 }
349
350 // === Single Touch Processing ===
351 if (state.active_touch_count == 1) {
352 auto touches = GetFirstTwoTouches();
353 TouchPoint* touch = touches[0];
354 if (!touch) return;
355
356 gesture.position = touch->position;
357 gesture.start_position = touch->start_position;
358
359 float movement = Distance(touch->position, touch->start_position);
360 double duration = current_time - touch->timestamp;
361
362 // Check for long press
363 if (!state.long_press_detected && duration >= config.long_press_duration &&
364 movement <= config.tap_max_movement) {
365 state.long_press_detected = true;
366 gesture.gesture = TouchGesture::kLongPress;
367 gesture.phase = TouchPhase::kBegan;
368 gesture.duration = duration;
369 return;
370 }
371
372 // If already detected long press, keep it
373 if (state.long_press_detected) {
374 gesture.gesture = TouchGesture::kLongPress;
375 gesture.phase = TouchPhase::kChanged;
376 gesture.duration = duration;
377 return;
378 }
379
380 // Check for potential tap (small movement, short duration still possible)
381 if (movement <= config.tap_max_movement &&
382 duration <= config.tap_max_duration) {
383 state.potential_tap = true;
384 // Don't set gesture yet - wait for release
385 return;
386 }
387
388 // Too much movement - not a tap, this is a drag
389 // Single finger drag acts as mouse drag (already handled by ImGui)
390 state.potential_tap = false;
391 }
392
393 // === Two Touch Processing ===
394 if (state.active_touch_count >= 2 && state.pan_zoom_enabled) {
395 auto touches = GetFirstTwoTouches();
396 TouchPoint* touch1 = touches[0];
397 TouchPoint* touch2 = touches[1];
398 if (!touch1 || !touch2) return;
399
400 // Cancel any tap detection
401 state.potential_tap = false;
402 state.long_press_detected = false;
403
404 // Calculate two-finger geometry
405 ImVec2 center = Midpoint(touch1->position, touch2->position);
406 float distance = Distance(touch1->position, touch2->position);
407 float angle = Angle(touch1->position, touch2->position);
408
409 // Initialize reference values on gesture start
410 if (gesture.gesture != TouchGesture::kPan &&
411 gesture.gesture != TouchGesture::kPinchZoom &&
412 gesture.gesture != TouchGesture::kRotate) {
413 state.initial_pinch_distance = distance;
414 state.initial_rotation_angle = angle;
415 state.initial_pan_center = center;
416 gesture.scale = 1.0f;
417 gesture.rotation = 0.0f;
418 }
419
420 // Calculate deltas
421 float scale_ratio =
422 (state.initial_pinch_distance > 0.0f)
423 ? distance / state.initial_pinch_distance
424 : 1.0f;
425
426 float rotation_delta = angle - state.initial_rotation_angle;
427 // Normalize rotation delta to [-PI, PI]
428 while (rotation_delta > M_PI) rotation_delta -= 2.0f * M_PI;
429 while (rotation_delta < -M_PI) rotation_delta += 2.0f * M_PI;
430
431 ImVec2 pan_delta = ImVec2(center.x - state.initial_pan_center.x,
432 center.y - state.initial_pan_center.y);
433
434 // Determine dominant gesture
435 float scale_change = std::abs(scale_ratio - 1.0f);
436 float pan_distance = Distance(center, state.initial_pan_center);
437 float rotation_change = std::abs(rotation_delta);
438
439 bool is_pinch = scale_change > config.pinch_threshold / 100.0f;
440 bool is_pan = pan_distance > config.pan_threshold;
441 bool is_rotate = config.enable_rotation &&
442 rotation_change > config.rotation_threshold;
443
444 // Prioritize: pinch > rotate > pan
445 if (is_pinch) {
446 gesture.gesture = TouchGesture::kPinchZoom;
447 gesture.phase = (gesture.gesture == TouchGesture::kPinchZoom)
450 gesture.scale = scale_ratio;
451 gesture.scale_delta = scale_ratio - gesture.scale;
452
453 // Apply zoom
454 float new_zoom = state.zoom_level * scale_ratio;
455 new_zoom = std::clamp(new_zoom, config.min_zoom, config.max_zoom);
456
457 // Calculate zoom delta to apply
458 float zoom_delta = new_zoom / state.zoom_level;
459 if (std::abs(zoom_delta - 1.0f) > 0.001f) {
460 state.zoom_level = new_zoom;
461 state.zoom_center = center;
462
463 // Reset reference for continuous zooming
464 state.initial_pinch_distance = distance;
465 }
466 } else if (is_rotate) {
467 gesture.gesture = TouchGesture::kRotate;
468 gesture.phase = (gesture.gesture == TouchGesture::kRotate)
471 gesture.rotation_delta = rotation_delta;
472 gesture.rotation += rotation_delta;
473 state.rotation = gesture.rotation;
474 state.initial_rotation_angle = angle;
475 } else if (is_pan) {
476 gesture.gesture = TouchGesture::kPan;
477 gesture.phase = (gesture.gesture == TouchGesture::kPan)
480 gesture.translation = pan_delta;
481
482 // Apply pan offset
483 state.pan_offset.x += pan_delta.x;
484 state.pan_offset.y += pan_delta.y;
485
486 // Track velocity for inertia
487 ImVec2 prev_center = Midpoint(touch1->previous_position,
488 touch2->previous_position);
489 state.inertia_velocity.x = center.x - prev_center.x;
490 state.inertia_velocity.y = center.y - prev_center.y;
491
492 // Reset reference for continuous panning
493 state.initial_pan_center = center;
494 }
495
496 gesture.position = center;
497 gesture.start_position = state.initial_pan_center;
498 }
499}
500
502 auto& state = g_touch_state;
503 auto& config = state.config;
504
505 if (!config.enable_inertia || !state.inertia_active) {
506 return;
507 }
508
509 float velocity_magnitude = Distance(ImVec2(0, 0), state.inertia_velocity);
510 if (velocity_magnitude < config.inertia_min_velocity) {
511 state.inertia_active = false;
512 state.inertia_velocity = ImVec2(0, 0);
513 return;
514 }
515
516 // Apply inertia to pan offset
517 state.pan_offset.x += state.inertia_velocity.x;
518 state.pan_offset.y += state.inertia_velocity.y;
519
520 // Decay velocity
521 state.inertia_velocity.x *= config.inertia_deceleration;
522 state.inertia_velocity.y *= config.inertia_deceleration;
523}
524
526 auto& state = g_touch_state;
527
528 if (!state.touch_mode && state.active_touch_count == 0) {
529 return;
530 }
531
532 ImGuiIO& io = ImGui::GetIO();
533
534 // Always indicate touch source when in touch mode
535 if (state.active_touch_count > 0) {
536 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
537 state.touch_mode = true;
538 }
539
540 // Handle single touch as mouse
541 if (state.active_touch_count == 1) {
542 auto touches = GetFirstTwoTouches();
543 TouchPoint* touch = touches[0];
544 if (touch) {
545 io.AddMousePosEvent(touch->position.x, touch->position.y);
546 io.AddMouseButtonEvent(0, true); // Left button down
547 }
548 } else if (state.active_touch_count == 0 &&
549 state.previous_gesture.touch_count > 0) {
550 // Touch ended - release mouse button
551 io.AddMouseButtonEvent(0, false);
552
553 // Handle tap as click
554 if (state.current_gesture.gesture == TouchGesture::kTap ||
555 state.current_gesture.gesture == TouchGesture::kDoubleTap) {
556 // Position should already be set from the touch
557 }
558
559 // Handle long press as right click
560 if (state.previous_gesture.gesture == TouchGesture::kLongPress) {
561 io.AddMouseButtonEvent(1, true); // Right click down
562 io.AddMouseButtonEvent(1, false); // Right click up
563 }
564 }
565
566 // For two-finger gestures, don't send mouse events
567 // The pan/zoom is handled separately via GetPanOffset()/GetZoomLevel()
568}
569
571 return GetTime();
572}
573
574// === Platform-Specific Implementation ===
575
576#ifdef __EMSCRIPTEN__
577
578// clang-format off
579EM_JS(void, yaze_setup_touch_handlers, (), {
580 // Check if touch gestures script is loaded
581 if (typeof window.YazeTouchGestures !== 'undefined') {
582 window.YazeTouchGestures.initialize();
583 console.log('Touch gesture handlers initialized via YazeTouchGestures');
584 } else {
585 console.log('YazeTouchGestures not loaded - using basic touch handling');
586
587 // Basic fallback touch handling
588 const canvas = document.getElementById('canvas');
589 if (!canvas) {
590 console.error('Canvas element not found for touch handling');
591 return;
592 }
593
594 // Prevent default touch behaviors
595 canvas.style.touchAction = 'none';
596
597 function handleTouch(event, type) {
598 event.preventDefault();
599
600 const rect = canvas.getBoundingClientRect();
601 const scaleX = canvas.width / rect.width;
602 const scaleY = canvas.height / rect.height;
603
604 for (let i = 0; i < event.changedTouches.length; i++) {
605 const touch = event.changedTouches[i];
606 const x = (touch.clientX - rect.left) * scaleX;
607 const y = (touch.clientY - rect.top) * scaleY;
608 const pressure = touch.force || 1.0;
609 const timestamp = event.timeStamp / 1000.0;
610
611 // Call C++ function
612 if (typeof Module._OnTouchEvent === 'function') {
613 Module._OnTouchEvent(type, touch.identifier, x, y, pressure, timestamp);
614 } else if (typeof Module.ccall === 'function') {
615 Module.ccall('OnTouchEvent', null,
616 ['number', 'number', 'number', 'number', 'number', 'number'],
617 [type, touch.identifier, x, y, pressure, timestamp]);
618 }
619 }
620 }
621
622 canvas.addEventListener('touchstart', (e) => handleTouch(e, 0), { passive: false });
623 canvas.addEventListener('touchmove', (e) => handleTouch(e, 1), { passive: false });
624 canvas.addEventListener('touchend', (e) => handleTouch(e, 2), { passive: false });
625 canvas.addEventListener('touchcancel', (e) => handleTouch(e, 3), { passive: false });
626 }
627});
628
629EM_JS(void, yaze_cleanup_touch_handlers, (), {
630 if (typeof window.YazeTouchGestures !== 'undefined') {
631 window.YazeTouchGestures.shutdown();
632 }
633});
634// clang-format on
635
636void TouchInput::InitializePlatform() {
637 yaze_setup_touch_handlers();
638}
639
640void TouchInput::ShutdownPlatform() {
641 yaze_cleanup_touch_handlers();
642}
643
644// Exported functions for JavaScript callbacks
645extern "C" {
646
647EMSCRIPTEN_KEEPALIVE
648void OnTouchEvent(int type, int id, float x, float y, float pressure,
649 double timestamp) {
650 TouchInput::OnTouchEvent(type, id, x, y, pressure, timestamp);
651}
652
653EMSCRIPTEN_KEEPALIVE
654void OnGestureEvent(int type, int phase, float x, float y, float scale,
655 float rotation) {
656 TouchInput::OnGestureEvent(type, phase, x, y, scale, rotation);
657}
658
659} // extern "C"
660
661void TouchInput::OnTouchEvent(int type, int id, float x, float y,
662 float pressure, double timestamp) {
663 auto& state = g_touch_state;
664
665 switch (type) {
666 case 0: { // Touch start
667 TouchPoint* touch = FindInactiveSlot();
668 if (touch) {
669 touch->id = id;
670 touch->position = ImVec2(x, y);
671 touch->start_position = ImVec2(x, y);
672 touch->previous_position = ImVec2(x, y);
673 touch->pressure = pressure;
674 touch->timestamp = timestamp;
675 touch->active = true;
676
677 // Track touch start time for tap detection
678 if (state.active_touch_count == 0) {
679 state.touch_start_time = timestamp;
680 }
681 }
682 break;
683 }
684 case 1: { // Touch move
685 TouchPoint* touch = FindTouchById(id);
686 if (touch) {
687 touch->previous_position = touch->position;
688 touch->position = ImVec2(x, y);
689 touch->pressure = pressure;
690 }
691 break;
692 }
693 case 2: // Touch end
694 case 3: { // Touch cancel
695 TouchPoint* touch = FindTouchById(id);
696 if (touch) {
697 touch->active = false;
698 }
699 break;
700 }
701 }
702
704 state.touch_mode = true;
705}
706
707void TouchInput::OnGestureEvent(int type, int phase, float x, float y,
708 float scale, float rotation) {
709 auto& state = g_touch_state;
710 auto& gesture = state.current_gesture;
711
712 gesture.gesture = static_cast<TouchGesture>(type);
713 gesture.phase = static_cast<TouchPhase>(phase);
714 gesture.position = ImVec2(x, y);
715 gesture.scale = scale;
716 gesture.rotation = rotation;
717
718 // Apply gesture effects
719 if (gesture.gesture == TouchGesture::kPinchZoom) {
720 auto& config = state.config;
721 float new_zoom = state.zoom_level * scale;
722 state.zoom_level = std::clamp(new_zoom, config.min_zoom, config.max_zoom);
723 state.zoom_center = ImVec2(x, y);
724 } else if (gesture.gesture == TouchGesture::kRotate) {
725 state.rotation = rotation;
726 }
727}
728
729#else // Non-Emscripten (desktop) builds
730
731void TouchInput::InitializePlatform() {
732 // On desktop, touch events come through SDL which is already handled
733 // by the ImGui SDL backend. We may need to register for SDL_FINGERDOWN etc.
734 // events if we want more control.
735 LOG_INFO("TouchInput", "Touch input: Desktop mode (SDL touch passthrough)");
736}
737
738void TouchInput::ShutdownPlatform() {
739 // Nothing to clean up on desktop
740}
741
742#endif // __EMSCRIPTEN__
743
744} // namespace gui
745} // namespace yaze
static ImVec2 GetPanOffset()
Get cumulative pan offset from touch gestures.
static bool IsTouchMode()
Check if we're in touch mode (vs mouse mode)
static void ApplyPanOffset(ImVec2 offset)
Apply pan offset to the current value.
static void Initialize()
Initialize the touch input system.
static TouchConfig & GetConfig()
Get the current touch configuration.
static int GetActiveTouchCount()
Get number of active touch points.
static void Shutdown()
Shutdown and cleanup touch input system.
static ImVec2 GetZoomCenter()
Get the zoom center point in screen coordinates.
static void ProcessInertia()
static TouchPoint GetTouchPoint(int index)
Get raw touch point data.
static void UpdateGestureRecognition()
static void SetZoomLevel(float zoom)
Set the zoom level directly.
static void InitializePlatform()
static void SetGestureCallback(std::function< void(const GestureState &)> callback)
Set gesture callback.
static bool IsTouchActive()
Check if touch input is currently being used.
static double GetCurrentTime()
static void SetPanOffset(ImVec2 offset)
Set the pan offset directly.
static float GetZoomLevel()
Get cumulative zoom level from pinch gestures.
static GestureState GetCurrentGesture()
Get the current gesture state.
static constexpr int kMaxTouchPoints
Maximum number of simultaneous touch points supported.
static void TranslateToImGuiEvents()
static float GetRotation()
Get rotation angle from two-finger rotation.
static void ShutdownPlatform()
static bool IsPanZoomEnabled()
Check if pan/zoom is enabled.
static void Update()
Process touch events for the current frame.
static void SetPanZoomEnabled(bool enabled)
Enable or disable pan/zoom gestures for canvas.
static void ResetCanvasState()
Reset canvas transform state.
#define LOG_INFO(category, format,...)
Definition log.h:105
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })
std::array< TouchPoint *, 2 > GetFirstTwoTouches()
TouchGesture
Gesture types recognized by the touch input system.
Definition touch_input.h:16
TouchPhase
Phase of a touch gesture.
Definition touch_input.h:29
Gesture recognition result.
Definition touch_input.h:52
Touch input configuration.
Definition touch_input.h:78
Individual touch point data.
Definition touch_input.h:39
std::array< TouchPoint, TouchInput::kMaxTouchPoints > touch_points
std::function< void(const GestureState &) gesture_callback)
#define M_PI