144 lines
5.0 KiB
C#
144 lines
5.0 KiB
C#
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
using System.Collections;
|
|
|
|
public class ScrollSelectionFollower : MonoBehaviour
|
|
{
|
|
[SerializeField] private ScrollRect scrollRect;
|
|
[SerializeField] private float padding = 8f; // отступы сверху/снизу/слева/справа
|
|
[SerializeField] private float duration = 0.15f; // длительность анимации
|
|
[SerializeField] private bool onlyWhenNotVisible = true; // если false — центрируем всегда
|
|
|
|
private GameObject _lastSelected;
|
|
private Coroutine _anim;
|
|
|
|
private void Reset()
|
|
{
|
|
scrollRect = GetComponentInChildren<ScrollRect>();
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
GameObject sel = EventSystem.current ? EventSystem.current.currentSelectedGameObject : null;
|
|
if (!sel || sel == _lastSelected) return;
|
|
|
|
// Только если элемент внутри нашего Content
|
|
if (!scrollRect || sel.transform is not RectTransform tr || !tr.IsChildOf(scrollRect.content)) return;
|
|
ScrollTo(tr);
|
|
_lastSelected = sel;
|
|
}
|
|
|
|
public void ScrollTo(RectTransform target)
|
|
{
|
|
if (!scrollRect || !target) return;
|
|
|
|
// Сначала убедимся, что лейауты посчитались
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
RectTransform content = scrollRect.content;
|
|
RectTransform viewport = scrollRect.viewport != null ? scrollRect.viewport : (RectTransform)scrollRect.transform;
|
|
|
|
// Получаем мировые углы и преобразуем в локальное пространство viewport
|
|
Vector3[] itemCorners = new Vector3[4];
|
|
target.GetWorldCorners(itemCorners);
|
|
|
|
Vector3[] viewportCorners = new Vector3[4];
|
|
viewport.GetWorldCorners(viewportCorners);
|
|
|
|
// Преобразуем в локальное пространство content
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
itemCorners[i] = content.InverseTransformPoint(itemCorners[i]);
|
|
viewportCorners[i] = content.InverseTransformPoint(viewportCorners[i]);
|
|
}
|
|
|
|
// Вычисляем границы
|
|
float itemTop = itemCorners[1].y;
|
|
float itemBottom = itemCorners[0].y;
|
|
float itemLeft = itemCorners[0].x;
|
|
float itemRight = itemCorners[2].x;
|
|
|
|
float viewTop = viewportCorners[1].y;
|
|
float viewBottom = viewportCorners[0].y;
|
|
float viewLeft = viewportCorners[0].x;
|
|
float viewRight = viewportCorners[2].x;
|
|
|
|
Vector2 offset = Vector2.zero;
|
|
|
|
// Вертикальная прокрутка
|
|
if (scrollRect.vertical)
|
|
{
|
|
float delta = 0f;
|
|
bool above = itemTop > viewTop - padding;
|
|
bool below = itemBottom < viewBottom + padding;
|
|
|
|
if (!onlyWhenNotVisible || above || below)
|
|
{
|
|
if (above && below)
|
|
{
|
|
// Элемент больше viewport - выравниваем по верхнему краю
|
|
delta = itemTop - (viewTop - padding);
|
|
}
|
|
else if (above)
|
|
{
|
|
delta = itemTop - (viewTop - padding);
|
|
}
|
|
else if (below)
|
|
{
|
|
delta = itemBottom - (viewBottom + padding);
|
|
}
|
|
offset.y = delta;
|
|
}
|
|
}
|
|
|
|
// Горизонтальная прокрутка
|
|
if (scrollRect.horizontal)
|
|
{
|
|
float delta = 0f;
|
|
bool left = itemLeft < viewLeft + padding;
|
|
bool right = itemRight > viewRight - padding;
|
|
|
|
if (!onlyWhenNotVisible || left || right)
|
|
{
|
|
if (left && right)
|
|
{
|
|
// Элемент больше viewport - выравниваем по левому краю
|
|
delta = itemLeft - (viewLeft + padding);
|
|
}
|
|
else if (left)
|
|
{
|
|
delta = itemLeft - (viewLeft + padding);
|
|
}
|
|
else if (right)
|
|
{
|
|
delta = itemRight - (viewRight - padding);
|
|
}
|
|
offset.x = delta;
|
|
}
|
|
}
|
|
|
|
if (offset == Vector2.zero) return;
|
|
|
|
Vector2 targetPos = content.anchoredPosition - offset;
|
|
|
|
if (_anim != null) StopCoroutine(_anim);
|
|
_anim = StartCoroutine(AnimateTo(content, targetPos, duration));
|
|
}
|
|
|
|
private static IEnumerator AnimateTo(RectTransform content, Vector2 target, float time)
|
|
{
|
|
Vector2 start = content.anchoredPosition;
|
|
float t = 0f;
|
|
while (t < 1f)
|
|
{
|
|
t += Time.unscaledDeltaTime / Mathf.Max(0.0001f, time);
|
|
// лёгкая ease-in-out
|
|
float s = t * t * (3f - 2f * t);
|
|
content.anchoredPosition = Vector2.LerpUnclamped(start, target, s);
|
|
yield return null;
|
|
}
|
|
content.anchoredPosition = target;
|
|
}
|
|
}
|