7#include "imgui/imgui.h"
11#include <emscripten.h>
12#include <emscripten/html5.h>
16#define M_PI 3.14159265358979323846
26 bool initialized =
false;
27 bool touch_mode =
false;
28 bool pan_zoom_enabled =
true;
32 int active_touch_count = 0;
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;
45 bool long_press_detected =
false;
46 double touch_start_time = 0.0;
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);
55 ImVec2 inertia_velocity = ImVec2(0, 0);
56 bool inertia_active =
false;
59 float initial_pinch_distance = 0.0f;
60 float initial_rotation_angle = 0.0f;
61 ImVec2 initial_pan_center = ImVec2(0, 0);
76 return std::sqrt(dx * dx + dy * dy);
79float Angle(ImVec2 a, ImVec2 b) {
80 return std::atan2(b.y - a.y, b.x - a.x);
84 return ImVec2((a.x + b.x) * 0.5f, (a.y + b.y) * 0.5f);
89 return emscripten_get_now() / 1000.0;
91 auto now = std::chrono::steady_clock::now();
92 auto duration = now.time_since_epoch();
93 return std::chrono::duration<double>(duration).count();
99 if (touch.active && touch.id ==
id) {
126 std::array<TouchPoint*, 2> result = {
nullptr,
nullptr};
129 if (touch.active && found < 2) {
130 result[found++] = &touch;
146 g_touch_state = TouchInputState();
157 LOG_INFO(
"TouchInput",
"Touch input system initialized");
168 LOG_INFO(
"TouchInput",
"Touch input system shut down");
251 auto& config = g_touch_state.
config;
252 g_touch_state.
zoom_level = std::clamp(zoom, config.min_zoom, config.max_zoom);
268 return g_touch_state.
config;
279 auto& state = g_touch_state;
281 auto& config = state.config;
282 double current_time = GetTime();
284 CountActiveTouches();
287 gesture.touch_count = state.active_touch_count;
289 if (state.active_touch_count == 0) {
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) {
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) {
302 state.potential_double_tap =
false;
306 state.last_tap_time = current_time;
307 state.last_tap_position = gesture.position;
308 state.potential_double_tap =
true;
319 state.inertia_active =
true;
328 state.potential_tap =
false;
329 state.long_press_detected =
false;
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;
351 if (state.active_touch_count == 1) {
352 auto touches = GetFirstTwoTouches();
360 double duration = current_time - touch->
timestamp;
363 if (!state.long_press_detected && duration >= config.long_press_duration &&
364 movement <= config.tap_max_movement) {
365 state.long_press_detected =
true;
368 gesture.duration = duration;
373 if (state.long_press_detected) {
376 gesture.duration = duration;
381 if (movement <= config.tap_max_movement &&
382 duration <= config.tap_max_duration) {
383 state.potential_tap =
true;
390 state.potential_tap =
false;
394 if (state.active_touch_count >= 2 && state.pan_zoom_enabled) {
395 auto touches = GetFirstTwoTouches();
398 if (!touch1 || !touch2)
return;
401 state.potential_tap =
false;
402 state.long_press_detected =
false;
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;
422 (state.initial_pinch_distance > 0.0f)
423 ? distance / state.initial_pinch_distance
426 float rotation_delta = angle - state.initial_rotation_angle;
428 while (rotation_delta >
M_PI) rotation_delta -= 2.0f *
M_PI;
429 while (rotation_delta < -
M_PI) rotation_delta += 2.0f *
M_PI;
431 ImVec2 pan_delta = ImVec2(center.x - state.initial_pan_center.x,
432 center.y - state.initial_pan_center.y);
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);
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;
450 gesture.scale = scale_ratio;
451 gesture.scale_delta = scale_ratio - gesture.scale;
454 float new_zoom = state.zoom_level * scale_ratio;
455 new_zoom = std::clamp(new_zoom, config.min_zoom, config.max_zoom);
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;
464 state.initial_pinch_distance = distance;
466 }
else if (is_rotate) {
471 gesture.rotation_delta = rotation_delta;
472 gesture.rotation += rotation_delta;
473 state.rotation = gesture.rotation;
474 state.initial_rotation_angle = angle;
480 gesture.translation = pan_delta;
483 state.pan_offset.x += pan_delta.x;
484 state.pan_offset.y += pan_delta.y;
489 state.inertia_velocity.x = center.x - prev_center.x;
490 state.inertia_velocity.y = center.y - prev_center.y;
493 state.initial_pan_center = center;
496 gesture.position = center;
497 gesture.start_position = state.initial_pan_center;
502 auto& state = g_touch_state;
503 auto& config = state.
config;
505 if (!config.enable_inertia || !state.inertia_active) {
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);
517 state.pan_offset.x += state.inertia_velocity.x;
518 state.pan_offset.y += state.inertia_velocity.y;
521 state.inertia_velocity.x *= config.inertia_deceleration;
522 state.inertia_velocity.y *= config.inertia_deceleration;
526 auto& state = g_touch_state;
528 if (!state.touch_mode && state.active_touch_count == 0) {
532 ImGuiIO& io = ImGui::GetIO();
535 if (state.active_touch_count > 0) {
536 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
537 state.touch_mode =
true;
541 if (state.active_touch_count == 1) {
542 auto touches = GetFirstTwoTouches();
546 io.AddMouseButtonEvent(0,
true);
548 }
else if (state.active_touch_count == 0 &&
549 state.previous_gesture.touch_count > 0) {
551 io.AddMouseButtonEvent(0,
false);
561 io.AddMouseButtonEvent(1,
true);
562 io.AddMouseButtonEvent(1,
false);
579EM_JS(
void, yaze_setup_touch_handlers, (), {
581 if (typeof window.YazeTouchGestures !==
'undefined') {
582 window.YazeTouchGestures.initialize();
583 console.log(
'Touch gesture handlers initialized via YazeTouchGestures');
585 console.log(
'YazeTouchGestures not loaded - using basic touch handling');
588 const canvas = document.getElementById(
'canvas');
590 console.error(
'Canvas element not found for touch handling');
595 canvas.style.touchAction =
'none';
597 function handleTouch(event, type) {
598 event.preventDefault();
600 const rect = canvas.getBoundingClientRect();
601 const scaleX = canvas.width / rect.width;
602 const scaleY = canvas.height / rect.height;
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;
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]);
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 });
629EM_JS(
void, yaze_cleanup_touch_handlers, (), {
630 if (typeof window.YazeTouchGestures !==
'undefined') {
631 window.YazeTouchGestures.shutdown();
636void TouchInput::InitializePlatform() {
637 yaze_setup_touch_handlers();
640void TouchInput::ShutdownPlatform() {
641 yaze_cleanup_touch_handlers();
648void OnTouchEvent(
int type,
int id,
float x,
float y,
float pressure,
650 TouchInput::OnTouchEvent(type,
id, x, y, pressure, timestamp);
654void OnGestureEvent(
int type,
int phase,
float x,
float y,
float scale,
656 TouchInput::OnGestureEvent(type, phase, x, y, scale, rotation);
661void TouchInput::OnTouchEvent(
int type,
int id,
float x,
float y,
662 float pressure,
double timestamp) {
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;
678 if (state.active_touch_count == 0) {
679 state.touch_start_time = timestamp;
687 touch->previous_position = touch->position;
688 touch->position = ImVec2(x, y);
689 touch->pressure = pressure;
697 touch->active =
false;
704 state.touch_mode =
true;
707void TouchInput::OnGestureEvent(
int type,
int phase,
float x,
float y,
708 float scale,
float rotation) {
713 gesture.phase =
static_cast<TouchPhase>(phase);
714 gesture.position = ImVec2(x, y);
715 gesture.scale = scale;
716 gesture.rotation = rotation;
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;
731void TouchInput::InitializePlatform() {
735 LOG_INFO(
"TouchInput",
"Touch input: Desktop mode (SDL touch passthrough)");
738void TouchInput::ShutdownPlatform() {
#define LOG_INFO(category, format,...)
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");} })
TouchGesture
Gesture types recognized by the touch input system.
TouchPhase
Phase of a touch gesture.
Gesture recognition result.
Touch input configuration.
Individual touch point data.