TH1/Unity/Assets/Scripts/TH1_UI/HintUI/HintWindowController.cs
2026-04-21 22:21:01 +08:00

165 lines
5.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
using TH1_UI.HintUI;
using UI.HintUI;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
[RequireComponent(typeof(RectTransform))]
public class HintWindowController : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI _text;
[SerializeField] private Vector2 _padding = new Vector2(16, 10);
[SerializeField] private Vector2 _anchorOffset = new Vector2(16, 16);
[Header("SubHint")]
[SerializeField] private GameObject _line;
[SerializeField] private RectTransform _subHintArea;
[SerializeField] private SubHintItemMono _subHintItemPrefab;
private List<SubHintItemMono> _subHintItems = new List<SubHintItemMono>();
private RectTransform _selfRt;
private Canvas _rootCanvas;
private Camera _uiCamera;
private void Awake()
{
_selfRt = (RectTransform)transform;
_rootCanvas = GetComponentInParent<Canvas>();
_uiCamera = _rootCanvas.worldCamera;
// 兜底找 Text
if (_text == null)
_text = GetComponentInChildren<TextMeshProUGUI>(true);
if (_text == null)
Debug.LogError("[HintWindowController] 找不到 TextMeshProUGUI", this);
}
/// <summary>
/// 设置 SubHintArea 的内容。传入 null 或空列表则隐藏 Line 和 SubHintArea。
/// 每个 HintDataProvider 对应一个 SubHintItemMono。
/// </summary>
public void SetSubHint(List<HintDataProvider> providers)
{
bool show = providers != null && providers.Count > 0;
if (_line != null) _line.SetActive(show);
if (_subHintArea != null) _subHintArea.gameObject.SetActive(show);
if (!show) return;
// 确保有足够的 SubHintItemMono 子对象
while (_subHintItems.Count < providers.Count && _subHintArea != null)
{
SubHintItemMono newItem;
if (_subHintItemPrefab != null)
{
newItem = Instantiate(_subHintItemPrefab, _subHintArea);
}
else
{
// 没有指定 Prefab 时,动态创建
var go = new GameObject($"SubHintItem_{_subHintItems.Count}", typeof(RectTransform));
go.transform.SetParent(_subHintArea, false);
var tmp = go.AddComponent<TextMeshProUGUI>();
tmp.fontSize = _text != null ? _text.fontSize : 14f;
tmp.color = _text != null ? _text.color : Color.white;
tmp.enableAutoSizing = false;
newItem = go.AddComponent<SubHintItemMono>();
}
_subHintItems.Add(newItem);
}
// 设置数据源并控制显隐
for (int i = 0; i < _subHintItems.Count; i++)
{
if (i < providers.Count)
{
_subHintItems[i].gameObject.SetActive(true);
_subHintItems[i].SetDataProvider(providers[i]);
}
else
{
_subHintItems[i].gameObject.SetActive(false);
}
}
}
public void Present(Vector2 mouseScreenPos)
{
if (_text == null) return;
RefreshSize();
ClampToScreen(mouseScreenPos);
}
public void RefreshSize()
{
_text.ForceMeshUpdate(false, false);
LayoutRebuilder.ForceRebuildLayoutImmediate(_text.rectTransform);
float w = _text.preferredWidth + _padding.x * 2f;
float h = _text.preferredHeight + _padding.y * 2f;
// 如果 SubHintArea 可见,把它的高度也算进去
if (_subHintArea != null && _subHintArea.gameObject.activeSelf)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(_subHintArea);
float lineH = (_line != null && _line.activeSelf) ? ((RectTransform)_line.transform).rect.height : 0f;
h += lineH + _subHintArea.rect.height;
}
_selfRt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, w);
_selfRt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, h);
Canvas.ForceUpdateCanvases();
}
private void ClampToScreen(Vector2 mouseScreenPos)
{
float scaleFactor = _rootCanvas.scaleFactor;
if (scaleFactor <= 0) scaleFactor = 1f;
float windowW = _selfRt.rect.width * scaleFactor;
float windowH = _selfRt.rect.height * scaleFactor;
Vector2 desiredTopLeft = mouseScreenPos + _anchorOffset;
Rect visibleRect = GetVisibleScreenRect();
float minX = visibleRect.xMin;
float maxX = Mathf.Max(minX, visibleRect.xMax - windowW);
float minY = visibleRect.yMin + windowH;
float maxY = Mathf.Max(minY, visibleRect.yMax);
float clampedX = Mathf.Clamp(desiredTopLeft.x, minX, maxX);
float clampedY = Mathf.Clamp(desiredTopLeft.y, minY, maxY);
Vector2 pivot = _selfRt.pivot;
Vector2 pivotScreenPos = new Vector2(
clampedX + pivot.x * windowW,
clampedY - (1 - pivot.y) * windowH
);
RectTransform canvasRt = _rootCanvas.GetComponent<RectTransform>();
RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvasRt,
pivotScreenPos,
_uiCamera,
out Vector2 localPos);
_selfRt.localPosition = localPos;
_selfRt.SetAsLastSibling();
}
private Rect GetVisibleScreenRect()
{
if (_uiCamera != null)
{
return _uiCamera.pixelRect;
}
return new Rect(0f, 0f, Screen.width, Screen.height);
}
}