디자인 적용

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

@@ -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;
}