디자인 적용

This commit is contained in:
logonkhi
2025-08-07 21:12:44 +09:00
parent 2e4718291e
commit 3297a5d1f3
139 changed files with 35760 additions and 736 deletions

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
@@ -523,5 +524,28 @@ namespace UVC.Extention
return -1;
}
/// <summary>
/// 지정된 리소스 경로에서 지정된 유형의 Unity 리소스를 로드합니다.
/// </summary>
/// <remarks>이 메서드는 Unity의 <see cref="UnityEngine.Resources.Load{T}(string)"/> 메서드를 사용하여 지정된 경로에서 리소스를 검색합니다. 리소스를 찾을 수 없거나 경로가 유효하지 않으면
/// 오류가 기록되고 <see langword="null"/>이 반환됩니다.</remarks>
/// <typeparam name="T">로드할 리소스의 유형입니다. <see cref="UnityEngine.Object"/>에서 파생되어야 합니다.</typeparam>
/// <param name="resourcePath">Unity 리소스 폴더 내 리소스 경로입니다. null이거나 비어 있을 수 없습니다.</param>
/// <returns> <typeparamref name="T"/> 유형의 로드된 리소스가 발견되면 해당 리소스를 반환합니다. 그렇지 않으면 <see langword="null"/>.</returns>
public static T? LoadResource<T>(this string resourcePath) where T : UnityEngine.Object
{
if (string.IsNullOrEmpty(resourcePath))
{
Debug.LogError("리소스 경로가 비어있습니다.");
return null;
}
T resource = Resources.Load<T>(resourcePath);
if (resource == null)
{
Debug.LogError($"리소스를 찾을 수 없습니다: {resourcePath}");
}
return resource;
}
}
}

View File

