Files
XRLib/Assets/Scripts/UVC/UI/Modal/ModalView.cs
2025-12-22 19:49:36 +09:00

290 lines
13 KiB
C#

#nullable enable
using Cysharp.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UVC.Extention;
using UVC.Log;
using UVC.Studio.Manager;
namespace UVC.UI.Modal
{
/// <summary>
/// 🖼️ 모달 창의 실제 '모습'을 담당하는 친구예요. Unity 에디터에서 만든 UI 요소들(버튼, 글상자 등)을 가지고 있어요.
/// `ModalContent`라는 레시피를 받아서, 그 내용대로 화면에 그림을 그려주는 역할을 해요.
/// 이 스크립트는 모달로 사용할 프리팹(Prefab)의 가장 바깥쪽 부모 게임 오브젝트에 붙여줘야 해요.
/// </summary>
/// <example>
/// <para>만약 입력창이 있는 특별한 모달을 만들고 싶다면, 이 ModalView를 상속받아서 만들 수 있어요:</para>
/// <code>
/// public class InputModalView : ModalView
/// {
/// [Header("My Special UI")]
/// public TMP_InputField myInputField; // Unity 에디터에서 연결해줘야 해요.
/// private string _inputValue = "";
///
/// public override async UniTask OnOpen(ModalContent content)
/// {
/// await base.OnOpen(content); // 부모의 OnOpen을 먼저 호출해서 기본 UI를 설정해요.
/// ULog.Debug("입력 모달이 열렸어요! 입력창을 초기화합니다.");
/// if (myInputField != null)
/// {
/// myInputField.text = ""; // 입력창 비우기
/// // 입력창에 변화가 있을 때마다 _inputValue를 업데이트하도록 설정할 수 있어요.
/// myInputField.OnValueChanged.AddListener(OnInputChanged);
/// }
/// }
///
/// private void OnInputChanged(string newValue)
/// {
/// _inputValue = newValue;
/// ULog.Debug($"입력된 값: {newValue}");
/// }
///
/// public override object GetResult()
/// {
/// // 이 모달이 닫힐 때, 입력된 글자를 결과로 돌려줘요.
/// return _inputValue;
/// }
///
/// public override async UniTask OnClose(ModalContent content)
/// {
/// ULog.Debug("입력 모달이 닫힙니다.");
/// if (myInputField != null)
/// {
/// myInputField.OnValueChanged.RemoveListener(OnInputChanged); // 리스너 정리
/// }
/// await base.OnClose(content);
/// }
/// }
///
/// // 이 InputModalView를 사용하는 방법:
/// // 1. InputModalView 스크립트가 붙어있는 프리팹을 만들고, myInputField를 연결해요.
/// // 2. ModalContent 만들 때 이 프리팹 경로를 사용해요.
/// // var inputContent = new ModalContent("Prefabs/UI/MyInputModalPrefab") { Title = "이름 입력", Message = "이름을 입력해주세요." };
/// // string enteredName = await Modal.Open<string>(inputContent);
/// // if (!string.IsNullOrEmpty(enteredName)) { ULog.Debug($"환영합니다, {enteredName}님!"); }
/// </code>
/// </example>
public class ModalView : MonoBehaviour
{
[Header("UI Elements")]
/// <summary>
/// 🏷️ 모달 창의 제목을 보여줄 글상자(TextMeshProUGUI)예요.
/// Unity 에디터의 인스펙터 창에서 실제 UI 요소를 끌어다 연결해줘야 해요.
/// </summary>
[SerializeField]
public TextMeshProUGUI titleText;
/// <summary>
/// 💬 모달 창의 주요 메시지를 보여줄 글상자예요. 이것도 연결해주세요!
/// </summary>
[SerializeField]
public TextMeshProUGUI messageText;
/// <summary>
/// ✅ '확인' 버튼이에요. 연결 필수!
/// </summary>
[SerializeField]
public Button confirmButton;
/// <summary>
/// 확인 버튼 안에 있는 글상자예요. 확인 버튼 글자를 바꿀 때 사용돼요.
/// </summary>
[SerializeField]
public TextMeshProUGUI confirmButtonText;
/// <summary>
/// ❌ '취소' 버튼이에요. 이것도 연결해주세요!
/// </summary>
[SerializeField]
public Button cancelButton;
/// <summary>
/// 취소 버튼 안에 있는 글상자예요. 취소 버튼 글자를 바꿀 때 사용돼요.
/// </summary>
[SerializeField]
public TextMeshProUGUI cancelButtonText;
/// <summary>
/// ✖️ 모달 창을 닫는 (보통 오른쪽 위에 있는 X 모양) 버튼이에요.
/// </summary>
[SerializeField]
public Button closeButton; // 닫기 버튼
// 필요에 따라 다른 UI 요소들을 추가할 수 있습니다.
// 예: public Image backgroundImage;
// 예: public InputField inputField;
/// <summary>
/// 🚀 모달 창이 화면에 나타날 때 `Modal` 클래스가 호출하는 마법이에요! (비동기 작업 가능)
/// `ModalContent` 레시피를 받아서, 이 `ModalView`의 UI 요소들(제목, 메시지, 버튼 등)을 레시피대로 설정해요.
/// `ModalContent.OnOpen()`이 호출된 *후에* 실행돼요.
/// </summary>
/// <param name="content">모달에 보여줄 내용과 설정을 담은 '레시피' (`ModalContent` 객체)예요.</param>
public virtual async UniTask OnOpen(ModalContent content)
{
//ULog.Debug($"[ModalView] {gameObject.name} OnOpen called.");
// 모달이 열리면 단축키 비활성화
if (ShortcutManager.Instance != null)
{
ShortcutManager.Instance.GlobalEnabled = false;
}
// ModalContent 레시피에 적힌 대로 UI 요소들을 설정해요.
if (titleText != null && content != null)
{
titleText.text = content.Title;
}
if (messageText != null && content != null)
{
messageText.text = content.Message;
}
// 확인 버튼 설정
if (confirmButton != null && content != null)
{
confirmButton.gameObject.SetActive(content.ShowConfirmButton);
if (content.ShowConfirmButton && confirmButtonText != null && !string.IsNullOrEmpty(content.ConfirmButtonText))
{
confirmButtonText.text = content.ConfirmButtonText;
}
}
// 취소 버튼 설정
if (cancelButton != null && content != null)
{
cancelButton.gameObject.SetActive(content.ShowCancelButton);
if (content.ShowCancelButton && cancelButtonText != null && !string.IsNullOrEmpty(content.CancelButtonText))
{
cancelButtonText.text = content.CancelButtonText;
}
}
// 닫기 버튼 설정
if (closeButton != null && content != null)
{
closeButton.gameObject.SetActive(content.ShowCloseButton);
}
AdjustPositions();
await UniTask.CompletedTask; // 비동기 메서드라서 마지막에 이걸 붙여줘요.
}
/// <summary>
/// 📐 활성화된 Text와 button이 Layout Group을 사용하고 있으면 자동 조절 되게 설정 합니다.
/// </summary>
protected virtual void AdjustPositions()
{
}
/// <summary>
/// 🎬 모달 창이 화면에서 사라질 때 `Modal` 클래스가 호출하는 마법이에요! (비동기 작업 가능)
/// `ModalContent.OnClose()`가 호출된 *후에* 실행돼요.
/// 모달이 닫히면서 특별히 정리해야 할 작업이 있다면 여기에 작성해요.
/// </summary>
/// <param name="content">이 모달을 열 때 사용했던 '레시피' (`ModalContent` 객체)예요.</param>
public virtual async UniTask OnClose(ModalContent content)
{
//ULog.Debug($"[ModalView] {gameObject.name} OnClose called.");
// 모달이 닫히면 단축키 다시 활성화
if (ShortcutManager.Instance != null)
{
ShortcutManager.Instance.GlobalEnabled = true;
}
// 예: 모달에서 사용했던 리소스를 해제하거나, UI 상태를 초기화하는 코드를 여기에 넣을 수 있어요.
await UniTask.CompletedTask;
}
/// <summary>
/// 🎁 모달 창이 닫힐 때, 이 모달이 어떤 '결과'를 만들었는지 알려주는 함수예요.
/// 기본적으로는 아무것도 안 알려줘요 (`null` 반환).
/// 만약 모달에서 사용자가 뭔가를 선택하거나 입력했다면, 이 함수를 **재정의(override)**해서
/// 그 선택/입력 값을 돌려주도록 만들 수 있어요.
/// `Modal.Open<T>()`를 호출할 때 `T`에 지정한 타입으로 이 결과가 변환돼요.
/// </summary>
/// <returns>모달의 처리 결과예요. (예: 사용자가 입력한 글자, 선택한 아이템, 또는 그냥 true/false)</returns>
/// <example>
/// <para>예를 들어, '예'/'아니오'를 선택하는 간단한 확인 모달이라면 이렇게 할 수 있어요:</para>
/// <code>
/// public class ConfirmModalView : ModalView
/// {
/// private bool _wasConfirmed = false;
///
/// // (OnOpen 등 다른 메서드들은 필요에 따라 구현)
///
/// // 확인 버튼이 눌렸을 때 호출될 메서드 (Modal.cs에서 연결해줌)
/// public void HandleConfirm()
/// {
/// _wasConfirmed = true;
/// // 실제로는 Modal.cs의 HandleModalActionAsync가 호출되어 모달이 닫힙니다.
/// }
///
/// // 취소 버튼이 눌렸을 때 호출될 메서드
/// public void HandleCancel()
/// {
/// _wasConfirmed = false;
/// }
///
/// public override object GetResult()
/// {
/// // 사용자가 '확인'을 눌렀는지 여부를 bool 값으로 반환해요.
/// return _wasConfirmed;
/// }
/// }
/// // Modal.Open<bool>(...) 이렇게 호출하면, GetResult()가 반환한 bool 값을 받을 수 있어요.
/// </code>
/// <para>또는, 입력 필드가 있는 모달이라면 입력된 텍스트를 반환할 수 있어요:</para>
/// <code>
/// public class InputModalView : ModalView
/// {
/// public TMP_InputField inputField;
/// // (OnOpen에서 inputField 초기화 및 이벤트 연결)
///
/// public override UniTask<object?> GetResult()
/// {
/// return inputField != null ? inputField.text : string.Empty;
/// }
/// }
/// // Modal.Open<string>(...) 이렇게 호출하면, 입력된 문자열을 받을 수 있어요.
/// </code>
/// </example>
public virtual UniTask<object?> GetResult()
{
return UniTask.FromResult<object?>(null);
}
/// <summary>
/// 💡 모달 안에 있는 모든 주요 버튼들(확인, 취소, 닫기)을 한꺼번에 클릭할 수 있게 하거나 못하게 만들어요.
/// 예를 들어, 모달이 뭔가 중요한 작업을 처리하는 동안에는 버튼을 잠시 못 누르게 할 때 사용해요.
/// </summary>
/// <param name="interactable">true로 설정하면 버튼들을 누를 수 있고, false면 못 눌러요.</param>
public virtual void SetAllButtonsInteractable(bool interactable)
{
if (confirmButton != null) confirmButton.interactable = interactable;
if (cancelButton != null) cancelButton.interactable = interactable;
if (closeButton != null) closeButton.interactable = interactable;
}
public virtual void OnConfirmButtonClicked()
{
// 확인 버튼이 눌렸을 때 호출되는 메서드예요.
// 기본적으로는 아무것도 하지 않지만, 필요하면 재정의해서 특별한 동작을 추가할 수 있어요.
// 예: ULog.Debug("확인 버튼이 눌렸어요!");
}
public virtual void OnCancelButtonClicked()
{
// 취소 버튼이 눌렸을 때 호출되는 메서드예요.
// 기본적으로는 아무것도 하지 않지만, 필요하면 재정의해서 특별한 동작을 추가할 수 있어요.
// 예: ULog.Debug("취소 버튼이 눌렸어요!");
}
public virtual void OnCloseButtonClicked()
{
// 닫기 버튼이 눌렸을 때 호출되는 메서드예요.
// 기본적으로는 아무것도 하지 않지만, 필요하면 재정의해서 특별한 동작을 추가할 수 있어요.
// 예: ULog.Debug("닫기 버튼이 눌렸어요!");
}
}
}