165 lines
5.5 KiB
C#
165 lines
5.5 KiB
C#
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);
|
||
}
|
||
}
|