/*
* @author Michael Holub
* @author Valentin Simonov / http://va.lent.in/
*/
using System;
using System.Collections.Generic;
using TouchScript.Pointers;
using TouchScript.Utils;
using UnityEngine;
using UnityEngine.Profiling;
namespace TouchScript.InputSources.InputHandlers
{
///
/// Unity touch handling implementation which can be embedded and controlled from other (input) classes.
///
public class TouchHandler : IInputSource, IDisposable
{
#region Public properties
///
public ICoordinatesRemapper CoordinatesRemapper { get; set; }
///
/// Gets a value indicating whether there any active pointers.
///
/// true if this instance has active pointers; otherwise, false.
public bool HasPointers
{
get { return pointersNum > 0; }
}
#endregion
#region Private variables
private PointerDelegate addPointer;
private PointerDelegate updatePointer;
private PointerDelegate pressPointer;
private PointerDelegate releasePointer;
private PointerDelegate removePointer;
private PointerDelegate cancelPointer;
private ObjectPool touchPool;
// Unity fingerId -> TouchScript touch info
private Dictionary systemToInternalId = new Dictionary(10);
private int pointersNum;
private CustomSampler updateSampler;
#endregion
///
/// Initializes a new instance of the class.
///
/// A function called when a new pointer is detected.
/// A function called when a pointer is moved or its parameter is updated.
/// A function called when a pointer touches the surface.
/// A function called when a pointer is lifted off.
/// A function called when a pointer is removed.
/// A function called when a pointer is cancelled.
public TouchHandler(PointerDelegate addPointer, PointerDelegate updatePointer, PointerDelegate pressPointer, PointerDelegate releasePointer, PointerDelegate removePointer, PointerDelegate cancelPointer)
{
this.addPointer = addPointer;
this.updatePointer = updatePointer;
this.pressPointer = pressPointer;
this.releasePointer = releasePointer;
this.removePointer = removePointer;
this.cancelPointer = cancelPointer;
touchPool = new ObjectPool(10, () => new TouchPointer(this), null, resetPointer);
touchPool.Name = "Touch";
updateSampler = CustomSampler.Create("[TouchScript] Update touch");
}
#region Public methods
///
public bool UpdateInput()
{
updateSampler.Begin();
for (var i = 0; i < Input.touchCount; ++i)
{
var t = Input.GetTouch(i);
TouchState touchState;
switch (t.phase)
{
case TouchPhase.Began:
if (systemToInternalId.TryGetValue(t.fingerId, out touchState) && touchState.Phase != TouchPhase.Canceled)
{
// Ending previous touch (missed a frame)
internalRemovePointer(touchState.Pointer);
systemToInternalId[t.fingerId] = new TouchState(internalAddPointer(t.position));
}
else
{
systemToInternalId.Add(t.fingerId, new TouchState(internalAddPointer(t.position)));
}
break;
case TouchPhase.Moved:
if (systemToInternalId.TryGetValue(t.fingerId, out touchState))
{
if (touchState.Phase != TouchPhase.Canceled)
{
touchState.Pointer.Position = t.position;
updatePointer(touchState.Pointer);
}
}
else
{
// Missed began phase
systemToInternalId.Add(t.fingerId, new TouchState(internalAddPointer(t.position)));
}
break;
// NOTE: Unity touch on Windows reports Cancelled as Ended
// when a touch goes out of display boundary
case TouchPhase.Ended:
if (systemToInternalId.TryGetValue(t.fingerId, out touchState))
{
systemToInternalId.Remove(t.fingerId);
if (touchState.Phase != TouchPhase.Canceled) internalRemovePointer(touchState.Pointer);
}
else
{
// Missed one finger begin-end transition
var pointer = internalAddPointer(t.position);
internalRemovePointer(pointer);
}
break;
case TouchPhase.Canceled:
if (systemToInternalId.TryGetValue(t.fingerId, out touchState))
{
systemToInternalId.Remove(t.fingerId);
if (touchState.Phase != TouchPhase.Canceled) internalCancelPointer(touchState.Pointer);
}
else
{
// Missed one finger begin-end transition
var pointer = internalAddPointer(t.position);
internalCancelPointer(pointer);
}
break;
case TouchPhase.Stationary:
if (systemToInternalId.TryGetValue(t.fingerId, out touchState)) {}
else
{
// Missed begin phase
systemToInternalId.Add(t.fingerId, new TouchState(internalAddPointer(t.position)));
}
break;
}
}
updateSampler.End();
return Input.touchCount > 0;
}
///
public void UpdateResolution() {}
///
public bool CancelPointer(Pointer pointer, bool shouldReturn)
{
var touch = pointer as TouchPointer;
if (touch == null) return false;
int fingerId = -1;
foreach (var touchState in systemToInternalId)
{
if (touchState.Value.Pointer == touch && touchState.Value.Phase != TouchPhase.Canceled)
{
fingerId = touchState.Key;
break;
}
}
if (fingerId > -1)
{
internalCancelPointer(touch);
if (shouldReturn) systemToInternalId[fingerId] = new TouchState(internalReturnPointer(touch));
else systemToInternalId[fingerId] = new TouchState(touch, TouchPhase.Canceled);
return true;
}
return false;
}
///
/// Releases resources.
///
public void Dispose()
{
foreach (var touchState in systemToInternalId)
{
if (touchState.Value.Phase != TouchPhase.Canceled) internalCancelPointer(touchState.Value.Pointer);
}
systemToInternalId.Clear();
}
#endregion
#region Internal methods
///
public void INTERNAL_DiscardPointer(Pointer pointer)
{
var p = pointer as TouchPointer;
if (p == null) return;
touchPool.Release(p);
}
#endregion
#region Private functions
private Pointer internalAddPointer(Vector2 position)
{
pointersNum++;
var pointer = touchPool.Get();
pointer.Position = remapCoordinates(position);
pointer.Buttons |= Pointer.PointerButtonState.FirstButtonDown | Pointer.PointerButtonState.FirstButtonPressed;
addPointer(pointer);
pressPointer(pointer);
return pointer;
}
private TouchPointer internalReturnPointer(TouchPointer pointer)
{
pointersNum++;
var newPointer = touchPool.Get();
newPointer.CopyFrom(pointer);
pointer.Buttons |= Pointer.PointerButtonState.FirstButtonDown | Pointer.PointerButtonState.FirstButtonPressed;
newPointer.Flags |= Pointer.FLAG_RETURNED;
addPointer(newPointer);
pressPointer(newPointer);
return newPointer;
}
private void internalRemovePointer(Pointer pointer)
{
pointersNum--;
pointer.Buttons &= ~Pointer.PointerButtonState.FirstButtonPressed;
pointer.Buttons |= Pointer.PointerButtonState.FirstButtonUp;
releasePointer(pointer);
removePointer(pointer);
}
private void internalCancelPointer(Pointer pointer)
{
pointersNum--;
cancelPointer(pointer);
}
private Vector2 remapCoordinates(Vector2 position)
{
if (CoordinatesRemapper != null) return CoordinatesRemapper.Remap(position);
return position;
}
private void resetPointer(Pointer p)
{
p.INTERNAL_Reset();
}
#endregion
private struct TouchState
{
public Pointer Pointer;
public TouchPhase Phase;
public TouchState(Pointer pointer, TouchPhase phase = TouchPhase.Began)
{
Pointer = pointer;
Phase = phase;
}
}
}
}