using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace AlicizaX.UI { [DisallowMultipleComponent] public sealed class ItemInteractionProxy : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler, #if UX_NAVIGATION ISelectHandler, IDeselectHandler, IMoveHandler, #endif IBeginDragHandler, IDragHandler, IEndDragHandler #if UX_NAVIGATION , ISubmitHandler, ICancelHandler #endif { private IItemInteractionHost host; private ItemInteractionFlags flags; private Selectable focusAnchor; private RecyclerItemSelectable ownedSelectable; private Scroller parentScroller; internal void Bind(IItemInteractionHost interactionHost) { host = interactionHost; flags = interactionHost?.InteractionFlags ?? ItemInteractionFlags.None; parentScroller = GetComponentInParent(); EnsureFocusAnchor(); if (ownedSelectable != null) { bool requiresSelection = RequiresSelection(flags); ownedSelectable.interactable = requiresSelection; ownedSelectable.enabled = requiresSelection; } InvalidateNavigationScope(); } public void Clear() { host = null; flags = ItemInteractionFlags.None; parentScroller = null; if (ownedSelectable != null) { ownedSelectable.interactable = false; ownedSelectable.enabled = false; } InvalidateNavigationScope(); } public Selectable GetSelectable() { EnsureFocusAnchor(); return focusAnchor; } internal bool TryGetFocusTarget(out GameObject target) { target = null; if (!TryGetFocusableSelectable(out Selectable selectable)) { return false; } target = selectable.gameObject; return target != null; } public void OnPointerClick(PointerEventData eventData) { if ((flags & ItemInteractionFlags.PointerClick) != 0) { host?.HandlePointerClick(eventData); } } public void OnPointerEnter(PointerEventData eventData) { if ((flags & ItemInteractionFlags.PointerEnter) != 0) { host?.HandlePointerEnter(eventData); } } public void OnPointerExit(PointerEventData eventData) { if ((flags & ItemInteractionFlags.PointerExit) != 0) { host?.HandlePointerExit(eventData); } } public void OnSelect(BaseEventData eventData) { #if UX_NAVIGATION if ((flags & ItemInteractionFlags.Select) != 0) { host?.HandleSelect(eventData); } #endif } public void OnDeselect(BaseEventData eventData) { #if UX_NAVIGATION if ((flags & ItemInteractionFlags.Deselect) != 0) { host?.HandleDeselect(eventData); } #endif } public void OnMove(AxisEventData eventData) { #if UX_NAVIGATION if ((flags & ItemInteractionFlags.Move) != 0) { host?.HandleMove(eventData); } #endif } public void OnBeginDrag(PointerEventData eventData) { if ((flags & ItemInteractionFlags.BeginDrag) != 0) { host?.HandleBeginDrag(eventData); return; } parentScroller?.OnBeginDrag(eventData); } public void OnDrag(PointerEventData eventData) { if ((flags & ItemInteractionFlags.Drag) != 0) { host?.HandleDrag(eventData); return; } parentScroller?.OnDrag(eventData); } public void OnEndDrag(PointerEventData eventData) { if ((flags & ItemInteractionFlags.EndDrag) != 0) { host?.HandleEndDrag(eventData); return; } parentScroller?.OnEndDrag(eventData); } public void OnSubmit(BaseEventData eventData) { #if UX_NAVIGATION if ((flags & ItemInteractionFlags.Submit) != 0) { host?.HandleSubmit(eventData); } #endif } public void OnCancel(BaseEventData eventData) { #if UX_NAVIGATION if ((flags & ItemInteractionFlags.Cancel) != 0) { host?.HandleCancel(eventData); } #endif } private void EnsureFocusAnchor() { if (focusAnchor != null) { return; } focusAnchor = GetComponent(); #if !UX_NAVIGATION if (focusAnchor is RecyclerItemSelectable) { focusAnchor = null; } #endif if (focusAnchor != null) { return; } #if UX_NAVIGATION ownedSelectable = GetComponent(); if (ownedSelectable == null) { ownedSelectable = gameObject.AddComponent(); } focusAnchor = ownedSelectable; #endif } private bool TryGetFocusableSelectable(out Selectable selectable) { EnsureFocusAnchor(); if (IsSelectableFocusable(focusAnchor)) { selectable = focusAnchor; return true; } Selectable[] selectables = GetComponentsInChildren(true); for (int i = 0; i < selectables.Length; i++) { Selectable candidate = selectables[i]; if (candidate == focusAnchor) { continue; } #if !UX_NAVIGATION if (candidate is RecyclerItemSelectable) { continue; } #endif if (IsSelectableFocusable(candidate)) { selectable = candidate; return true; } } selectable = null; return false; } private static bool IsSelectableFocusable(Selectable selectable) { return selectable != null && selectable.IsActive() && selectable.IsInteractable(); } private static bool RequiresSelection(ItemInteractionFlags interactionFlags) { #if !UX_NAVIGATION return false; #else const ItemInteractionFlags selectionFlags = ItemInteractionFlags.Select | ItemInteractionFlags.Deselect | ItemInteractionFlags.Move | ItemInteractionFlags.Submit | ItemInteractionFlags.Cancel; return (interactionFlags & selectionFlags) != 0; #endif } [System.Diagnostics.Conditional("INPUTSYSTEM_SUPPORT")] private void InvalidateNavigationScope() { #if INPUTSYSTEM_SUPPORT && UX_NAVIGATION var scope = GetComponentInParent(true); scope?.InvalidateSelectableCache(); #endif } } }