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