using Cysharp.Threading.Tasks; using DG.Tweening; using System; using System.Collections.Generic; using TMPro; using UnityEngine; using UVC.Log; using UVC.Util; namespace UVC.UI.Modal.DatePicker { public class DatePicker : MonoBehaviour { private static string PrefabPath = "Prefabs/UI/Modal/DatePicker/DatePicker"; private const string blockerPrefabPath = "Prefabs/UI/Modal/ModalBlocker"; //menu blocker prefab path private static DatePicker instance; private static Action _onDateSelectedCallback; private Canvas parentCanvas; private static GameObject? blockerPrefabObj; private static GameObject currentBlockerInstance; [Header("Calendar")] [SerializeField] public GameObject _calendarPanel; [SerializeField] public TextMeshProUGUI _yearNumText; [SerializeField] public TextMeshProUGUI _monthNumText; [SerializeField] public GameObject _item; [Header("Colors")] [SerializeField] public Color textColor = ColorUtil.FromHex("808080"); [SerializeField] public Color selectColor = ColorUtil.FromHex("F5A623"); [SerializeField] public Color todayColor = Color.white; [SerializeField] public Color saturdayColor = ColorUtil.FromHex("4A90E2"); [SerializeField] public Color sundayColor = ColorUtil.FromHex("FF5E5E"); private List _dateItems = new List(); private const int _totalDateNum = 42; private DateTime _initTime; private DateTime _dateTime; public static async UniTask Show(DateTime initialDate, Action onDateSelected) { bool hasInstance = true; if (instance == null) { hasInstance = false; var prefab = await Resources.LoadAsync(PrefabPath) as DatePicker; instance = Instantiate(prefab); if (instance == null) { Debug.LogError("No DatePicker prefab active on 'Start' in scene"); return; } Canvas[] canvases = FindObjectsByType(FindObjectsSortMode.None); //sortingOrder로 정렬 내림차순 - sortingOrder가 높은 캔버스가 먼저 오도록 Array.Sort(canvases, (x, y) => y.sortingOrder.CompareTo(x.sortingOrder)); foreach (Canvas c in canvases) { if (c.renderMode != RenderMode.WorldSpace) { instance.transform.SetParent(c.transform, false); instance.parentCanvas = c; break; } } } instance.gameObject.SetActive(true); instance._initTime = initialDate; instance._dateTime = initialDate; //화면 센터에 위치 시키기 RectTransform rectTransform = instance.GetComponent(); rectTransform.anchoredPosition = new Vector2((Screen.width - rectTransform.sizeDelta.x) / 2, -(Screen.height - rectTransform.sizeDelta.y) / 2); _onDateSelectedCallback = onDateSelected; if (hasInstance) instance.CreateCalendar(); ShowBlock(); } public static void Hide() { if (instance != null) instance.gameObject.SetActive(false); _onDateSelectedCallback = null; // 콜백 참조 정리 HideBlock(); } private static async void ShowBlock() { if (blockerPrefabObj == null) { blockerPrefabObj = await Resources.LoadAsync(blockerPrefabPath) as GameObject; } if (blockerPrefabObj != null) { // 화면에서 가장 큰 그림판(Canvas)을 찾아서 그 위에 방패를 놓을 거예요. Canvas mainCanvasForBlocker = instance.parentCanvas; if (mainCanvasForBlocker != null) { // 방패를 복제해서(Instantiate) 그림판 위에 놓고, 가장 위로 오도록 순서를 조정해요. currentBlockerInstance = UnityEngine.Object.Instantiate(blockerPrefabObj, mainCanvasForBlocker.transform); int siblingIndex = instance.transform.GetSiblingIndex(); currentBlockerInstance.transform.SetSiblingIndex(Math.Max(0, siblingIndex - 1)); // 방패가 부드럽게 나타나도록 CanvasGroup 컴포넌트를 사용해요. 없으면 새로 추가! CanvasGroup blockerCanvasGroup = currentBlockerInstance.GetComponent(); if (blockerCanvasGroup == null) blockerCanvasGroup = currentBlockerInstance.AddComponent(); blockerCanvasGroup.alpha = 0f;// 처음엔 완전히 투명하게 blockerCanvasGroup.DOFade(0.7f, 0.3f);// 0.3초 동안 서서히 나타나게 (투명도 70%) } else ULog.Error("[Modal] UIBlocker를 표시할 Canvas를 찾을 수 없습니다."); // 그림판을 못 찾으면 에러! } else ULog.Warning($"[Modal] UIBlocker 프리팹을 다음 경로에서 찾을 수 없습니다: {blockerPrefabPath}"); } private static void HideBlock() { if (currentBlockerInstance != null) { var blockerCG = currentBlockerInstance.GetComponent(); if (blockerCG != null) { blockerCG.DOFade(0f, 0.3f).OnComplete(() => { UnityEngine.Object.Destroy(currentBlockerInstance); // 완전히 제거 }); // 부드럽게 사라지게 } } } void Start() { Vector3 startPos = _item.transform.localPosition; _dateItems.Clear(); _dateItems.Add(_item); for (int i = 1; i < _totalDateNum; i++) { GameObject item = GameObject.Instantiate(_item) as GameObject; item.name = "Item" + (i + 1).ToString(); item.transform.SetParent(_item.transform.parent); item.transform.localScale = Vector3.one; item.transform.localRotation = Quaternion.identity; item.transform.localPosition = new Vector3((i % 7) * 36 + startPos.x, startPos.y - (i / 7) * 30, startPos.z); _dateItems.Add(item); } //_dateTime = DateTime.Now; CreateCalendar(); } void CreateCalendar() { DateTime firstDay = _dateTime.AddDays(-(_dateTime.Day - 1)); int index = GetDays(firstDay.DayOfWeek); int date = 0; for (int i = 0; i < _totalDateNum; i++) { TextMeshProUGUI label = _dateItems[i].GetComponentInChildren(); _dateItems[i].SetActive(false); DateTime day = firstDay.AddDays(date); Color txtColor = textColor; //토요일 색 변경 if (day.DayOfWeek == DayOfWeek.Saturday) //토요일 { txtColor = saturdayColor; } else if (day.DayOfWeek == DayOfWeek.Sunday) //일요일 { txtColor = sundayColor; } //오늘 날짜인지 확인해서 text 색 변경 if (day.Date == DateTime.Now.Date) { txtColor = todayColor; } if (day.Date == _initTime.Date) { //_initTime날짜와 같은 날짜면 색 변경 txtColor = selectColor; } label.color = txtColor; if (i >= index) { DateTime thatDay = firstDay.AddDays(date); if (thatDay.Month == firstDay.Month) { _dateItems[i].SetActive(true); label.text = (date + 1).ToString(); date++; } } } _yearNumText.text = _dateTime.Year.ToString(); _monthNumText.text = _dateTime.Month.ToString("D2"); } int GetDays(DayOfWeek day) { switch (day) { case DayOfWeek.Monday: return 1; case DayOfWeek.Tuesday: return 2; case DayOfWeek.Wednesday: return 3; case DayOfWeek.Thursday: return 4; case DayOfWeek.Friday: return 5; case DayOfWeek.Saturday: return 6; case DayOfWeek.Sunday: return 0; } return 0; } public void YearPrev() { _dateTime = _dateTime.AddYears(-1); CreateCalendar(); } public void YearNext() { _dateTime = _dateTime.AddYears(1); CreateCalendar(); } public void MonthPrev() { _dateTime = _dateTime.AddMonths(-1); CreateCalendar(); } public void MonthNext() { _dateTime = _dateTime.AddMonths(1); CreateCalendar(); } //Item 클릭했을 경우 Text에 표시. public void OnDateItemClick(string day) { string date = _yearNumText.text + "-" + _monthNumText.text + "-" + int.Parse(day).ToString("D2"); _onDateSelectedCallback?.Invoke(DateTime.Parse(date)); Hide(); } } }