using System; using Core; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.EnhancedTouch; using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch; namespace Minigames.StatueDressup.UI { /// /// Detects and processes pinch gestures for scaling and rotation using Unity's new Input System (EnhancedTouch). /// Also detects single-tap dismissal and provides keyboard shortcuts for editor testing. /// public class PinchGestureHandler : MonoBehaviour { [Header("Gesture Settings")] [Tooltip("Minimum distance change to register as pinch scale gesture")] [SerializeField] private float minPinchDistance = 10f; [Tooltip("Sensitivity multiplier for scale changes")] [SerializeField] private float scaleSensitivity = 0.01f; [Tooltip("Sensitivity multiplier for rotation changes")] [SerializeField] private float rotationSensitivity = 1.0f; [Header("Editor Testing (Keyboard)")] [Tooltip("Keyboard rotation speed in degrees per second")] [SerializeField] private float keyboardRotationSpeed = 90f; [Tooltip("Keyboard scale speed per second")] [SerializeField] private float keyboardScaleSpeed = 0.5f; // Events public event Action OnPinchScale; public event Action OnPinchRotate; public event Action OnDismissTap; private bool _isActive; private float _previousDistance; private float _previousAngle; private double _touchStartTime; private bool _wasTouchTracked; /// /// Activate gesture detection /// public void Activate() { _isActive = true; _previousDistance = 0f; _previousAngle = 0f; _wasTouchTracked = false; // Enable enhanced touch support for new Input System if (!EnhancedTouchSupport.enabled) { EnhancedTouchSupport.Enable(); } Logging.Debug("[PinchGestureHandler] Activated"); } /// /// Deactivate gesture detection /// public void Deactivate() { _isActive = false; Logging.Debug("[PinchGestureHandler] Deactivated"); } private void Update() { if (!_isActive) return; // Handle touch input (mobile) - using new Input System if (Touch.activeTouches.Count > 0) { HandleTouchInput(); } // Handle keyboard input (editor testing) else if (Application.isEditor) { HandleKeyboardInput(); } } /// /// Process touch input for pinch gestures and single-tap dismissal /// private void HandleTouchInput() { var activeTouches = Touch.activeTouches; // Single tap for dismissal if (activeTouches.Count == 1) { Touch touch = activeTouches[0]; // Track touch start time if (touch.phase == UnityEngine.InputSystem.TouchPhase.Began) { _touchStartTime = touch.startTime; _wasTouchTracked = true; } // Detect tap (touch began and ended quickly - within 0.3 seconds) else if (touch.phase == UnityEngine.InputSystem.TouchPhase.Ended && _wasTouchTracked) { double touchDuration = Time.timeAsDouble - _touchStartTime; if (touchDuration < 0.3) { Logging.Debug("[PinchGestureHandler] Single tap detected for dismissal"); OnDismissTap?.Invoke(); } _wasTouchTracked = false; } } // Two-finger pinch for scale and rotation else if (activeTouches.Count == 2) { Touch touch0 = activeTouches[0]; Touch touch1 = activeTouches[1]; // Calculate current positions and distance Vector2 currentTouch0 = touch0.screenPosition; Vector2 currentTouch1 = touch1.screenPosition; float currentDistance = Vector2.Distance(currentTouch0, currentTouch1); float currentAngle = Mathf.Atan2(currentTouch1.y - currentTouch0.y, currentTouch1.x - currentTouch0.x) * Mathf.Rad2Deg; // Initialize on first frame of pinch if (touch0.phase == UnityEngine.InputSystem.TouchPhase.Began || touch1.phase == UnityEngine.InputSystem.TouchPhase.Began) { _previousDistance = currentDistance; _previousAngle = currentAngle; Logging.Debug("[PinchGestureHandler] Pinch gesture started"); return; } // Process pinch scale (distance change) if (touch0.phase == UnityEngine.InputSystem.TouchPhase.Moved || touch1.phase == UnityEngine.InputSystem.TouchPhase.Moved) { if (_previousDistance > minPinchDistance) { float distanceDelta = currentDistance - _previousDistance; float scaleDelta = distanceDelta * scaleSensitivity; if (Mathf.Abs(scaleDelta) > 0.001f) { OnPinchScale?.Invoke(scaleDelta); Logging.Debug($"[PinchGestureHandler] Scale delta: {scaleDelta:F3}"); } } // Process pinch rotation (angle change) float angleDelta = Mathf.DeltaAngle(_previousAngle, currentAngle) * rotationSensitivity; if (Mathf.Abs(angleDelta) > 0.5f) { OnPinchRotate?.Invoke(angleDelta); Logging.Debug($"[PinchGestureHandler] Rotation delta: {angleDelta:F1}°"); } // Update previous values _previousDistance = currentDistance; _previousAngle = currentAngle; } } } /// /// Process keyboard input for editor testing /// Q/E for rotation, +/- for scale, Escape for dismissal /// private void HandleKeyboardInput() { var keyboard = Keyboard.current; if (keyboard == null) return; // Rotation with Q/E if (keyboard.qKey.isPressed) { float rotateDelta = -keyboardRotationSpeed * Time.deltaTime; OnPinchRotate?.Invoke(rotateDelta); } else if (keyboard.eKey.isPressed) { float rotateDelta = keyboardRotationSpeed * Time.deltaTime; OnPinchRotate?.Invoke(rotateDelta); } // Scale with +/- (equals key is where + is on keyboard) if (keyboard.equalsKey.isPressed || keyboard.numpadPlusKey.isPressed) { float scaleDelta = keyboardScaleSpeed * Time.deltaTime; OnPinchScale?.Invoke(scaleDelta); } else if (keyboard.minusKey.isPressed || keyboard.numpadMinusKey.isPressed) { float scaleDelta = -keyboardScaleSpeed * Time.deltaTime; OnPinchScale?.Invoke(scaleDelta); } // Dismissal with Escape or Space if (keyboard.escapeKey.wasPressedThisFrame || keyboard.spaceKey.wasPressedThisFrame) { Logging.Debug("[PinchGestureHandler] Keyboard dismissal detected"); OnDismissTap?.Invoke(); } } } }