using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
///
/// 캐릭터를 우클릭하면 뜨는 포즈 목록. 항목을 누르면 그 포즈가 된다.
///
/// 이전에는 우클릭할 때마다 다음 포즈로 넘어갔는데, 그러면 어떤 포즈가 있는지
/// 보이지 않고 원하는 것까지 여러 번 눌러야 한다. 무엇보다 목록에 없는 포즈는
/// 도달할 방법이 아예 없어서(예: 가림 포즈만 따로 설정한 경우) 조용히 안 되는
/// 상황이 생긴다. 목록을 눈에 보이게 하면 그 문제가 사라진다.
///
/// 채팅창과 같은 방식으로 만든다 — 런타임 uGUI, 클릭 통과 히트테스트에 사각형 등록.
/// 등록하지 않으면 클릭이 뒤 창으로 새어나가 버튼이 눌리지 않는다.
///
/// 키보드 포커스는 필요 없다(마우스만 쓴다). 그래서 채팅창과 달리
/// WS_EX_NOACTIVATE 를 건드리지 않는다.
///
public class PoseMenuUI : MonoBehaviour
{
[Header("모양")]
[SerializeField] float menuWidth = 132f;
[SerializeField] float itemHeight = 32f;
[SerializeField] float padding = 6f;
[SerializeField] int fontSize = 13;
[Header("참조 (비우면 씬에서 탐색)")]
[SerializeField] ClickThroughHitTest hitTest;
Canvas canvas;
RectTransform panel;
RectTransform itemRoot;
Action onPick;
Func hitRegion;
bool prevLeftDown;
public bool IsOpen => panel != null && panel.gameObject.activeSelf;
void Awake()
{
if (hitTest == null) hitTest = FindFirstObjectByType();
ChatUiBuilder.EnsureEventSystem();
Build();
panel.gameObject.SetActive(false);
}
void OnEnable()
{
if (hitTest != null)
{
hitRegion = ContainsScreenPoint;
hitTest.RegisterInteractiveRegion(hitRegion);
}
}
void OnDisable()
{
if (hitTest != null && hitRegion != null)
{
hitTest.UnregisterInteractiveRegion(hitRegion);
hitRegion = null;
}
Hide();
}
void Update()
{
if (!IsOpen) return;
// 바깥을 누르면 닫는다. 전체 화면 배경판을 깔면 화면 전체가 클릭을 받아버려서
// 데스크톱 오버레이로서는 쓸 수 없다. 그래서 직접 커서를 확인한다.
#if !UNITY_EDITOR && UNITY_STANDALONE_WIN
bool leftDown = Win32.IsKeyDown(Win32.VK_LBUTTON);
#else
var mouse = UnityEngine.InputSystem.Mouse.current;
bool leftDown = mouse != null && mouse.leftButton.isPressed;
#endif
if (leftDown && !prevLeftDown)
{
if (!DesktopCursor.TryGetScreenPosition(CursorHwnd, out Vector2 cursor) ||
!ContainsScreenPoint(cursor))
{
Hide();
}
}
prevLeftDown = leftDown;
var keyboard = UnityEngine.InputSystem.Keyboard.current;
if (keyboard != null && keyboard.escapeKey.wasPressedThisFrame) Hide();
}
IntPtr CursorHwnd
{
get
{
var window = hitTest != null ? hitTest.GetComponent() : null;
return window != null ? window.Hwnd : IntPtr.Zero;
}
}
// ------------------------------------------------------------------ 조작
/// 커서 위치에 목록을 띄운다. 이미 열려 있으면 다시 그린다.
public void Show(Vector2 screenPos, IReadOnlyList poses,
Action pick)
{
if (panel == null || poses == null || poses.Count == 0) return;
onPick = pick;
Rebuild(poses);
panel.gameObject.SetActive(true);
Place(screenPos);
// 이번 프레임의 우클릭이 "바깥 클릭"으로 오인되지 않게 한 번 흡수한다.
prevLeftDown = true;
}
public void Hide()
{
if (panel != null) panel.gameObject.SetActive(false);
onPick = null;
}
public bool ContainsScreenPoint(Vector2 screenPoint)
{
if (!IsOpen || panel == null) return false;
return RectTransformUtility.RectangleContainsScreenPoint(panel, screenPoint, null);
}
void Place(Vector2 screenPos)
{
float scale = canvas != null ? canvas.scaleFactor : 1f;
Vector2 size = panel.sizeDelta * scale;
// 커서 오른쪽 아래에 띄우되 화면 밖으로 나가지 않게 한다.
float x = Mathf.Clamp(screenPos.x, 0f, Mathf.Max(0f, Screen.width - size.x));
float y = Mathf.Clamp(screenPos.y - size.y, 0f, Mathf.Max(0f, Screen.height - size.y));
panel.anchoredPosition = new Vector2(x, y) / scale;
}
// ------------------------------------------------------------------ 계층
void Build()
{
var canvasGo = new GameObject("PoseMenuCanvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
canvasGo.transform.SetParent(transform, false);
canvas = canvasGo.GetComponent