using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace InteractiveVideo
{
///
/// Sits on the video RawImage. Turns pointer events (mouse, touch, XR ray via the EventSystem) into a hit test
/// against the tracking manager and updates the selection.
///
/// Needs the usual UI plumbing: an EventSystem in the scene, a GraphicRaycaster on the Canvas (or a
/// TrackedDeviceGraphicRaycaster for XR), and raycastTarget on the RawImage (forced on in Awake).
///
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
[AddComponentMenu("Interactive Video/Video Click Handler")]
public sealed class VideoClickHandler : MonoBehaviour, IPointerClickHandler, IPointerDownHandler
{
public enum SelectTrigger { Click, PointerDown }
public enum EmptyClickBehavior { KeepSelection, ClearSelection }
[SerializeField] private InteractiveVideoPlayer player;
[SerializeField] private VideoTrackingManager trackingManager;
[Header("Behaviour")]
[Tooltip("Click = press and release on the image (ignores drags). PointerDown = react immediately on press.")]
[SerializeField] private SelectTrigger trigger = SelectTrigger.Click;
[Tooltip("What a click on the background (or on a letterbox bar) does to the current selection.")]
[SerializeField] private EmptyClickBehavior emptyClick = EmptyClickBehavior.ClearSelection;
[Tooltip("Clicking the already selected object deselects it.")]
[SerializeField] private bool toggleOnReselect;
[Tooltip("Only react to the primary pointer (left mouse button / first finger).")]
[SerializeField] private bool primaryButtonOnly = true;
/// Raised on every handled click. hit is null for background/letterbox clicks; point is in TRACKING coordinates.
public event Action VideoClicked;
public EmptyClickBehavior EmptyClick { get => emptyClick; set => emptyClick = value; }
public SelectTrigger Trigger { get => trigger; set => trigger = value; }
private RectTransform _rectTransform;
private void Reset()
{
player = GetComponentInParent();
if (player == null) player = FindAnyObjectByType();
if (player != null) trackingManager = player.TrackingManager;
}
private void Awake()
{
_rectTransform = (RectTransform)transform;
var graphic = GetComponent();
if (graphic == null)
Debug.LogWarning("[InteractiveVideo] VideoClickHandler needs a Graphic (RawImage) on the same object to receive pointer events.", this);
else
graphic.raycastTarget = true;
if (player == null) Debug.LogWarning("[InteractiveVideo] VideoClickHandler has no InteractiveVideoPlayer assigned.", this);
if (trackingManager == null && player != null) trackingManager = player.TrackingManager;
}
public void OnPointerClick(PointerEventData eventData)
{
if (trigger == SelectTrigger.Click) Handle(eventData);
}
public void OnPointerDown(PointerEventData eventData)
{
if (trigger == SelectTrigger.PointerDown) Handle(eventData);
}
private void Handle(PointerEventData eventData)
{
if (primaryButtonOnly && eventData.button != PointerEventData.InputButton.Left) return;
if (player == null || trackingManager == null) return;
bool onVideo = TryEventToTracking(eventData, out Vector2 trackingPoint);
TrackedObjectInstance hit = onVideo ? trackingManager.GetObjectAtPoint(trackingPoint) : null;
if (hit != null)
{
if (toggleOnReselect && trackingManager.IsSelected(hit.TrackId)) trackingManager.Deselect(hit.TrackId);
else trackingManager.Select(hit.TrackId);
}
else if (emptyClick == EmptyClickBehavior.ClearSelection)
{
trackingManager.ClearSelection();
}
VideoClicked?.Invoke(trackingPoint, hit);
}
///
/// Pointer event to tracking coordinates. Screen-space events go through the screen point; XR ray events
/// (raycaster is not a GraphicRaycaster) use the world hit position so world-space canvases work too.
///
public bool TryEventToTracking(PointerEventData eventData, out Vector2 trackingPoint)
{
trackingPoint = default;
if (player == null) return false;
var raycast = eventData.pointerCurrentRaycast;
bool worldHit = raycast.isValid && !(raycast.module is GraphicRaycaster) && raycast.worldPosition != Vector3.zero;
if (worldHit)
{
Vector2 local = _rectTransform.InverseTransformPoint(raycast.worldPosition);
return player.TryLocalPointToTracking(local, out trackingPoint);
}
Camera cam = eventData.pressEventCamera != null ? eventData.pressEventCamera : eventData.enterEventCamera;
return player.TryScreenPointToTracking(eventData.position, cam, out trackingPoint);
}
}
}