using System.Collections.Generic; using GeometryTD.DataTable; using GeometryTD.Definition; using TMPro; using UnityEngine; using UnityEngine.UI; using UnityGameFramework.Runtime; namespace GeometryTD.UI { public class ItemDescForm : UGuiForm { [SerializeField] private RectTransform _content; [SerializeField] private TMP_Text _itemTitle; [SerializeField] private TMP_Text _itemTypeText; [SerializeField] private TMP_Text _itemDescription; [SerializeField] private TMP_Text _itemPrice; [SerializeField] private Transform _tagAreaParent; [SerializeField] private GameObject _tagItemPrefab; [SerializeField] private bool _autoResizeHeight = true; [SerializeField] private float _fixedTopHeight = 150f; [SerializeField] private float _fixedBottomHeight = 150f; [SerializeField] private float _fixedWidth = 300f; [SerializeField] private float _screenEdgePadding = 0f; [SerializeField] private float _sideGap = 16f; [SerializeField] private float _anchorItemWidth = 0f; [SerializeField] private Graphic _blankAreaGraphic; [SerializeField] private bool _disableBlankAreaRaycast = true; [SerializeField] private bool _closeOnPointerDownOutsideContent = true; private readonly List _runtimeTagItems = new List(); private ItemDescFormContext _context; private Vector3 _targetPos; private bool _isOpened; public void RefreshUI(ItemDescFormContext context) { if (context == null) { Log.Warning("ItemDescForm context is invalid."); return; } _context = context; _targetPos = ConvertWorldToAnchored(_context.TargetPos); if (_content != null) { _content.anchoredPosition3D = _targetPos; } if (_itemTitle != null) _itemTitle.text = _context.Title ?? string.Empty; if (_itemTypeText != null) _itemTypeText.text = _context.TypeText ?? string.Empty; if (_itemDescription != null) _itemDescription.text = _context.Description ?? string.Empty; if (_itemPrice != null) _itemPrice.text = $"Price: {_context.Price} Gold"; RefreshTags(_context.Tags); ResizeToFitContent(); ApplySideAnchoredTargetPos(); } protected override void OnOpen(object userData) { base.OnOpen(userData); _isOpened = true; if (_disableBlankAreaRaycast && _blankAreaGraphic != null) { _blankAreaGraphic.raycastTarget = false; } if (!(userData is ItemDescFormContext context)) { Log.Error("ItemDescFormContext is invalid."); return; } RefreshUI(context); } protected override void OnClose(bool isShutdown, object userData) { _isOpened = false; ClearTags(); _context = null; base.OnClose(isShutdown, userData); } public void OnBlankAreaClick() { CloseSelf(); } public void OnBackgroundClick() { CloseSelf(); } private void Update() { if (!_isOpened || !_closeOnPointerDownOutsideContent || _content == null) { return; } if (!TryGetPointerDownScreenPosition(out Vector2 screenPoint)) { return; } if (IsPointerInContent(screenPoint)) { return; } CloseSelf(); } private void ResizeToFitContent() { if (!_autoResizeHeight || _content == null) { return; } if (_itemDescription == null) { return; } _itemDescription.ForceMeshUpdate(); var descriptionSize = _itemDescription.rectTransform.sizeDelta; descriptionSize.y = _itemDescription.preferredHeight; _itemDescription.rectTransform.sizeDelta = descriptionSize; float descriptionHeight = Mathf.Max(0f, _itemDescription.preferredHeight); float targetHeight = _fixedTopHeight + descriptionHeight + _fixedBottomHeight; Vector2 size = new Vector2(_fixedWidth, targetHeight); _content.sizeDelta = size; } private void ApplySideAnchoredTargetPos() { if (_content == null) { return; } RectTransform parent = _content.parent as RectTransform; if (parent == null) { return; } Vector2 size = _content.sizeDelta; if (size.x <= 0f || size.y <= 0f) { size = _content.rect.size; } Vector2 pivot = _content.pivot; float halfWidth = parent.rect.width * 0.5f; float halfHeight = parent.rect.height * 0.5f; float minX = -halfWidth + size.x * pivot.x + _screenEdgePadding; float maxX = halfWidth - size.x * (1f - pivot.x) - _screenEdgePadding; float minY = -halfHeight + size.y * pivot.y + _screenEdgePadding; float maxY = halfHeight - size.y * (1f - pivot.y) - _screenEdgePadding; float clampedY = minY <= maxY ? Mathf.Clamp(_targetPos.y, minY, maxY) : 0f; float anchorHalfWidth = Mathf.Max(0f, _anchorItemWidth * 0.5f); float sideGap = Mathf.Max(0f, _sideGap); float horizontalOffset = anchorHalfWidth + size.x * 0.5f + sideGap; float leftCandidateX = _targetPos.x - horizontalOffset; float rightCandidateX = _targetPos.x + horizontalOffset; float finalX; if (IsInsideBounds(leftCandidateX, minX, maxX)) { finalX = leftCandidateX; } else if (IsInsideBounds(rightCandidateX, minX, maxX)) { finalX = rightCandidateX; } else { finalX = minX <= maxX ? Mathf.Clamp(leftCandidateX, minX, maxX) : 0f; } _content.anchoredPosition3D = new Vector3(finalX, clampedY, _targetPos.z); } private Vector3 ConvertWorldToAnchored(Vector3 worldPos) { if (_content == null) { return worldPos; } RectTransform parent = _content.parent as RectTransform; if (parent == null) { return worldPos; } Canvas canvas = _content.GetComponentInParent(); Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null; Camera uiCamera = null; if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay) { uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main; } Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(uiCamera, worldPos); if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parent, screenPoint, uiCamera, out Vector2 localPoint)) { return worldPos; } float z = _content.anchoredPosition3D.z; return new Vector3(localPoint.x, localPoint.y, z); } private static bool IsInsideBounds(float value, float min, float max) { return value >= min && value <= max; } private void RefreshTags(List tags) { ClearTags(); if (_tagAreaParent == null || _tagItemPrefab == null || tags == null || tags.Count <= 0) { return; } if (_tagItemPrefab.scene.IsValid() && _tagItemPrefab.transform.IsChildOf(_tagAreaParent)) { _tagItemPrefab.SetActive(false); } for (int i = 0; i < tags.Count; i++) { string tagName = ResolveTagName(tags[i]); if (string.IsNullOrWhiteSpace(tagName)) { continue; } GameObject tagGo = Instantiate(_tagItemPrefab, _tagAreaParent); tagGo.SetActive(true); if (tagGo.TryGetComponent(out TagItem tagItem)) { tagItem.OnInit(new TagItemContext { TagName = tagName }); } else { TMP_Text tagText = tagGo.GetComponentInChildren(true); if (tagText != null) { tagText.text = tagName; } } _runtimeTagItems.Add(tagGo); } } private void ClearTags() { for (int i = _runtimeTagItems.Count - 1; i >= 0; i--) { GameObject tagGo = _runtimeTagItems[i]; if (tagGo != null) { Destroy(tagGo); } } _runtimeTagItems.Clear(); } private static string ResolveTagName(TagType tagType) { if (tagType == TagType.None) { return string.Empty; } var tagTable = GameEntry.DataTable.GetDataTable(); if (tagTable != null) { DRTag tagRow = tagTable.GetDataRow((int)tagType); if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name)) { return tagRow.Name; } } return tagType.ToString(); } private void CloseSelf() { GameEntry.UI.CloseUIForm(this); } private bool IsPointerInContent(Vector2 screenPoint) { Canvas canvas = _content.GetComponentInParent(); Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null; Camera uiCamera = null; if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay) { uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main; } return RectTransformUtility.RectangleContainsScreenPoint(_content, screenPoint, uiCamera); } private static bool TryGetPointerDownScreenPosition(out Vector2 screenPoint) { if (Input.touchCount > 0) { for (int i = 0; i < Input.touchCount; i++) { Touch touch = Input.GetTouch(i); if (touch.phase != TouchPhase.Began) { continue; } screenPoint = touch.position; return true; } } if (Input.GetMouseButtonDown(0)) { screenPoint = Input.mousePosition; return true; } screenPoint = default; return false; } } }