LMQT/Assets/TouchScript/Scripts/Gestures/Gesture.cs
2024-12-10 09:03:45 +08:00

1113 lines
37 KiB
C#

/*
* @author Valentin Simonov / http://va.lent.in/
*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using TouchScript.Hit;
using TouchScript.Utils;
using TouchScript.Utils.Attributes;
using TouchScript.Pointers;
using UnityEngine;
using UnityEngine.Events;
using TouchScript.Core;
namespace TouchScript.Gestures
{
/// <summary>
/// Base class for all gestures.
/// </summary>
public abstract class Gesture : DebuggableMonoBehaviour
{
#region Constants
/// <summary>
/// Unity event for gesture state changes.
/// </summary>
[Serializable]
public class GestureEvent : UnityEvent<Gesture> {}
/// <summary>
/// Message sent when gesture changes state if SendMessage is used.
/// </summary>
public const string STATE_CHANGE_MESSAGE = "OnGestureStateChange";
/// <summary>
/// Message sent when gesture is cancelled if SendMessage is used.
/// </summary>
public const string CANCEL_MESSAGE = "OnGestureCancel";
/// <summary>
/// Possible states of a gesture.
/// </summary>
public enum GestureState
{
/// <summary>
/// Gesture is idle.
/// </summary>
Idle,
/// <summary>
/// Gesture started looking for the patern.
/// </summary>
Possible,
/// <summary>
/// Continuous gesture has just begun.
/// </summary>
Began,
/// <summary>
/// Started continuous gesture is updated.
/// </summary>
Changed,
/// <summary>
/// Continuous gesture is ended.
/// </summary>
Ended,
/// <summary>
/// Gesture is cancelled.
/// </summary>
Cancelled,
/// <summary>
/// Gesture is failed by itself or by another recognized gesture.
/// </summary>
Failed,
/// <summary>
/// Gesture is recognized.
/// </summary>
Recognized = Ended
}
/// <summary>
/// Current state of the number of pointers.
/// </summary>
protected enum PointersNumState
{
/// <summary>
/// The number of pointers is between min and max thresholds.
/// </summary>
InRange,
/// <summary>
/// The number of pointers is less than min threshold.
/// </summary>
TooFew,
/// <summary>
/// The number of pointers is greater than max threshold.
/// </summary>
TooMany,
/// <summary>
/// The number of pointers passed min threshold this frame and is now in range.
/// </summary>
PassedMinThreshold,
/// <summary>
/// The number of pointers passed max threshold this frame and is now in range.
/// </summary>
PassedMaxThreshold,
/// <summary>
/// The number of pointers passed both min and max thresholds.
/// </summary>
PassedMinMaxThreshold
}
#endregion
#region Events
/// <summary>
/// Occurs when gesture changes state.
/// </summary>
public event EventHandler<GestureStateChangeEventArgs> StateChanged
{
add { stateChangedInvoker += value; }
remove { stateChangedInvoker -= value; }
}
/// <summary>
/// Occurs when gesture is cancelled.
/// </summary>
public event EventHandler<EventArgs> Cancelled
{
add { cancelledInvoker += value; }
remove { cancelledInvoker -= value; }
}
// Needed to overcome iOS AOT limitations
private EventHandler<GestureStateChangeEventArgs> stateChangedInvoker;
private EventHandler<EventArgs> cancelledInvoker;
/// <summary>
/// Occurs when gesture changes state.
/// </summary>
public GestureEvent OnStateChange = new GestureEvent();
#endregion
#region Public properties
/// <summary>
/// Gets or sets minimum number of pointers this gesture reacts to.
/// The gesture will not be recognized if it has less than <see cref="MinPointers"/> pointers.
/// </summary>
/// <value> Minimum number of pointers. </value>
public int MinPointers
{
get { return minPointers; }
set
{
if (value < 0) return;
minPointers = value;
}
}
/// <summary>
/// Gets or sets maximum number of pointers this gesture reacts to.
/// The gesture will not be recognized if it has more than <see cref="MaxPointers"/> pointers.
/// </summary>
/// <value> Maximum number of pointers. </value>
public int MaxPointers
{
get { return maxPointers; }
set
{
if (value < 0) return;
maxPointers = value;
}
}
/// <summary>
/// Gets or sets another gesture which must fail before this gesture can be recognized.
/// </summary>
/// <value> The gesture which must fail before this gesture can be recognized. </value>
public Gesture RequireGestureToFail
{
get { return requireGestureToFail; }
set
{
if (!Application.isPlaying) return;
if (requireGestureToFail != null)
requireGestureToFail.StateChanged -= requiredToFailGestureStateChangedHandler;
requireGestureToFail = value;
if (requireGestureToFail != null)
requireGestureToFail.StateChanged += requiredToFailGestureStateChangedHandler;
}
}
/// <summary>
/// Gets or sets whether gesture should use Unity's SendMessage in addition to C# events.
/// </summary>
/// <value> <c>true</c> if gesture uses SendMessage; otherwise, <c>false</c>. </value>
public bool UseSendMessage
{
get { return useSendMessage; }
set { useSendMessage = value; }
}
/// <summary>
/// Gets or sets a value indicating whether state change events are broadcasted if <see cref="UseSendMessage"/> is true.
/// </summary>
/// <value> <c>true</c> if state change events should be broadcaster; otherwise, <c>false</c>. </value>
public bool SendStateChangeMessages
{
get { return sendStateChangeMessages; }
set { sendStateChangeMessages = value; }
}
/// <summary>
/// Gets or sets the target of Unity messages sent from this gesture.
/// </summary>
/// <value> The target of Unity messages. </value>
public GameObject SendMessageTarget
{
get { return sendMessageTarget; }
set
{
sendMessageTarget = value;
if (value == null) sendMessageTarget = gameObject;
}
}
/// <summary>
/// Gets or sets whether gesture should use Unity Events in addition to C# events.
/// </summary>
/// <value> <c>true</c> if gesture uses Unity Events; otherwise, <c>false</c>. </value>
public bool UseUnityEvents
{
get { return useUnityEvents; }
set { useUnityEvents = value; }
}
/// <summary>
/// Gets or sets a value indicating whether state change events are broadcasted if <see cref="UseUnityEvents"/> is true.
/// </summary>
/// <value> <c>true</c> if state change events should be broadcaster; otherwise, <c>false</c>. </value>
public bool SendStateChangeEvents
{
get { return sendStateChangeEvents; }
set { sendStateChangeEvents = value; }
}
/// <summary>
/// Gets current gesture state.
/// </summary>
/// <value> Current state of the gesture. </value>
public GestureState State
{
get { return state; }
private set
{
PreviousState = state;
state = value;
switch (value)
{
case GestureState.Idle:
onIdle();
break;
case GestureState.Possible:
onPossible();
break;
case GestureState.Began:
retainPointers();
onBegan();
break;
case GestureState.Changed:
onChanged();
break;
case GestureState.Recognized:
// Only retain/release pointers for continuos gestures
if (PreviousState == GestureState.Changed || PreviousState == GestureState.Began)
releasePointers(true);
onRecognized();
break;
case GestureState.Failed:
onFailed();
break;
case GestureState.Cancelled:
if (PreviousState == GestureState.Changed || PreviousState == GestureState.Began)
releasePointers(false);
onCancelled();
break;
}
if (stateChangedInvoker != null)
stateChangedInvoker.InvokeHandleExceptions(this, GestureStateChangeEventArgs.GetCachedEventArgs(state, PreviousState));
if (useSendMessage && sendStateChangeMessages && SendMessageTarget != null)
sendMessageTarget.SendMessage(STATE_CHANGE_MESSAGE, this, SendMessageOptions.DontRequireReceiver);
if (useUnityEvents && sendStateChangeEvents) OnStateChange.Invoke(this);
}
}
/// <summary>
/// Gets previous gesture state.
/// </summary>
/// <value> Previous state of the gesture. </value>
public GestureState PreviousState { get; private set; }
/// <summary>
/// Gets current screen position.
/// </summary>
/// <value> Gesture's position in screen coordinates. </value>
public virtual Vector2 ScreenPosition
{
get
{
if (NumPointers == 0)
{
if (!TouchManager.IsInvalidPosition(cachedScreenPosition)) return cachedScreenPosition;
return TouchManager.INVALID_POSITION;
}
return activePointers[0].Position;
}
}
/// <summary>
/// Gets previous screen position.
/// </summary>
/// <value> Gesture's previous position in screen coordinates. </value>
public virtual Vector2 PreviousScreenPosition
{
get
{
if (NumPointers == 0)
{
if (!TouchManager.IsInvalidPosition(cachedPreviousScreenPosition))
return cachedPreviousScreenPosition;
return TouchManager.INVALID_POSITION;
}
return activePointers[0].PreviousPosition;
}
}
/// <summary>
/// Gets normalized screen position.
/// </summary>
/// <value> Gesture's position in normalized screen coordinates. </value>
public Vector2 NormalizedScreenPosition
{
get
{
var position = ScreenPosition;
if (TouchManager.IsInvalidPosition(position)) return TouchManager.INVALID_POSITION;
return new Vector2(position.x / Screen.width, position.y / Screen.height);
}
}
/// <summary>
/// Gets previous screen position.
/// </summary>
/// <value> Gesture's previous position in normalized screen coordinates. </value>
public Vector2 PreviousNormalizedScreenPosition
{
get
{
var position = PreviousScreenPosition;
if (TouchManager.IsInvalidPosition(position)) return TouchManager.INVALID_POSITION;
return new Vector2(position.x / Screen.width, position.y / Screen.height);
}
}
/// <summary>
/// Gets list of gesture's active pointers.
/// </summary>
/// <value> The list of pointers owned by this gesture. </value>
public IList<Pointer> ActivePointers
{
get
{
if (readonlyActivePointers == null)
readonlyActivePointers = new ReadOnlyCollection<Pointer>(activePointers);
return readonlyActivePointers;
}
}
/// <summary>
/// Gets the number of active pointerss.
/// </summary>
/// <value> The number of pointers owned by this gesture. </value>
public int NumPointers
{
get { return numPointers; }
}
/// <summary>
/// Gets or sets an object implementing <see cref="IGestureDelegate"/> to be asked for gesture specific actions.
/// </summary>
/// <value> The delegate. </value>
public IGestureDelegate Delegate { get; set; }
#endregion
#region Private variables
/// <summary>
/// Reference to global GestureManager.
/// </summary>
protected IGestureManager gestureManager
{
// implemented as a property because it returns IGestureManager but we need to reference GestureManagerInstance to access internal methods
get { return gestureManagerInstance; }
}
/// <summary>
/// Reference to global TouchManager.
/// </summary>
protected TouchManagerInstance touchManager { get; private set; }
/// <summary>
/// The state of min/max number of pointers.
/// </summary>
protected PointersNumState pointersNumState { get; private set; }
/// <summary>
/// Pointers the gesture currently owns and works with.
/// </summary>
protected List<Pointer> activePointers = new List<Pointer>(10);
/// <summary>
/// Cached transform of the parent object.
/// </summary>
protected Transform cachedTransform;
/// <exclude />
[SerializeField]
[HideInInspector]
protected bool basicEditor = true;
[SerializeField]
[HideInInspector]
private bool generalProps; // Used in the custom inspector
[SerializeField]
[HideInInspector]
private bool limitsProps; // Used in the custom inspector
[SerializeField]
[HideInInspector]
private bool advancedProps; // Used in the custom inspector
[SerializeField]
private int minPointers = 0;
[SerializeField]
private int maxPointers = 0;
[SerializeField]
[ToggleLeft]
private bool useSendMessage = false;
[SerializeField]
[ToggleLeft]
private bool sendStateChangeMessages = false;
[SerializeField]
private GameObject sendMessageTarget;
[SerializeField]
private bool useUnityEvents = false;
[SerializeField]
[ToggleLeft]
private bool sendStateChangeEvents = false;
[SerializeField]
[NullToggle]
private Gesture requireGestureToFail;
[SerializeField]
// Serialized list of gestures for Unity IDE.
private List<Gesture> friendlyGestures = new List<Gesture>();
private int numPointers;
private ReadOnlyCollection<Pointer> readonlyActivePointers;
private GestureManagerInstance gestureManagerInstance;
private GestureState delayedStateChange = GestureState.Idle;
private bool requiredGestureFailed = false;
private FakePointer fakePointer = new FakePointer();
private GestureState state = GestureState.Idle;
/// <summary>
/// Cached screen position.
/// Used to keep tap's position which can't be calculated from pointers when the gesture is recognized since all pointers are gone.
/// </summary>
protected Vector2 cachedScreenPosition;
/// <summary>
/// Cached previous screen position.
/// Used to keep tap's position which can't be calculated from pointers when the gesture is recognized since all pointers are gone.
/// </summary>
protected Vector2 cachedPreviousScreenPosition;
#endregion
#region Public methods
/// <summary>
/// Adds a friendly gesture.
/// </summary>
/// <param name="gesture"> The gesture. </param>
public void AddFriendlyGesture(Gesture gesture)
{
if (gesture == null || gesture == this) return;
registerFriendlyGesture(gesture);
gesture.registerFriendlyGesture(this);
}
/// <summary>
/// Checks if a gesture is friendly with this gesture.
/// </summary>
/// <param name="gesture"> A gesture to check. </param>
/// <returns> <c>true</c> if gestures are friendly; <c>false</c> otherwise. </returns>
public bool IsFriendly(Gesture gesture)
{
return friendlyGestures.Contains(gesture);
}
/// <summary>
/// Determines whether gesture controls a pointer.
/// </summary>
/// <param name="pointer"> The pointer. </param>
/// <returns> <c>true</c> if gesture controls the pointer point; <c>false</c> otherwise. </returns>
public bool HasPointer(Pointer pointer)
{
return activePointers.Contains(pointer);
}
/// <summary>
/// Determines whether this instance can prevent the specified gesture.
/// </summary>
/// <param name="gesture"> The gesture. </param>
/// <returns> <c>true</c> if this instance can prevent the specified gesture; <c>false</c> otherwise. </returns>
public virtual bool CanPreventGesture(Gesture gesture)
{
if (Delegate == null)
{
if (gesture.CanBePreventedByGesture(this)) return !IsFriendly(gesture);
return false;
}
return !Delegate.ShouldRecognizeSimultaneously(this, gesture);
}
/// <summary>
/// Determines whether this instance can be prevented by specified gesture.
/// </summary>
/// <param name="gesture"> The gesture. </param>
/// <returns> <c>true</c> if this instance can be prevented by specified gesture; <c>false</c> otherwise. </returns>
public virtual bool CanBePreventedByGesture(Gesture gesture)
{
if (Delegate == null) return !IsFriendly(gesture);
return !Delegate.ShouldRecognizeSimultaneously(this, gesture);
}
/// <summary>
/// Specifies if gesture can receive this specific pointer point.
/// </summary>
/// <param name="pointer"> The pointer. </param>
/// <returns> <c>true</c> if this pointer should be received by the gesture; <c>false</c> otherwise. </returns>
public virtual bool ShouldReceivePointer(Pointer pointer)
{
if (Delegate == null) return true;
return Delegate.ShouldReceivePointer(this, pointer);
}
/// <summary>
/// Specifies if gesture can begin or recognize.
/// </summary>
/// <returns> <c>true</c> if gesture should begin; <c>false</c> otherwise. </returns>
public virtual bool ShouldBegin()
{
if (Delegate == null) return true;
return Delegate.ShouldBegin(this);
}
/// <summary>
/// Cancels this gesture.
/// </summary>
/// <param name="cancelPointers"> if set to <c>true</c> also implicitly cancels all pointers owned by the gesture. </param>
/// <param name="returnPointers"> if set to <c>true</c> redispatched all canceled pointers. </param>
public void Cancel(bool cancelPointers, bool returnPointers)
{
switch (state)
{
case GestureState.Cancelled:
case GestureState.Failed:
return;
}
setState(GestureState.Cancelled);
if (!cancelPointers) return;
for (var i = 0; i < numPointers; i++) touchManager.CancelPointer(activePointers[i].Id, returnPointers);
}
/// <summary>
/// Cancels this gesture.
/// </summary>
public void Cancel()
{
Cancel(false, false);
}
/// <summary>
/// Returns <see cref="HitData"/> for gesture's <see cref="ScreenPosition"/>, i.e. what is right beneath it.
/// </summary>
public virtual HitData GetScreenPositionHitData()
{
HitData hit;
fakePointer.Position = ScreenPosition;
LayerManager.Instance.GetHitTarget(fakePointer, out hit);
return hit;
}
#endregion
#region Unity methods
/// <inheritdoc />
protected virtual void Awake()
{
cachedTransform = transform;
var count = friendlyGestures.Count;
for (var i = 0; i < count; i++)
{
AddFriendlyGesture(friendlyGestures[i]);
}
RequireGestureToFail = requireGestureToFail;
}
/// <summary>
/// Unity Start handler.
/// </summary>
protected virtual void OnEnable()
{
// TouchManager might be different in another scene
touchManager = TouchManager.Instance as TouchManagerInstance;
gestureManagerInstance = GestureManager.Instance as GestureManagerInstance;
if (touchManager == null)
Debug.LogError("No TouchManager found! Please add an instance of TouchManager to the scene!");
if (gestureManagerInstance == null)
Debug.LogError("No GesturehManager found! Please add an instance of GesturehManager to the scene!");
if (sendMessageTarget == null) sendMessageTarget = gameObject;
INTERNAL_Reset();
}
/// <summary>
/// Unity OnDisable handler.
/// </summary>
protected virtual void OnDisable()
{
setState(GestureState.Cancelled);
}
/// <summary>
/// Unity OnDestroy handler.
/// </summary>
protected virtual void OnDestroy()
{
var copy = new List<Gesture>(friendlyGestures);
var count = copy.Count;
for (var i = 0; i < count; i++)
{
INTERNAL_RemoveFriendlyGesture(copy[i]);
}
RequireGestureToFail = null;
}
#endregion
#region Internal functions
internal void INTERNAL_SetState(GestureState value)
{
setState(value);
}
internal void INTERNAL_Reset()
{
activePointers.Clear();
numPointers = 0;
delayedStateChange = GestureState.Idle;
pointersNumState = PointersNumState.TooFew;
requiredGestureFailed = false;
reset();
}
internal void INTERNAL_PointersPressed(IList<Pointer> pointers)
{
var count = pointers.Count;
var total = numPointers + count;
pointersNumState = PointersNumState.InRange;
if (minPointers <= 0)
{
// MinPointers is not set and we got our first pointers
if (numPointers == 0) pointersNumState = PointersNumState.PassedMinThreshold;
}
else
{
if (numPointers < minPointers)
{
// had < MinPointers, got >= MinPointers
if (total >= minPointers) pointersNumState = PointersNumState.PassedMinThreshold;
else pointersNumState = PointersNumState.TooFew;
}
}
if (maxPointers > 0)
{
if (numPointers <= maxPointers)
{
if (total > maxPointers)
{
// this event we crossed both MinPointers and MaxPointers
if (pointersNumState == PointersNumState.PassedMinThreshold) pointersNumState = PointersNumState.PassedMinMaxThreshold;
// this event we crossed MaxPointers
else pointersNumState = PointersNumState.PassedMaxThreshold;
}
}
// last event we already were over MaxPointers
else pointersNumState = PointersNumState.TooMany;
}
if (state == GestureState.Began || state == GestureState.Changed)
{
for (var i = 0; i < count; i++) pointers[i].INTERNAL_Retain();
}
activePointers.AddRange(pointers);
numPointers = total;
pointersPressed(pointers);
}
internal void INTERNAL_PointersUpdated(IList<Pointer> pointers)
{
pointersNumState = PointersNumState.InRange;
if (minPointers > 0 && numPointers < minPointers) pointersNumState = PointersNumState.TooFew;
if (maxPointers > 0 && pointersNumState == PointersNumState.InRange && numPointers > maxPointers) pointersNumState = PointersNumState.TooMany;
pointersUpdated(pointers);
}
internal void INTERNAL_PointersReleased(IList<Pointer> pointers)
{
var count = pointers.Count;
var total = numPointers - count;
pointersNumState = PointersNumState.InRange;
if (minPointers <= 0)
{
// have no pointers
if (total == 0) pointersNumState = PointersNumState.PassedMinThreshold;
}
else
{
if (numPointers >= minPointers)
{
// had >= MinPointers, got < MinPointers
if (total < minPointers) pointersNumState = PointersNumState.PassedMinThreshold;
}
// last event we already were under MinPointers
else pointersNumState = PointersNumState.TooFew;
}
if (maxPointers > 0)
{
if (numPointers > maxPointers)
{
if (total <= maxPointers)
{
// this event we crossed both MinPointers and MaxPointers
if (pointersNumState == PointersNumState.PassedMinThreshold) pointersNumState = PointersNumState.PassedMinMaxThreshold;
// this event we crossed MaxPointers
else pointersNumState = PointersNumState.PassedMaxThreshold;
}
// last event we already were over MaxPointers
else pointersNumState = PointersNumState.TooMany;
}
}
for (var i = 0; i < count; i++) activePointers.Remove(pointers[i]);
numPointers = total;
if (NumPointers == 0)
{
var lastPoint = pointers[count - 1];
if (shouldCachePointerPosition(lastPoint))
{
cachedScreenPosition = lastPoint.Position;
cachedPreviousScreenPosition = lastPoint.PreviousPosition;
}
else
{
cachedScreenPosition = TouchManager.INVALID_POSITION;
cachedPreviousScreenPosition = TouchManager.INVALID_POSITION;
}
}
pointersReleased(pointers);
}
internal void INTERNAL_PointersCancelled(IList<Pointer> pointers)
{
var count = pointers.Count;
var total = numPointers - count;
pointersNumState = PointersNumState.InRange;
if (minPointers <= 0)
{
// have no pointers
if (total == 0) pointersNumState = PointersNumState.PassedMinThreshold;
}
else
{
if (numPointers >= minPointers)
{
// had >= MinPointers, got < MinPointers
if (total < minPointers) pointersNumState = PointersNumState.PassedMinThreshold;
}
// last event we already were under MinPointers
else pointersNumState = PointersNumState.TooFew;
}
if (maxPointers > 0)
{
if (numPointers > maxPointers)
{
if (total <= maxPointers)
{
// this event we crossed both MinPointers and MaxPointers
if (pointersNumState == PointersNumState.PassedMinThreshold) pointersNumState = PointersNumState.PassedMinMaxThreshold;
// this event we crossed MaxPointers
else pointersNumState = PointersNumState.PassedMaxThreshold;
}
// last event we already were over MaxPointers
else pointersNumState = PointersNumState.TooMany;
}
}
for (var i = 0; i < count; i++) activePointers.Remove(pointers[i]);
numPointers = total;
pointersCancelled(pointers);
}
internal virtual void INTERNAL_RemoveFriendlyGesture(Gesture gesture)
{
if (gesture == null || gesture == this) return;
unregisterFriendlyGesture(gesture);
gesture.unregisterFriendlyGesture(this);
}
#endregion
#region Protected methods
/// <summary>
/// Should the gesture cache this pointers to use it later in calculation of <see cref="ScreenPosition"/>.
/// </summary>
/// <param name="value"> Pointer to cache. </param>
/// <returns> <c>true</c> if pointers should be cached; <c>false</c> otherwise. </returns>
protected virtual bool shouldCachePointerPosition(Pointer value)
{
return true;
}
/// <summary>
/// Tries to change gesture state.
/// </summary>
/// <param name="value"> New state. </param>
/// <returns> <c>true</c> if state was changed; otherwise, <c>false</c>. </returns>
protected bool setState(GestureState value)
{
if (gestureManagerInstance == null) return false;
if (requireGestureToFail != null)
{
switch (value)
{
case GestureState.Recognized:
case GestureState.Began:
if (!requiredGestureFailed)
{
delayedStateChange = value;
return false;
}
break;
case GestureState.Idle:
case GestureState.Possible:
case GestureState.Failed:
case GestureState.Cancelled:
delayedStateChange = GestureState.Idle;
break;
}
}
var newState = gestureManagerInstance.INTERNAL_GestureChangeState(this, value);
State = newState;
return value == newState;
}
#endregion
#region Callbacks
/// <summary>
/// Called when new pointers appear.
/// </summary>
/// <param name="pointers"> The pointers. </param>
protected virtual void pointersPressed(IList<Pointer> pointers) {}
/// <summary>
/// Called for moved pointers.
/// </summary>
/// <param name="pointers"> The pointers. </param>
protected virtual void pointersUpdated(IList<Pointer> pointers) {}
/// <summary>
/// Called if pointers are removed.
/// </summary>
/// <param name="pointers"> The pointers. </param>
protected virtual void pointersReleased(IList<Pointer> pointers) {}
/// <summary>
/// Called when pointers are cancelled.
/// </summary>
/// <param name="pointers"> The pointers. </param>
protected virtual void pointersCancelled(IList<Pointer> pointers)
{
if (pointersNumState == PointersNumState.PassedMinThreshold)
{
// moved below the threshold
switch (state)
{
case GestureState.Began:
case GestureState.Changed:
// cancel started gestures
setState(GestureState.Cancelled);
break;
}
}
}
/// <summary>
/// Called to reset gesture state after it fails or recognizes.
/// </summary>
protected virtual void reset()
{
cachedScreenPosition = TouchManager.INVALID_POSITION;
cachedPreviousScreenPosition = TouchManager.INVALID_POSITION;
}
/// <summary>
/// Called when state is changed to Idle.
/// </summary>
protected virtual void onIdle() {}
/// <summary>
/// Called when state is changed to Possible.
/// </summary>
protected virtual void onPossible() {}
/// <summary>
/// Called when state is changed to Began.
/// </summary>
protected virtual void onBegan() {}
/// <summary>
/// Called when state is changed to Changed.
/// </summary>
protected virtual void onChanged() {}
/// <summary>
/// Called when state is changed to Recognized.
/// </summary>
protected virtual void onRecognized() {}
/// <summary>
/// Called when state is changed to Failed.
/// </summary>
protected virtual void onFailed() {}
/// <summary>
/// Called when state is changed to Cancelled.
/// </summary>
protected virtual void onCancelled()
{
if (cancelledInvoker != null) cancelledInvoker.InvokeHandleExceptions(this, EventArgs.Empty);
if (useSendMessage && SendMessageTarget != null)
sendMessageTarget.SendMessage(CANCEL_MESSAGE, this, SendMessageOptions.DontRequireReceiver);
}
#endregion
#region Private functions
private void retainPointers()
{
var total = NumPointers;
for (var i = 0; i < total; i++) activePointers[i].INTERNAL_Retain();
}
private void releasePointers(bool cancel)
{
var total = NumPointers;
for (var i = 0; i < total; i++)
{
var pointer = activePointers[i];
if (pointer.INTERNAL_Release() == 0 && cancel) touchManager.CancelPointer(pointer.Id, true);
}
}
private void registerFriendlyGesture(Gesture gesture)
{
if (gesture == null || gesture == this) return;
if (!friendlyGestures.Contains(gesture)) friendlyGestures.Add(gesture);
}
private void unregisterFriendlyGesture(Gesture gesture)
{
if (gesture == null || gesture == this) return;
friendlyGestures.Remove(gesture);
}
#endregion
#region Event handlers
private void requiredToFailGestureStateChangedHandler(object sender, GestureStateChangeEventArgs e)
{
if ((sender as Gesture) != requireGestureToFail) return;
switch (e.State)
{
case GestureState.Failed:
requiredGestureFailed = true;
if (delayedStateChange != GestureState.Idle)
{
setState(delayedStateChange);
}
break;
case GestureState.Began:
case GestureState.Recognized:
case GestureState.Cancelled:
if (state != GestureState.Failed) setState(GestureState.Failed);
break;
}
}
#endregion
}
/// <summary>
/// Event arguments for Gesture state change events.
/// </summary>
public class GestureStateChangeEventArgs : EventArgs
{
/// <summary>
/// Previous gesture state.
/// </summary>
public Gesture.GestureState PreviousState { get; private set; }
/// <summary>
/// Current gesture state.
/// </summary>
public Gesture.GestureState State { get; private set; }
private static GestureStateChangeEventArgs instance;
/// <summary>
/// Initializes a new instance of the <see cref="GestureStateChangeEventArgs"/> class.
/// </summary>
public GestureStateChangeEventArgs() {}
/// <summary>
/// Returns cached instance of EventArgs.
/// This cached EventArgs is reused throughout the library not to alocate new ones on every call.
/// </summary>
/// <param name="state"> Current gesture state. </param>
/// <param name="previousState"> Previous gesture state. </param>
/// <returns>Cached EventArgs object.</returns>
public static GestureStateChangeEventArgs GetCachedEventArgs(Gesture.GestureState state, Gesture.GestureState previousState)
{
if (instance == null) instance = new GestureStateChangeEventArgs();
instance.State = state;
instance.PreviousState = previousState;
return instance;
}
}
}