755 lines
24 KiB
C#
755 lines
24 KiB
C#
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
|
using AlicizaX.UI.Runtime;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace UnityEngine.UI
|
|
{
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("UI/UX Navigation Scope")]
|
|
public sealed class UXNavigationScope : MonoBehaviour, ISelectHandler
|
|
{
|
|
private const int InvalidIndex = -1;
|
|
private const int DefaultRuntimeSelectableCapacity = 16;
|
|
|
|
[SerializeField, Header("默认选中控件")] private Selectable _defaultSelectable;
|
|
[SerializeField, Header("编辑器烘焙导航控件")] private Selectable[] _bakedSelectables = System.Array.Empty<Selectable>();
|
|
[SerializeField, Header("动态控件容量")] private int _runtimeSelectableCapacity = DefaultRuntimeSelectableCapacity;
|
|
[SerializeField, Header("记住上次选中")] private bool _rememberLastSelection = true;
|
|
[SerializeField, Header("手柄/键盘模式必须有选中")] private bool _requireSelectionWhenGamepad = true;
|
|
[SerializeField, Header("阻断下层导航域")] private bool _blockLowerScopes = true;
|
|
[SerializeField, Header("自动选中首个可用控件")] private bool _autoSelectFirstAvailable = true;
|
|
|
|
private Selectable[] _runtimeSelectables;
|
|
private Navigation[] _bakedBaselineNavigation;
|
|
private Navigation[] _runtimeBaselineNavigation;
|
|
private Canvas _cachedCanvas;
|
|
private UIHolderObjectBase _cachedHolder;
|
|
private Selectable _lastSelected;
|
|
private bool _navigationSuppressed;
|
|
private int _cachedHierarchyDepth = -1;
|
|
private bool _cachedIsSkipped;
|
|
private bool _isSkippedCacheValid;
|
|
private int _runtimeSelectableCount;
|
|
private int[] _bakedSelectableHashIds = System.Array.Empty<int>();
|
|
private int[] _bakedSelectableHashIndices = System.Array.Empty<int>();
|
|
private int[] _runtimeSelectableHashIds = System.Array.Empty<int>();
|
|
private int[] _runtimeSelectableHashIndices = System.Array.Empty<int>();
|
|
private bool _selectableSetDirty = true;
|
|
private bool _selectableAvailabilityDirty = true;
|
|
private int _availableSelectableCount;
|
|
private Selectable _firstAvailableSelectable;
|
|
|
|
internal int RuntimeIndex { get; set; } = InvalidIndex;
|
|
internal ulong ActivationSerial { get; set; }
|
|
internal bool IsAvailable { get; set; }
|
|
internal bool WasAvailable { get; set; }
|
|
public bool NavigationSuppressed => _navigationSuppressed;
|
|
internal int BakedSelectableCount => _bakedSelectables != null ? _bakedSelectables.Length : 0;
|
|
public int RuntimeSelectableCount => _runtimeSelectableCount;
|
|
|
|
public Selectable DefaultSelectable
|
|
{
|
|
get => _defaultSelectable;
|
|
set
|
|
{
|
|
_defaultSelectable = value;
|
|
MarkSelectableAvailabilityDirty();
|
|
MarkRuntimeStateDirty();
|
|
}
|
|
}
|
|
|
|
public bool RememberLastSelection => _rememberLastSelection;
|
|
public bool RequireSelectionWhenGamepad => _requireSelectionWhenGamepad;
|
|
public bool BlockLowerScopes => _blockLowerScopes;
|
|
|
|
internal bool IsNavigationSkipped
|
|
{
|
|
get
|
|
{
|
|
if (!_isSkippedCacheValid)
|
|
{
|
|
_cachedIsSkipped = HasSkipInParents();
|
|
_isSkippedCacheValid = true;
|
|
}
|
|
|
|
return _cachedIsSkipped;
|
|
}
|
|
}
|
|
|
|
private bool HasSkipInParents()
|
|
{
|
|
Transform current = transform;
|
|
while (current != null)
|
|
{
|
|
if (current.TryGetComponent(out UXNavigationSkip skip))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
current = current.parent;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
internal Canvas Canvas
|
|
{
|
|
get
|
|
{
|
|
if (_cachedCanvas == null)
|
|
{
|
|
_cachedCanvas = GetComponentInParent<Canvas>(true);
|
|
}
|
|
|
|
return _cachedCanvas;
|
|
}
|
|
}
|
|
|
|
internal UIHolderObjectBase Holder
|
|
{
|
|
get
|
|
{
|
|
if (_cachedHolder == null)
|
|
{
|
|
_cachedHolder = GetComponent<UIHolderObjectBase>();
|
|
if (_cachedHolder == null)
|
|
{
|
|
_cachedHolder = GetComponentInParent<UIHolderObjectBase>(true);
|
|
}
|
|
}
|
|
|
|
return _cachedHolder;
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
EnsureRuntimeBuffers(false);
|
|
RefreshBaselineWhenUnsuppressed();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
EnsureRuntimeBuffers(false);
|
|
RefreshBaselineWhenUnsuppressed();
|
|
UXNavigationRuntime.EnsureInstance().RegisterScope(this);
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
SetNavigationSuppressed(false);
|
|
if (UXNavigationRuntime.TryGetInstance(out var runtime))
|
|
{
|
|
runtime.UnregisterScope(this);
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
SetNavigationSuppressed(false);
|
|
}
|
|
|
|
private void OnTransformChildrenChanged()
|
|
{
|
|
MarkSelectableAvailabilityDirty();
|
|
MarkRuntimeStateDirty();
|
|
}
|
|
|
|
private void OnTransformParentChanged()
|
|
{
|
|
_cachedCanvas = null;
|
|
_cachedHolder = null;
|
|
_cachedHierarchyDepth = -1;
|
|
_isSkippedCacheValid = false;
|
|
MarkRuntimeStateDirty();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
private void OnValidate()
|
|
{
|
|
if (_runtimeSelectableCapacity < 0)
|
|
{
|
|
_runtimeSelectableCapacity = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public bool RegisterSelectable(Selectable selectable)
|
|
{
|
|
if (selectable == null || !Owns(selectable.gameObject) || ContainsSelectable(selectable))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
EnsureRuntimeBuffers(true);
|
|
if (_runtimeSelectableCount >= _runtimeSelectables.Length)
|
|
{
|
|
ReportCapacityExceeded();
|
|
return false;
|
|
}
|
|
|
|
int index = _runtimeSelectableCount++;
|
|
_runtimeSelectables[index] = selectable;
|
|
_runtimeBaselineNavigation[index] = selectable.navigation;
|
|
MarkSelectableSetDirty();
|
|
if (_navigationSuppressed)
|
|
{
|
|
SetSelectableSuppressed(selectable);
|
|
}
|
|
MarkRuntimeStateDirty();
|
|
return true;
|
|
}
|
|
|
|
public bool UnregisterSelectable(Selectable selectable)
|
|
{
|
|
if (selectable == null || _runtimeSelectables == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int index = FindRuntimeIndex(selectable);
|
|
if (index < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_navigationSuppressed)
|
|
{
|
|
selectable.navigation = _runtimeBaselineNavigation[index];
|
|
}
|
|
|
|
int last = --_runtimeSelectableCount;
|
|
Selectable movedSelectable = _runtimeSelectables[last];
|
|
Navigation movedNavigation = _runtimeBaselineNavigation[last];
|
|
_runtimeSelectables[last] = null;
|
|
_runtimeBaselineNavigation[last] = default(Navigation);
|
|
if (index != last)
|
|
{
|
|
_runtimeSelectables[index] = movedSelectable;
|
|
_runtimeBaselineNavigation[index] = movedNavigation;
|
|
}
|
|
|
|
if (_lastSelected == selectable)
|
|
{
|
|
_lastSelected = null;
|
|
}
|
|
MarkSelectableSetDirty();
|
|
MarkRuntimeStateDirty();
|
|
return true;
|
|
}
|
|
|
|
public void InvalidateSelectableCache()
|
|
{
|
|
if (_navigationSuppressed)
|
|
{
|
|
ApplySuppression(_bakedSelectables, _bakedBaselineNavigation, BakedSelectableCount, false);
|
|
ApplySuppression(_runtimeSelectables, _runtimeBaselineNavigation, _runtimeSelectableCount, false);
|
|
CaptureBaselineBeforeSuppress();
|
|
ApplySuppression(_bakedSelectables, _bakedBaselineNavigation, BakedSelectableCount, true);
|
|
ApplySuppression(_runtimeSelectables, _runtimeBaselineNavigation, _runtimeSelectableCount, true);
|
|
}
|
|
else
|
|
{
|
|
RefreshBaselineWhenUnsuppressed();
|
|
}
|
|
MarkRuntimeStateDirty();
|
|
}
|
|
|
|
public void OnSelect(BaseEventData eventData)
|
|
{
|
|
GameObject selectedObject = eventData != null ? eventData.selectedObject : null;
|
|
UXNavigationRuntime.NotifySelection(selectedObject);
|
|
}
|
|
|
|
internal void InvalidateSkipCacheOnly()
|
|
{
|
|
_isSkippedCacheValid = false;
|
|
}
|
|
|
|
internal int GetHierarchyDepth()
|
|
{
|
|
if (_cachedHierarchyDepth >= 0)
|
|
{
|
|
return _cachedHierarchyDepth;
|
|
}
|
|
|
|
int depth = 0;
|
|
Transform current = transform;
|
|
while (current != null)
|
|
{
|
|
depth++;
|
|
current = current.parent;
|
|
}
|
|
|
|
_cachedHierarchyDepth = depth;
|
|
return _cachedHierarchyDepth;
|
|
}
|
|
|
|
internal bool Owns(GameObject target)
|
|
{
|
|
if (target == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UXNavigationScope nearestScope = target.GetComponentInParent<UXNavigationScope>(true);
|
|
return nearestScope == this;
|
|
}
|
|
|
|
internal Selectable GetPreferredSelectable()
|
|
{
|
|
RefreshSelectableAvailabilityIfDirty();
|
|
if (_rememberLastSelection && IsSelectableValid(_lastSelected))
|
|
{
|
|
return _lastSelected;
|
|
}
|
|
|
|
if (IsSelectableValid(_defaultSelectable))
|
|
{
|
|
return _defaultSelectable;
|
|
}
|
|
|
|
return _autoSelectFirstAvailable ? _firstAvailableSelectable : null;
|
|
}
|
|
|
|
internal bool HasAvailableSelectable()
|
|
{
|
|
RefreshSelectableAvailabilityIfDirty();
|
|
if (IsSelectableValid(_defaultSelectable))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return _autoSelectFirstAvailable && _availableSelectableCount > 0;
|
|
}
|
|
|
|
internal void RecordSelection(GameObject selectedObject)
|
|
{
|
|
if (!_rememberLastSelection || selectedObject == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Selectable selectable = GetSelectableFromObject(selectedObject);
|
|
if (IsSelectableValid(selectable))
|
|
{
|
|
_lastSelected = selectable;
|
|
}
|
|
}
|
|
|
|
internal bool IsSelectableOwnedAndValid(GameObject selectedObject)
|
|
{
|
|
return IsSelectableValid(GetSelectableFromObject(selectedObject));
|
|
}
|
|
|
|
internal void SetNavigationSuppressed(bool suppressed)
|
|
{
|
|
if (_navigationSuppressed == suppressed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (suppressed)
|
|
{
|
|
CaptureBaselineBeforeSuppress();
|
|
}
|
|
|
|
_navigationSuppressed = suppressed;
|
|
ApplySuppression(_bakedSelectables, _bakedBaselineNavigation, BakedSelectableCount, suppressed);
|
|
ApplySuppression(_runtimeSelectables, _runtimeBaselineNavigation, _runtimeSelectableCount, suppressed);
|
|
}
|
|
|
|
private void EnsureRuntimeBuffers(bool preserveRuntimeSelectables)
|
|
{
|
|
int capacity = _runtimeSelectableCapacity > 0 ? _runtimeSelectableCapacity : 0;
|
|
if (_runtimeSelectables == null || _runtimeSelectables.Length != capacity)
|
|
{
|
|
Selectable[] previousSelectables = _runtimeSelectables;
|
|
Navigation[] previousBaseline = _runtimeBaselineNavigation;
|
|
int previousCount = _runtimeSelectableCount;
|
|
_runtimeSelectables = capacity > 0 ? new Selectable[capacity] : System.Array.Empty<Selectable>();
|
|
_runtimeBaselineNavigation = capacity > 0 ? new Navigation[capacity] : System.Array.Empty<Navigation>();
|
|
CreateRuntimeHash(capacity);
|
|
_runtimeSelectableCount = 0;
|
|
|
|
if (preserveRuntimeSelectables && previousSelectables != null && capacity > 0)
|
|
{
|
|
int copyCount = previousCount < capacity ? previousCount : capacity;
|
|
for (int i = 0; i < copyCount; i++)
|
|
{
|
|
Selectable selectable = previousSelectables[i];
|
|
if (selectable == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_runtimeSelectables[_runtimeSelectableCount] = selectable;
|
|
_runtimeBaselineNavigation[_runtimeSelectableCount] = previousBaseline != null && i < previousBaseline.Length
|
|
? previousBaseline[i]
|
|
: selectable.navigation;
|
|
_runtimeSelectableCount++;
|
|
}
|
|
}
|
|
|
|
MarkSelectableSetDirty();
|
|
}
|
|
|
|
int bakedCount = BakedSelectableCount;
|
|
if (_bakedBaselineNavigation == null || _bakedBaselineNavigation.Length != bakedCount)
|
|
{
|
|
_bakedBaselineNavigation = bakedCount > 0 ? new Navigation[bakedCount] : System.Array.Empty<Navigation>();
|
|
CreateBakedHash(bakedCount);
|
|
MarkSelectableSetDirty();
|
|
}
|
|
}
|
|
|
|
private void CaptureBaselineBeforeSuppress()
|
|
{
|
|
EnsureRuntimeBuffers(true);
|
|
RefreshSelectableHashesIfDirty();
|
|
CaptureBaseline(_bakedSelectables, _bakedBaselineNavigation, BakedSelectableCount);
|
|
CaptureBaseline(_runtimeSelectables, _runtimeBaselineNavigation, _runtimeSelectableCount);
|
|
}
|
|
|
|
private void RefreshBaselineWhenUnsuppressed()
|
|
{
|
|
if (_navigationSuppressed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CaptureBaselineBeforeSuppress();
|
|
}
|
|
|
|
private static void CaptureBaseline(Selectable[] selectables, Navigation[] baseline, int count)
|
|
{
|
|
if (selectables == null || baseline == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Selectable selectable = selectables[i];
|
|
if (selectable != null)
|
|
{
|
|
baseline[i] = selectable.navigation;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ApplySuppression(Selectable[] selectables, Navigation[] baseline, int count, bool suppressed)
|
|
{
|
|
if (selectables == null || baseline == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Selectable selectable = selectables[i];
|
|
if (selectable == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (suppressed)
|
|
{
|
|
SetSelectableSuppressed(selectable);
|
|
}
|
|
else
|
|
{
|
|
selectable.navigation = baseline[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void SetSelectableSuppressed(Selectable selectable)
|
|
{
|
|
if (selectable == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Navigation navigation = selectable.navigation;
|
|
navigation.mode = Navigation.Mode.None;
|
|
selectable.navigation = navigation;
|
|
}
|
|
|
|
private bool ContainsSelectable(Selectable selectable)
|
|
{
|
|
if (selectable == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
RefreshSelectableHashesIfDirty();
|
|
int instanceId = selectable.GetInstanceID();
|
|
return FindHashIndex(_bakedSelectableHashIds, _bakedSelectableHashIndices, instanceId) >= 0
|
|
|| FindHashIndex(_runtimeSelectableHashIds, _runtimeSelectableHashIndices, instanceId) >= 0;
|
|
}
|
|
|
|
private bool IsSelectableValid(Selectable selectable)
|
|
{
|
|
return IsSelectableUsable(selectable) && ContainsSelectable(selectable);
|
|
}
|
|
|
|
private void MarkSelectableSetDirty()
|
|
{
|
|
_selectableSetDirty = true;
|
|
MarkSelectableAvailabilityDirty();
|
|
}
|
|
|
|
private void MarkSelectableAvailabilityDirty()
|
|
{
|
|
_selectableAvailabilityDirty = true;
|
|
}
|
|
|
|
public void NotifySelectableStateChanged()
|
|
{
|
|
MarkSelectableAvailabilityDirty();
|
|
MarkRuntimeStateDirty();
|
|
}
|
|
|
|
private void RefreshSelectableAvailabilityIfDirty()
|
|
{
|
|
if (!_selectableAvailabilityDirty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_availableSelectableCount = 0;
|
|
_firstAvailableSelectable = null;
|
|
AccumulateAvailableSelectables(_bakedSelectables, BakedSelectableCount);
|
|
AccumulateAvailableSelectables(_runtimeSelectables, _runtimeSelectableCount);
|
|
_selectableAvailabilityDirty = false;
|
|
}
|
|
|
|
private void AccumulateAvailableSelectables(Selectable[] selectables, int count)
|
|
{
|
|
if (selectables == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Selectable selectable = selectables[i];
|
|
if (!IsSelectableUsable(selectable))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (_firstAvailableSelectable == null)
|
|
{
|
|
_firstAvailableSelectable = selectable;
|
|
}
|
|
|
|
_availableSelectableCount++;
|
|
}
|
|
}
|
|
private void RefreshSelectableHashesIfDirty()
|
|
{
|
|
if (!_selectableSetDirty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RebuildBakedHash();
|
|
RebuildRuntimeHash();
|
|
_selectableSetDirty = false;
|
|
}
|
|
|
|
private void RebuildBakedHash()
|
|
{
|
|
ClearHash(_bakedSelectableHashIds, _bakedSelectableHashIndices);
|
|
for (int i = 0; i < BakedSelectableCount; i++)
|
|
{
|
|
Selectable selectable = _bakedSelectables[i];
|
|
if (selectable != null)
|
|
{
|
|
AddHash(_bakedSelectableHashIds, _bakedSelectableHashIndices, selectable.GetInstanceID(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RebuildRuntimeHash()
|
|
{
|
|
ClearHash(_runtimeSelectableHashIds, _runtimeSelectableHashIndices);
|
|
for (int i = 0; i < _runtimeSelectableCount; i++)
|
|
{
|
|
Selectable selectable = _runtimeSelectables[i];
|
|
if (selectable != null)
|
|
{
|
|
AddHash(_runtimeSelectableHashIds, _runtimeSelectableHashIndices, selectable.GetInstanceID(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateBakedHash(int itemCapacity)
|
|
{
|
|
int hashCapacity = GetHashCapacity(itemCapacity);
|
|
_bakedSelectableHashIds = hashCapacity > 0 ? new int[hashCapacity] : System.Array.Empty<int>();
|
|
_bakedSelectableHashIndices = hashCapacity > 0 ? new int[hashCapacity] : System.Array.Empty<int>();
|
|
}
|
|
|
|
private void CreateRuntimeHash(int itemCapacity)
|
|
{
|
|
int hashCapacity = GetHashCapacity(itemCapacity);
|
|
_runtimeSelectableHashIds = hashCapacity > 0 ? new int[hashCapacity] : System.Array.Empty<int>();
|
|
_runtimeSelectableHashIndices = hashCapacity > 0 ? new int[hashCapacity] : System.Array.Empty<int>();
|
|
}
|
|
|
|
private static int GetHashCapacity(int itemCapacity)
|
|
{
|
|
if (itemCapacity <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int hashCapacity = 1;
|
|
int required = itemCapacity << 1;
|
|
while (hashCapacity < required)
|
|
{
|
|
hashCapacity <<= 1;
|
|
}
|
|
|
|
return hashCapacity;
|
|
}
|
|
|
|
private static void ClearHash(int[] ids, int[] indices)
|
|
{
|
|
if (ids == null || indices == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < ids.Length; i++)
|
|
{
|
|
ids[i] = 0;
|
|
indices[i] = InvalidIndex;
|
|
}
|
|
}
|
|
|
|
private static int FindHashIndex(int[] ids, int[] indices, int instanceId)
|
|
{
|
|
if (ids == null || indices == null || ids.Length == 0 || instanceId == 0)
|
|
{
|
|
return InvalidIndex;
|
|
}
|
|
|
|
int mask = ids.Length - 1;
|
|
int index = instanceId & mask;
|
|
for (int i = 0; i < ids.Length; i++)
|
|
{
|
|
int storedId = ids[index];
|
|
if (storedId == 0)
|
|
{
|
|
return InvalidIndex;
|
|
}
|
|
|
|
if (storedId == instanceId)
|
|
{
|
|
return indices[index];
|
|
}
|
|
|
|
index = (index + 1) & mask;
|
|
}
|
|
|
|
return InvalidIndex;
|
|
}
|
|
|
|
private static void AddHash(int[] ids, int[] indices, int instanceId, int selectableIndex)
|
|
{
|
|
if (ids == null || indices == null || ids.Length == 0 || instanceId == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int mask = ids.Length - 1;
|
|
int index = instanceId & mask;
|
|
for (int i = 0; i < ids.Length; i++)
|
|
{
|
|
int storedId = ids[index];
|
|
if (storedId == 0 || storedId == instanceId)
|
|
{
|
|
ids[index] = instanceId;
|
|
indices[index] = selectableIndex;
|
|
return;
|
|
}
|
|
|
|
index = (index + 1) & mask;
|
|
}
|
|
}
|
|
|
|
private int FindRuntimeIndex(Selectable selectable)
|
|
{
|
|
if (selectable == null)
|
|
{
|
|
return InvalidIndex;
|
|
}
|
|
|
|
RefreshSelectableHashesIfDirty();
|
|
return FindHashIndex(_runtimeSelectableHashIds, _runtimeSelectableHashIndices, selectable.GetInstanceID());
|
|
}
|
|
private static int IndexOf(Selectable[] selectables, int count, Selectable selectable)
|
|
{
|
|
if (selectables == null || selectable == null)
|
|
{
|
|
return InvalidIndex;
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (selectables[i] == selectable)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return InvalidIndex;
|
|
}
|
|
|
|
private static bool IsSelectableUsable(Selectable selectable)
|
|
{
|
|
return selectable != null && selectable.IsActive() && selectable.IsInteractable();
|
|
}
|
|
|
|
private static Selectable GetSelectableFromObject(GameObject selectedObject)
|
|
{
|
|
if (selectedObject == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return selectedObject.TryGetComponent(out Selectable selectable)
|
|
? selectable
|
|
: selectedObject.GetComponentInParent<Selectable>();
|
|
}
|
|
|
|
private void MarkRuntimeStateDirty()
|
|
{
|
|
if (UXNavigationRuntime.TryGetInstance(out var runtime))
|
|
{
|
|
runtime.MarkStateDirty();
|
|
}
|
|
}
|
|
|
|
private static void ReportCapacityExceeded()
|
|
{
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
Debug.LogError("UXNavigationScope runtime selectable capacity exceeded.");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|