Last active
August 30, 2017 11:50
-
-
Save alexshen/8b2de438f286f82aca83524526c79ab2 to your computer and use it in GitHub Desktop.
A simple pinch event input module for Unity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define DEBUG_PINCH | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using UnityEngine; | |
using UnityEngine.EventSystems; | |
public abstract class PinchEventData : BaseEventData | |
{ | |
protected PinchEventData(EventSystem eventSystem) | |
: base(eventSystem) | |
{ } | |
public abstract Vector2 GetCurrentPinchPos(int index); | |
public virtual float currentFingerDistance | |
{ | |
get { return Vector2.Distance(GetCurrentPinchPos(0), GetCurrentPinchPos(1)); } | |
} | |
public abstract Vector2 GetLastPinchPos(int index); | |
public virtual float lastFingerDistance | |
{ | |
get { return Vector2.Distance(GetLastPinchPos(0), GetLastPinchPos(1)); } | |
} | |
} | |
public interface IPinchHandler : IEventSystemHandler | |
{ | |
void OnBeginPinch(PinchEventData eventData); | |
void OnPinch(PinchEventData eventData); | |
void OnEndPinch(PinchEventData eventData); | |
} | |
public static class CustomEventHandlers | |
{ | |
public static readonly ExecuteEvents.EventFunction<IPinchHandler> beginPinchHandler = ExecuteBeginPinch; | |
public static readonly ExecuteEvents.EventFunction<IPinchHandler> pinchHandler = ExecutePinch; | |
public static readonly ExecuteEvents.EventFunction<IPinchHandler> endPinchHandler = ExecuteEndPinch; | |
private static void ExecuteBeginPinch(IPinchHandler handler, BaseEventData eventData) | |
{ | |
handler.OnBeginPinch(ExecuteEvents.ValidateEventData<PinchEventData>(eventData)); | |
} | |
private static void ExecutePinch(IPinchHandler handler, BaseEventData eventData) | |
{ | |
handler.OnPinch(ExecuteEvents.ValidateEventData<PinchEventData>(eventData)); | |
} | |
private static void ExecuteEndPinch(IPinchHandler handler, BaseEventData eventData) | |
{ | |
handler.OnEndPinch(ExecuteEvents.ValidateEventData<PinchEventData>(eventData)); | |
} | |
} | |
public class CustomInputModule : StandaloneInputModule | |
{ | |
public float pinchThreshold; | |
public class PinchFinger | |
{ | |
public int fingerId; | |
public Vector2 position; | |
public Vector2 lastPosition; | |
} | |
private GameObject m_pinchTarget; | |
private readonly List<PinchFinger> m_pinchFingers = new List<PinchFinger>(2); | |
private float m_lastPinchDistance; | |
private bool m_pinching; | |
private PinchEventData m_pinchEventData; | |
private class PinchEventDataImpl : PinchEventData | |
{ | |
private readonly CustomInputModule m_inputModule; | |
public PinchEventDataImpl(EventSystem eventSystem, CustomInputModule module) | |
: base(eventSystem) | |
{ | |
m_inputModule = module; | |
} | |
public override Vector2 GetCurrentPinchPos(int index) | |
{ | |
return m_inputModule.m_pinchFingers[index].position; | |
} | |
public override Vector2 GetLastPinchPos(int index) | |
{ | |
return m_inputModule.m_pinchFingers[index].lastPosition; | |
} | |
} | |
private class TouchEventData | |
{ | |
public Touch touch; | |
public bool released; | |
public bool pressed; | |
public PointerEventData pointer; | |
} | |
private readonly List<TouchEventData> m_touchEventData = new List<TouchEventData>(); | |
private readonly Stack<TouchEventData> m_touchEventPool = new Stack<TouchEventData>(); | |
protected override void Awake() | |
{ | |
base.Awake(); | |
m_InputOverride = GetComponent<BaseInput>(); | |
} | |
protected override void OnEnable() | |
{ | |
base.OnEnable(); | |
m_pinchEventData = new PinchEventDataImpl(eventSystem, this); | |
} | |
public override void DeactivateModule() | |
{ | |
base.DeactivateModule(); | |
EndPinch(); | |
m_pinchFingers.Clear(); | |
m_pinchTarget = null; | |
} | |
private void EndPinch() | |
{ | |
if (m_pinching) | |
{ | |
m_pinching = false; | |
ExecuteEvents.Execute(m_pinchTarget, m_pinchEventData, CustomEventHandlers.endPinchHandler); | |
} | |
} | |
public override void Process() | |
{ | |
bool usedEvent = SendUpdateEventToSelectedObject(); | |
if (eventSystem.sendNavigationEvents) | |
{ | |
if (!usedEvent) | |
usedEvent |= SendMoveEventToSelectedObject(); | |
if (!usedEvent) | |
SendSubmitEventToSelectedObject(); | |
} | |
// touch needs to take precedence because of the mouse emulation layer | |
if (!ProcessTouchEvents()) | |
ProcessMouseEvent(); | |
} | |
private bool ProcessTouchEvents() | |
{ | |
bool pinchMoved = false; | |
for (int i = 0; i < input.touchCount; ++i) | |
{ | |
Touch touch = input.GetTouch(i); | |
if (touch.type == TouchType.Indirect) | |
continue; | |
bool released; | |
bool pressed; | |
var pointer = GetTouchPointerEventData(touch, out pressed, out released); | |
var eventData = GetTouchEventData(); | |
eventData.touch = touch; | |
eventData.released = released; | |
eventData.pressed = pressed; | |
eventData.pointer = pointer; | |
m_touchEventData.Add(eventData); | |
int pinchIndex = m_pinchFingers.FindIndex(x => x.fingerId == touch.fingerId); | |
if (released && pinchIndex != -1) | |
{ | |
#if DEBUG_PINCH | |
Debug.Log("released pinch finger" + touch.fingerId); | |
#endif | |
EndPinch(); | |
m_pinchFingers.RemoveAt(pinchIndex); | |
if (m_pinchFingers.Count == 0) | |
{ | |
m_pinchTarget = null; | |
} | |
} | |
else if (pressed && pinchIndex == -1) | |
{ | |
var handlerGo = ExecuteEvents.GetEventHandler<IPinchHandler>(pointer.pointerCurrentRaycast.gameObject); | |
if (handlerGo != null) | |
{ | |
if (m_pinchTarget == null) | |
{ | |
m_pinchTarget = handlerGo; | |
} | |
if (m_pinchTarget == handlerGo) | |
{ | |
#if DEBUG_PINCH | |
Debug.Log("new pinch finger: " + touch.fingerId); | |
#endif | |
m_pinchFingers.Add(new PinchFinger { | |
fingerId = touch.fingerId, | |
lastPosition = touch.position, | |
position = touch.position, | |
}); | |
} | |
if (m_pinchFingers.Count == 2) | |
{ | |
m_lastPinchDistance = Vector2.Distance(m_pinchFingers[0].position, m_pinchFingers[1].position); | |
} | |
} | |
} | |
else if (pinchIndex != -1) | |
{ | |
if (m_pinchFingers[pinchIndex].lastPosition != touch.position) | |
{ | |
//Debug.Log("update pinch finger position: " + input.fingerId + " " + input.position); | |
m_pinchFingers[pinchIndex].lastPosition = m_pinchFingers[pinchIndex].position; | |
m_pinchFingers[pinchIndex].position = touch.position; | |
pinchMoved = true; | |
} | |
} | |
} | |
if (m_pinchFingers.Count == 2) | |
{ | |
if (!m_pinching && pinchMoved) | |
{ | |
var moveDistance = Vector2.Distance(m_pinchFingers[0].position, m_pinchFingers[1].position) - m_lastPinchDistance; | |
if (Mathf.Abs(moveDistance) >= pinchThreshold) | |
{ | |
#if DEBUG_PINCH | |
Debug.Log("begin pinch"); | |
#endif | |
m_pinching = true; | |
ExecuteEvents.Execute(m_pinchTarget, m_pinchEventData, CustomEventHandlers.beginPinchHandler); | |
for (int i = 0; i < m_touchEventData.Count; ++i) | |
{ | |
var eventData = m_touchEventData[i]; | |
CancelTouchPress(eventData.pointer); | |
RemovePointerData(eventData.pointer); | |
} | |
} | |
} | |
if (m_pinching && pinchMoved) | |
{ | |
ExecuteEvents.Execute(m_pinchTarget, m_pinchEventData, CustomEventHandlers.pinchHandler); | |
} | |
} | |
if (!m_pinching) | |
{ | |
for (int i = 0; i < m_touchEventData.Count; ++i) | |
{ | |
var eventData = m_touchEventData[i]; | |
ProcessTouchPress(eventData.pointer, eventData.pressed, eventData.released); | |
if (!eventData.released) | |
{ | |
ProcessMove(eventData.pointer); | |
ProcessDrag(eventData.pointer); | |
} | |
else | |
RemovePointerData(eventData.pointer); | |
} | |
} | |
ReleaseAllTouchEventData(); | |
return input.touchCount > 0; | |
} | |
private TouchEventData GetTouchEventData() | |
{ | |
if (m_touchEventPool.Count == 0) | |
{ | |
return new TouchEventData(); | |
} | |
return m_touchEventPool.Pop(); | |
} | |
private void ReleaseAllTouchEventData() | |
{ | |
for (int i = 0; i < m_touchEventData.Count; ++i) | |
{ | |
m_touchEventPool.Push(m_touchEventData[i]); | |
} | |
m_touchEventData.Clear(); | |
} | |
private void CancelTouchPress(PointerEventData pointerEvent) | |
{ | |
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler); | |
pointerEvent.eligibleForClick = false; | |
pointerEvent.pointerPress = null; | |
pointerEvent.rawPointerPress = null; | |
if (pointerEvent.pointerDrag != null && pointerEvent.dragging) | |
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler); | |
pointerEvent.dragging = false; | |
pointerEvent.pointerDrag = null; | |
// send exit events as we need to simulate this on touch up on touch device | |
ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler); | |
pointerEvent.pointerEnter = null; | |
} | |
private void Reset() | |
{ | |
pinchThreshold = GetComponent<EventSystem>().pixelDragThreshold; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment