Files
test/Assets/PerfectWorld/Scripts/UI/EC_UIUtility.cs
T
2026-02-03 15:01:57 +07:00

197 lines
7.9 KiB
C#

// Filename: EC_UIUtility.cs
// Creator: Extracted from EC_InventoryUI.cs
// Date: 2024
// Purpose: Reusable UI helper functions for panel positioning, text handling, and layout management
using UnityEngine;
using UnityEngine.UI;
namespace BrewMonster.Scripts.UI
{
/// <summary>
/// General-purpose UI utility class providing reusable functions for:
/// - Smart panel positioning near buttons with edge detection
/// - Dual text component support (Legacy Text + TextMeshPro)
/// - Layout refresh helpers
/// </summary>
public static class EC_UIUtility
{
/// <summary>
/// Position a panel near a button with smart edge detection to keep panel on screen.
/// Automatically chooses left or right placement based on available space.
/// </summary>
/// <param name="panelRect">The panel RectTransform to position</param>
/// <param name="buttonRect">The button RectTransform to position near</param>
/// <param name="offset">Offset from button (x: horizontal spacing, y: vertical offset)</param>
public static void PositionPanelNearButton(RectTransform panelRect, RectTransform buttonRect, Vector2 offset)
{
if (panelRect == null || buttonRect == null)
{
Debug.LogWarning("[EC_UIUtility] PositionPanelNearButton: panelRect or buttonRect is null");
return;
}
var canvas = panelRect.GetComponentInParent<Canvas>();
if (canvas == null)
{
Debug.LogWarning("[EC_UIUtility] PositionPanelNearButton: Cannot find parent Canvas");
return;
}
var parentRect = panelRect.parent as RectTransform;
if (parentRect == null)
{
Debug.LogWarning("[EC_UIUtility] PositionPanelNearButton: Panel parent is not a RectTransform");
return;
}
Camera eventCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
Vector3 worldCenter = buttonRect.TransformPoint(buttonRect.rect.center);
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(eventCamera, worldCenter);
Vector2 localPoint;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRect, screenPoint, eventCamera, out localPoint))
{
Debug.LogWarning("[EC_UIUtility] PositionPanelNearButton: Failed to convert screen point to local point");
return;
}
float btnHalfW = buttonRect.rect.width * 0.5f;
float panelW = panelRect.rect.width;
float panelH = panelRect.rect.height;
float pivotX = panelRect.pivot.x;
float pivotY = panelRect.pivot.y;
// Compute right-placement candidate (panel's left edge at button's right edge + offset)
float leftEdgeRightPlacement = localPoint.x + btnHalfW + offset.x;
float candidateXRight = leftEdgeRightPlacement + pivotX * panelW;
// Compute left-placement candidate (panel's right edge at button's left edge - offset)
float rightEdgeLeftPlacement = localPoint.x - btnHalfW - offset.x;
float candidateXLeft = rightEdgeLeftPlacement - (1f - pivotX) * panelW;
// Vertical clamping honoring pivot
float minY = parentRect.rect.yMin + pivotY * panelH;
float maxY = parentRect.rect.yMax - (1f - pivotY) * panelH;
float candidateY = Mathf.Clamp(localPoint.y + offset.y, minY, maxY);
// Choose side based on available space
float rightEdgeOfRight = candidateXRight + (1f - pivotX) * panelW;
float canvasRight = parentRect.rect.xMax;
float canvasLeft = parentRect.rect.xMin;
float leftEdgeOfLeft = candidateXLeft - pivotX * panelW;
Vector2 finalPos;
if (rightEdgeOfRight <= canvasRight)
{
// Right placement fits
finalPos = new Vector2(candidateXRight, candidateY);
}
else if (leftEdgeOfLeft >= canvasLeft)
{
// Left placement fits
finalPos = new Vector2(candidateXLeft, candidateY);
}
else
{
// Fallback: clamp within canvas horizontally
float minX = canvasLeft + pivotX * panelW;
float maxX = canvasRight - (1f - pivotX) * panelW;
finalPos = new Vector2(Mathf.Clamp(candidateXRight, minX, maxX), candidateY);
}
panelRect.anchoredPosition = finalPos;
}
/// <summary>
/// Force Unity to rebuild layout immediately for a RectTransform.
/// Useful after dynamically changing content that affects layout.
/// </summary>
/// <param name="rectTransform">The RectTransform to refresh</param>
public static void RefreshLayout(RectTransform rectTransform)
{
if (rectTransform == null)
{
Debug.LogWarning("[EC_UIUtility] RefreshLayout: rectTransform is null");
return;
}
// Force Unity to rebuild layout immediately
rectTransform.ForceUpdateRectTransforms();
LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform);
}
/// <summary>
/// Show or hide a panel with layout refresh.
/// </summary>
/// <param name="panel">The GameObject panel to show/hide</param>
/// <param name="show">True to show, false to hide</param>
public static void ShowPanel(GameObject panel, bool show)
{
if (panel == null)
{
Debug.LogWarning("[EC_UIUtility] ShowPanel: panel is null");
return;
}
panel.SetActive(show);
if (show)
{
var rectTransform = panel.GetComponent<RectTransform>();
if (rectTransform != null)
{
RefreshLayout(rectTransform);
}
}
}
/// <summary>
/// Helper class for managing dual text component support (Legacy Text + TextMeshPro).
/// Allows UI components to work with both text systems simultaneously.
/// </summary>
[System.Serializable]
public class TextOutlet
{
[SerializeField] public Text legacy;
[SerializeField] public TMPro.TextMeshProUGUI tmp;
/// <summary>
/// Set text on both legacy and TMP components with automatic formatting.
/// </summary>
/// <param name="value">The text to display</param>
public void Set(string value)
{
if (legacy != null)
{
legacy.text = EC_Utility.FormatForLegacyText(value ?? string.Empty);
}
if (tmp != null)
{
tmp.text = EC_Utility.FormatForTextMeshPro(value ?? string.Empty);
}
}
/// <summary>
/// Set text with explicit formatting preference.
/// </summary>
/// <param name="value">Raw text with formatting codes</param>
/// <param name="preferTextMeshPro">Whether to prefer TextMeshPro formatting</param>
public void SetFormatted(string value, bool preferTextMeshPro = true)
{
string formattedText = preferTextMeshPro ?
EC_Utility.FormatForTextMeshPro(value ?? string.Empty) :
EC_Utility.FormatForLegacyText(value ?? string.Empty);
if (legacy != null)
{
legacy.text = formattedText;
}
if (tmp != null)
{
tmp.text = formattedText;
}
}
}
}
}