using System; using System.Collections.Generic; using UnityEngine.EventSystems; using UnityEngine.XR.Interaction.Toolkit.Interactors; namespace UnityEngine.XR.Interaction.Toolkit.UI { /// /// Defines the type of interaction expected from the interactor when interacting with UI. /// /// public enum UIInteractionType { /// /// The UI interaction is a ray interaction. /// Ray, /// /// The UI interaction is a poke interaction. /// Poke, } /// /// Models a tracked device. /// public partial struct TrackedDeviceModel { internal struct ImplementationData { /// /// This tracks the current GUI targets being hovered over. /// /// public List hoverTargets { get; set; } /// /// Tracks the current enter/exit target being hovered over at any given moment. /// /// public GameObject pointerTarget { get; set; } /// /// Whether or not the current mouse button is being dragged. /// /// public bool isDragging { get; set; } /// /// The last time this button was pressed. /// /// public float pressedTime { get; set; } /// /// The position on the screen. /// /// public Vector2 position { get; set; } /// /// The position on the screen that this button was last pressed. /// In the same scale as , and caches the same value as . /// /// public Vector2 pressedPosition { get; set; } /// /// The position in world space that this button was last pressed. /// This is used to recalculate the relative screen space during head movement. /// /// /// public Vector3 pressedWorldPosition { get; set; } /// /// The Raycast data from the time it was pressed. /// /// public RaycastResult pressedRaycast { get; set; } /// /// The last GameObject pressed on that can handle press or click events. /// /// public GameObject pressedGameObject { get; set; } /// /// The last GameObject pressed on regardless of whether it can handle events or not. /// /// public GameObject pressedGameObjectRaw { get; set; } /// /// The GameObject currently being dragged if any. /// /// public GameObject draggedGameObject { get; set; } /// /// Resets this object to its default, unused state. /// public void Reset() { isDragging = false; pressedTime = 0f; position = Vector2.zero; pressedPosition = Vector2.zero; pressedWorldPosition = Vector3.zero; pressedRaycast = new RaycastResult(); pressedGameObject = null; pressedGameObjectRaw = null; draggedGameObject = null; pointerTarget = null; if (hoverTargets == null) hoverTargets = new List(); else hoverTargets.Clear(); } } ImplementationData m_ImplementationData; internal ImplementationData implementationData => m_ImplementationData; /// /// (Read Only) A unique Id to identify this model from others within the UI system. /// public int pointerId { get; } bool m_SelectDown; /// /// Whether or not the model should be selecting UI at this moment. This is the equivalent of left mouse down for a mouse. /// public bool select { get => m_SelectDown; set { if (m_SelectDown != value) { m_SelectDown = value; selectDelta |= value ? ButtonDeltaState.Pressed : ButtonDeltaState.Released; changedThisFrame = true; } } } /// /// Whether the state of the select option has changed this frame. /// public ButtonDeltaState selectDelta { get; private set; } /// /// Checks whether this model has meaningfully changed this frame. /// This is used by the UI system to avoid excessive work. Use to reset. /// public bool changedThisFrame { get; private set; } Vector3 m_Position; /// /// The world starting position of the cast for the tracked device. /// /// public Vector3 position { get => m_PositionProvider?.Invoke() ?? m_Position; set { if (m_Position != value) { m_Position = value; changedThisFrame = true; } } } Func m_PositionProvider; /// /// Overrides the position point used for the world starting position of the cast with a function that returns the position instead. /// This position function allows for accurate just in time position querying. /// Necessary for poke interaction with UGUI if the frame of reference is moving rapidly. /// /// public Func positionProvider { get => m_PositionProvider; set { if (m_PositionProvider != value) { m_PositionProvider = value; changedThisFrame = true; } } } Quaternion m_Orientation; /// /// The world starting orientation of the cast for the tracked device. /// public Quaternion orientation { get => m_Orientation; set { if (m_Orientation != value) { m_Orientation = value; changedThisFrame = true; } } } List m_RaycastPoints; /// /// A series of Ray segments used to hit UI. /// /// /// A polygonal chain represented by a list of endpoints which form line segments /// to approximate the curve. Each line segment is where the ray cast starts and ends. /// World space coordinates. /// public List raycastPoints { get => m_RaycastPoints; set { changedThisFrame |= m_RaycastPoints.Count != value.Count; m_RaycastPoints = value; } } /// /// The last ray cast done for this model. /// /// public RaycastResult currentRaycast { get; private set; } /// /// The endpoint index within the list of ray cast points that the refers to when a hit occurred. /// Otherwise, a value of 0 if no hit occurred. /// /// /// /// public int currentRaycastEndpointIndex { get; private set; } LayerMask m_RaycastLayerMask; /// /// Layer mask for ray casts. /// public LayerMask raycastLayerMask { get => m_RaycastLayerMask; set { if (m_RaycastLayerMask != value) { changedThisFrame = true; m_RaycastLayerMask = value; } } } Vector2 m_ScrollDelta; /// /// The amount of scroll since the last update. /// /// public Vector2 scrollDelta { get => m_ScrollDelta; set { if (m_ScrollDelta != value) { m_ScrollDelta = value; changedThisFrame = true; } } } float m_PokeDepth; /// /// Distance along the poke interactable interaction axis that allows for a poke to be triggered. /// Only applies with . /// /// public float pokeDepth { get => m_PokeDepth; set { if (m_PokeDepth != value) { m_PokeDepth = value; changedThisFrame = true; } } } UIInteractionType m_InteractionType; /// /// The interaction type expected by this . /// It affects the handling of raycasts in the . /// /// public UIInteractionType interactionType { get => m_InteractionType; set { if (m_InteractionType != value) { m_InteractionType = value; changedThisFrame = true; } } } /// /// Interactor that is tracking and updating the state of this model. /// internal IUIInteractor interactor { get; set; } /// /// Updates the internal select state of the model. This is required for poke interactions /// due to the frame timing when performing sphere casts against the . /// Only applies with . /// /// internal void UpdatePokeSelectState() { if (m_InteractionType == UIInteractionType.Poke) select = TrackedDeviceGraphicRaycaster.IsPokeSelectingWithUI(interactor); } /// /// Tracks the current selectable UI element being hovered /// public GameObject selectableObject { get; set; } /// /// Tracks whether the current hovered UI elements support scrolling. /// public bool isScrollable { get; set; } /// /// Returns a struct representing an invalid pointer id. /// This can be used when returning an instance from a method to represent invalid data. /// /// public static TrackedDeviceModel invalid { get; } = new TrackedDeviceModel(-1); /// /// Initializes and returns an instance of . /// /// The pointer id. public TrackedDeviceModel(int pointerId) : this() { this.pointerId = pointerId; m_RaycastPoints = new List(); m_ImplementationData = new ImplementationData(); Reset(); } /// /// Resets this object back to defaults. /// /// If , will reset only the external state of the object, and not internal, UI-used variables. Defaults to . public void Reset(bool resetImplementation = true) { m_Orientation = Quaternion.identity; m_Position = Vector3.zero; m_PositionProvider = null; changedThisFrame = false; m_SelectDown = false; selectDelta = ButtonDeltaState.NoChange; m_RaycastPoints?.Clear(); currentRaycastEndpointIndex = 0; m_RaycastLayerMask = Physics.DefaultRaycastLayers; m_ScrollDelta = Vector2.zero; if (resetImplementation) m_ImplementationData.Reset(); } /// /// To be called at the end of each frame to reset any tracking of changes within the frame. /// /// /// public void OnFrameFinished() { selectDelta = ButtonDeltaState.NoChange; m_ScrollDelta = Vector2.zero; changedThisFrame = false; } /// /// Copies data from this model to the UI Event Data. /// /// The event that copies the data. /// public void CopyTo(TrackedDeviceEventData eventData) { eventData.rayPoints = m_RaycastPoints; eventData.layerMask = m_RaycastLayerMask; eventData.pointerId = pointerId; eventData.scrollDelta = m_ScrollDelta; eventData.pointerEnter = m_ImplementationData.pointerTarget; eventData.dragging = m_ImplementationData.isDragging; eventData.clickTime = m_ImplementationData.pressedTime; eventData.position = m_ImplementationData.position; eventData.pressPosition = m_ImplementationData.pressedPosition; eventData.pressWorldPosition = m_ImplementationData.pressedWorldPosition; eventData.pointerPressRaycast = m_ImplementationData.pressedRaycast; eventData.pointerPress = m_ImplementationData.pressedGameObject; eventData.rawPointerPress = m_ImplementationData.pressedGameObjectRaw; eventData.pointerDrag = m_ImplementationData.draggedGameObject; eventData.hovered.Clear(); eventData.hovered.AddRange(m_ImplementationData.hoverTargets); } /// /// Copies data from the UI Event Data to this model. /// /// The data to copy from. /// public void CopyFrom(TrackedDeviceEventData eventData) { m_ImplementationData.pointerTarget = eventData.pointerEnter; m_ImplementationData.isDragging = eventData.dragging; m_ImplementationData.pressedTime = eventData.clickTime; m_ImplementationData.position = eventData.position; m_ImplementationData.pressedPosition = eventData.pressPosition; m_ImplementationData.pressedWorldPosition = eventData.pressWorldPosition; m_ImplementationData.pressedRaycast = eventData.pointerPressRaycast; m_ImplementationData.pressedGameObject = eventData.pointerPress; m_ImplementationData.pressedGameObjectRaw = eventData.rawPointerPress; m_ImplementationData.draggedGameObject = eventData.pointerDrag; m_ImplementationData.hoverTargets.Clear(); m_ImplementationData.hoverTargets.AddRange(eventData.hovered); currentRaycast = eventData.pointerCurrentRaycast; currentRaycastEndpointIndex = eventData.rayHitIndex; } } }