#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 { /// /// πŸ–ΌοΈ λͺ¨λ‹¬ 창의 μ‹€μ œ 'λͺ¨μŠ΅'을 λ‹΄λ‹Ήν•˜λŠ” μΉœκ΅¬μ˜ˆμš”. Unity μ—λ””ν„°μ—μ„œ λ§Œλ“  UI μš”μ†Œλ“€(λ²„νŠΌ, κΈ€μƒμž λ“±)을 κ°€μ§€κ³  μžˆμ–΄μš”. /// `ModalContent`λΌλŠ” λ ˆμ‹œν”Όλ₯Ό λ°›μ•„μ„œ, κ·Έ λ‚΄μš©λŒ€λ‘œ 화면에 그림을 κ·Έλ €μ£ΌλŠ” 역할을 ν•΄μš”. /// 이 μŠ€ν¬λ¦½νŠΈλŠ” λͺ¨λ‹¬λ‘œ μ‚¬μš©ν•  ν”„λ¦¬νŒΉ(Prefab)의 κ°€μž₯ λ°”κΉ₯μͺ½ λΆ€λͺ¨ κ²Œμž„ μ˜€λΈŒμ νŠΈμ— λΆ™μ—¬μ€˜μ•Ό ν•΄μš”. /// /// /// λ§Œμ•½ μž…λ ₯창이 μžˆλŠ” νŠΉλ³„ν•œ λͺ¨λ‹¬μ„ λ§Œλ“€κ³  μ‹Άλ‹€λ©΄, 이 ModalViewλ₯Ό μƒμ†λ°›μ•„μ„œ λ§Œλ“€ 수 μžˆμ–΄μš”: /// /// 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(inputContent); /// // if (!string.IsNullOrEmpty(enteredName)) { ULog.Debug($"ν™˜μ˜ν•©λ‹ˆλ‹€, {enteredName}λ‹˜!"); } /// /// public class ModalView : MonoBehaviour { [Header("UI Elements")] /// /// 🏷️ λͺ¨λ‹¬ 창의 제λͺ©μ„ 보여쀄 κΈ€μƒμž(TextMeshProUGUI)μ˜ˆμš”. /// Unity μ—λ””ν„°μ˜ μΈμŠ€νŽ™ν„° μ°½μ—μ„œ μ‹€μ œ UI μš”μ†Œλ₯Ό λŒμ–΄λ‹€ μ—°κ²°ν•΄μ€˜μ•Ό ν•΄μš”. /// [SerializeField] public TextMeshProUGUI titleText; /// /// πŸ’¬ λͺ¨λ‹¬ 창의 μ£Όμš” λ©”μ‹œμ§€λ₯Ό 보여쀄 κΈ€μƒμžμ˜ˆμš”. 이것도 μ—°κ²°ν•΄μ£Όμ„Έμš”! /// [SerializeField] public TextMeshProUGUI messageText; /// /// βœ… '확인' λ²„νŠΌμ΄μ—μš”. μ—°κ²° ν•„μˆ˜! /// [SerializeField] public Button confirmButton; /// /// 확인 λ²„νŠΌ μ•ˆμ— μžˆλŠ” κΈ€μƒμžμ˜ˆμš”. 확인 λ²„νŠΌ κΈ€μžλ₯Ό λ°”κΏ€ λ•Œ μ‚¬μš©λΌμš”. /// [SerializeField] public TextMeshProUGUI confirmButtonText; /// /// ❌ 'μ·¨μ†Œ' λ²„νŠΌμ΄μ—μš”. 이것도 μ—°κ²°ν•΄μ£Όμ„Έμš”! /// [SerializeField] public Button cancelButton; /// /// μ·¨μ†Œ λ²„νŠΌ μ•ˆμ— μžˆλŠ” κΈ€μƒμžμ˜ˆμš”. μ·¨μ†Œ λ²„νŠΌ κΈ€μžλ₯Ό λ°”κΏ€ λ•Œ μ‚¬μš©λΌμš”. /// [SerializeField] public TextMeshProUGUI cancelButtonText; /// /// βœ–οΈ λͺ¨λ‹¬ 창을 λ‹«λŠ” (보톡 였λ₯Έμͺ½ μœ„μ— μžˆλŠ” X λͺ¨μ–‘) λ²„νŠΌμ΄μ—μš”. /// [SerializeField] public Button closeButton; // λ‹«κΈ° λ²„νŠΌ // ν•„μš”μ— 따라 λ‹€λ₯Έ UI μš”μ†Œλ“€μ„ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€. // 예: public Image backgroundImage; // 예: public InputField inputField; /// /// πŸš€ λͺ¨λ‹¬ 창이 화면에 λ‚˜νƒ€λ‚  λ•Œ `Modal` ν΄λž˜μŠ€κ°€ ν˜ΈμΆœν•˜λŠ” λ§ˆλ²•μ΄μ—μš”! (비동기 μž‘μ—… κ°€λŠ₯) /// `ModalContent` λ ˆμ‹œν”Όλ₯Ό λ°›μ•„μ„œ, 이 `ModalView`의 UI μš”μ†Œλ“€(제λͺ©, λ©”μ‹œμ§€, λ²„νŠΌ λ“±)을 λ ˆμ‹œν”ΌλŒ€λ‘œ μ„€μ •ν•΄μš”. /// `ModalContent.OnOpen()`이 호좜된 *후에* μ‹€ν–‰λΌμš”. /// /// λͺ¨λ‹¬μ— 보여쀄 λ‚΄μš©κ³Ό 섀정을 담은 'λ ˆμ‹œν”Ό' (`ModalContent` 객체)μ˜ˆμš”. 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; // 비동기 λ©”μ„œλ“œλΌμ„œ λ§ˆμ§€λ§‰μ— 이걸 λΆ™μ—¬μ€˜μš”. } /// /// πŸ“ ν™œμ„±ν™”λœ Text와 button이 Layout Group을 μ‚¬μš©ν•˜κ³  있으면 μžλ™ 쑰절 되게 μ„€μ • ν•©λ‹ˆλ‹€. /// protected virtual void AdjustPositions() { } /// /// 🎬 λͺ¨λ‹¬ 창이 ν™”λ©΄μ—μ„œ μ‚¬λΌμ§ˆ λ•Œ `Modal` ν΄λž˜μŠ€κ°€ ν˜ΈμΆœν•˜λŠ” λ§ˆλ²•μ΄μ—μš”! (비동기 μž‘μ—… κ°€λŠ₯) /// `ModalContent.OnClose()`κ°€ 호좜된 *후에* μ‹€ν–‰λΌμš”. /// λͺ¨λ‹¬μ΄ λ‹«νžˆλ©΄μ„œ νŠΉλ³„νžˆ 정리해야 ν•  μž‘μ—…μ΄ μžˆλ‹€λ©΄ 여기에 μž‘μ„±ν•΄μš”. /// /// 이 λͺ¨λ‹¬μ„ μ—΄ λ•Œ μ‚¬μš©ν–ˆλ˜ 'λ ˆμ‹œν”Ό' (`ModalContent` 객체)μ˜ˆμš”. 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; } /// /// 🎁 λͺ¨λ‹¬ 창이 λ‹«νž λ•Œ, 이 λͺ¨λ‹¬μ΄ μ–΄λ–€ 'κ²°κ³Ό'λ₯Ό λ§Œλ“€μ—ˆλŠ”μ§€ μ•Œλ €μ£ΌλŠ” ν•¨μˆ˜μ˜ˆμš”. /// κΈ°λ³Έμ μœΌλ‘œλŠ” 아무것도 μ•ˆ μ•Œλ €μ€˜μš” (`null` λ°˜ν™˜). /// λ§Œμ•½ λͺ¨λ‹¬μ—μ„œ μ‚¬μš©μžκ°€ λ­”κ°€λ₯Ό μ„ νƒν•˜κ±°λ‚˜ μž…λ ₯ν–ˆλ‹€λ©΄, 이 ν•¨μˆ˜λ₯Ό **μž¬μ •μ˜(override)**ν•΄μ„œ /// κ·Έ 선택/μž…λ ₯ 값을 λŒλ €μ£Όλ„λ‘ λ§Œλ“€ 수 μžˆμ–΄μš”. /// `Modal.Open()`λ₯Ό ν˜ΈμΆœν•  λ•Œ `T`에 μ§€μ •ν•œ νƒ€μž…μœΌλ‘œ 이 κ²°κ³Όκ°€ λ³€ν™˜λΌμš”. /// /// λͺ¨λ‹¬μ˜ 처리 κ²°κ³Όμ˜ˆμš”. (예: μ‚¬μš©μžκ°€ μž…λ ₯ν•œ κΈ€μž, μ„ νƒν•œ μ•„μ΄ν…œ, λ˜λŠ” κ·Έλƒ₯ true/false) /// /// 예λ₯Ό λ“€μ–΄, '예'/'μ•„λ‹ˆμ˜€'λ₯Ό μ„ νƒν•˜λŠ” κ°„λ‹¨ν•œ 확인 λͺ¨λ‹¬μ΄λΌλ©΄ μ΄λ ‡κ²Œ ν•  수 μžˆμ–΄μš”: /// /// 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(...) μ΄λ ‡κ²Œ ν˜ΈμΆœν•˜λ©΄, GetResult()κ°€ λ°˜ν™˜ν•œ bool 값을 받을 수 μžˆμ–΄μš”. /// /// λ˜λŠ”, μž…λ ₯ ν•„λ“œκ°€ μžˆλŠ” λͺ¨λ‹¬μ΄λΌλ©΄ μž…λ ₯된 ν…μŠ€νŠΈλ₯Ό λ°˜ν™˜ν•  수 μžˆμ–΄μš”: /// /// public class InputModalView : ModalView /// { /// public TMP_InputField inputField; /// // (OnOpenμ—μ„œ inputField μ΄ˆκΈ°ν™” 및 이벀트 μ—°κ²°) /// /// public override UniTask GetResult() /// { /// return inputField != null ? inputField.text : string.Empty; /// } /// } /// // Modal.Open(...) μ΄λ ‡κ²Œ ν˜ΈμΆœν•˜λ©΄, μž…λ ₯된 λ¬Έμžμ—΄μ„ 받을 수 μžˆμ–΄μš”. /// /// public virtual UniTask GetResult() { return UniTask.FromResult(null); } /// /// πŸ’‘ λͺ¨λ‹¬ μ•ˆμ— μžˆλŠ” λͺ¨λ“  μ£Όμš” λ²„νŠΌλ“€(확인, μ·¨μ†Œ, λ‹«κΈ°)을 ν•œκΊΌλ²ˆμ— 클릭할 수 있게 ν•˜κ±°λ‚˜ λͺ»ν•˜κ²Œ λ§Œλ“€μ–΄μš”. /// 예λ₯Ό λ“€μ–΄, λͺ¨λ‹¬μ΄ λ­”κ°€ μ€‘μš”ν•œ μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” λ™μ•ˆμ—λŠ” λ²„νŠΌμ„ μž μ‹œ λͺ» λˆ„λ₯΄κ²Œ ν•  λ•Œ μ‚¬μš©ν•΄μš”. /// /// true둜 μ„€μ •ν•˜λ©΄ λ²„νŠΌλ“€μ„ λˆ„λ₯Ό 수 있고, falseλ©΄ λͺ» λˆŒλŸ¬μš”. 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("λ‹«κΈ° λ²„νŠΌμ΄ λˆŒλ Έμ–΄μš”!"); } } }