mouse event 처리 중

This commit is contained in:
logonkhi
2025-06-23 20:06:15 +09:00
parent f79372b4de
commit d8e753c1a1
68 changed files with 2676 additions and 272 deletions

View File

@@ -4,6 +4,7 @@ using UnityEngine.UI;
using UVC.Locale;
using UVC.Log;
using UVC.UI.Commands;
using UVC.UI.Modal;
namespace UVC.UI.Menu
{
@@ -226,7 +227,22 @@ namespace UVC.UI.Menu
}),
new MenuItemData("preferences", "menu_preferences", new DebugLogCommand("환경설정 선택됨 (Command 실행)"))
}));
model.MenuItems.Add(new MenuItemData("modal", "모달", subMenuItems: new List<MenuItemData>
{
new MenuItemData("alert", "Alert", new ActionCommand(async () => {
await Alert.Show("알림", "이것은 간단한 알림 메시지입니다.");
await Alert.Show("경고", "데이터를 저장할 수 없습니다.", "알겠습니다");
await Alert.Show("error", "error_network_not", "button_retry");
})),
new MenuItemData("confirm", "Confirm", new ActionCommand(async () => {
bool result = await Confirm.Show("확인", "이것은 간단한 알림 메시지입니다.");
ULog.Debug($"사용자가 확인 버튼을 눌렀나요? {result}");
result = await Confirm.Show("경고", "데이터를 저장할 수 없습니다.", "알겠습니다", "아니요");
ULog.Debug($"사용자가 알림을 확인했나요? {result}");
result = await Confirm.Show("error", "error_network_not", "button_retry", "button_cancel");
ULog.Debug($"사용자가 네트워크 오류 알림을 확인했나요? {result}");
}))
}));
model.MenuItems.Add(new MenuItemData("language", "menu_language", subMenuItems: new List<MenuItemData>
{
// 각 언어 메뉴 아이템에 ChangeLanguageCommand를 연결하여 언어 변경 기능 수행

View File

@@ -166,8 +166,7 @@ namespace UVC.UI.Menu
{
uiBlockerInstance = new GameObject("TopMenuUIBlocker");
// Canvas를 찾아 그 자식으로 설정합니다. 씬에 여러 Canvas가 있다면, 적절한 Canvas를 찾는 로직이 필요할 수 있습니다.
// 여기서는 FindFirstObjectByType을 사용하여 첫 번째 활성 Canvas를 찾습니다.
Canvas canvas = FindFirstObjectByType<Canvas>();
Canvas canvas = GetComponentInParent<Canvas>();
Transform blockerParent = canvas != null ? canvas.transform : transform.parent; // Canvas가 없으면 TopMenuView의 부모를 사용
if (blockerParent == null) // 부모를 찾지 못한 극단적인 경우, TopMenuView 자신을 부모로 설정 (권장되지 않음)

View File

@@ -4,6 +4,7 @@ using System; // System.Type 사용을 위해 추가
using System.Threading;
using UnityEngine;
using UVC.Log;
using UVC.util;
namespace UVC.UI.Modal
{
@@ -133,7 +134,7 @@ namespace UVC.UI.Modal
if (blockerPrefabObj != null)
{
// 화면에서 가장 큰 그림판(Canvas)을 찾아서 그 위에 방패를 놓을 거예요.
Canvas mainCanvasForBlocker = UnityEngine.Object.FindFirstObjectByType<Canvas>();
Canvas mainCanvasForBlocker = CanvasUtil.GetOrCreate("ModalCanvas");
if (mainCanvasForBlocker != null)
{
// 방패를 복제해서(Instantiate) 그림판 위에 놓고, 가장 위로 오도록 순서를 조정해요.
@@ -162,7 +163,7 @@ namespace UVC.UI.Modal
}
// 모달 창도 가장 큰 그림판 위에 놓을 거예요.
Canvas mainCanvasForModal = UnityEngine.Object.FindFirstObjectByType<Canvas>();
Canvas mainCanvasForModal = CanvasUtil.GetOrCreate("ModalCanvas");
if (mainCanvasForModal == null) // 그림판을 못 찾으면,
{
ULog.Error("[Modal] 모달을 표시할 Canvas를 찾을 수 없습니다.");
@@ -198,7 +199,7 @@ namespace UVC.UI.Modal
{
ULog.Debug("[Modal] 활성 모달 인스턴스가 외부에서 파괴되어 취소로 처리합니다.");
// 파괴된 모달에서 ModalView를 가져오려고 시도해요 (없을 수도 있지만).
ModalView viewOnDestroy = currentModalInstance != null ? currentModalInstance.GetComponent<ModalView>() : null;
ModalView? viewOnDestroy = currentModalInstance != null ? currentModalInstance.GetComponent<ModalView>() : null;
// 그리고 "외부에서 파괴됐으니 취소할게요" 라고 알리면서 정리해요.
await CleanupCurrentModalResources(currentContent, viewOnDestroy, false, true, tcs, typeof(T));
}

View File

@@ -302,16 +302,16 @@ namespace UVC.UI.Toolbar.View
public void Update()
{
// 마우스 왼쪽 버튼이 클릭되었고, 하위 메뉴가 열려있는 상태일 때
if (Input.GetMouseButtonDown(0) && _currentSubMenu != null && _currentSubMenu.activeSelf && _view.rootCanvas != null)
if (Input.GetMouseButtonDown(0) && _currentSubMenu != null && _currentSubMenu.activeSelf && _view.Canvas != null)
{
RectTransform subMenuRect = _currentSubMenu.GetComponent<RectTransform>();
if (subMenuRect == null) return;
// 캔버스의 렌더 모드에 따라 이벤트 카메라를 가져옵니다.
Camera eventCamera = null;
if (_view.rootCanvas.renderMode == RenderMode.ScreenSpaceCamera || _view.rootCanvas.renderMode == RenderMode.WorldSpace)
if (_view.Canvas.renderMode == RenderMode.ScreenSpaceCamera || _view.Canvas.renderMode == RenderMode.WorldSpace)
{
eventCamera = _view.rootCanvas.worldCamera;
eventCamera = _view.Canvas.worldCamera;
}
// 마우스 포인터가 하위 메뉴 영역 바깥에 있는지 확인합니다.

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -6,6 +7,7 @@ using UVC.Extension;
using UVC.Locale;
using UVC.UI.Toolbar.Model;
using UVC.UI.Tooltip;
using UVC.util;
namespace UVC.UI.Toolbar.View
{
@@ -112,16 +114,24 @@ namespace UVC.UI.Toolbar.View
/// </summary>
private Dictionary<System.Type, IButtonViewProcessor> _buttonViewProcessors = new Dictionary<System.Type, IButtonViewProcessor>();
/// <summary>
/// 툴팁 표시에 사용될 루트 Canvas입니다.
/// Inspector에서 할당하거나, Awake에서 자동으로 찾으려고 시도합니다.
/// </summary>
internal Canvas rootCanvas;
// --- 헬퍼 클래스 ---
private ToggleGroupManager _toggleGroupManager;
private SubMenuHandler _subMenuHandler;
private Canvas _canvas;
/// <summary>
/// 렌더링 작업에 사용되는 정적 캔버스 인스턴스를 가져옵니다.
/// </summary>
public Canvas Canvas
{
get
{
if (_canvas == null) _canvas = CanvasUtil.GetOrCreate("StaticCanvas");
return _canvas;
}
}
/// <summary>
/// MonoBehaviour의 Awake 메서드입니다.
/// 필수 참조를 확인 및 초기화하고, 버튼 뷰 프로세서와 헬퍼 클래스들을 준비합니다.
@@ -151,25 +161,6 @@ namespace UVC.UI.Toolbar.View
}
if (layoutGroup == null) Debug.LogError("ToolbarView: layoutGroup이 할당되지 않았습니다.", this);
// 툴팁 표시에 필요한 루트 캔버스를 찾습니다.
if (rootCanvas == null) rootCanvas = GetComponentInParent<Canvas>();
if (rootCanvas == null)
{
Canvas[] canvases = FindObjectsByType<Canvas>(FindObjectsSortMode.None);
foreach (Canvas c in canvases) { if (c.isRootCanvas) { rootCanvas = c; break; } }
if (rootCanvas == null && canvases.Length > 0) rootCanvas = canvases[0];
}
// 툴팁 매니저를 초기화합니다.
if (rootCanvas != null)
{
if (!TooltipManager.Instance.IsInitialized) TooltipManager.Instance.Initialize(rootCanvas.transform, rootCanvas);
}
else
{
Debug.LogError("ToolbarView: rootCanvas를 찾을 수 없어 TooltipManager를 초기화할 수 없습니다.");
}
// 각 버튼 타입에 대한 뷰 프로세서를 등록합니다.
_buttonViewProcessors[typeof(ToolbarStandardButton)] = new ToolbarStandardButtonViewProcessor();
_buttonViewProcessors[typeof(ToolbarToggleButton)] = new ToolbarToggleButtonViewProcessor();

View File

@@ -63,6 +63,7 @@ namespace UVC.UI.Tooltip
private Coroutine _showTooltipCoroutine; // 툴팁 표시 지연을 위한 코루틴 참조
private const float TooltipDelay = 0.5f; // 툴팁 표시까지의 지연 시간 (초 단위)
private const float MouseMoveThreshold = 5f; // 마우스 이동 감지 임계값 (픽셀 단위)
/// <summary>
/// 마우스 포인터가 이 UI 요소의 영역 안으로 들어왔을 때 호출됩니다. (IPointerEnterHandler 인터페이스 구현)
@@ -90,12 +91,36 @@ namespace UVC.UI.Tooltip
/// 지정된 시간(TooltipDelay)만큼 대기한 후, OnPointerEnterAction을 호출하여 툴팁 표시를 요청하는 코루틴입니다.
/// </summary>
/// <param name="tooltip">표시할 툴팁 내용 또는 다국어 키입니다.</param>
/// <param name="mousePosition">현재 마우스 포인터의 화면 좌표입니다.</param>
private IEnumerator ShowTooltipAfterDelayCoroutine(string tooltip, Vector3 mousePosition)
/// <param name="initialMousePosition">현재 마우스 포인터의 화면 좌표입니다.</param>
private IEnumerator ShowTooltipAfterDelayCoroutine(string tooltip, Vector3 initialMousePosition)
{
yield return new WaitForSeconds(TooltipDelay); // 지정된 시간만큼 대기
float stillTime = 0f; // 마우스가 움직이지 않은 시간
Vector3 lastMousePosition = initialMousePosition;
while (stillTime < TooltipDelay)
{
// 현재 마우스 위치와 마지막 기록된 위치 간의 거리 계산
float distance = Vector3.Distance(Input.mousePosition, lastMousePosition);
// 거리가 임계값을 초과하면 마우스가 움직인 것으로 간주
if (distance > MouseMoveThreshold)
{
// 마우스가 움직였으므로 정지 시간을 리셋
stillTime = 0f;
lastMousePosition = Input.mousePosition;
}
else
{
// 마우스가 거의 움직이지 않으면 정지 시간 증가
stillTime += Time.unscaledDeltaTime;
}
yield return null; // 다음 프레임까지 대기
}
// TooltipDelay 시간 동안 마우스가 거의 움직이지 않았으면 툴팁 표시
_showTooltipCoroutine = null; // 코루틴 완료 후 참조 null 처리
OnPointerEnterAction?.Invoke(tooltip, mousePosition); // 연결된 액션 호출 (TooltipManager.HandlePointerEnter)
OnPointerEnterAction?.Invoke(tooltip, Input.mousePosition); // 현재 마우스 위치 사용
}
/// <summary>

View File

@@ -1,7 +1,8 @@
using TMPro;
#nullable enable
using TMPro;
using UnityEngine;
using UVC.Locale;
using UVC.Log; // LocalizationManager를 사용한다면 필요합니다.
using UVC.util; // LocalizationManager를 사용한다면 필요합니다.
namespace UVC.UI.Tooltip
{
@@ -40,7 +41,7 @@ namespace UVC.UI.Tooltip
#endregion
protected Transform _defaultParentTransform; // 툴팁 인스턴스가 생성될 기본 부모 Transform
protected Canvas _rootCanvas; // 화면 좌표 계산 및 UI 스케일링에 사용될 Canvas
protected Canvas canvas; // 화면 좌표 계산 및 UI 스케일링에 사용될 Canvas
protected GameObject _activeTooltipInstance; // 현재 활성화된 툴팁 게임 오브젝트
protected TextMeshProUGUI _tooltipTextElement; // 툴팁 텍스트를 표시하는 TextMeshProUGUI 컴포넌트
@@ -81,15 +82,15 @@ namespace UVC.UI.Tooltip
/// void Start()
/// {
/// // mainCanvas.transform을 부모로, mainCanvas를 루트 캔버스로 하여 초기화
/// TooltipManager.Instance.Initialize(mainCanvas.transform, mainCanvas);
/// TooltipManager.Instance.Initialize(mainCanvas.transform);
///
/// // 특정 프리팹 경로를 사용하고 싶다면:
/// // TooltipManager.Instance.Initialize(mainCanvas.transform, mainCanvas, "MyCustomTooltipPrefab");
/// // TooltipManager.Instance.Initialize(mainCanvas.transform, "MyCustomTooltipPrefab");
/// }
/// }
/// </code>
/// </example>
public void Initialize(Transform defaultParent, Canvas rootCanvas, string tooltipPrefabPath = null)
public void Initialize(Transform? defaultParent = null, string? tooltipPrefabPath = null)
{
if (_isInitialized)
{
@@ -97,17 +98,6 @@ namespace UVC.UI.Tooltip
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; // 사용자 지정 경로가 제공되면 업데이트
@@ -124,8 +114,11 @@ namespace UVC.UI.Tooltip
return;
}
canvas = CanvasUtil.GetOrCreate("ModalCanvas");
defaultParent ??= canvas.transform; // 기본 부모가 null인 경우, 새로 생성한 Canvas의 Transform을 사용
_defaultParentTransform = defaultParent;
_rootCanvas = rootCanvas;
// 툴팁 인스턴스 생성 및 초기화
_activeTooltipInstance = GameObject.Instantiate(tooltipPrefab, _defaultParentTransform);
@@ -211,10 +204,10 @@ namespace UVC.UI.Tooltip
_tooltipTextElement.text = text; // 텍스트 설정
_activeTooltipInstance.SetActive(true); // 툴팁 활성화
// 툴팁을 현재 부모 내에서 가장 마지막 자식으로 만들어 다른 UI 요소들 위에 표시되도록 합니다.
// 툴팁을 현재 부모 내에서 가장 첫번쨰 자식으로 만들어 다른 UI 요소들 위에 표시되도록 합니다.
if (_activeTooltipInstance.transform.parent != null)
{
_activeTooltipInstance.transform.SetAsLastSibling();
_activeTooltipInstance.transform.SetAsFirstSibling();
}
_tooltipTextElement.ForceMeshUpdate(); // 텍스트 변경 후 메쉬 강제 업데이트 (정확한 크기 계산 위함)
@@ -232,15 +225,15 @@ namespace UVC.UI.Tooltip
/// <param name="mousePosition">현재 마우스 포인터의 화면 좌표입니다.</param>
private void AdjustPosition(Vector3 mousePosition)
{
if (_rootCanvas == null || _tooltipRectTransform == null) return;
if (canvas == null || _tooltipRectTransform == null) return;
Vector2 localPoint; // Canvas 내 로컬 좌표
// 현재 Canvas의 Render Mode에 따라 적절한 카메라 사용
Camera eventCamera = (_rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay) ? null : _rootCanvas.worldCamera;
Camera eventCamera = (canvas.renderMode == RenderMode.ScreenSpaceOverlay) ? null : canvas.worldCamera;
// 화면 좌표(mousePosition)를 _rootCanvas의 RectTransform 내 로컬 좌표로 변환
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_rootCanvas.transform as RectTransform, // 좌표 변환의 기준이 될 RectTransform
canvas.transform as RectTransform, // 좌표 변환의 기준이 될 RectTransform
mousePosition, // 변환할 화면 좌표
eventCamera, // 이벤트 카메라 (ScreenSpaceOverlay의 경우 null)
out localPoint // 변환된 로컬 좌표 결과
@@ -278,17 +271,17 @@ namespace UVC.UI.Tooltip
/// </summary>
private void AdjustPositionWithinScreenBounds()
{
if (_tooltipRectTransform == null || _activeTooltipInstance == null || !_activeTooltipInstance.activeSelf || _rootCanvas == null) return;
if (_tooltipRectTransform == null || _activeTooltipInstance == null || !_activeTooltipInstance.activeSelf || canvas == null) return;
Vector3[] tooltipCorners = new Vector3[4]; // 툴팁의 네 꼭짓점 월드 좌표
_tooltipRectTransform.GetWorldCorners(tooltipCorners);
RectTransform canvasRectTransform = _rootCanvas.transform as RectTransform;
RectTransform canvasRectTransform = canvas.transform as RectTransform;
// 화면 경계 좌표 설정
float minX, maxX, minY, maxY;
if (_rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay)
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
// Screen Space Overlay 모드에서는 Screen.width/height를 사용
minX = 0;
@@ -308,7 +301,7 @@ namespace UVC.UI.Tooltip
}
Vector3 currentPosition = _tooltipRectTransform.position; // 현재 툴팁 위치 (월드 좌표)
Vector2 size = _tooltipRectTransform.sizeDelta * _rootCanvas.scaleFactor; // Canvas 스케일을 고려한 실제 픽셀 크기
Vector2 size = _tooltipRectTransform.sizeDelta * canvas.scaleFactor; // Canvas 스케일을 고려한 실제 픽셀 크기
Vector2 pivot = _tooltipRectTransform.pivot; // 툴팁의 Pivot
// 오른쪽 경계 넘어감: 왼쪽으로 이동
@@ -326,8 +319,8 @@ namespace UVC.UI.Tooltip
{
Vector3 mouseWorldPos = Vector3.zero;
// 마우스 포인터의 월드 Y 좌표를 가져옴
if (_rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay) mouseWorldPos = Input.mousePosition;
else RectTransformUtility.ScreenPointToWorldPointInRectangle(canvasRectTransform, Input.mousePosition, _rootCanvas.worldCamera, out mouseWorldPos);
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay) mouseWorldPos = Input.mousePosition;
else RectTransformUtility.ScreenPointToWorldPointInRectangle(canvasRectTransform, Input.mousePosition, canvas.worldCamera, out mouseWorldPos);
// 마우스 Y 위치 + 툴팁 높이 (pivot 고려) + 약간의 오프셋
currentPosition.y = mouseWorldPos.y + (size.y * (1 - pivot.y)) + 15f;
@@ -355,7 +348,7 @@ namespace UVC.UI.Tooltip
_tooltipTextElement = null;
_tooltipRectTransform = null;
_defaultParentTransform = null;
_rootCanvas = null;
canvas = null;
_isInitialized = false;
_instance = null; // 싱글톤 인스턴스 참조 해제
}