@@ -0,0 +1,21 @@
#nullable enable
using Cysharp.Threading.Tasks;
using UnityEngine;
using UVC.UI.Tab;
namespace UVC.Factory.Modal.Settings
{
public class AlarmSettingTabContent : MonoBehaviour, ITabContent
{
public void SetContentData(object? data)
{
}
public UniTask OnCloseAsync()
{
return UniTask.CompletedTask;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c12e6348aba465a49947b665316b4bd2

View File

@@ -2,9 +2,9 @@
using UVC.UI.Commands;
using UVC.UI.Modal;
namespace UVC.Factory.Modal.Config
namespace UVC.Factory.Modal.Settings
{
public class ConfigDataOrderCommand : ICommand
public class DisplayDataOrderCommand : ICommand
{
public async void Execute(object? parameter = null)
{

View File

@@ -4,9 +4,9 @@ using UnityEngine;
using UnityEngine.UI;
using UVC.UI.List.Draggable;
namespace UVC.Factory.Modal.Config
namespace UVC.Factory.Modal.Settings
{
public class ConfigDataOrderListItemView : ListItemView
public class DisplayDataOrderListItemView : ListItemView
{
[Header("UI 추가 요소 연결")]
[SerializeField]

View File

@@ -6,9 +6,9 @@ using UVC.Data.Core;
using UVC.UI.Modal;
using UVC.UI.Tab;
namespace UVC.Factory.Modal.Config
namespace UVC.Factory.Modal.Settings
{
public class ConfigDataOrderModal : ModalView
public class DisplayDataOrderModal : ModalView
{
[SerializeField]
public TabController tabController; // 탭 컨트롤러
@@ -27,7 +27,7 @@ namespace UVC.Factory.Modal.Config
{
Debug.Log($"Key: {item.Key}, Value: {item.Value}");
// 1. TabConfig 설정
tabController.AddTabConfig(item.Key, item.Key, "Prefabs/UI/Tab/ConfigDataOrderTabContent", null, item.Key, true);
tabController.AddTabConfig(item.Key, item.Key, "Prefabs/UI/Tab/DisplayDataOrderTabContent", "", item.Key, true);
}
// 2. 컨트롤러 초기화

View File

@@ -1,15 +1,15 @@
#nullable enable
using Cysharp.Threading.Tasks;
using System.Collections.Generic;
using UnityEngine;
using UVC.Data;
using UVC.Data.Core;
using UVC.Extention;
using UVC.UI.List.Draggable;
using UVC.UI.Tab;
namespace UVC.Factory.Modal.Config
namespace UVC.Factory.Modal.Settings
{
public class ConfigDataOrderTabContent : MonoBehaviour, ITabContent
public class DisplayDataOrderTabContent : MonoBehaviour, ITabContent
{
[SerializeField]
@@ -48,7 +48,7 @@ namespace UVC.Factory.Modal.Config
totalData = dataObject1;
}
}
if (totalData != null)
{
viewData = UserSetting.Get(key);
@@ -112,6 +112,11 @@ namespace UVC.Factory.Modal.Config
draggableList.OnChangedItemData -= OnChangedItemData;
}
}
public UniTask OnCloseAsync()
{
return UniTask.CompletedTask;
}
}

View File

@@ -0,0 +1,42 @@
#nullable enable
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;
using UnityEngine;
using UVC.Data;
using UVC.Data.Core;
using UVC.UI.Tab;
namespace UVC.Factory.Modal.Settings
{
public class DisplaySettingTabContent: MonoBehaviour, ITabContent
{
[SerializeField]
public TabController tabController; // 탭 컨트롤러
public void SetContentData(object? data)
{
if (tabController != null)
{
var dic = DataMask.DataMasks;
foreach (var item in dic)
{
Debug.Log($"Key: {item.Key}, Value: {item.Value}");
// 1. TabConfig 설정
tabController.AddTabConfig(item.Key, item.Key, "Prefabs/UI/Modal/Setting/DisplayDataOrderTabContent", "", item.Key, true);
}
// 2. 컨트롤러 초기화
tabController.Initialize();
}
}
public async UniTask OnCloseAsync()
{
await UserSetting.SaveToAppData(); // 데이터 마스크를 앱 데이터에 저장합니다.
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 29601c9ca05a68547be502ea74d3e2d4

View File

@@ -0,0 +1,29 @@
#nullable enable
using Cysharp.Threading.Tasks;
using TMPro;
using UnityEngine;
using UVC.UI.Tab;
namespace UVC.Factory.Modal.Settings
{
/// <summary>
/// 설정 모달의 일반 정보 탭 콘텐츠를 표시하는 컴포넌트입니다.
/// </summary>
public class GeneralInfoTabContent : MonoBehaviour, ITabContent
{
[SerializeField]
private TextMeshProUGUI text;
public void SetContentData(object? data)
{
Debug.Log("GeneralInfoTabContent: SetContentData called");
text.text = $"Version: {Application.version}";
}
public UniTask OnCloseAsync()
{
Debug.Log("GeneralInfoTabContent: OnClose called");
return UniTask.CompletedTask;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 80fbf6f993a6f7e4a8d10c845fb4627b

View File

@@ -0,0 +1,21 @@
#nullable enable
using Cysharp.Threading.Tasks;
using UnityEngine;
using UVC.UI.Tab;
namespace UVC.Factory.Modal.Settings
{
public class InputSettingTabContent : MonoBehaviour, ITabContent
{
public void SetContentData(object? data)
{
}
public UniTask OnCloseAsync()
{
return UniTask.CompletedTask;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: faa1e60d74e7bd342a44792cac36e576

View File

@@ -0,0 +1,47 @@
#nullable enable
using Cysharp.Threading.Tasks;
using UnityEngine;
using UVC.Data.Core;
using UVC.UI.Modal;
using UVC.UI.Tab;
namespace UVC.Factory.Modal.Settings
{
public class SettingModal : ModalView
{
[SerializeField]
protected TabController? tabController;
/// <summary>
/// 모달이 열릴 때 호출됩니다. (비동기)
/// </summary>
/// <param name="content">모달에 표시할 내용/설정</param>
public override async UniTask OnOpen(ModalContent content)
{
await base.OnOpen(content); // 부모의 OnOpen을 먼저 호출해서 기본 UI를 설정해요.
if (tabController != null)
{
// 코드로 탭 설정하기
SetupTabs();
}
}
private void SetupTabs()
{
// 1. TabConfig 설정
tabController?.AddTabConfig("GeneralInfo", "일반 정보", "Prefabs/UI/Modal/Setting/GeneralInfoTabContent", "Prefabs/UI/images/icon_info", null, true);
tabController?.AddTabConfig("DisplaySetting", "표시 설정", "Prefabs/UI/Modal/Setting/DisplaySettingTabContent", "Prefabs/UI/images/icon_eye", null, true);
tabController?.AddTabConfig("AlarmSetting", "알람 설정", "Prefabs/UI/Modal/Setting/AlarmSettingTabContent", "Prefabs/UI/images/icon_alarm", null, true);
tabController?.AddTabConfig("InputSetting", "입력 설정", "Prefabs/UI/Modal/Setting/InputSettingTabContent", "Prefabs/UI/images/icon_mouse", null, true);
// 2. 컨트롤러 초기화
tabController?.Initialize();
tabController.OnTabChanged += (index) =>
{
Debug.Log($"탭이 변경되었습니다: {index}");
};
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d704b4006b448124e856f25c87127459

View File

@@ -0,0 +1,21 @@
#nullable enable
using UVC.UI.Commands;
using UVC.UI.Commands.Mono;
using UVC.UI.Modal;
namespace UVC.Factory.Modal.Settings
{
public class SettingOpenCommand : ICommand
{
public async void Execute(object? parameter = null)
{
FactoryCameraController.Instance.Enable = false; // 카메라 컨트롤러 비활성화
var modalContent = new ModalContent("Prefabs/UI/Modal/SettingModal")
{
Title = "설정 카테고리"
};
await UVC.UI.Modal.Modal.Open<object>(modalContent);
FactoryCameraController.Instance.Enable = true;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c07a218cc2d93974eb835e945162791d

View File

@@ -0,0 +1,13 @@
using UVC.UI.Commands.Mono;
namespace UVC.Factory.Modal.Settings
{
public class SettingOpenCommandMono : MonoBehaviourCommand
{
public override void Execute()
{
var command = new SettingOpenCommand();
command.Execute();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5d423a9dfc5026744932396f61861380

View File

@@ -3,7 +3,7 @@ using UnityEngine;
using UnityEngine.UI;
using UVC.Data.Core;
using UVC.Factory.Component;
using UVC.Factory.Modal.Config;
using UVC.Factory.Modal.Settings;
using UVC.Factory.Playback;
using UVC.Locale;
using UVC.Log;
@@ -243,10 +243,7 @@ namespace UVC.UI.Menu
}))
}));
model.MenuItems.Add(new MenuItemData("Settings", "Settings", subMenuItems: new List<MenuItemData>
{
new MenuItemData("info", "info", new ConfigDataOrderCommand()),
}));
model.MenuItems.Add(new MenuItemData("Settings", "Settings", new SettingOpenCommand()));
}
/// <summary>

View File

@@ -105,8 +105,6 @@ namespace UVC.UI.Modal
[SerializeField]
public Button closeButton; // 닫기 버튼
protected bool autoPositionButtons = true; // 버튼 위치 자동 조정 여부 (기본값: true)
// 필요에 따라 다른 UI 요소들을 추가할 수 있습니다.
// 예: public Image backgroundImage;
// 예: public InputField inputField;
@@ -129,18 +127,6 @@ namespace UVC.UI.Modal
if (messageText != null && content != null)
{
messageText.text = content.Message;
if(titleText != null)
{
// 메시지 텍스트의 위치를 제목 아래로 조정해요. messageText를 titleText 높이 + 14만큼 아래로 이동시켜요.
RectTransform messageRect = messageText.GetComponent<RectTransform>();
if (messageRect != null)
{
float preferredHeight = titleText.preferredHeight;
Vector2 anchoredPosition = titleText.rectTransform.anchoredPosition;
float newY = anchoredPosition.y - (content.Title.IsNullOrEmpty() ? 0 : preferredHeight + 14);
messageRect.anchoredPosition = new Vector2(messageRect.anchoredPosition.x, newY);
}
}
}
// 확인 버튼 설정
@@ -150,25 +136,6 @@ namespace UVC.UI.Modal
if (content.ShowConfirmButton && confirmButtonText != null && !string.IsNullOrEmpty(content.ConfirmButtonText))
{
confirmButtonText.text = content.ConfirmButtonText;
// text가 너무 길면 버튼 크기를 조정해요.
RectTransform confirmButtonRect = confirmButton.GetComponent<RectTransform>();
if (confirmButtonRect != null && confirmButtonText.preferredWidth > confirmButtonRect.sizeDelta.x - 10)
{
// 버튼의 너비를 글자 크기에 맞춰 조정해요.
confirmButtonRect.sizeDelta = new Vector2(confirmButtonText.preferredWidth + 20, confirmButtonRect.sizeDelta.y);
//취소 버튼이 null이 아니고, 취소 버튼이 활성화되어 있다면 변경 된 confirmButtonRect에 맞춰 왼쪽으로 이동시켜요.
//confirm 버튼과 cancel 버튼 간격은 12
if (cancelButton != null && content.ShowCancelButton)
{
RectTransform cancelButtonRect = cancelButton.GetComponent<RectTransform>();
if (cancelButtonRect != null)
{
// confirm 버튼의 왼쪽에 cancel 버튼을 배치해요.
cancelButtonRect.anchoredPosition = new Vector2(confirmButtonRect.anchoredPosition.x - confirmButtonRect.sizeDelta.x - 12, cancelButtonRect.anchoredPosition.y);
}
}
}
}
}
@@ -179,13 +146,6 @@ namespace UVC.UI.Modal
if (content.ShowCancelButton && cancelButtonText != null && !string.IsNullOrEmpty(content.CancelButtonText))
{
cancelButtonText.text = content.CancelButtonText;
// text가 너무 길면 버튼 크기를 조정해요.
RectTransform cancelButtonRect = cancelButton.GetComponent<RectTransform>();
if (cancelButtonRect != null && cancelButtonText.preferredWidth > cancelButtonRect.sizeDelta.x - 10)
{
// 버튼의 너비를 글자 크기에 맞춰 조정해요.
cancelButtonRect.sizeDelta = new Vector2(cancelButtonText.preferredWidth + 20, cancelButtonRect.sizeDelta.y);
}
}
}
@@ -194,53 +154,17 @@ namespace UVC.UI.Modal
{
closeButton.gameObject.SetActive(content.ShowCloseButton);
}
// 버튼 위치를 예쁘게 조정해요 (예: 버튼이 하나만 있으면 가운데로).
if(autoPositionButtons) AdjustButtonPositions();
AdjustPositions();
await UniTask.CompletedTask; // 비동기 메서드라서 마지막에 이걸 붙여줘요.
}
/// <summary>
/// 📐 활성화된 버튼(확인/취소)의 수에 따라 버튼 위치를 보기 좋게 조정해요.
/// 예를 들어, 버튼이 하나만 있다면 화면 가운데에 오도록 할 수 있어요.
/// 이 메서드는 `OnOpen`에서 호출돼요.
/// 📐 활성화된 Text와 button이 Layout Group을 사용하고 있으면 자동 조절 되게 설정 합니다.
/// </summary>
protected virtual void AdjustButtonPositions()
protected virtual void AdjustPositions()
{
return; // 기본 구현은 아무것도 안 해요. 필요하면 재정의해서 사용하세요.
bool isConfirmActive = confirmButton != null && confirmButton.gameObject.activeSelf;
bool isCancelActive = cancelButton != null && cancelButton.gameObject.activeSelf;
// 확인 버튼만 활성화되어 있다면,
if (isConfirmActive && !isCancelActive)
{
RectTransform confirmButtonRect = confirmButton.GetComponent<RectTransform>();
if (confirmButtonRect != null)
{
// 예시: 확인 버튼을 부모 UI 요소의 가로 중앙으로 이동시켜요.
// (정확한 값은 여러분의 UI 레이아웃에 따라 달라질 수 있어요!)
confirmButtonRect.anchorMin = new Vector2(0.5f, confirmButtonRect.anchorMin.y);
confirmButtonRect.anchorMax = new Vector2(0.5f, confirmButtonRect.anchorMax.y);
confirmButtonRect.pivot = new Vector2(0.5f, confirmButtonRect.pivot.y);
confirmButtonRect.anchoredPosition = new Vector2(0, confirmButtonRect.anchoredPosition.y);
}
}
// 취소 버튼만 활성화되어 있다면,
else if (!isConfirmActive && isCancelActive)
{
RectTransform cancelButtonRect = cancelButton.GetComponent<RectTransform>();
if (cancelButtonRect != null)
{
// 예시: 취소 버튼도 부모 UI 요소의 가로 중앙으로 이동시켜요.
cancelButtonRect.anchorMin = new Vector2(0.5f, cancelButtonRect.anchorMin.y);
cancelButtonRect.anchorMax = new Vector2(0.5f, cancelButtonRect.anchorMax.y);
cancelButtonRect.pivot = new Vector2(0.5f, cancelButtonRect.pivot.y);
cancelButtonRect.anchoredPosition = new Vector2(0, cancelButtonRect.anchoredPosition.y);
}
}
// 두 버튼이 모두 보이거나 모두 안 보일 때는 특별히 위치를 바꾸지 않아요 (기본 레이아웃 사용).
// 필요하다면 이 부분에 다른 정렬 로직을 추가할 수도 있어요!
}

View File

@@ -1,4 +1,6 @@
#nullable enable
using Cysharp.Threading.Tasks;
namespace UVC.UI.Tab
{
/// <summary>
@@ -12,5 +14,11 @@ namespace UVC.UI.Tab
/// </summary>
/// <param name="data">전달할 데이터 객체</param>
void SetContentData(object? data);
/// <summary>
/// 닫힐 때 실행되는 로직을 처리합니다.
/// </summary>
/// <returns>비동기 닫기 작업을 나타내는 <see cref="UniTask"/>입니다.</returns>
UniTask OnCloseAsync();
}
}

View File

@@ -3,6 +3,7 @@ using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UVC.Extention;
namespace UVC.UI.Tab
{
@@ -23,11 +24,17 @@ namespace UVC.UI.Tab
[Tooltip("탭 배경 이미지 컴포넌트")]
[SerializeField] private Image? background;
[Tooltip("탭이 활성화되었을 때의 색상")]
[SerializeField] private Color? activeColor;// = Color.white;
[Tooltip("탭이 활성화되었을 때의 배경 색상")]
[SerializeField] private Color activeColor = Color.white;
[Tooltip("탭이 비활성화되었을 때의 색상")]
[SerializeField] private Color? inactiveColor;// = new Color(0.8f, 0.8f, 0.8f);
[Tooltip("탭이 비활성화되었을 때의 배경 색상")]
[SerializeField] private Color inactiveColor = Color.gray;
[Tooltip("탭이 활성화되었을 때의 배경 색상")]
[SerializeField] private Color activeTextColor = Color.black;
[Tooltip("탭이 비활성화되었을 때의 배경 색상")]
[SerializeField] private Color inactiveTextColor = Color.white;
private int _tabIndex;
private Action<int>? _onTabSelected;
@@ -40,7 +47,10 @@ namespace UVC.UI.Tab
// 버튼 클릭 이벤트 연결
if (button != null)
{
button.onClick.AddListener(OnButtonClick);
button.transition = Selectable.Transition.None; // 버튼의 전환 효과를 비활성화
}
}
/// <summary>
@@ -48,9 +58,9 @@ namespace UVC.UI.Tab
/// </summary>
/// <param name="index">탭 인덱스</param>
/// <param name="tabName">탭 이름</param>
/// <param name="icon">탭 아이콘</param>
/// <param name="iconpath">탭 아이콘 경로</param>
/// <param name="onSelectedCallback">탭 선택 시 호출될 콜백</param>
public void Setup(int index, string tabName, Sprite? icon, Action<int> onSelectedCallback)
public void Setup(int index, string tabName, string iconpath, Action<int> onSelectedCallback)
{
_tabIndex = index;
_onTabSelected = onSelectedCallback;
@@ -62,9 +72,9 @@ namespace UVC.UI.Tab
// 탭 아이콘 설정
if (tabIcon != null)
{
if (icon != null)
if (!iconpath.IsNullOrEmpty())
{
tabIcon.sprite = icon;
tabIcon.sprite = iconpath.LoadResource<Sprite>();
tabIcon.gameObject.SetActive(true);
}
else
@@ -81,18 +91,24 @@ namespace UVC.UI.Tab
public void SetActive(bool isActive)
{
// 배경 색상 변경
if (background != null && activeColor.HasValue && inactiveColor.HasValue)
if (background != null)
{
background.color = isActive ? activeColor.Value : inactiveColor.Value;
background.color = isActive ? activeColor : inactiveColor;
}
else if(button != null)
{
button.image.color = isActive ? button.colors.pressedColor : button.colors.normalColor;
}
if(tabIcon != null)
{
tabIcon.color = isActive ? activeTextColor : inactiveTextColor;
}
// 텍스트 스타일 변경
if (tabText != null) {
tabText.fontStyle = isActive ? FontStyles.Bold : FontStyles.Normal;
tabText.color = isActive ? activeTextColor : inactiveTextColor;
}
}
@@ -105,6 +121,7 @@ namespace UVC.UI.Tab
// 게임 오브젝트가 제거될 때 이벤트 리스너 제거
private void OnDestroy()
{
_onTabSelected = null;
if (button != null)
button.onClick.RemoveListener(OnButtonClick);
}

View File

@@ -12,16 +12,16 @@ namespace UVC.UI.Tab
public string tabID = "";
public string tabName = "";
public string contentPath = "";
public Sprite? tabIcon = null;
public string tabIconPath = "";
public bool useLazyLoading = false;
public object? initialData = null;
public TabContentConfig(string id, string name, string path, Sprite? icon = null, bool lazy = false, object? data = null)
public TabContentConfig(string id, string name, string path, string iconPath = "", bool lazy = false, object? data = null)
{
tabID = id;
tabName = name;
contentPath = path;
tabIcon = icon;
tabIconPath = iconPath;
useLazyLoading = lazy;
initialData = data;
}

View File

@@ -1,4 +1,5 @@
#nullable enable
using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using UnityEngine;
@@ -101,7 +102,7 @@ namespace UVC.UI.Tab
/// <summary>
/// 코드에서 탭 설정을 추가합니다.
/// </summary>
public bool AddTabConfig(string id, string name, string path, Sprite? icon = null, object? data = null, bool useLazyLoading = false)
public bool AddTabConfig(string id, string name, string path, string iconPath = "", object? data = null, bool useLazyLoading = false)
{
if (_isInitialized)
{
@@ -109,7 +110,7 @@ namespace UVC.UI.Tab
return false;
}
var config = new TabContentConfig(id, name, path, icon, useLazyLoading, data);
var config = new TabContentConfig(id, name, path, iconPath, useLazyLoading, data);
_additionalTabConfigs.Add(config);
return true;
}
@@ -182,7 +183,7 @@ namespace UVC.UI.Tab
config.tabID,
config.tabName,
config.contentPath,
config.tabIcon,
config.tabIconPath,
config.initialData
));
}
@@ -207,10 +208,12 @@ namespace UVC.UI.Tab
if (activeTabData == null) return;
// TabView에 탭 변경 전달
tabView.UpdateActiveTab(newTabIndex, activeTabData);
tabView.UpdateActiveTab(newTabIndex, _tabModel.PrevTabIndex, activeTabData).ContinueWith(() =>
{
// 외부 이벤트 발생
OnTabChanged?.Invoke(newTabIndex);
});
// 외부 이벤트 발생
OnTabChanged?.Invoke(newTabIndex);
}
#endregion

View File

@@ -13,7 +13,7 @@ namespace UVC.UI.Tab
public string tabID; // 탭 고유 식별자
public string tabName; // 탭 표시 이름
public string contentPath; // 탭 내용을 담고 있는 Prefab 경로 (Resources 폴더 기준)
public Sprite? tabIcon; // 탭 아이콘 (선택사항, null 가능)
public string tabIconPath; // 탭 아이콘 (선택사항, null 가능)
public object? contentData; // 탭 콘텐츠에 전달할 데이터 객체 (null 가능)
// 프리팹 경로로 초기화하는 생성자
@@ -23,14 +23,14 @@ namespace UVC.UI.Tab
/// <param name="id">탭의 고유 식별자 (예: "inventory", "settings")</param>
/// <param name="name">탭의 표시 이름 (예: "인벤토리", "설정")</param>
/// <param name="path">탭 컨텐츠 프리팹의 리소스 경로 (예: "Prefabs/UI/InventoryTab")</param>
/// <param name="icon">탭 아이콘 이미지 (선택사항)</param>
/// <param name="iconPath">탭 아이콘 이미지 리소스 경로(선택사항)</param>
/// <param name="data">탭 컨텐츠에 전달할 초기 데이터 (선택사항)</param>
public TabData(string id, string name, string path, Sprite? icon = null, object? data = null)
public TabData(string id, string name, string path, string iconPath = "", object? data = null)
{
tabID = id;
tabName = name;
contentPath = path;
tabIcon = icon;
tabIconPath = iconPath;
contentData = data;
}
}

View File

@@ -14,6 +14,7 @@ namespace UVC.UI.Tab
private List<TabData> _tabs = new List<TabData>();
// 현재 활성화된 탭의 인덱스 (-1은 활성화된 탭이 없음을 의미)
private int _activeTabIndex = -1;
private int _prevTabIndex = -1;
// 탭 컨텐츠 인스턴스를 저장하는 Dictionary (키: tabID)
private Dictionary<string, GameObject> _contentInstances = new Dictionary<string, GameObject>();
@@ -23,6 +24,11 @@ namespace UVC.UI.Tab
/// </summary>
public int ActiveTabIndex => _activeTabIndex;
/// <summary>
/// 이전에 선택된 탭 인덱스를 가져옵니다.
/// </summary>
public int PrevTabIndex => _prevTabIndex;
/// <summary>
/// 등록된 모든 탭의 목록을 가져옵니다.
/// </summary>
@@ -66,6 +72,8 @@ namespace UVC.UI.Tab
{
return;
}
Debug.Log($"탭 전환: {_activeTabIndex} -> {tabIndex}");
_prevTabIndex = _activeTabIndex;
// 활성화 탭 인덱스 업데이트
_activeTabIndex = tabIndex;

View File

@@ -1,6 +1,9 @@
#nullable enable
using Cysharp.Threading.Tasks;
using NUnit.Framework.Interfaces;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
namespace UVC.UI.Tab
@@ -195,7 +198,7 @@ namespace UVC.UI.Tab
}
// 탭 버튼 설정
tabButton.Setup(index, tabData.tabName, tabData.tabIcon, OnTabButtonClicked);
tabButton.Setup(index, tabData.tabName, tabData.tabIconPath, OnTabButtonClicked);
_tabButtons.Add(tabButton);
}
@@ -212,11 +215,11 @@ namespace UVC.UI.Tab
/// 필요한 경우 컨텐츠를 로드합니다.
/// </summary>
/// <param name="tabIndex">활성화할 탭 인덱스</param>
/// <param name="prevTabIndex">현재 활성화 된 탭 인덱스. 없으면 -1</param>
/// <param name="tabData">탭 데이터</param>
public void UpdateActiveTab(int tabIndex, TabData tabData)
public async UniTask UpdateActiveTab(int tabIndex, int prevTabIndex, TabData tabData)
{
if (tabIndex < 0 || tabIndex >= _tabConfigs.Count) return;
var config = _tabConfigs[tabIndex];
// 컨텐츠가 아직 로드되지 않은 경우 로드
@@ -243,6 +246,17 @@ namespace UVC.UI.Tab
}
}
//현재 활성화 된 탭의 컨텐츠가 있다면 OnClose 호출
if (prevTabIndex > -1)
{
GameObject? contentInstance = _allContentInstances[prevTabIndex];
if (contentInstance != null)
{
ITabContent? tabContent = contentInstance.GetComponent<ITabContent>();
if (tabContent != null) await tabContent.OnCloseAsync();
}
}
// 모든 컨텐츠 비활성화
foreach (var content in _allContentInstances)
{
@@ -257,11 +271,8 @@ namespace UVC.UI.Tab
activeContent.SetActive(true);
// 데이터 업데이트
if (tabData.contentData != null)
{
ITabContent? tabContent = activeContent.GetComponent<ITabContent>();
tabContent?.SetContentData(tabData.contentData);
}
ITabContent? tabContent = activeContent.GetComponent<ITabContent>();
tabContent?.SetContentData(tabData.contentData);
}
// 탭 버튼 상태 업데이트
@@ -476,6 +487,20 @@ namespace UVC.UI.Tab
// 설정 클리어
_tabConfigs.Clear();
_onTabButtonClicked = null;
}
protected virtual void OnDestroy()
{
// 탭과 관련된 모든 리소스를 정리합니다.
ClearTabs();
// ContentLoader가 있다면 정리
if (contentLoader != null)
{
contentLoader.ClearLazyContents();
contentLoader = null;
}
}
}
}

View File

@@ -130,12 +130,11 @@ namespace UVC.UI
if (parentCanvas != null)
{
canvasCamera = parentCanvas.worldCamera;
// dragArea가 설정되지 않았다면, Canvas를 드래그 영역으로 설정
if (dragArea == null)
{
dragArea = parentCanvas.transform as RectTransform;
}
}
// dragArea가 설정되지 않았다면, Canvas를 드래그 영역으로 설정
if (dragArea == null && parentCanvas != null)
{
dragArea = parentCanvas.transform as RectTransform;
}
}
@@ -151,6 +150,11 @@ namespace UVC.UI
return;
}
if (parentCanvas == null)
{
Debug.LogWarning("[UIDragger] Canvas를 찾을 수 없습니다. 드래그 기능이 제한될 수 있습니다.", this);
}
if (dragArea == null)
{
Debug.LogError("[UIDragger] dragArea를 찾을 수 없습니다. Canvas를 찾을 수 없습니다.", this);
@@ -158,10 +162,6 @@ namespace UVC.UI
return;
}
if (parentCanvas == null)
{
Debug.LogWarning("[UIDragger] Canvas를 찾을 수 없습니다. 드래그 기능이 제한될 수 있습니다.", this);
}
}
/// <summary>
@@ -300,11 +300,12 @@ namespace UVC.UI
float leftBoundary = dragAreaRect.xMin + (size.x * pivot.x);
float rightBoundary = dragAreaRect.xMax - (size.x * (1f - pivot.x));
float bottomBoundary = dragAreaRect.yMin + (size.y * pivot.y) + yMinHeight;
float topBoundary = dragAreaRect.yMax - (size.y * (1f - pivot.y));
float bottomBoundary = (size.y * pivot.y) + yMinHeight;// dragAreaRect.yMin + (size.y * pivot.y) + yMinHeight;
float topBoundary = dragAreaRect.height - (size.y * (1f - pivot.y)); //dragAreaRect.yMax - (size.y * (1f - pivot.y));
position.x = Mathf.Clamp(position.x, leftBoundary, rightBoundary);
position.y = Mathf.Clamp(position.y, bottomBoundary, topBoundary);
Debug.Log($"Clamped Position: {position} within Area: {dragAreaRect}, bottomBoundary:{bottomBoundary}, topBoundary:{topBoundary}");
return position;
}

View File

@@ -1,3 +1,4 @@
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
@@ -23,21 +24,30 @@ namespace UVC.UI.Util
[SerializeField]
private Color disabledColor = Color.gray; // 비활성화 시 컬러
[SerializeField]
private Image targetImage; // 컬러를 변경할 이미지 컴포넌트
private Image[] targetImages; // 컬러를 변경할 이미지 컴포넌트
[SerializeField]
private TextMeshProUGUI[] targetTexts; // 컬러를 변경할 이미지 컴포넌트
[SerializeField]
private Selectable button; // 버튼 컴포넌트
private void Awake()
{
if (targetImage == null)
if (targetImages == null)
{
targetImage = GetComponent<UnityEngine.UI.Image>();
var img = GetComponent<Image>();
if(img != null) targetImages = new Image[] { img };
}
if (targetImage != null)
if (targetTexts == null)
{
targetImage.color = originalColor; // 초기 컬러 설정
targetImage.color = enabled ? originalColor : disabledColor;
var txt = GetComponent<TextMeshProUGUI>();
if(txt != null) targetTexts = new TextMeshProUGUI[] { txt };
}
ChangeColor(enabled ? originalColor : disabledColor);
if (button == null)
{
button = GetComponent<Button>();
@@ -59,16 +69,31 @@ namespace UVC.UI.Util
toggle.onValueChanged.AddListener(onValueChangedToggle);
}
if (targetImage != null) targetImage.color = color;
ChangeColor(color);
}
}
private void ChangeColor(Color color)
{
if (targetImages != null)
{
foreach (var img in targetImages)
{
img.color = color;
}
}
if (targetTexts != null)
{
foreach (var txt in targetTexts)
{
txt.color = color;
}
}
}
private void onValueChangedToggle(bool isOn)
{
if (targetImage != null)
{
targetImage.color = isOn ? clickColor : originalColor; // 토글 상태에 따라 컬러 변경
}
ChangeColor(isOn ? clickColor : originalColor); // 토글 상태에 따라 컬러 변경
}
private void OnDestroy()
@@ -83,17 +108,11 @@ namespace UVC.UI.Util
{
if ((button != null && button.interactable) || (button == null && enabled))
{
if (targetImage != null)
{
targetImage.color = clickColor; // 클릭 시 컬러 변경
}
ChangeColor(clickColor); // 클릭 시 컬러 변경
}
else
{
if (targetImage != null)
{
targetImage.color = disabledColor; // 비활성화 시 컬러 변경
}
ChangeColor(disabledColor); // 비활성화 상태일 때 컬러 변경
}
}
@@ -101,10 +120,7 @@ namespace UVC.UI.Util
{
if ((button != null && button.interactable) || (button == null && enabled))
{
if (targetImage != null)
{
targetImage.color = hoverColor; // 마우스 오버 시 컬러 변경
}
ChangeColor(hoverColor); // 마우스 오버 시 컬러 변경
}
}
@@ -114,23 +130,16 @@ namespace UVC.UI.Util
{
if (button != null && button is Toggle toggle)
{
if (targetImage != null)
targetImage.color = toggle.isOn ? clickColor : originalColor; // 토글 상태에 따라 컬러 변경
ChangeColor(toggle.isOn ? clickColor : originalColor); // 토글 상태에 따라 컬러 변경
}
else
{
if (targetImage != null)
{
targetImage.color = originalColor; // 마우스가 벗어날 때 원래 컬러로 변경
}
ChangeColor(originalColor); // 마우스가 벗어날 때 원래 컬러로 변경
}
}
else
{
if (targetImage != null)
{
targetImage.color = disabledColor; // 비활성화 상태일 때 컬러 변경
}
ChangeColor(disabledColor); // 비활성화 상태일 때 컬러 변경
}
}
@@ -138,17 +147,11 @@ namespace UVC.UI.Util
{
if ((button != null && button.interactable) || (button == null && enabled))
{
if (targetImage != null)
{
targetImage.color = hoverColor; // 클릭 후 원래 컬러로 변경
}
ChangeColor(hoverColor); // 클릭 후 원래 컬러로 변경
}
else
{
if (targetImage != null)
{
targetImage.color = disabledColor; // 비활성화 상태일 때 컬러 변경
}
ChangeColor(disabledColor); // 비활성화 상태일 때 컬러 변경
}
}