/* * @author Valentin Simonov / http://va.lent.in/ */ using System; using System.Collections.Generic; using TouchScript.Utils; using TouchScript.Pointers; using UnityEngine; using UnityEngine.Profiling; namespace TouchScript.Gestures { /// /// Recognizes fast movement before releasing pointers. Doesn't care how much time pointers were on surface and how much they moved. /// [AddComponentMenu("TouchScript/Gestures/Flick Gesture")] [HelpURL("http://touchscript.github.io/docs/html/T_TouchScript_Gestures_FlickGesture.htm")] public class FlickGesture : Gesture { #region Constants /// /// Message name when gesture is recognized /// public const string FLICK_MESSAGE = "OnFlick"; /// /// Direction of a flick. /// public enum GestureDirection { /// /// Direction doesn't matter. /// Any, /// /// Only horizontal. /// Horizontal, /// /// Only vertical. /// Vertical, } #endregion #region Events /// /// Occurs when gesture is recognized. /// public event EventHandler Flicked { add { flickedInvoker += value; } remove { flickedInvoker -= value; } } // Needed to overcome iOS AOT limitations private EventHandler flickedInvoker; #endregion #region Public properties /// /// Gets or sets time interval in seconds in which pointers must move by for gesture to succeed. /// /// Interval in seconds in which pointers must move by for gesture to succeed. public float FlickTime { get { return flickTime; } set { flickTime = value; } } /// /// Gets or sets minimum distance in cm to move in before ending gesture for it to be recognized. /// /// Minimum distance in cm to move in before ending gesture for it to be recognized. public float MinDistance { get { return minDistance; } set { minDistance = value; } } /// /// Gets or sets minimum distance in cm pointers must move to start recognizing this gesture. /// /// Minimum distance in cm pointers must move to start recognizing this gesture. /// Prevents misinterpreting taps. public float MovementThreshold { get { return movementThreshold; } set { movementThreshold = value; } } /// /// Gets or sets direction to look for. /// /// Direction of movement. public GestureDirection Direction { get { return direction; } set { direction = value; } } /// /// Gets flick direction (not normalized) when gesture is recognized. /// public Vector2 ScreenFlickVector { get; private set; } /// /// Gets flick time in seconds pointers moved by . /// public float ScreenFlickTime { get; private set; } #endregion #region Private variables [SerializeField] private float flickTime = .1f; [SerializeField] private float minDistance = 1f; [SerializeField] private float movementThreshold = .5f; [SerializeField] private GestureDirection direction = GestureDirection.Any; private bool moving = false; private Vector2 movementBuffer = Vector2.zero; private bool isActive = false; private TimedSequence deltaSequence = new TimedSequence(); private CustomSampler gestureSampler; #endregion #region Unity methods /// protected override void Awake() { base.Awake(); gestureSampler = CustomSampler.Create("[TouchScript] Flick Gesture"); } /// protected void LateUpdate() { if (!isActive) return; deltaSequence.Add(ScreenPosition - PreviousScreenPosition); } [ContextMenu("Basic Editor")] private void switchToBasicEditor() { basicEditor = true; } #endregion #region Gesture callbacks /// protected override void pointersPressed(IList pointers) { gestureSampler.Begin(); base.pointersPressed(pointers); if (pointersNumState == PointersNumState.PassedMaxThreshold || pointersNumState == PointersNumState.PassedMinMaxThreshold) { if (State == GestureState.Possible) setState(GestureState.Failed); } else if (pointersNumState == PointersNumState.PassedMinThreshold) { // Starting the gesture when it is already active? => we released one finger and pressed again while moving if (isActive) setState(GestureState.Failed); else isActive = true; } gestureSampler.End(); } /// protected override void pointersUpdated(IList pointers) { gestureSampler.Begin(); base.pointersUpdated(pointers); if (isActive || !moving) { movementBuffer += ScreenPosition - PreviousScreenPosition; var dpiMovementThreshold = MovementThreshold * touchManager.DotsPerCentimeter; if (movementBuffer.sqrMagnitude >= dpiMovementThreshold * dpiMovementThreshold) { moving = true; } } gestureSampler.End(); } /// protected override void pointersReleased(IList pointers) { gestureSampler.Begin(); base.pointersReleased(pointers); if (NumPointers == 0) { if (!isActive || !moving) { setState(GestureState.Failed); gestureSampler.End(); return; } deltaSequence.Add(ScreenPosition - PreviousScreenPosition); float lastTime; var deltas = deltaSequence.FindElementsLaterThan(Time.unscaledTime - FlickTime, out lastTime); var totalMovement = Vector2.zero; var count = deltas.Count; for (var i = 0; i < count; i++) totalMovement += deltas[i]; switch (Direction) { case GestureDirection.Horizontal: totalMovement.y = 0; break; case GestureDirection.Vertical: totalMovement.x = 0; break; } if (totalMovement.magnitude < MinDistance * touchManager.DotsPerCentimeter) { setState(GestureState.Failed); } else { ScreenFlickVector = totalMovement; ScreenFlickTime = Time.unscaledTime - lastTime; setState(GestureState.Recognized); } } gestureSampler.End(); } /// protected override void onRecognized() { base.onRecognized(); if (flickedInvoker != null) flickedInvoker.InvokeHandleExceptions(this, EventArgs.Empty); if (UseSendMessage && SendMessageTarget != null) SendMessageTarget.SendMessage(FLICK_MESSAGE, this, SendMessageOptions.DontRequireReceiver); } /// protected override void reset() { base.reset(); isActive = false; moving = false; movementBuffer = Vector2.zero; } #endregion } }