276 lines
12 KiB
C#
276 lines
12 KiB
C#
|
|
using TMPro;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UVC.Locale; // LocalizationManager를 사용한다면 필요합니다.
|
|||
|
|
|
|||
|
|
namespace UVC.UI.Tooltip
|
|||
|
|
{
|
|||
|
|
public class TooltipManager
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
#region Singleton Implementation
|
|||
|
|
private static TooltipManager _instance;
|
|||
|
|
public static TooltipManager Instance
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
if (_instance == null)
|
|||
|
|
{
|
|||
|
|
_instance = new TooltipManager();
|
|||
|
|
}
|
|||
|
|
return _instance;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 외부에서 직접 생성하지 못하도록 protected 생성자로 변경
|
|||
|
|
protected TooltipManager() { }
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
protected Transform _defaultParentTransform; // 툴팁 인스턴스가 생성될 기본 부모 Transform
|
|||
|
|
protected Canvas _rootCanvas; // 화면 좌표 계산 및 UI 스케일링에 사용될 Canvas
|
|||
|
|
|
|||
|
|
protected GameObject _activeTooltipInstance;
|
|||
|
|
protected TextMeshProUGUI _tooltipTextElement;
|
|||
|
|
protected RectTransform _tooltipRectTransform;
|
|||
|
|
|
|||
|
|
protected bool _isInitialized = false;
|
|||
|
|
|
|||
|
|
protected string tooltipPrefabPath = "Prefabs/UI/Tooltip/Tooltip";
|
|||
|
|
protected virtual string TooltipPrefabPath => tooltipPrefabPath;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// TooltipVisualManager를 초기화합니다. 이 메서드는 한 번만 호출되어야 합니다.
|
|||
|
|
/// 일반적으로 애플리케이션 시작 시점이나 UI 시스템이 로드될 때 호출됩니다.
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="defaultParent">생성된 툴팁 인스턴스의 기본 부모 Transform입니다. 보통 Canvas의 Transform입니다.</param>
|
|||
|
|
/// <param name="rootCanvas">툴팁의 위치와 크기를 계산하는 데 사용될 최상위 Canvas입니다.</param>
|
|||
|
|
/// <param name="tooltipPrefabPath">Resources 폴더 내의 툴팁 UI 프리팹 경로입니다.</param>
|
|||
|
|
public void Initialize(Transform defaultParent, Canvas rootCanvas, string tooltipPrefabPath = null)
|
|||
|
|
{
|
|||
|
|
if (_isInitialized)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning("TooltipVisualManager는 이미 초기화되었습니다.");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (defaultParent == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError("TooltipVisualManager 초기화 실패: defaultParent가 null입니다.");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (rootCanvas == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError("TooltipVisualManager 초기화 실패: rootCanvas가 null입니다.");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(tooltipPrefabPath))
|
|||
|
|
{
|
|||
|
|
this.tooltipPrefabPath = tooltipPrefabPath; // 사용자 지정 경로가 제공되면 업데이트
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
GameObject loadedTooltipPrefab = Resources.Load<GameObject>(this.tooltipPrefabPath);
|
|||
|
|
|
|||
|
|
if (loadedTooltipPrefab == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"TooltipManager 초기화 실패: Resources 폴더에서 '{this.tooltipPrefabPath}' 경로의 프리팹을 로드할 수 없습니다.");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_defaultParentTransform = defaultParent;
|
|||
|
|
_rootCanvas = rootCanvas;
|
|||
|
|
|
|||
|
|
// 툴팁 인스턴스 생성 및 초기화
|
|||
|
|
_activeTooltipInstance = GameObject.Instantiate(loadedTooltipPrefab, _defaultParentTransform);
|
|||
|
|
_tooltipTextElement = _activeTooltipInstance.GetComponentInChildren<TextMeshProUGUI>();
|
|||
|
|
_tooltipRectTransform = _activeTooltipInstance.GetComponent<RectTransform>();
|
|||
|
|
|
|||
|
|
if (_tooltipTextElement == null || _tooltipRectTransform == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError("TooltipVisualManager 초기화 실패: tooltipPrefab의 구성요소가 올바르지 않습니다. TextMeshProUGUI와 RectTransform이 필요합니다.");
|
|||
|
|
if (_activeTooltipInstance != null) GameObject.Destroy(_activeTooltipInstance);
|
|||
|
|
_activeTooltipInstance = null; // 초기화 실패 상태로 설정
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_activeTooltipInstance.SetActive(false); // 처음에는 숨김
|
|||
|
|
_isInitialized = true;
|
|||
|
|
// Debug.Log("TooltipVisualManager 초기화 완료.");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void HandlePointerEnter(string tooltip, Vector3 mousePosition)
|
|||
|
|
{
|
|||
|
|
if (!_isInitialized || _activeTooltipInstance == null)
|
|||
|
|
{
|
|||
|
|
// Debug.LogWarning("TooltipVisualManager가 초기화되지 않았거나 툴팁 인스턴스가 없습니다.");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string tooltipText = tooltip; // 기본값은 키 자체
|
|||
|
|
if (LocalizationManager.Instance != null && !string.IsNullOrEmpty(tooltip))
|
|||
|
|
{
|
|||
|
|
string localizedText = LocalizationManager.Instance.GetString(tooltip);
|
|||
|
|
if (!string.IsNullOrEmpty(localizedText) && localizedText != tooltip) // 번역 성공 시
|
|||
|
|
{
|
|||
|
|
tooltipText = localizedText;
|
|||
|
|
}
|
|||
|
|
else if (string.IsNullOrEmpty(localizedText)) // 번역 결과가 비어있으면 숨김
|
|||
|
|
{
|
|||
|
|
HideTooltip();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (string.IsNullOrEmpty(tooltipText))
|
|||
|
|
{
|
|||
|
|
HideTooltip();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
ShowTooltip(tooltipText, mousePosition);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void HandlePointerExit()
|
|||
|
|
{
|
|||
|
|
if (!_isInitialized) return;
|
|||
|
|
HideTooltip();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ShowTooltip(string text, Vector3 mousePosition)
|
|||
|
|
{
|
|||
|
|
if (_activeTooltipInstance == null || _tooltipTextElement == null || _tooltipRectTransform == null) return;
|
|||
|
|
|
|||
|
|
_tooltipTextElement.text = text;
|
|||
|
|
_activeTooltipInstance.SetActive(true);
|
|||
|
|
|
|||
|
|
_tooltipTextElement.ForceMeshUpdate();
|
|||
|
|
Vector2 textSize = _tooltipTextElement.GetRenderedValues(false);
|
|||
|
|
Vector2 padding = new Vector2(10, 2); // 툴팁 내부 여백 (값 증가)
|
|||
|
|
_tooltipRectTransform.sizeDelta = textSize + padding * 2;
|
|||
|
|
|
|||
|
|
AdjustPosition(mousePosition);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AdjustPosition(Vector3 mousePosition)
|
|||
|
|
{
|
|||
|
|
if (_rootCanvas == null || _tooltipRectTransform == null) return;
|
|||
|
|
|
|||
|
|
Vector2 localPoint;
|
|||
|
|
Camera eventCamera = (_rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay) ? null : _rootCanvas.worldCamera;
|
|||
|
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
|||
|
|
_rootCanvas.transform as RectTransform,
|
|||
|
|
mousePosition,
|
|||
|
|
eventCamera,
|
|||
|
|
out localPoint
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 툴팁을 마우스 오른쪽 아래에 표시 (오프셋 조정)
|
|||
|
|
// Pivot이 (0,1) (좌상단)이라고 가정하고 위치 조정
|
|||
|
|
Vector2 pivotOffset = new Vector2(
|
|||
|
|
_tooltipRectTransform.sizeDelta.x * _tooltipRectTransform.pivot.x + 10f,
|
|||
|
|
_tooltipRectTransform.sizeDelta.y * (1 - _tooltipRectTransform.pivot.y)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
Debug.Log($"Tooltip Positioning: Local Point = {localPoint}, Pivot Offset = {pivotOffset}");
|
|||
|
|
|
|||
|
|
// 기본 위치: 마우스 포인터의 오른쪽 아래
|
|||
|
|
//Vector2 tooltipPosition = localPoint + new Vector2(0f, -0f - _tooltipRectTransform.sizeDelta.y);
|
|||
|
|
|
|||
|
|
|
|||
|
|
// Pivot을 고려하여 최종 localPosition 설정
|
|||
|
|
// _tooltipRectTransform.localPosition = tooltipPosition; // Pivot (0.5, 0.5) 기준이라면 이렇게 간단할 수 있음
|
|||
|
|
// 현재는 Pivot에 따라 위치를 보정하는 코드가 복잡해질 수 있으므로,
|
|||
|
|
// 툴팁 프리팹의 Pivot을 (0,1) (좌상단) 또는 (0.5, 0.5) (중앙) 등으로 통일하는 것을 권장합니다.
|
|||
|
|
// 여기서는 간단히 localPoint를 기준으로 오프셋만 적용합니다. 실제 Pivot에 맞게 조정 필요.
|
|||
|
|
_tooltipRectTransform.localPosition = localPoint + pivotOffset;// localPoint + new Vector2(15, -_tooltipRectTransform.sizeDelta.y - 15);
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 화면 경계 처리
|
|||
|
|
AdjustPositionWithinScreenBounds();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public void HideTooltip()
|
|||
|
|
{
|
|||
|
|
if (_activeTooltipInstance != null)
|
|||
|
|
{
|
|||
|
|
_activeTooltipInstance.SetActive(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AdjustPositionWithinScreenBounds()
|
|||
|
|
{
|
|||
|
|
if (_tooltipRectTransform == null || _activeTooltipInstance == null || !_activeTooltipInstance.activeSelf || _rootCanvas == null) return;
|
|||
|
|
|
|||
|
|
Vector3[] tooltipCorners = new Vector3[4];
|
|||
|
|
_tooltipRectTransform.GetWorldCorners(tooltipCorners); // 툴팁의 월드 좌표 코너
|
|||
|
|
|
|||
|
|
RectTransform canvasRectTransform = _rootCanvas.transform as RectTransform;
|
|||
|
|
Vector3[] canvasCorners = new Vector3[4];
|
|||
|
|
canvasRectTransform.GetWorldCorners(canvasCorners); // 캔버스의 월드 좌표 코너 (Screen Space Overlay에서는 화면 전체)
|
|||
|
|
|
|||
|
|
// Screen Space Overlay에서는 Screen.width/height를 사용하는 것이 더 직관적일 수 있음
|
|||
|
|
float minX = 0;
|
|||
|
|
float maxX = Screen.width;
|
|||
|
|
float minY = 0;
|
|||
|
|
float maxY = Screen.height;
|
|||
|
|
|
|||
|
|
if (_rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
|
|||
|
|
{
|
|||
|
|
// Screen Space Camera 또는 World Space의 경우 캔버스 경계를 사용
|
|||
|
|
minX = canvasCorners[0].x; // Bottom-left X
|
|||
|
|
maxX = canvasCorners[2].x; // Top-right X
|
|||
|
|
minY = canvasCorners[0].y; // Bottom-left Y
|
|||
|
|
maxY = canvasCorners[1].y; // Top-left Y (또는 corners[2].y)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Vector3 currentPosition = _tooltipRectTransform.position;
|
|||
|
|
Vector2 size = _tooltipRectTransform.sizeDelta * _rootCanvas.scaleFactor; // 실제 픽셀 크기
|
|||
|
|
Vector2 pivot = _tooltipRectTransform.pivot;
|
|||
|
|
|
|||
|
|
// 오른쪽 경계 넘어감
|
|||
|
|
if (tooltipCorners[2].x > maxX)
|
|||
|
|
{
|
|||
|
|
currentPosition.x -= (tooltipCorners[2].x - maxX);
|
|||
|
|
}
|
|||
|
|
// 왼쪽 경계 넘어감
|
|||
|
|
if (tooltipCorners[0].x < minX)
|
|||
|
|
{
|
|||
|
|
currentPosition.x += (minX - tooltipCorners[0].x);
|
|||
|
|
}
|
|||
|
|
// 아래쪽 경계 넘어감 -> 위로 표시 (마우스 포인터 위쪽으로)
|
|||
|
|
if (tooltipCorners[0].y < minY)
|
|||
|
|
{
|
|||
|
|
// 마우스 포인터의 월드 Y + 약간의 오프셋 + 툴팁 높이 (pivot 고려)
|
|||
|
|
Vector3 mouseWorldPos = Vector3.zero;
|
|||
|
|
if (_rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay) mouseWorldPos = Input.mousePosition;
|
|||
|
|
else RectTransformUtility.ScreenPointToWorldPointInRectangle(canvasRectTransform, Input.mousePosition, _rootCanvas.worldCamera, out mouseWorldPos);
|
|||
|
|
|
|||
|
|
currentPosition.y = mouseWorldPos.y + (size.y * (1 - pivot.y)) + 15f; // 15f는 오프셋
|
|||
|
|
}
|
|||
|
|
// 위쪽 경계 넘어감
|
|||
|
|
if (tooltipCorners[1].y > maxY) // Top-left corner Y
|
|||
|
|
{
|
|||
|
|
currentPosition.y -= (tooltipCorners[1].y - maxY);
|
|||
|
|
}
|
|||
|
|
_tooltipRectTransform.position = currentPosition;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// TooltipVisualManager가 사용하던 리소스를 해제합니다.
|
|||
|
|
/// 애플리케이션 종료 시 또는 UI 시스템 해제 시 호출하는 것이 좋습니다.
|
|||
|
|
/// </summary>
|
|||
|
|
public void Dispose()
|
|||
|
|
{
|
|||
|
|
if (_activeTooltipInstance != null)
|
|||
|
|
{
|
|||
|
|
GameObject.Destroy(_activeTooltipInstance);
|
|||
|
|
_activeTooltipInstance = null;
|
|||
|
|
}
|
|||
|
|
_tooltipTextElement = null;
|
|||
|
|
_tooltipRectTransform = null;
|
|||
|
|
_defaultParentTransform = null;
|
|||
|
|
_rootCanvas = null;
|
|||
|
|
_isInitialized = false;
|
|||
|
|
_instance = null; // 싱글톤 인스턴스 참조 해제
|
|||
|
|
// Debug.Log("TooltipVisualManager Disposed.");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|