using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace DanroJump.UI.Focus
{
///
/// Собирает линейное кольцо фокусируемых UI Toolkit-элементов для навигации с клавиатуры или автомата.
///
public sealed class UiFocusRing
{
private readonly List elements = new();
///
/// Текущий список фокусируемых элементов.
///
public IReadOnlyList Elements => elements;
///
/// Количество элементов в кольце фокуса.
///
public int Count => elements.Count;
///
/// Пересобирает список видимых и активных элементов, доступных для фокуса.
///
public void Rebuild(VisualElement root)
{
elements.Clear();
if (root == null)
{
return;
}
CollectFocusableElements(root);
}
///
/// Проверяет, входит ли элемент в текущее кольцо фокуса.
///
public bool Contains(VisualElement element)
{
return elements.Contains(element);
}
///
/// Возвращает первый элемент кольца фокуса.
///
public VisualElement GetFirst()
{
return elements.Count > 0 ? elements[0] : null;
}
///
/// Возвращает следующий элемент с зацикливанием по краям списка.
///
public VisualElement GetNext(VisualElement current, int direction)
{
if (elements.Count == 0)
{
return null;
}
var currentIndex = current != null ? elements.IndexOf(current) : -1;
if (direction == 0)
{
return currentIndex >= 0 ? elements[currentIndex] : elements[0];
}
if (currentIndex < 0)
{
return direction > 0 ? elements[0] : elements[^1];
}
var targetIndex = (currentIndex + direction + elements.Count) % elements.Count;
return elements[targetIndex];
}
///
/// Ставит фокус на первый доступный элемент.
///
public void FocusFirst()
{
TryFocus(GetFirst());
}
///
/// Перемещает фокус относительно текущего элемента.
///
public bool FocusRelativeTo(VisualElement current, int direction)
{
var next = GetNext(current, direction);
if (next == null)
{
return false;
}
return TryFocus(next);
}
///
/// Ставит фокус на элемент, если он все еще доступен в текущем кольце.
///
public bool TryFocus(VisualElement element)
{
if (element == null || !elements.Contains(element) || !IsFocusable(element))
{
return false;
}
element.Focus();
return true;
}
///
/// Обходит визуальное дерево в порядке отображения и собирает доступные элементы.
///
private void CollectFocusableElements(VisualElement root)
{
if (!CanTraverse(root))
{
return;
}
if (IsFocusable(root))
{
elements.Add(root);
}
for (var index = 0; index < root.childCount; index++)
{
CollectFocusableElements(root[index]);
}
}
///
/// Проверяет базовые условия доступности элемента для фокуса.
///
private static bool IsFocusable(VisualElement element)
{
return element.focusable &&
CanTraverse(element);
}
///
/// Проверяет, нужно ли учитывать элемент и его дочернее дерево при сборке фокуса.
///
private static bool CanTraverse(VisualElement element)
{
return element != null &&
element.enabledInHierarchy &&
element.resolvedStyle.display != DisplayStyle.None &&
element.resolvedStyle.visibility != Visibility.Hidden;
}
}
}