using System;
using System.Collections.Generic;
using System.Linq;
using Core.Utilities;
using UnityEngine;
using UnityEngine.EventSystems;
using Debug = System.Diagnostics.Debug;
using UnityInput = UnityEngine.Input;
namespace Core.Input
{
///
/// Class to manage tap/drag/pinch gestures and other controls
///
public class InputController : Singleton
{
///
/// How quickly flick velocity is accumulated with movements
///
const float k_FlickAccumulationFactor = 0.8f;
///
/// How far fingers must move before starting a drag
///
public float dragThresholdTouch = 5;
///
/// How far mouse must move before starting a drag
///
public float dragThresholdMouse;
///
/// How long before a touch can no longer be considered a tap
///
public float tapTime = 0.2f;
///
/// How long before a touch is considered a hold
///
public float holdTime = 0.8f;
///
/// Sensitivity of mouse-wheel based zoom
///
public float mouseWheelSensitivity = 1.0f;
///
/// How many mouse buttons to track
///
public int trackMouseButtons = 2;
///
/// Flick movement threshold
///
public float flickThreshold = 2f;
///
/// All the touches we're tracking
///
List m_Touches;
///
/// Mouse button info
///
List m_MouseInfo;
///
/// Gets the number of active touches
///
public int activeTouchCount
{
get { return m_Touches.Count; }
}
///
/// Tracks if any of the mouse buttons were pressed this frame
///
public bool mouseButtonPressedThisFrame { get; private set; }
///
/// Tracks if the mouse moved this frame
///
public bool mouseMovedOnThisFrame { get; private set; }
///
/// Tracks if a touch began this frame
///
public bool touchPressedThisFrame { get; private set; }
///
/// Current mouse pointer info
///
public PointerInfo basicMouseInfo { get; private set; }
///
/// Event called when a pointer press is detected
///
public event Action pressed;
///
/// Event called when a pointer is released
///
public event Action released;
///
/// Event called when a pointer is tapped
///
public event Action tapped;
///
/// Event called when a drag starts
///
public event Action startedDrag;
///
/// Event called when a pointer is dragged
///
public event Action dragged;
///
/// Event called when a pointer starts a hold
///
public event Action startedHold;
///
/// Event called when the user scrolls the mouse wheel
///
public event Action spunWheel;
///
/// Event called when the user performs a pinch gesture
///
public event Action pinched;
///
/// Event called whenever the mouse is moved
///
public event Action mouseMoved;
protected override void Awake()
{
base.Awake();
m_Touches = new List();
// Mouse specific initialization
if (UnityInput.mousePresent)
{
m_MouseInfo = new List();
basicMouseInfo = new MouseCursorInfo { currentPosition = UnityInput.mousePosition };
for (int i = 0; i < trackMouseButtons; ++i)
{
m_MouseInfo.Add(new MouseButtonInfo
{
currentPosition = UnityInput.mousePosition,
mouseButtonId = i
});
}
}
UnityInput.simulateMouseWithTouches = false;
}
///
/// Update all input
///
void Update()
{
if (basicMouseInfo != null)
{
// Mouse was detected as present
UpdateMouse();
}
// Handle touches
UpdateTouches();
}
///
/// Perform logic to update mouse/pointing device
///
void UpdateMouse()
{
basicMouseInfo.previousPosition = basicMouseInfo.currentPosition;
basicMouseInfo.currentPosition = UnityInput.mousePosition;
basicMouseInfo.delta = basicMouseInfo.currentPosition - basicMouseInfo.previousPosition;
mouseMovedOnThisFrame = basicMouseInfo.delta.sqrMagnitude >= Mathf.Epsilon;
mouseButtonPressedThisFrame = false;
// Move event
if (basicMouseInfo.delta.sqrMagnitude > Mathf.Epsilon)
{
if (mouseMoved != null)
{
mouseMoved(basicMouseInfo);
}
}
// Button events
for (int i = 0; i < trackMouseButtons; ++i)
{
MouseButtonInfo mouseButton = m_MouseInfo[i];
mouseButton.delta = basicMouseInfo.delta;
mouseButton.previousPosition = basicMouseInfo.previousPosition;
mouseButton.currentPosition = basicMouseInfo.currentPosition;
if (UnityInput.GetMouseButton(i))
{
if (!mouseButton.isDown)
{
// First press
mouseButtonPressedThisFrame = true;
mouseButton.isDown = true;
mouseButton.startPosition = UnityInput.mousePosition;
mouseButton.startTime = Time.realtimeSinceStartup;
mouseButton.startedOverUI = EventSystem.current.IsPointerOverGameObject(-mouseButton.mouseButtonId - 1);
// Reset some stuff
mouseButton.totalMovement = 0;
mouseButton.isDrag = false;
mouseButton.wasHold = false;
mouseButton.isHold = false;
mouseButton.flickVelocity = Vector2.zero;
if (pressed != null)
{
pressed(mouseButton);
}
}
else
{
float moveDist = mouseButton.delta.magnitude;
// Dragging?
mouseButton.totalMovement += moveDist;
if (mouseButton.totalMovement > dragThresholdMouse)
{
bool wasDrag = mouseButton.isDrag;
mouseButton.isDrag = true;
if (mouseButton.isHold)
{
mouseButton.wasHold = mouseButton.isHold;
mouseButton.isHold = false;
}
// Did it just start now?
if (!wasDrag)
{
if (startedDrag != null)
{
startedDrag(mouseButton);
}
}
if (dragged != null)
{
dragged(mouseButton);
}
// Flick?
if (moveDist > flickThreshold)
{
mouseButton.flickVelocity =
(mouseButton.flickVelocity * (1 - k_FlickAccumulationFactor)) +
(mouseButton.delta * k_FlickAccumulationFactor);
}
else
{
mouseButton.flickVelocity = Vector2.zero;
}
}
else
{
// Stationary?
if (!mouseButton.isHold &&
!mouseButton.isDrag &&
Time.realtimeSinceStartup - mouseButton.startTime >= holdTime)
{
mouseButton.isHold = true;
if (startedHold != null)
{
startedHold(mouseButton);
}
}
}
}
}
else // Mouse button not up
{
if (mouseButton.isDown) // Released
{
mouseButton.isDown = false;
// Quick enough (with no drift) to be a tap?
if (!mouseButton.isDrag &&
Time.realtimeSinceStartup - mouseButton.startTime < tapTime)
{
if (tapped != null)
{
tapped(mouseButton);
}
}
if (released != null)
{
released(mouseButton);
}
}
}
}
// Mouse wheel
if (Mathf.Abs(UnityInput.GetAxis("Mouse ScrollWheel")) > Mathf.Epsilon)
{
if (spunWheel != null)
{
spunWheel(new WheelInfo
{
zoomAmount = UnityInput.GetAxis("Mouse ScrollWheel") * mouseWheelSensitivity
});
}
}
}
///
/// Update all touches
///
void UpdateTouches()
{
touchPressedThisFrame = false;
//for (int i = 0; i < UnityInput.touchCount; ++i)
if (UnityInput.touchCount > 0)
{
Touch touch = UnityInput.GetTouch(0);
// Find existing touch, or create new one
TouchInfo existingTouch = m_Touches.FirstOrDefault(t => t.touchId == touch.fingerId);
if (existingTouch == null)
{
existingTouch = new TouchInfo
{
touchId = touch.fingerId,
startPosition = touch.position,
currentPosition = touch.position,
previousPosition = touch.position,
startTime = Time.realtimeSinceStartup,
startedOverUI = EventSystem.current.IsPointerOverGameObject(touch.fingerId)
};
m_Touches.Add(existingTouch);
// Sanity check
Debug.Assert(touch.phase == TouchPhase.Began);
}
switch (touch.phase)
{
case TouchPhase.Began:
touchPressedThisFrame = true;
if (pressed != null)
{
pressed(existingTouch);
}
break;
case TouchPhase.Moved:
bool wasDrag = existingTouch.isDrag;
UpdateMovingFinger(touch, existingTouch);
// Is this a drag?
existingTouch.isDrag = existingTouch.totalMovement >= dragThresholdTouch;
if (existingTouch.isDrag)
{
if (existingTouch.isHold)
{
existingTouch.wasHold = existingTouch.isHold;
existingTouch.isHold = false;
}
// Did it just start now?
if (!wasDrag)
{
if (startedDrag != null)
{
startedDrag(existingTouch);
}
}
if (dragged != null)
{
dragged(existingTouch);
}
if (existingTouch.delta.sqrMagnitude > flickThreshold * flickThreshold)
{
existingTouch.flickVelocity =
(existingTouch.flickVelocity * (1 - k_FlickAccumulationFactor)) +
(existingTouch.delta * k_FlickAccumulationFactor);
}
else
{
existingTouch.flickVelocity = Vector2.zero;
}
}
else
{
UpdateHoldingFinger(existingTouch);
}
break;
case TouchPhase.Canceled:
case TouchPhase.Ended:
// Could have moved a bit
UpdateMovingFinger(touch, existingTouch);
// Quick enough (with no drift) to be a tap?
if (!existingTouch.isDrag &&
Time.realtimeSinceStartup - existingTouch.startTime < tapTime)
{
if (tapped != null)
{
tapped(existingTouch);
}
}
if (released != null)
{
released(existingTouch);
}
// Remove from track list
m_Touches.Remove(existingTouch);
break;
case TouchPhase.Stationary:
UpdateMovingFinger(touch, existingTouch);
UpdateHoldingFinger(existingTouch);
existingTouch.flickVelocity = Vector2.zero;
break;
}
}
if (activeTouchCount >= 2 && (m_Touches[0].isDrag ||
m_Touches[1].isDrag))
{
if (pinched != null)
{
pinched(new PinchInfo
{
touch1 = m_Touches[0],
touch2 = m_Touches[1]
});
}
}
}
///
/// Update a TouchInfo that might be holding
///
///
void UpdateHoldingFinger(PointerActionInfo existingTouch)
{
if (!existingTouch.isHold &&
!existingTouch.isDrag &&
Time.realtimeSinceStartup - existingTouch.startTime >= holdTime)
{
existingTouch.isHold = true;
if (startedHold != null)
{
startedHold(existingTouch);
}
}
}
///
/// Update a TouchInfo with movement
///
/// The Unity touch object
/// The object that's tracking Unity's touch
void UpdateMovingFinger(Touch touch, PointerActionInfo existingTouch)
{
float dragDist = touch.deltaPosition.magnitude;
existingTouch.previousPosition = existingTouch.currentPosition;
existingTouch.currentPosition = touch.position;
existingTouch.delta = touch.deltaPosition;
existingTouch.totalMovement += dragDist;
}
}
}