218 lines
8.6 KiB
C#
218 lines
8.6 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 const int OverlayPanelSortOrder = 2000;
|
|
|
|
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>Draw panel above other UI dialogs (sibling order + canvas sort).</summary>
|
|
public static void BringPanelToFront(GameObject panel)
|
|
{
|
|
if (panel == null)
|
|
return;
|
|
|
|
panel.transform.SetAsLastSibling();
|
|
|
|
var canvas = panel.GetComponent<Canvas>();
|
|
if (canvas == null)
|
|
canvas = panel.AddComponent<Canvas>();
|
|
|
|
canvas.overrideSorting = true;
|
|
canvas.sortingOrder = OverlayPanelSortOrder;
|
|
|
|
if (panel.GetComponent<GraphicRaycaster>() == null)
|
|
panel.AddComponent<GraphicRaycaster>();
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|