com.alicizax.unity.ui.exten.../Runtime/UXComponent/Navigation/UXNavigationScope.cs

303 lines
8.7 KiB
C#

#if INPUTSYSTEM_SUPPORT
using System.Collections.Generic;
using AlicizaX.UI.Runtime;
namespace UnityEngine.UI
{
[DisallowMultipleComponent]
[AddComponentMenu("UI/UX Navigation Scope")]
public sealed class UXNavigationScope : MonoBehaviour
{
[SerializeField, Header("默认选中控件")] private Selectable _defaultSelectable;
[SerializeField, Header("显式导航控件列表")] private List<Selectable> _explicitSelectables = new();
[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 readonly List<Selectable> _cachedSelectables = new(16);
private readonly Dictionary<Selectable, Navigation> _baselineNavigation = new();
private readonly List<Selectable> _removeBuffer = new(8);
private Canvas _cachedCanvas;
private UIHolderObjectBase _cachedHolder;
private Selectable _lastSelected;
private bool _cacheDirty = true;
private bool _navigationSuppressed;
internal ulong ActivationSerial { get; set; }
internal bool WasAvailable { get; set; }
public Selectable DefaultSelectable
{
get => _defaultSelectable;
set => _defaultSelectable = value;
}
public bool RememberLastSelection => _rememberLastSelection;
public bool RequireSelectionWhenGamepad => _requireSelectionWhenGamepad;
public bool BlockLowerScopes => _blockLowerScopes;
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 OnEnable()
{
_cacheDirty = true;
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()
{
_cacheDirty = true;
}
#if UNITY_EDITOR
private void OnValidate()
{
_cacheDirty = true;
}
#endif
internal int GetHierarchyDepth()
{
int depth = 0;
Transform current = transform;
while (current != null)
{
depth++;
current = current.parent;
}
return depth;
}
internal bool Owns(GameObject target)
{
if (target == null)
{
return false;
}
var nearestScope = target.GetComponentInParent<UXNavigationScope>(true);
return nearestScope == this;
}
internal Selectable GetPreferredSelectable()
{
RefreshSelectableCache();
if (_rememberLastSelection && IsSelectableValid(_lastSelected))
{
return _lastSelected;
}
if (IsSelectableValid(_defaultSelectable))
{
return _defaultSelectable;
}
if (!_autoSelectFirstAvailable)
{
return null;
}
for (int i = 0; i < _cachedSelectables.Count; i++)
{
Selectable selectable = _cachedSelectables[i];
if (IsSelectableValid(selectable))
{
return selectable;
}
}
return null;
}
internal void RecordSelection(GameObject selectedObject)
{
if (!_rememberLastSelection || selectedObject == null)
{
return;
}
Selectable selectable = selectedObject.GetComponent<Selectable>();
if (selectable == null)
{
selectable = selectedObject.GetComponentInParent<Selectable>();
}
if (IsSelectableValid(selectable))
{
_lastSelected = selectable;
}
}
internal bool IsSelectableOwnedAndValid(GameObject selectedObject)
{
if (selectedObject == null || !Owns(selectedObject))
{
return false;
}
Selectable selectable = selectedObject.GetComponent<Selectable>();
if (selectable == null)
{
selectable = selectedObject.GetComponentInParent<Selectable>();
}
return IsSelectableValid(selectable);
}
internal void SetNavigationSuppressed(bool suppressed)
{
RefreshSelectableCache();
if (_navigationSuppressed == suppressed)
{
return;
}
_navigationSuppressed = suppressed;
for (int i = 0; i < _cachedSelectables.Count; i++)
{
Selectable selectable = _cachedSelectables[i];
if (selectable == null)
{
continue;
}
if (!_baselineNavigation.ContainsKey(selectable))
{
_baselineNavigation[selectable] = selectable.navigation;
}
if (suppressed)
{
Navigation navigation = selectable.navigation;
navigation.mode = Navigation.Mode.None;
selectable.navigation = navigation;
}
else if (_baselineNavigation.TryGetValue(selectable, out Navigation navigation))
{
selectable.navigation = navigation;
}
}
}
internal void RefreshSelectableCache()
{
if (!_cacheDirty)
{
return;
}
_cacheDirty = false;
_cachedSelectables.Clear();
if (_explicitSelectables != null && _explicitSelectables.Count > 0)
{
for (int i = 0; i < _explicitSelectables.Count; i++)
{
TryAddSelectable(_explicitSelectables[i]);
}
}
else
{
Selectable[] selectables = GetComponentsInChildren<Selectable>(true);
for (int i = 0; i < selectables.Length; i++)
{
TryAddSelectable(selectables[i]);
}
}
_removeBuffer.Clear();
foreach (var pair in _baselineNavigation)
{
if (!_cachedSelectables.Contains(pair.Key))
{
_removeBuffer.Add(pair.Key);
}
}
for (int i = 0; i < _removeBuffer.Count; i++)
{
_baselineNavigation.Remove(_removeBuffer[i]);
}
}
internal void InvalidateSelectableCache()
{
_cacheDirty = true;
}
private void TryAddSelectable(Selectable selectable)
{
if (selectable == null || !Owns(selectable.gameObject))
{
return;
}
_cachedSelectables.Add(selectable);
if (!_baselineNavigation.ContainsKey(selectable) || !_navigationSuppressed)
{
_baselineNavigation[selectable] = selectable.navigation;
}
}
private bool IsSelectableValid(Selectable selectable)
{
return selectable != null
&& selectable.IsActive()
&& selectable.IsInteractable()
&& Owns(selectable.gameObject);
}
}
}
#endif