158 lines
5.2 KiB
C#
158 lines
5.2 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
namespace RR.Common.UI
|
|
{
|
|
[DisallowMultipleComponent]
|
|
public class ModalFocusTrap : MonoBehaviour
|
|
{
|
|
[Header("Modal")]
|
|
public GameObject modalRoot;
|
|
public Selectable firstSelected;
|
|
|
|
[Header("Behavior")]
|
|
[Tooltip("Следить и возвращать фокус внутрь модалки, если он уходит наружу.")]
|
|
public bool enforce = true;
|
|
|
|
private bool _wasOpenLastFrame;
|
|
private GameObject _prevSelectedGlobal;
|
|
private readonly List<Selectable> _outside = new();
|
|
private readonly Dictionary<Selectable, bool> _outsidePrev = new();
|
|
private Selectable _lastModalSelected;
|
|
|
|
void Reset()
|
|
{
|
|
if (modalRoot == null)
|
|
{
|
|
var sibling = transform.parent ? transform.parent.Find("LevelSelectModal") : null;
|
|
if (sibling) modalRoot = sibling.gameObject;
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (modalRoot == null) return;
|
|
|
|
bool isOpen = modalRoot.activeSelf;
|
|
if (isOpen && !_wasOpenLastFrame) OnModalOpened();
|
|
else if (!isOpen && _wasOpenLastFrame) OnModalClosed();
|
|
|
|
_wasOpenLastFrame = isOpen;
|
|
|
|
if (isOpen && enforce) EnsureFocusInsideModal();
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
if (_wasOpenLastFrame) OnModalClosed();
|
|
}
|
|
|
|
void OnModalOpened()
|
|
{
|
|
// запомнить, что было выделено до открытия
|
|
_prevSelectedGlobal = EventSystem.current ? EventSystem.current.currentSelectedGameObject : null;
|
|
|
|
// выключить интерактив вне модалки
|
|
CollectOutsideSelectables();
|
|
foreach (var s in _outside)
|
|
{
|
|
if (!s) continue;
|
|
_outsidePrev[s] = s.interactable;
|
|
s.interactable = false;
|
|
}
|
|
|
|
// сфокусировать элемент внутри модалки
|
|
GameObject toSelect = null;
|
|
if (firstSelected && firstSelected.gameObject.activeInHierarchy)
|
|
toSelect = firstSelected.gameObject;
|
|
else
|
|
{
|
|
var any = modalRoot.GetComponentInChildren<Selectable>(includeInactive: false);
|
|
if (any) toSelect = any.gameObject;
|
|
}
|
|
|
|
if (toSelect && EventSystem.current)
|
|
{
|
|
EventSystem.current.SetSelectedGameObject(null);
|
|
EventSystem.current.SetSelectedGameObject(toSelect);
|
|
_lastModalSelected = toSelect.GetComponent<Selectable>();
|
|
}
|
|
}
|
|
|
|
void OnModalClosed()
|
|
{
|
|
foreach (var kv in _outsidePrev)
|
|
{
|
|
var s = kv.Key;
|
|
if (s) s.interactable = kv.Value;
|
|
}
|
|
_outsidePrev.Clear();
|
|
_outside.Clear();
|
|
|
|
if (EventSystem.current)
|
|
{
|
|
if (_prevSelectedGlobal && _prevSelectedGlobal.activeInHierarchy)
|
|
EventSystem.current.SetSelectedGameObject(_prevSelectedGlobal);
|
|
else
|
|
EventSystem.current.SetSelectedGameObject(null);
|
|
}
|
|
|
|
_lastModalSelected = null;
|
|
_prevSelectedGlobal = null;
|
|
}
|
|
|
|
void CollectOutsideSelectables()
|
|
{
|
|
_outside.Clear();
|
|
_outsidePrev.Clear();
|
|
|
|
// ✅ Новый API Unity 6
|
|
var all = Object.FindObjectsByType<Selectable>(
|
|
FindObjectsInactive.Include,
|
|
FindObjectsSortMode.None);
|
|
|
|
foreach (var s in all)
|
|
{
|
|
if (!s || !s.gameObject.activeInHierarchy) continue;
|
|
if (!IsUnderModal(s.transform))
|
|
_outside.Add(s);
|
|
}
|
|
}
|
|
|
|
bool IsUnderModal(Transform t)
|
|
{
|
|
if (!modalRoot) return false;
|
|
var root = modalRoot.transform;
|
|
for (var cur = t; cur != null; cur = cur.parent)
|
|
if (cur == root) return true;
|
|
return false;
|
|
}
|
|
|
|
void EnsureFocusInsideModal()
|
|
{
|
|
if (!EventSystem.current) return;
|
|
|
|
var cur = EventSystem.current.currentSelectedGameObject;
|
|
if (cur == null || !IsUnderModal(cur.transform))
|
|
{
|
|
GameObject fallback =
|
|
(_lastModalSelected && _lastModalSelected.gameObject.activeInHierarchy) ? _lastModalSelected.gameObject :
|
|
(firstSelected && firstSelected.gameObject.activeInHierarchy) ? firstSelected.gameObject :
|
|
modalRoot.GetComponentInChildren<Selectable>()?.gameObject;
|
|
|
|
if (fallback)
|
|
{
|
|
EventSystem.current.SetSelectedGameObject(fallback);
|
|
_lastModalSelected = fallback.GetComponent<Selectable>();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_lastModalSelected = cur.GetComponent<Selectable>();
|
|
}
|
|
}
|
|
}
|
|
}
|