/*
* @author Valentin Simonov / http://va.lent.in/
*/
using System;
using TouchScript.Pointers;
using TouchScript.Utils;
using UnityEngine;
namespace TouchScript.InputSources.InputHandlers
{
///
/// Unity mouse handling implementation which can be embedded and controlled from other (input) classes.
///
public class MouseHandler : IInputSource, IDisposable
{
#region Consts
private enum State
{
///
/// Only mouse pointer is active
///
Mouse,
///
/// ALT is pressed but mouse isn't
///
WaitingForFake,
///
/// Mouse and fake pointers are moving together after ALT+PRESS
///
MouseAndFake,
///
/// After ALT+RELEASE fake pointer is stationary while mouse can move freely
///
StationaryFake
}
#endregion
#region Public properties
///
public ICoordinatesRemapper CoordinatesRemapper { get; set; }
///
/// Gets or sets a value indicating whether second pointer emulation using ALT+CLICK should be enabled.
///
///
/// true if second pointer emulation is enabled; otherwise, false.
///
public bool EmulateSecondMousePointer
{
get { return emulateSecondMousePointer; }
set
{
emulateSecondMousePointer = value;
if (fakeMousePointer != null) CancelPointer(fakeMousePointer, false);
}
}
#endregion
#region Private variables
private bool emulateSecondMousePointer = true;
private PointerDelegate addPointer;
private PointerDelegate updatePointer;
private PointerDelegate pressPointer;
private PointerDelegate releasePointer;
private PointerDelegate removePointer;
private PointerDelegate cancelPointer;
private State state;
private ObjectPool mousePool;
private MousePointer mousePointer, fakeMousePointer;
private Vector3 mousePointPos = Vector3.zero;
#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 MouseHandler(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;
mousePool = new ObjectPool(4, () => new MousePointer(this), null, resetPointer);
mousePointPos = Input.mousePosition;
mousePointer = internalAddPointer(remapCoordinates(mousePointPos));
stateMouse();
}
#region Public methods
///
/// Cancels the mouse pointer.
///
public void CancelMousePointer()
{
if (mousePointer != null)
{
cancelPointer(mousePointer);
mousePointer = null;
}
}
///
public bool UpdateInput()
{
var pos = Input.mousePosition;
Vector2 remappedPos = new Vector2(0, 0);
bool updated = false;
if (mousePointPos != pos)
{
remappedPos = remapCoordinates(new Vector2(pos.x, pos.y));
if (mousePointer == null)
{
mousePointer = internalAddPointer(remappedPos);
}
else
{
mousePointer.Position = remappedPos;
updatePointer(mousePointer);
}
updated = true;
}
if (mousePointer == null) return false;
var buttons = state == State.MouseAndFake ? fakeMousePointer.Buttons : mousePointer.Buttons;
var newButtons = getMouseButtons();
var scroll = Input.mouseScrollDelta;
if (!Mathf.Approximately(scroll.sqrMagnitude, 0.0f))
{
mousePointer.ScrollDelta = scroll;
updatePointer(mousePointer);
}
else
{
mousePointer.ScrollDelta = Vector2.zero;
}
if (emulateSecondMousePointer)
{
switch (state)
{
case State.Mouse:
if (Input.GetKeyDown(KeyCode.LeftAlt) && !Input.GetKeyUp(KeyCode.LeftAlt)
&& ((newButtons & Pointer.PointerButtonState.AnyButtonPressed) == 0))
{
stateWaitingForFake();
}
else
{
if (buttons != newButtons) updateButtons(buttons, newButtons);
}
break;
case State.WaitingForFake:
if (Input.GetKey(KeyCode.LeftAlt))
{
if ((newButtons & Pointer.PointerButtonState.AnyButtonDown) != 0)
{
// A button is down while holding Alt
fakeMousePointer = internalAddPointer(pos, newButtons, mousePointer.Flags | Pointer.FLAG_ARTIFICIAL);
pressPointer(fakeMousePointer);
stateMouseAndFake();
}
}
else
{
stateMouse();
}
break;
case State.MouseAndFake:
if (fakeTouchReleased())
{
stateMouse();
}
else
{
if (mousePointPos != pos)
{
fakeMousePointer.Position = remappedPos;
updatePointer(fakeMousePointer);
}
if ((newButtons & Pointer.PointerButtonState.AnyButtonPressed) == 0)
{
// All buttons are released, Alt is still holding
stateStationaryFake();
}
else if (buttons != newButtons)
{
fakeMousePointer.Buttons = newButtons;
updatePointer(fakeMousePointer);
}
}
break;
case State.StationaryFake:
if (buttons != newButtons) updateButtons(buttons, newButtons);
if (fakeTouchReleased())
{
stateMouse();
}
break;
}
}
else
{
if (buttons != newButtons)
{
updateButtons(buttons, newButtons);
updated = true;
}
}
mousePointPos = pos;
return updated;
}
///
public void UpdateResolution()
{
TouchManager.Instance.CancelPointer(mousePointer.Id);
}
///
public bool CancelPointer(Pointer pointer, bool shouldReturn)
{
if (pointer.Equals(mousePointer))
{
cancelPointer(mousePointer);
if (shouldReturn) mousePointer = internalReturnPointer(mousePointer);
else mousePointer = internalAddPointer(mousePointer.Position); // can't totally cancel mouse pointer
return true;
}
if (pointer.Equals(fakeMousePointer))
{
cancelPointer(fakeMousePointer);
if (shouldReturn) fakeMousePointer = internalReturnPointer(fakeMousePointer);
else fakeMousePointer = null;
return true;
}
return false;
}
///
/// Releases resources.
///
public void Dispose()
{
if (mousePointer != null)
{
cancelPointer(mousePointer);
mousePointer = null;
}
if (fakeMousePointer != null)
{
cancelPointer(fakeMousePointer);
fakeMousePointer = null;
}
}
#endregion
#region Internal methods
///
public void INTERNAL_DiscardPointer(Pointer pointer)
{
var p = pointer as MousePointer;
if (p == null) return;
mousePool.Release(p);
}
#endregion
#region Private functions
private Pointer.PointerButtonState getMouseButtons()
{
Pointer.PointerButtonState buttons = Pointer.PointerButtonState.Nothing;
if (Input.GetMouseButton(0)) buttons |= Pointer.PointerButtonState.FirstButtonPressed;
if (Input.GetMouseButtonDown(0)) buttons |= Pointer.PointerButtonState.FirstButtonDown;
if (Input.GetMouseButtonUp(0)) buttons |= Pointer.PointerButtonState.FirstButtonUp;
if (Input.GetMouseButton(1)) buttons |= Pointer.PointerButtonState.SecondButtonPressed;
if (Input.GetMouseButtonDown(1)) buttons |= Pointer.PointerButtonState.SecondButtonDown;
if (Input.GetMouseButtonUp(1)) buttons |= Pointer.PointerButtonState.SecondButtonUp;
if (Input.GetMouseButton(2)) buttons |= Pointer.PointerButtonState.ThirdButtonPressed;
if (Input.GetMouseButtonDown(2)) buttons |= Pointer.PointerButtonState.ThirdButtonDown;
if (Input.GetMouseButtonUp(2)) buttons |= Pointer.PointerButtonState.ThirdButtonUp;
return buttons;
}
private void updateButtons(Pointer.PointerButtonState oldButtons, Pointer.PointerButtonState newButtons)
{
// pressed something
if (oldButtons == Pointer.PointerButtonState.Nothing)
{
// pressed and released this frame
if ((newButtons & Pointer.PointerButtonState.AnyButtonPressed) == 0)
{
// Add pressed buttons for processing
mousePointer.Buttons = PointerUtils.PressDownButtons(newButtons);
pressPointer(mousePointer);
internalReleaseMousePointer(newButtons);
}
// pressed this frame
else
{
mousePointer.Buttons = newButtons;
pressPointer(mousePointer);
}
}
// released or button state changed
else
{
// released this frame
if ((newButtons & Pointer.PointerButtonState.AnyButtonPressed) == 0)
{
mousePointer.Buttons = newButtons;
internalReleaseMousePointer(newButtons);
}
// button state changed this frame
else
{
mousePointer.Buttons = newButtons;
updatePointer(mousePointer);
}
}
}
private bool fakeTouchReleased()
{
if (!Input.GetKey(KeyCode.LeftAlt))
{
// Alt is released, need to kill the fake touch
fakeMousePointer.Buttons = PointerUtils.UpPressedButtons(fakeMousePointer.Buttons); // Convert current pressed buttons to UP
releasePointer(fakeMousePointer);
removePointer(fakeMousePointer);
fakeMousePointer = null; // Will be returned to the pool by INTERNAL_DiscardPointer
return true;
}
return false;
}
private MousePointer internalAddPointer(Vector2 position, Pointer.PointerButtonState buttons = Pointer.PointerButtonState.Nothing, uint flags = 0)
{
var pointer = mousePool.Get();
pointer.Position = position;
pointer.Buttons |= buttons;
pointer.Flags |= flags;
addPointer(pointer);
updatePointer(pointer);
return pointer;
}
private void internalReleaseMousePointer(Pointer.PointerButtonState buttons)
{
mousePointer.Flags &= ~Pointer.FLAG_RETURNED;
releasePointer(mousePointer);
}
private MousePointer internalReturnPointer(MousePointer pointer)
{
var newPointer = mousePool.Get();
newPointer.CopyFrom(pointer);
newPointer.Flags |= Pointer.FLAG_RETURNED;
addPointer(newPointer);
if ((newPointer.Buttons & Pointer.PointerButtonState.AnyButtonPressed) != 0)
{
// Adding down state this frame
newPointer.Buttons = PointerUtils.DownPressedButtons(newPointer.Buttons);
pressPointer(newPointer);
}
return newPointer;
}
private Vector2 remapCoordinates(Vector2 position)
{
if (CoordinatesRemapper != null) return CoordinatesRemapper.Remap(position);
return position;
}
private void resetPointer(Pointer p)
{
p.INTERNAL_Reset();
}
#endregion
#region State logic
private void stateMouse()
{
setState(State.Mouse);
}
private void stateWaitingForFake()
{
setState(State.WaitingForFake);
}
private void stateMouseAndFake()
{
setState(State.MouseAndFake);
}
private void stateStationaryFake()
{
setState(State.StationaryFake);
}
private void setState(State newState)
{
state = newState;
}
#endregion
}
}