Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Добавил больше настроек в систему настроек - Пока у большинства настроек нет функционала - Немного изменил внешний вид настроек
198 lines
7.8 KiB
C#
198 lines
7.8 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
|
||
namespace RR.Common.UI
|
||
{
|
||
public class UIPointer : MonoBehaviour
|
||
{
|
||
[Header("Настройки")]
|
||
[SerializeField] private GameObject pointer;
|
||
[SerializeField] private RectTransform content; // Content ScrollView, где находятся элементы
|
||
[SerializeField] private float smoothSpeed = 10f; // Скорость перемещения указателя
|
||
[SerializeField] private Vector2 offset = Vector2.zero; // Смещение указателя относительно элемента
|
||
|
||
[Header("Поведение")]
|
||
[Tooltip("Не скрывать указатель при отсутствии выбора (оставаться на последней позиции)")]
|
||
[SerializeField] private bool keepVisibleWhenNoSelection = true;
|
||
|
||
[Tooltip("Задержка перед скрытием указателя (в секундах)")]
|
||
[SerializeField] private float hideDelay = 0.5f;
|
||
|
||
[Header("Исключения")]
|
||
[Tooltip("Элементы, при выборе которых курсор скрывается")]
|
||
[SerializeField] private List<GameObject> hideOnSelectElements = new();
|
||
|
||
private RectTransform pointerRect;
|
||
private float hideTimer;
|
||
private bool hasEverSelected; // Был ли когда-либо выбран элемент
|
||
|
||
private void Start()
|
||
{
|
||
if (pointer != null)
|
||
{
|
||
pointerRect = pointer.GetComponent<RectTransform>();
|
||
if (pointerRect == null)
|
||
{
|
||
Debug.LogError("UIPointer: pointer не имеет RectTransform!");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("UIPointer: pointer не назначен!");
|
||
}
|
||
|
||
// Если content не задан, пытаемся найти его автоматически
|
||
if (content == null)
|
||
{
|
||
content = GetComponent<RectTransform>();
|
||
}
|
||
|
||
hideTimer = hideDelay;
|
||
}
|
||
|
||
private void LateUpdate()
|
||
{
|
||
if (!pointerRect || !content) return;
|
||
|
||
// Получаем текущий выбранный элемент из EventSystem
|
||
GameObject selected = EventSystem.current?.currentSelectedGameObject;
|
||
|
||
// Если ничего не выбрано
|
||
if (selected == null)
|
||
{
|
||
HandleNoSelection();
|
||
return;
|
||
}
|
||
|
||
// Проверяем, находится ли выбранный элемент в списке исключений
|
||
if (IsInHideList(selected))
|
||
{
|
||
HidePointer();
|
||
return;
|
||
}
|
||
|
||
// Проверяем, что выбранный элемент находится внутри нашего content
|
||
RectTransform selectedRect = selected.GetComponent<RectTransform>();
|
||
if (!selectedRect || !selectedRect.IsChildOf(content))
|
||
{
|
||
HandleNoSelection();
|
||
return;
|
||
}
|
||
|
||
// Сбрасываем таймер и отмечаем что был выбор
|
||
hideTimer = hideDelay;
|
||
hasEverSelected = true;
|
||
|
||
// Показываем указатель, если он был скрыт
|
||
if (!pointer.activeSelf)
|
||
pointer.SetActive(true);
|
||
|
||
// Вычисляем целевую позицию
|
||
Vector2 targetPos = GetTargetPosition(selectedRect);
|
||
|
||
// Плавно перемещаем указатель
|
||
if (smoothSpeed > 0)
|
||
{
|
||
pointerRect.anchoredPosition = Vector2.Lerp(
|
||
pointerRect.anchoredPosition,
|
||
targetPos,
|
||
Time.unscaledDeltaTime * smoothSpeed
|
||
);
|
||
}
|
||
else
|
||
{
|
||
pointerRect.anchoredPosition = targetPos;
|
||
}
|
||
}
|
||
|
||
private void HandleNoSelection()
|
||
{
|
||
// Если включено сохранение видимости или ещё не было выбора - не скрываем
|
||
if (keepVisibleWhenNoSelection || !hasEverSelected)
|
||
return;
|
||
|
||
// Уменьшаем таймер
|
||
hideTimer -= Time.unscaledDeltaTime;
|
||
|
||
if (hideTimer <= 0 && pointer.activeSelf)
|
||
{
|
||
pointer.SetActive(false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, находится ли элемент в списке исключений или является его дочерним элементом
|
||
/// </summary>
|
||
private bool IsInHideList(GameObject obj)
|
||
{
|
||
if (hideOnSelectElements == null || hideOnSelectElements.Count == 0)
|
||
return false;
|
||
|
||
foreach (var hideElement in hideOnSelectElements)
|
||
{
|
||
if (hideElement == null) continue;
|
||
|
||
// Проверяем сам элемент или его родителей
|
||
if (obj == hideElement || obj.transform.IsChildOf(hideElement.transform))
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Немедленно скрывает указатель
|
||
/// </summary>
|
||
private void HidePointer()
|
||
{
|
||
if (pointer != null && pointer.activeSelf)
|
||
{
|
||
pointer.SetActive(false);
|
||
}
|
||
}
|
||
|
||
private Vector2 GetTargetPosition(RectTransform target)
|
||
{
|
||
// Получаем мировые координаты центра целевого элемента
|
||
Vector3[] corners = new Vector3[4];
|
||
target.GetWorldCorners(corners);
|
||
Vector3 worldCenter = (corners[0] + corners[2]) * 0.5f;
|
||
|
||
// Конвертируем мировую позицию в локальную позицию родителя указателя
|
||
RectTransform pointerParent = pointerRect.parent as RectTransform;
|
||
if (pointerParent == null)
|
||
{
|
||
Debug.LogWarning("UIPointer: pointer не имеет RectTransform родителя!");
|
||
return pointerRect.anchoredPosition;
|
||
}
|
||
|
||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||
pointerParent,
|
||
RectTransformUtility.WorldToScreenPoint(null, worldCenter),
|
||
null,
|
||
out Vector2 localPos
|
||
);
|
||
|
||
// X фиксирована, меняется только Y
|
||
return new Vector2(
|
||
pointerRect.anchoredPosition.x, // X остается как есть
|
||
localPos.y + offset.y // Y от целевого элемента + смещение
|
||
);
|
||
}
|
||
|
||
// Метод для ручного обновления позиции указателя (если нужно)
|
||
public void UpdatePointerPosition()
|
||
{
|
||
if (pointerRect == null || content == null) return;
|
||
|
||
GameObject selected = EventSystem.current?.currentSelectedGameObject;
|
||
if (selected == null) return;
|
||
|
||
RectTransform selectedRect = selected.GetComponent<RectTransform>();
|
||
if (selectedRect == null || !selectedRect.IsChildOf(content)) return;
|
||
|
||
pointerRect.anchoredPosition = GetTargetPosition(selectedRect);
|
||
}
|
||
}
|
||
} |