//========= Copyright 2016-2018, HTC Corporation. All rights reserved. =========== using HTC.UnityPlugin.Utility; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; namespace HTC.UnityPlugin.ColliderEvent { public interface IColliderEventCaster { GameObject gameObject { get; } Transform transform { get; } MonoBehaviour monoBehaviour { get; } Rigidbody rigid { get; } } [RequireComponent(typeof(Rigidbody))] public class ColliderEventCaster : MonoBehaviour, IColliderEventCaster { private static HashSet s_gos = new HashSet(); private bool isUpdating; private bool isDisabled; private IndexedSet stayingColliders = new IndexedSet(); private IndexedSet hoveredObjects = new IndexedSet(); private Rigidbody m_rigid; private ColliderHoverEventData hoverEventData; protected readonly List buttonEventDataList = new List(); protected readonly List axisEventDataList = new List(); private List hoverEnterHandlers = new List(); private List hoverExitHandlers = new List(); protected class ButtonHandlers { public List pressEnterHandlers = new List(); public List pressExitHandlers = new List(); public List pressDownHandlers = new List(); public List pressUpHandlers = new List(); public List clickHandlers = new List(); public List dragStartHandlers = new List(); public List dragFixedUpdateHandlers = new List(); public List dragUpdateHandlers = new List(); public List dragEndHandlers = new List(); public List dropHandlers = new List(); } protected class AxisHandlers { public List axisChangedHandlers = new List(); } private List buttonEventHandlerList = new List(); private List axisEventHanderList = new List(); public MonoBehaviour monoBehaviour { get { return this; } } public Rigidbody rigid { get { return m_rigid ?? (m_rigid = GetComponent()); } } public ColliderHoverEventData HoverEventData { get { return hoverEventData ?? (hoverEventData = new ColliderHoverEventData(this)); } protected set { hoverEventData = value; } } private bool CannotHandlerDragAnymore(GameObject handler) { return !ExecuteEvents.CanHandleEvent(handler); } protected virtual void OnEnable() { isDisabled = false; } protected virtual void FixedUpdate() { isUpdating = true; // fixed dragging for (int i = 0, imax = buttonEventDataList.Count; i < imax; ++i) { var eventData = buttonEventDataList[i]; var handlers = GetButtonHandlers(i); eventData.draggingHandlers.RemoveAll(CannotHandlerDragAnymore); if (!eventData.isPressed) { continue; } for (int j = eventData.draggingHandlers.Count - 1; j >= 0; --j) { handlers.dragFixedUpdateHandlers.Add(eventData.draggingHandlers[j]); } } ExecuteAllEvents(); if (isDisabled) { CleanUp(); } stayingColliders.Clear(); isUpdating = false; } protected virtual void OnTriggerStay(Collider other) { stayingColliders.AddUnique(other); } protected virtual void Update() { isUpdating = true; // process enter var hoveredObjectsPrev = hoveredObjects; hoveredObjects = IndexedSetPool.Get(); for (int i = stayingColliders.Count - 1; i >= 0; --i) { var collider = stayingColliders[i]; if (collider == null) { continue; } // travel from collider's gameObject to its root for (var tr = collider.transform; !ReferenceEquals(tr, null); tr = tr.parent) { var go = tr.gameObject; if (!hoveredObjects.AddUnique(go)) { break; } // hit traveled gameObject, break and travel from the next collider if (hoveredObjectsPrev.Remove(go)) { continue; } // gameObject already existed in last frame, no need to execute enter event hoverEnterHandlers.Add(go); } } // process leave for (int i = hoveredObjectsPrev.Count - 1; i >= 0; --i) { hoverExitHandlers.Add(hoveredObjectsPrev[i]); } IndexedSetPool.Release(hoveredObjectsPrev); // process button events for (int i = 0, imax = buttonEventDataList.Count; i < imax; ++i) { var eventData = buttonEventDataList[i]; var handlers = GetButtonHandlers(i); eventData.draggingHandlers.RemoveAll(CannotHandlerDragAnymore); // process button press if (!eventData.isPressed) { if (eventData.GetPress()) { ProcessPressDown(eventData, handlers); ProcessPressing(eventData, handlers); } } else { if (eventData.GetPress()) { ProcessPressing(eventData, handlers); } else { ProcessPressUp(eventData, handlers); } } // process pressed button enter/exit if (eventData.isPressed) { var pressEnteredObjectsPrev = eventData.pressEnteredObjects; eventData.pressEnteredObjects = IndexedSetPool.Get(); for (int j = hoveredObjects.Count - 1; j >= 0; --j) { eventData.pressEnteredObjects.Add(hoveredObjects[j]); if (pressEnteredObjectsPrev.Remove(hoveredObjects[j])) { continue; } // gameObject already existed in last frame, no need to execute enter event handlers.pressEnterHandlers.Add(hoveredObjects[j]); } for (int j = pressEnteredObjectsPrev.Count - 1; j >= 0; --j) { eventData.clickingHandlers.Remove(pressEnteredObjectsPrev[j]); // remove the obj from clicking obj if it leaved handlers.pressExitHandlers.Add(pressEnteredObjectsPrev[j]); } IndexedSetPool.Release(pressEnteredObjectsPrev); } else { for (int j = eventData.pressEnteredObjects.Count - 1; j >= 0; --j) { handlers.pressExitHandlers.Add(eventData.pressEnteredObjects[j]); } eventData.pressEnteredObjects.Clear(); } } // process axis events for (int i = 0, imax = axisEventDataList.Count; i < imax; ++i) { var eventData = axisEventDataList[i]; if ((eventData.v4 = eventData.GetDelta()) == Vector4.zero) { continue; } var handlers = GetAxisHandlers(i); GetEventHandlersFromHoveredColliders(handlers.axisChangedHandlers); } ExecuteAllEvents(); if (isDisabled) { CleanUp(); } isUpdating = false; } protected void ProcessPressDown(ColliderButtonEventData eventData, ButtonHandlers handlers) { eventData.isPressed = true; eventData.pressPosition = transform.position; eventData.pressRotation = transform.rotation; // click start GetEventHandlersFromHoveredColliders(eventData.clickingHandlers); // press down GetEventHandlersFromHoveredColliders(handlers.pressDownHandlers); // drag start GetEventHandlersFromHoveredColliders(eventData.draggingHandlers, handlers.dragStartHandlers); } protected void ProcessPressing(ColliderButtonEventData eventData, ButtonHandlers handlers) { // dragging for (int i = eventData.draggingHandlers.Count - 1; i >= 0; --i) { handlers.dragUpdateHandlers.Add(eventData.draggingHandlers[i]); } } protected void ProcessPressUp(ColliderButtonEventData eventData, ButtonHandlers handlers) { eventData.isPressed = false; // press up GetEventHandlersFromHoveredColliders(handlers.pressUpHandlers); // drag end for (int i = eventData.draggingHandlers.Count - 1; i >= 0; --i) { handlers.dragEndHandlers.Add(eventData.draggingHandlers[i]); } // drop if (eventData.draggingHandlers.Count > 0) { GetEventHandlersFromHoveredColliders(handlers.dropHandlers); } // click end (execute only if pressDown handler and pressUp handler are the same) for (int i = stayingColliders.Count - 1; i >= 0; --i) { var collider = stayingColliders[i]; if (collider == null) { continue; } var handler = ExecuteEvents.GetEventHandler(collider.gameObject); if (ReferenceEquals(handler, null)) { continue; } if (!eventData.clickingHandlers.Remove(handler)) { continue; } handlers.clickHandlers.Add(handler); } eventData.clickingHandlers.Clear(); eventData.draggingHandlers.Clear(); } protected virtual void OnDisable() { isDisabled = true; if (!isUpdating) { CleanUp(); } } private void CleanUp() { // release all for (int i = 0, imax = buttonEventDataList.Count; i < imax; ++i) { var eventData = buttonEventDataList[i]; var handlers = GetButtonHandlers(i); eventData.draggingHandlers.RemoveAll(CannotHandlerDragAnymore); if (eventData.isPressed) { ProcessPressUp(eventData, handlers); } for (int j = eventData.pressEnteredObjects.Count - 1; j >= 0; --j) { handlers.pressExitHandlers.Add(eventData.pressEnteredObjects[j]); } eventData.clickingHandlers.Clear(); eventData.draggingHandlers.Clear(); eventData.pressEnteredObjects.Clear(); } // exit all for (int i = hoveredObjects.Count - 1; i >= 0; --i) { hoverExitHandlers.Add(hoveredObjects[i]); } hoveredObjects.Clear(); stayingColliders.Clear(); ExecuteAllEvents(); } private ButtonHandlers GetButtonHandlers(int i) { while (i >= buttonEventHandlerList.Count) { buttonEventHandlerList.Add(null); } return buttonEventHandlerList[i] ?? (buttonEventHandlerList[i] = new ButtonHandlers()); } private AxisHandlers GetAxisHandlers(int i) { while (i >= axisEventHanderList.Count) { axisEventHanderList.Add(null); } return axisEventHanderList[i] ?? (axisEventHanderList[i] = new AxisHandlers()); } private void GetEventHandlersFromHoveredColliders(params IList[] appendHandlers) where T : IEventSystemHandler { for (int i = stayingColliders.Count - 1; i >= 0; --i) { var collider = stayingColliders[i]; if (collider == null) { continue; } var handler = ExecuteEvents.GetEventHandler(collider.gameObject); if (ReferenceEquals(handler, null)) { continue; } if (!s_gos.Add(handler.GetInstanceID())) { continue; } foreach (var handlers in appendHandlers) { handlers.Add(handler); } } s_gos.Clear(); } private void ExecuteAllEvents() { ExcuteHandlersEvents(hoverEnterHandlers, HoverEventData, ExecuteColliderEvents.HoverEnterHandler); for (int i = buttonEventHandlerList.Count - 1; i >= 0; --i) { if (buttonEventHandlerList[i] == null) { continue; } ExcuteHandlersEvents(buttonEventHandlerList[i].pressEnterHandlers, buttonEventDataList[i], ExecuteColliderEvents.PressEnterHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].pressDownHandlers, buttonEventDataList[i], ExecuteColliderEvents.PressDownHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].pressUpHandlers, buttonEventDataList[i], ExecuteColliderEvents.PressUpHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].dragStartHandlers, buttonEventDataList[i], ExecuteColliderEvents.DragStartHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].dragFixedUpdateHandlers, buttonEventDataList[i], ExecuteColliderEvents.DragFixedUpdateHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].dragUpdateHandlers, buttonEventDataList[i], ExecuteColliderEvents.DragUpdateHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].dragEndHandlers, buttonEventDataList[i], ExecuteColliderEvents.DragEndHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].dropHandlers, buttonEventDataList[i], ExecuteColliderEvents.DropHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].clickHandlers, buttonEventDataList[i], ExecuteColliderEvents.ClickHandler); ExcuteHandlersEvents(buttonEventHandlerList[i].pressExitHandlers, buttonEventDataList[i], ExecuteColliderEvents.PressExitHandler); } for (int i = axisEventHanderList.Count - 1; i >= 0; --i) { if (axisEventHanderList[i] == null) { continue; } ExcuteHandlersEvents(axisEventHanderList[i].axisChangedHandlers, axisEventDataList[i], ExecuteColliderEvents.AxisChangedHandler); } ExcuteHandlersEvents(hoverExitHandlers, HoverEventData, ExecuteColliderEvents.HoverExitHandler); } private void ExcuteHandlersEvents(List handlers, BaseEventData eventData, ExecuteEvents.EventFunction functor) where T : IEventSystemHandler { for (int i = handlers.Count - 1; i >= 0; --i) { ExecuteEvents.Execute(handlers[i], eventData, functor); } handlers.Clear(); } } }