StyleGuide Sample 완료

This commit is contained in:
logonkhi
2026-01-13 20:39:45 +09:00
parent c8ff7b503d
commit ee86f93814
47 changed files with 20319 additions and 88 deletions

View File

@@ -1,5 +1,7 @@
#nullable enable
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
@@ -10,6 +12,104 @@ namespace UVC.UIToolkit
/// 텍스트와 아이콘을 동시에 표시하거나, 아이콘만 표시할 수 있습니다.
/// 배경 색상, 외곽선 굵기 등을 설정할 수 있습니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 버튼 생성
/// var btn = new UTKButton("확인");
/// btn.OnClicked += () => Debug.Log("클릭됨!");
///
/// // 텍스트와 Material Icon이 있는 버튼
/// var saveBtn = new UTKButton("저장", UTKMaterialIcons.Save, UTKButton.ButtonVariant.Primary);
///
/// // 텍스트와 아이콘 (크기 지정)
/// var largeIconBtn = new UTKButton("설정", UTKMaterialIcons.Settings, UTKButton.ButtonVariant.Primary, 28);
/// var smallImageBtn = new UTKButton("닫기", UTKImageIcons.BtnClose22, UTKButton.ButtonVariant.Danger, 20);
///
/// // Material Icon 설정
/// // 텍스트와 아이콘 (크기 지정)
/// var largeIconBtn = new UTKButton("설정", UTKMaterialIcons.Settings, UTKButton.ButtonVariant.Primary, 28);
/// var smallImageBtn = new UTKButton("닫기", UTKImageIcons.BtnClose22, UTKButton.ButtonVariant.Danger, 20);
///
/// // Text 버튼 스타일에서 아이콘 크기 지정
/// var textSmallIcon = new UTKButton("Small", UTKMaterialIcons.Settings, UTKButton.ButtonVariant.Text, 16);
/// var textMediumIcon = new UTKButton("Medium", UTKMaterialIcons.Settings, UTKButton.ButtonVariant.Text, 24);
/// var textLargeIcon = new UTKButton("Large", UTKMaterialIcons.Settings, UTKButton.ButtonVariant.Text, 32);
///
/// // Material Icon 설정
/// var imgBtn = new UTKButton("닫기");
/// imgBtn.SetImageIcon(UTKImageIcons.BtnClose22);
/// imgBtn.SetImageIconByName("icon_setting_22");
///
/// // 비동기 아이콘 설정
/// await btn.SetMaterialIconAsync(UTKMaterialIcons.Search);
/// await btn.SetImageIconAsync(UTKImageIcons.IconSetting22);
///
/// // 아이콘만 표시하는 버튼
/// var iconOnlyBtn = new UTKButton { IconOnly = true };
/// iconOnlyBtn.SetMaterialIcon(UTKMaterialIcons.Close);
///
/// // 버튼 스타일 변형
/// btn.Variant = UTKButton.ButtonVariant.Primary;
/// btn.Variant = UTKButton.ButtonVariant.Danger;
/// btn.Variant = UTKButton.ButtonVariant.Ghost;
///
/// // 버튼 크기
/// btn.Size = UTKButton.ButtonSize.Small;
/// btn.Size = UTKButton.ButtonSize.Large;
///
/// // 주의: 생성자에서 icon 파라미터로 아이콘을 전달하면 다음 순서로 타입이 감지됩니다.
/// // 1. UTKMaterialIcons에서 먼저 검사 (Material Symbols Outlined 아이콘)
/// // 2. UTKImageIcons에서 검사 (이미지 기반 아이콘)
/// // 3. 둘 다 아니면 텍스트로 처리됨
///
/// // 아이콘 제거
/// btn.ClearIcon();
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <!-- 네임스페이스 선언 -->
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
///
/// <!-- 기본 버튼 -->
/// <utk:UTKButton Text="확인" />
///
/// <!-- Material Icon 버튼 (유니코드 직접 입력) -->
/// <utk:UTKButton Text="설정" Icon="&#xe8b8;" />
///
/// <!-- 버튼 변형 -->
/// <utk:UTKButton Text="저장" Variant="Primary" />
/// <utk:UTKButton Text="삭제" Variant="Danger" />
/// <utk:UTKButton Text="취소" Variant="Ghost" />
/// <utk:UTKButton Text="링크" Variant="Text" />
///
/// <!-- 외곽선 변형 -->
/// <utk:UTKButton Text="확인" Variant="OutlinePrimary" />
/// <utk:UTKButton Text="삭제" Variant="OutlineDanger" />
///
/// <!-- 크기 변형 -->
/// <utk:UTKButton Text="작은 버튼" Size="Small" />
/// <utk:UTKButton Text="큰 버튼" Size="Large" />
///
/// <!-- 아이콘만 표시 -->
/// <utk:UTKButton Icon="&#xe5cd;" IconOnly="true" />
/// <utk:UTKButton Text="" Icon="Close" Variant="Text" IconSize="12" />
///
/// <!-- 비활성화 -->
/// <utk:UTKButton Text="비활성화" IsEnabled="false" />
///
/// <!-- 외곽선 굵기 -->
/// <utk:UTKButton Text="두꺼운 외곽선" BorderWidth="2" />
///
/// </ui:UXML>
/// </code>
/// <para><b>UXML에서 로드 후 C#에서 아이콘 설정:</b></para>
/// <code>
/// var root = GetComponent<UIDocument>().rootVisualElement;
/// var btn = root.Q<UTKButton>("my-button");
/// btn.SetMaterialIcon(UTKMaterialIcons.Settings);
/// </code>
/// </example>
[UxmlElement]
public partial class UTKButton : VisualElement, IDisposable
{
@@ -21,6 +121,7 @@ namespace UVC.UIToolkit
private bool _disposed;
private Label? _iconLabel;
private Label? _textLabel;
private VisualElement? _imageIcon;
private string _text = "";
private string _icon = "";
@@ -145,7 +246,9 @@ namespace UVC.UIToolkit
Danger,
OutlineNormal,
OutlinePrimary,
OutlineDanger
OutlineDanger,
/// <summary>배경과 외곽선이 투명하고 텍스트/아이콘만 표시</summary>
Text
}
public enum ButtonSize
@@ -170,15 +273,52 @@ namespace UVC.UIToolkit
CreateUI();
SetupEvents();
SubscribeToThemeChanges();
// UXML에서 로드될 때 속성이 설정된 후 UI 갱신
RegisterCallback<AttachToPanelEvent>(_ =>
{
UpdateContent();
UpdateVariant();
UpdateSize();
});
}
public UTKButton(string text, string icon = "", ButtonVariant variant = ButtonVariant.Normal) : this()
/// <summary>
/// 텍스트, 아이콘, 변형, 크기를 설정하여 버튼을 생성합니다.
/// </summary>
/// <param name="text">버튼에 표시할 텍스트</param>
/// <param name="icon">아이콘 이름 또는 경로 (Material Icon 또는 Image Icon)</param>
/// <param name="variant">버튼 스타일 변형</param>
/// <param name="iconSize">아이콘 크기 (생략 시 기본값 사용)</param>
/// <remarks>
/// 아이콘 타입 감지 순서:
/// 1. UTKMaterialIcons 검사 (Material Symbols Outlined 아이콘)
/// 2. UTKImageIcons 검사 (이미지 기반 아이콘)
/// 3. 둘 다 아니면 텍스트로 처리됨
/// </remarks>
public UTKButton(string text, string icon = "", ButtonVariant variant = ButtonVariant.Normal, int? iconSize = null) : this()
{
_text = text;
_icon = icon;
_variant = variant;
UpdateContent();
UpdateVariant();
// 아이콘 타입 자동 감지 및 적용
if (!string.IsNullOrEmpty(icon))
{
// 1순위: UTKMaterialIcons에 해당하는지 확인
if (UTKMaterialIcons.GetIcon(icon) != null)
{
SetMaterialIcon(icon, iconSize);
}
// 2순위: UTKImageIcons에 해당하는지 확인
else if (!string.IsNullOrEmpty(UTKImageIcons.GetPath(icon)))
{
SetImageIcon(icon, iconSize);
}
// 3순위: 둘 다 아니면 현재 로직 유지 (UpdateContent에서 이미 처리됨)
}
}
#endregion
@@ -187,6 +327,7 @@ namespace UVC.UIToolkit
{
AddToClassList("utk-button");
focusable = true;
pickingMode = PickingMode.Position;
_iconLabel = new Label
{
@@ -281,6 +422,7 @@ namespace UVC.UIToolkit
RemoveFromClassList("utk-button--outline-normal");
RemoveFromClassList("utk-button--outline-primary");
RemoveFromClassList("utk-button--outline-danger");
RemoveFromClassList("utk-button--text");
var variantClass = _variant switch
{
@@ -291,6 +433,7 @@ namespace UVC.UIToolkit
ButtonVariant.OutlineNormal => "utk-button--outline-normal",
ButtonVariant.OutlinePrimary => "utk-button--outline-primary",
ButtonVariant.OutlineDanger => "utk-button--outline-danger",
ButtonVariant.Text => "utk-button--text",
_ => "utk-button--normal"
};
AddToClassList(variantClass);
@@ -339,6 +482,179 @@ namespace UVC.UIToolkit
}
#endregion
#region Icon Methods
/// <summary>
/// Material Icon을 설정합니다.
/// </summary>
/// <param name="icon">Material Icon 유니코드 문자 (예: UTKMaterialIcons.Settings)</param>
/// <param name="fontSize">아이콘 폰트 크기 (null이면 버튼 크기에 맞춤)</param>
public void SetMaterialIcon(string icon, int? fontSize = null)
{
ClearImageIcon();
Icon = icon;
if (_iconLabel != null)
{
UTKMaterialIcons.ApplyIconStyle(_iconLabel, fontSize ?? GetDefaultIconSize());
}
}
/// <summary>
/// Material Icon을 비동기로 설정합니다.
/// </summary>
/// <param name="icon">Material Icon 유니코드 문자 (예: UTKMaterialIcons.Settings)</param>
/// <param name="ct">취소 토큰</param>
/// <param name="fontSize">아이콘 폰트 크기 (null이면 버튼 크기에 맞춤)</param>
public async UniTask SetMaterialIconAsync(string icon, CancellationToken ct = default, int? fontSize = null)
{
ClearImageIcon();
Icon = icon;
if (_iconLabel != null)
{
await UTKMaterialIcons.ApplyIconStyleAsync(_iconLabel, ct, fontSize ?? GetDefaultIconSize());
}
}
/// <summary>
/// 아이콘 이름으로 Material Icon을 설정합니다.
/// </summary>
/// <param name="iconName">아이콘 이름 (예: "settings", "home")</param>
/// <param name="fontSize">아이콘 폰트 크기 (null이면 버튼 크기에 맞춤)</param>
public void SetMaterialIconByName(string iconName, int? fontSize = null)
{
var iconChar = UTKMaterialIcons.GetIcon(iconName);
if (!string.IsNullOrEmpty(iconChar))
{
SetMaterialIcon(iconChar, fontSize);
}
else
{
Debug.LogWarning($"[UTKButton] Material icon '{iconName}'을(를) 찾을 수 없습니다.");
}
}
/// <summary>
/// Image Icon을 설정합니다.
/// </summary>
/// <param name="resourcePath">이미지 리소스 경로 (예: UTKImageIcons.IconSetting22)</param>
/// <param name="iconSize">아이콘 크기 (null이면 버튼 크기에 맞춤)</param>
public void SetImageIcon(string resourcePath, int? iconSize = null)
{
var texture = UTKImageIcons.LoadTexture(resourcePath);
if (texture != null)
{
ApplyImageIcon(texture, iconSize);
}
else
{
Debug.LogWarning($"[UTKButton] Image icon '{resourcePath}'을(를) 로드할 수 없습니다.");
}
}
/// <summary>
/// Image Icon을 비동기로 설정합니다.
/// </summary>
/// <param name="resourcePath">이미지 리소스 경로 (예: UTKImageIcons.IconSetting22)</param>
/// <param name="ct">취소 토큰</param>
/// <param name="iconSize">아이콘 크기 (null이면 버튼 크기에 맞춤)</param>
public async UniTask SetImageIconAsync(string resourcePath, CancellationToken ct = default, int? iconSize = null)
{
var texture = await UTKImageIcons.LoadTextureAsync(resourcePath, ct);
if (texture != null)
{
ApplyImageIcon(texture, iconSize);
}
else
{
Debug.LogWarning($"[UTKButton] Image icon '{resourcePath}'을(를) 로드할 수 없습니다.");
}
}
/// <summary>
/// 아이콘 이름으로 Image Icon을 설정합니다.
/// </summary>
/// <param name="iconName">아이콘 이름 (예: "icon_setting_22", "btn_close_16")</param>
/// <param name="iconSize">아이콘 크기 (null이면 버튼 크기에 맞춤)</param>
public void SetImageIconByName(string iconName, int? iconSize = null)
{
var texture = UTKImageIcons.LoadTextureByName(iconName);
if (texture != null)
{
ApplyImageIcon(texture, iconSize);
}
else
{
Debug.LogWarning($"[UTKButton] Image icon '{iconName}'을(를) 찾을 수 없습니다.");
}
}
/// <summary>
/// 모든 아이콘을 제거합니다.
/// </summary>
public void ClearIcon()
{
Icon = "";
ClearImageIcon();
if (_iconLabel != null)
{
_iconLabel.style.display = DisplayStyle.None;
}
}
private void ApplyImageIcon(Texture2D texture, int? iconSize)
{
// 기존 아이콘 Label 숨기기
Icon = "";
if (_iconLabel != null)
{
_iconLabel.style.display = DisplayStyle.None;
}
// 이미지 아이콘용 VisualElement 생성 또는 재사용
if (_imageIcon == null)
{
_imageIcon = new VisualElement
{
name = "image-icon",
pickingMode = PickingMode.Ignore
};
_imageIcon.AddToClassList("utk-button__image-icon");
Insert(0, _imageIcon);
}
var size = iconSize ?? GetDefaultIconSize();
_imageIcon.style.width = size;
_imageIcon.style.height = size;
_imageIcon.style.backgroundImage = new StyleBackground(texture);
_imageIcon.style.display = DisplayStyle.Flex;
EnableInClassList("utk-button--has-image-icon", true);
}
private void ClearImageIcon()
{
if (_imageIcon != null)
{
_imageIcon.style.display = DisplayStyle.None;
}
EnableInClassList("utk-button--has-image-icon", false);
}
private int GetDefaultIconSize()
{
return _size switch
{
ButtonSize.Small => 16,
ButtonSize.Large => 24,
_ => 20 // Medium
};
}
#endregion
#region IDisposable
public void Dispose()
{

View File

@@ -2,6 +2,7 @@
using System;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit.Common;
namespace UVC.UIToolkit
{
@@ -9,12 +10,37 @@ namespace UVC.UIToolkit
/// 체크박스 컴포넌트.
/// 선택/해제 상태를 토글할 수 있습니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 체크박스
/// var checkbox = new UTKCheckBox();
/// checkbox.Text = "약관에 동의합니다";
/// checkbox.OnValueChanged += (isChecked) => Debug.Log($"체크: {isChecked}");
///
/// // 상태 설정
/// checkbox.IsChecked = true;
/// checkbox.IsIndeterminate = true; // 부분 선택 상태
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 체크박스 -->
/// <utk:UTKCheckBox Text="이메일 수신 동의" />
///
/// <!-- 기본값 체크됨 -->
/// <utk:UTKCheckBox Text="자동 로그인" IsChecked="true" />
///
/// <!-- 비활성화 -->
/// <utk:UTKCheckBox Text="필수 동의" IsEnabled="false" IsChecked="true" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKCheckBox : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Button/UTKCheckBox";
private const string CHECK_ICON = "✓";
#endregion
#region Fields
@@ -95,6 +121,13 @@ namespace UVC.UIToolkit
CreateUI();
SetupEvents();
SubscribeToThemeChanges();
// UXML에서 로드될 때 속성이 설정된 후 UI 갱신
RegisterCallback<AttachToPanelEvent>(_ =>
{
if (_label != null) _label.text = _text;
UpdateState();
});
}
public UTKCheckBox(string text, bool isChecked = false) : this()
@@ -113,8 +146,9 @@ namespace UVC.UIToolkit
_checkBox = new VisualElement { name = "checkbox" };
_checkBox.AddToClassList("utk-checkbox__box");
_checkIcon = new Label { name = "check-icon", text = CHECK_ICON };
_checkIcon = new Label { name = "check-icon", text = UTKMaterialIcons.Check };
_checkIcon.AddToClassList("utk-checkbox__icon");
UTKMaterialIcons.ApplyIconStyle(_checkIcon, 14);
_checkBox.Add(_checkIcon);
Add(_checkBox);
@@ -200,7 +234,7 @@ namespace UVC.UIToolkit
if (_checkIcon != null)
{
_checkIcon.text = _isIndeterminate ? "" : CHECK_ICON;
_checkIcon.text = _isIndeterminate ? UTKMaterialIcons.Remove : UTKMaterialIcons.Check;
}
}
#endregion

View File

@@ -9,6 +9,33 @@ namespace UVC.UIToolkit
/// 라디오 버튼 컴포넌트.
/// Unity RadioButton을 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 라디오 버튼 그룹
/// var group = new RadioButtonGroup();
/// var radio1 = new UTKRadioButton { Label = "옵션 1" };
/// var radio2 = new UTKRadioButton { Label = "옵션 2" };
/// var radio3 = new UTKRadioButton { Label = "옵션 3" };
/// group.Add(radio1);
/// group.Add(radio2);
/// group.Add(radio3);
///
/// radio1.OnValueChanged += (isSelected) => {
/// if (isSelected) Debug.Log("옵션 1 선택됨");
/// };
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <ui:RadioButtonGroup>
/// <utk:UTKRadioButton label="소형" />
/// <utk:UTKRadioButton label="중형" value="true" />
/// <utk:UTKRadioButton label="대형" />
/// </ui:RadioButtonGroup>
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKRadioButton : RadioButton, IDisposable
{

View File

@@ -9,6 +9,29 @@ namespace UVC.UIToolkit
/// 토글 스위치 컴포넌트.
/// Unity Toggle을 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 토글
/// var toggle = new UTKToggle();
/// toggle.Label = "알림 받기";
/// toggle.IsOn = true;
/// toggle.OnValueChanged += (isOn) => Debug.Log($"토글: {isOn}");
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 토글 -->
/// <utk:UTKToggle label="다크 모드" />
///
/// <!-- 기본값 켜짐 -->
/// <utk:UTKToggle label="자동 저장" value="true" />
///
/// <!-- 비활성화 -->
/// <utk:UTKToggle label="프리미엄 기능" IsEnabled="false" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKToggle : Toggle, IDisposable
{

View File

@@ -10,6 +10,37 @@ namespace UVC.UIToolkit
/// 토글 버튼 그룹 컴포넌트.
/// Unity ToggleButtonGroup을 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 토글 버튼 그룹 생성
/// var group = new UTKToggleButtonGroup();
/// group.Add(new Button { text = "왼쪽" });
/// group.Add(new Button { text = "가운데" });
/// group.Add(new Button { text = "오른쪽" });
///
/// // 단일 선택 모드
/// group.allowEmptySelection = false;
///
/// // 다중 선택 모드
/// group.isMultipleSelection = true;
///
/// // 선택 변경 이벤트
/// group.OnSelectionChanged += (indices) => {
/// Debug.Log($"선택됨: {string.Join(", ", indices)}");
/// };
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKToggleButtonGroup allow-empty-selection="false">
/// <ui:Button text="왼쪽" />
/// <ui:Button text="가운데" />
/// <ui:Button text="오른쪽" />
/// </utk:UTKToggleButtonGroup>
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKToggleButtonGroup : ToggleButtonGroup, IDisposable
{

View File

@@ -9,6 +9,44 @@ namespace UVC.UIToolkit
/// 카드 컴포넌트.
/// 콘텐츠를 카드 형태로 표시합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 카드
/// var card = new UTKCard();
/// card.Title = "카드 제목";
/// card.Subtitle = "부제목";
/// card.Variant = UTKCard.CardVariant.Elevated;
///
/// // 이미지 설정
/// card.SetImage(myTexture);
///
/// // 콘텐츠 추가
/// card.AddContent(new Label("카드 내용"));
///
/// // 액션 버튼 추가
/// card.AddActionButton("자세히", () => Debug.Log("클릭"));
///
/// // 클릭 가능 카드
/// card.IsClickable = true;
/// card.OnClicked += () => Debug.Log("카드 클릭");
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 카드 -->
/// <utk:UTKCard Title="제목" Subtitle="부제목" Variant="Elevated">
/// <ui:Label text="카드 내용" />
/// </utk:UTKCard>
///
/// <!-- 클릭 가능 카드 -->
/// <utk:UTKCard Title="클릭해보세요" IsClickable="true" />
///
/// <!-- 외곽선 카드 -->
/// <utk:UTKCard Title="외곽선" Variant="Outlined" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKCard : VisualElement, IDisposable
{
@@ -138,6 +176,23 @@ namespace UVC.UIToolkit
CreateUI();
SetupEvents();
SubscribeToThemeChanges();
// UXML에서 로드될 때 속성이 설정된 후 UI 갱신
RegisterCallback<AttachToPanelEvent>(_ =>
{
if (_titleLabel != null)
{
_titleLabel.text = _title;
_titleLabel.style.display = string.IsNullOrEmpty(_title) ? DisplayStyle.None : DisplayStyle.Flex;
}
if (_subtitleLabel != null)
{
_subtitleLabel.text = _subtitle;
_subtitleLabel.style.display = string.IsNullOrEmpty(_subtitle) ? DisplayStyle.None : DisplayStyle.Flex;
}
UpdateVariant();
UpdateHeaderVisibility();
});
}
public UTKCard(string title, string subtitle = "") : this()

View File

@@ -9,6 +9,38 @@ namespace UVC.UIToolkit
/// 접을 수 있는 섹션 컴포넌트.
/// Unity Foldout을 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 폴드아웃
/// var foldout = new UTKFoldout("고급 설정", expanded: false);
/// foldout.Add(new Label("옵션 1"));
/// foldout.Add(new Label("옵션 2"));
///
/// // 상태 변경 이벤트
/// foldout.OnValueChanged += (isExpanded) => {
/// Debug.Log(isExpanded ? "펼쳐짐" : "접힘");
/// };
///
/// // 프로그래밍 방식으로 상태 제어
/// foldout.IsExpanded = true;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 폴드아웃 -->
/// <utk:UTKFoldout text="설정" value="true">
/// <ui:Label text="내용 1" />
/// <ui:Label text="내용 2" />
/// </utk:UTKFoldout>
///
/// <!-- 접힌 상태 -->
/// <utk:UTKFoldout text="고급 옵션" value="false">
/// <ui:Label text="숨겨진 내용" />
/// </utk:UTKFoldout>
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKFoldout : Foldout, IDisposable
{

View File

@@ -9,6 +9,44 @@ namespace UVC.UIToolkit
/// 도움말 박스 컴포넌트.
/// Unity HelpBox를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 정보 메시지
/// var infoBox = new UTKHelpBox()
/// {
/// Message = "이 기능은 베타 버전입니다.",
/// messageType = HelpBoxMessageType.Info
/// };
///
/// // 경고 메시지
/// var warningBox = new UTKHelpBox()
/// {
/// Message = "주의: 되돌릴 수 없습니다.",
/// messageType = HelpBoxMessageType.Warning
/// };
///
/// // 오류 메시지
/// var errorBox = new UTKHelpBox()
/// {
/// Message = "오류: 파일을 찾을 수 없습니다.",
/// messageType = HelpBoxMessageType.Error
/// };
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 정보 -->
/// <utk:UTKHelpBox text="정보 메시지입니다." message-type="Info" />
///
/// <!-- 경고 -->
/// <utk:UTKHelpBox text="경고 메시지입니다." message-type="Warning" />
///
/// <!-- 오류 -->
/// <utk:UTKHelpBox text="오류 메시지입니다." message-type="Error" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKHelpBox : HelpBox, IDisposable
{

View File

@@ -0,0 +1,359 @@
// <auto-generated>
// 이 파일은 UTKImageIconsGenerator에 의해 자동 생성되었습니다.
// 직접 수정하지 마세요. Tools > UTK > Image Icons Generator 메뉴로 재생성하세요.
// Source: Assets/Resources/UIToolkit/Images
// </auto-generated>
#nullable enable
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace UVC.UIToolkit
{
/// <summary>
/// 이미지 아이콘 리소스 경로 상수 클래스입니다.
/// 총 41개의 아이콘을 포함합니다.
/// </summary>
/// <example>
/// <code>
/// // 상수로 리소스 경로 사용
/// string path = UTKImageIcons.Home;
///
/// // 동기 Sprite 로드 (캐싱됨)
/// Sprite sprite = UTKImageIcons.LoadSprite(UTKImageIcons.Home);
///
/// // 비동기 Sprite 로드 (UniTask, 캐싱됨)
/// Sprite? sprite = await UTKImageIcons.LoadSpriteAsync(UTKImageIcons.Home, cancellationToken);
///
/// // 동기 Texture2D 로드 (캐싱됨)
/// Texture2D texture = UTKImageIcons.LoadTexture(UTKImageIcons.Settings);
///
/// // 비동기 Texture2D 로드 (UniTask, 캐싱됨)
/// Texture2D? texture = await UTKImageIcons.LoadTextureAsync(UTKImageIcons.Settings, cancellationToken);
///
/// // 이름으로 Sprite 로드
/// Sprite icon = UTKImageIcons.LoadSpriteByName("home");
///
/// // 이름으로 비동기 Sprite 로드
/// Sprite? icon = await UTKImageIcons.LoadSpriteByNameAsync("home", cancellationToken);
///
/// // 이름으로 경로 조회
/// string iconPath = UTKImageIcons.GetPath("settings");
///
/// // 아이콘 존재 여부 확인
/// if (UTKImageIcons.HasIcon("search")) { }
///
/// // 전체 아이콘 이름 순회
/// foreach (var name in UTKImageIcons.GetAllIconNames()) { }
///
/// // 캐시 클리어
/// UTKImageIcons.ClearCache();
/// </code>
/// </example>
/// <remarks>
/// <para><b>UXML에서 사용하기:</b></para>
/// <para>UXML에서 이미지 아이콘을 사용하려면 USS에서 background-image를 설정합니다.</para>
/// <code>
/// /* USS 파일 */
/// .my-icon {
/// width: 24px;
/// height: 24px;
/// background-image: resource('UIToolkit/Images/icon_setting_22');
/// }
/// </code>
/// <code>
/// <!-- UXML 파일 -->
/// <ui:VisualElement class="my-icon" />
/// </code>
/// <para><b>C# 코드에서 UXML 요소에 이미지 적용:</b></para>
/// <code>
/// var iconElement = root.Q<VisualElement>("my-icon");
/// var texture = UTKImageIcons.LoadTextureByName("icon_setting_22");
/// iconElement.style.backgroundImage = new StyleBackground(texture);
/// </code>
/// <para><b>Image 요소에서 사용:</b></para>
/// <code>
/// var image = root.Q<Image>("my-image");
/// image.sprite = UTKImageIcons.LoadSpriteByName("btn_close_16");
/// </code>
/// </remarks>
public static class UTKImageIcons
{
/// <summary>btn_cancel_64.png</summary>
public const string BtnCancel64 = "UIToolkit/Images/btn_cancel_64";
/// <summary>btn_close_16.png</summary>
public const string BtnClose16 = "UIToolkit/Images/btn_close_16";
/// <summary>btn_close_22.png</summary>
public const string BtnClose22 = "UIToolkit/Images/btn_close_22";
/// <summary>cursor_arc_32.png</summary>
public const string CursorArc32 = "UIToolkit/Images/cursor_arc_32";
/// <summary>cursor_ask_32.png</summary>
public const string CursorAsk32 = "UIToolkit/Images/cursor_ask_32";
/// <summary>cursor_context_menu_32.png</summary>
public const string CursorContextMenu32 = "UIToolkit/Images/cursor_context_menu_32";
/// <summary>cursor_copy_32.png</summary>
public const string CursorCopy32 = "UIToolkit/Images/cursor_copy_32";
/// <summary>cursor_default_black_32.png</summary>
public const string CursorDefaultBlack32 = "UIToolkit/Images/cursor_default_black_32";
/// <summary>cursor_default_white_32.png</summary>
public const string CursorDefaultWhite32 = "UIToolkit/Images/cursor_default_white_32";
/// <summary>cursor_export_32.png</summary>
public const string CursorExport32 = "UIToolkit/Images/cursor_export_32";
/// <summary>cursor_grabbing_32.png</summary>
public const string CursorGrabbing32 = "UIToolkit/Images/cursor_grabbing_32";
/// <summary>cursor_hand_32.png</summary>
public const string CursorHand32 = "UIToolkit/Images/cursor_hand_32";
/// <summary>cursor_link_32.png</summary>
public const string CursorLink32 = "UIToolkit/Images/cursor_link_32";
/// <summary>cursor_move_32.png</summary>
public const string CursorMove32 = "UIToolkit/Images/cursor_move_32";
/// <summary>cursor_node_32.png</summary>
public const string CursorNode32 = "UIToolkit/Images/cursor_node_32";
/// <summary>cursor_point_white_32.png</summary>
public const string CursorPointWhite32 = "UIToolkit/Images/cursor_point_white_32";
/// <summary>cursor_resize_col_32.png</summary>
public const string CursorResizeCol32 = "UIToolkit/Images/cursor_resize_col_32";
/// <summary>cursor_resize_down_32.png</summary>
public const string CursorResizeDown32 = "UIToolkit/Images/cursor_resize_down_32";
/// <summary>cursor_resize_h_32.png</summary>
public const string CursorResizeH32 = "UIToolkit/Images/cursor_resize_h_32";
/// <summary>cursor_resize_left_32.png</summary>
public const string CursorResizeLeft32 = "UIToolkit/Images/cursor_resize_left_32";
/// <summary>cursor_resize_right_32.png</summary>
public const string CursorResizeRight32 = "UIToolkit/Images/cursor_resize_right_32";
/// <summary>cursor_resize_row_32.png</summary>
public const string CursorResizeRow32 = "UIToolkit/Images/cursor_resize_row_32";
/// <summary>cursor_resize_tlbr_32.png</summary>
public const string CursorResizeTlbr32 = "UIToolkit/Images/cursor_resize_tlbr_32";
/// <summary>cursor_resize_trbl_32.png</summary>
public const string CursorResizeTrbl32 = "UIToolkit/Images/cursor_resize_trbl_32";
/// <summary>cursor_resize_up_32.png</summary>
public const string CursorResizeUp32 = "UIToolkit/Images/cursor_resize_up_32";
/// <summary>cursor_resize_v_32.png</summary>
public const string CursorResizeV32 = "UIToolkit/Images/cursor_resize_v_32";
/// <summary>cursor_rotate_bottom_left_32.png</summary>
public const string CursorRotateBottomLeft32 = "UIToolkit/Images/cursor_rotate_bottom_left_32";
/// <summary>cursor_rotate_bottom_right_32.png</summary>
public const string CursorRotateBottomRight32 = "UIToolkit/Images/cursor_rotate_bottom_right_32";
/// <summary>cursor_rotate_top_left_32.png</summary>
public const string CursorRotateTopLeft32 = "UIToolkit/Images/cursor_rotate_top_left_32";
/// <summary>cursor_rotate_top_right_32.png</summary>
public const string CursorRotateTopRight32 = "UIToolkit/Images/cursor_rotate_top_right_32";
/// <summary>cursor_select_32.png</summary>
public const string CursorSelect32 = "UIToolkit/Images/cursor_select_32";
/// <summary>cursor_wait_32.png</summary>
public const string CursorWait32 = "UIToolkit/Images/cursor_wait_32";
/// <summary>cursor_zoom_in_32.png</summary>
public const string CursorZoomIn32 = "UIToolkit/Images/cursor_zoom_in_32";
/// <summary>cursor_zoom_out_32.png</summary>
public const string CursorZoomOut32 = "UIToolkit/Images/cursor_zoom_out_32";
/// <summary>icon_down_20x16.png</summary>
public const string IconDown20x16 = "UIToolkit/Images/icon_down_20x16";
/// <summary>icon_down_22.png</summary>
public const string IconDown22 = "UIToolkit/Images/icon_down_22";
/// <summary>icon_eye_22x16.png</summary>
public const string IconEye22x16 = "UIToolkit/Images/icon_eye_22x16";
/// <summary>icon_eye_close_22x16.png</summary>
public const string IconEyeClose22x16 = "UIToolkit/Images/icon_eye_close_22x16";
/// <summary>icon_right_22.png</summary>
public const string IconRight22 = "UIToolkit/Images/icon_right_22";
/// <summary>icon_search_22x16.png</summary>
public const string IconSearch22x16 = "UIToolkit/Images/icon_search_22x16";
/// <summary>icon_setting_22.png</summary>
public const string IconSetting22 = "UIToolkit/Images/icon_setting_22";
#region Lookup & Load
private static readonly Dictionary<string, string> _pathsByName = new()
{
["btn_cancel_64"] = "UIToolkit/Images/btn_cancel_64",
["btn_close_16"] = "UIToolkit/Images/btn_close_16",
["btn_close_22"] = "UIToolkit/Images/btn_close_22",
["cursor_arc_32"] = "UIToolkit/Images/cursor_arc_32",
["cursor_ask_32"] = "UIToolkit/Images/cursor_ask_32",
["cursor_context_menu_32"] = "UIToolkit/Images/cursor_context_menu_32",
["cursor_copy_32"] = "UIToolkit/Images/cursor_copy_32",
["cursor_default_black_32"] = "UIToolkit/Images/cursor_default_black_32",
["cursor_default_white_32"] = "UIToolkit/Images/cursor_default_white_32",
["cursor_export_32"] = "UIToolkit/Images/cursor_export_32",
["cursor_grabbing_32"] = "UIToolkit/Images/cursor_grabbing_32",
["cursor_hand_32"] = "UIToolkit/Images/cursor_hand_32",
["cursor_link_32"] = "UIToolkit/Images/cursor_link_32",
["cursor_move_32"] = "UIToolkit/Images/cursor_move_32",
["cursor_node_32"] = "UIToolkit/Images/cursor_node_32",
["cursor_point_white_32"] = "UIToolkit/Images/cursor_point_white_32",
["cursor_resize_col_32"] = "UIToolkit/Images/cursor_resize_col_32",
["cursor_resize_down_32"] = "UIToolkit/Images/cursor_resize_down_32",
["cursor_resize_h_32"] = "UIToolkit/Images/cursor_resize_h_32",
["cursor_resize_left_32"] = "UIToolkit/Images/cursor_resize_left_32",
["cursor_resize_right_32"] = "UIToolkit/Images/cursor_resize_right_32",
["cursor_resize_row_32"] = "UIToolkit/Images/cursor_resize_row_32",
["cursor_resize_tlbr_32"] = "UIToolkit/Images/cursor_resize_tlbr_32",
["cursor_resize_trbl_32"] = "UIToolkit/Images/cursor_resize_trbl_32",
["cursor_resize_up_32"] = "UIToolkit/Images/cursor_resize_up_32",
["cursor_resize_v_32"] = "UIToolkit/Images/cursor_resize_v_32",
["cursor_rotate_bottom_left_32"] = "UIToolkit/Images/cursor_rotate_bottom_left_32",
["cursor_rotate_bottom_right_32"] = "UIToolkit/Images/cursor_rotate_bottom_right_32",
["cursor_rotate_top_left_32"] = "UIToolkit/Images/cursor_rotate_top_left_32",
["cursor_rotate_top_right_32"] = "UIToolkit/Images/cursor_rotate_top_right_32",
["cursor_select_32"] = "UIToolkit/Images/cursor_select_32",
["cursor_wait_32"] = "UIToolkit/Images/cursor_wait_32",
["cursor_zoom_in_32"] = "UIToolkit/Images/cursor_zoom_in_32",
["cursor_zoom_out_32"] = "UIToolkit/Images/cursor_zoom_out_32",
["icon_down_20x16"] = "UIToolkit/Images/icon_down_20x16",
["icon_down_22"] = "UIToolkit/Images/icon_down_22",
["icon_eye_22x16"] = "UIToolkit/Images/icon_eye_22x16",
["icon_eye_close_22x16"] = "UIToolkit/Images/icon_eye_close_22x16",
["icon_right_22"] = "UIToolkit/Images/icon_right_22",
["icon_search_22x16"] = "UIToolkit/Images/icon_search_22x16",
["icon_setting_22"] = "UIToolkit/Images/icon_setting_22",
};
private static readonly Dictionary<string, Sprite?> _spriteCache = new();
private static readonly Dictionary<string, Texture2D?> _textureCache = new();
/// <summary>
/// 아이콘 이름으로 리소스 경로를 조회합니다.
/// </summary>
/// <param name="iconName">아이콘 파일명 (확장자 제외)</param>
/// <returns>리소스 경로, 없으면 빈 문자열</returns>
public static string GetPath(string iconName)
{
return _pathsByName.TryGetValue(iconName, out var path) ? path : string.Empty;
}
/// <summary>
/// 아이콘이 존재하는지 확인합니다.
/// </summary>
public static bool HasIcon(string iconName) => _pathsByName.ContainsKey(iconName);
/// <summary>
/// 모든 아이콘 이름 목록을 반환합니다.
/// </summary>
public static IEnumerable<string> GetAllIconNames() => _pathsByName.Keys;
/// <summary>
/// 전체 아이콘 수를 반환합니다.
/// </summary>
public static int Count => 41;
/// <summary>
/// 리소스 경로로 Sprite를 동기로 로드합니다. (캐싱됨)
/// </summary>
/// <param name="resourcePath">리소스 경로 (예: Icons/Home)</param>
public static Sprite? LoadSprite(string resourcePath)
{
if (_spriteCache.TryGetValue(resourcePath, out var cached))
return cached;
var sprite = Resources.Load<Sprite>(resourcePath);
_spriteCache[resourcePath] = sprite;
return sprite;
}
/// <summary>
/// 리소스 경로로 Sprite를 비동기로 로드합니다. (캐싱됨)
/// </summary>
/// <param name="resourcePath">리소스 경로 (예: Icons/Home)</param>
/// <param name="ct">취소 토큰</param>
public static async UniTask<Sprite?> LoadSpriteAsync(string resourcePath, CancellationToken ct = default)
{
if (_spriteCache.TryGetValue(resourcePath, out var cached))
return cached;
var request = Resources.LoadAsync<Sprite>(resourcePath);
await request.ToUniTask(cancellationToken: ct);
var sprite = request.asset as Sprite;
_spriteCache[resourcePath] = sprite;
return sprite;
}
/// <summary>
/// 리소스 경로로 Texture2D를 동기로 로드합니다. (캐싱됨)
/// </summary>
/// <param name="resourcePath">리소스 경로 (예: Icons/Home)</param>
public static Texture2D? LoadTexture(string resourcePath)
{
if (_textureCache.TryGetValue(resourcePath, out var cached))
return cached;
var texture = Resources.Load<Texture2D>(resourcePath);
_textureCache[resourcePath] = texture;
return texture;
}
/// <summary>
/// 리소스 경로로 Texture2D를 비동기로 로드합니다. (캐싱됨)
/// </summary>
/// <param name="resourcePath">리소스 경로 (예: Icons/Home)</param>
/// <param name="ct">취소 토큰</param>
public static async UniTask<Texture2D?> LoadTextureAsync(string resourcePath, CancellationToken ct = default)
{
if (_textureCache.TryGetValue(resourcePath, out var cached))
return cached;
var request = Resources.LoadAsync<Texture2D>(resourcePath);
await request.ToUniTask(cancellationToken: ct);
var texture = request.asset as Texture2D;
_textureCache[resourcePath] = texture;
return texture;
}
/// <summary>
/// 아이콘 이름으로 Sprite를 동기로 로드합니다. (캐싱됨)
/// </summary>
/// <param name="iconName">아이콘 파일명 (확장자 제외)</param>
public static Sprite? LoadSpriteByName(string iconName)
{
var path = GetPath(iconName);
return string.IsNullOrEmpty(path) ? null : LoadSprite(path);
}
/// <summary>
/// 아이콘 이름으로 Sprite를 비동기로 로드합니다. (캐싱됨)
/// </summary>
/// <param name="iconName">아이콘 파일명 (확장자 제외)</param>
/// <param name="ct">취소 토큰</param>
public static async UniTask<Sprite?> LoadSpriteByNameAsync(string iconName, CancellationToken ct = default)
{
var path = GetPath(iconName);
return string.IsNullOrEmpty(path) ? null : await LoadSpriteAsync(path, ct);
}
/// <summary>
/// 아이콘 이름으로 Texture2D를 동기로 로드합니다. (캐싱됨)
/// </summary>
/// <param name="iconName">아이콘 파일명 (확장자 제외)</param>
public static Texture2D? LoadTextureByName(string iconName)
{
var path = GetPath(iconName);
return string.IsNullOrEmpty(path) ? null : LoadTexture(path);
}
/// <summary>
/// 아이콘 이름으로 Texture2D를 비동기로 로드합니다. (캐싱됨)
/// </summary>
/// <param name="iconName">아이콘 파일명 (확장자 제외)</param>
/// <param name="ct">취소 토큰</param>
public static async UniTask<Texture2D?> LoadTextureByNameAsync(string iconName, CancellationToken ct = default)
{
var path = GetPath(iconName);
return string.IsNullOrEmpty(path) ? null : await LoadTextureAsync(path, ct);
}
/// <summary>
/// 캐시를 클리어합니다.
/// </summary>
public static void ClearCache()
{
_spriteCache.Clear();
_textureCache.Clear();
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,37 @@ namespace UVC.UIToolkit
/// 스크롤 뷰 컴포넌트.
/// Unity ScrollView를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 스크롤 뷰
/// var scrollView = new UTKScrollView();
/// scrollView.Add(new Label("내용 1"));
/// scrollView.Add(new Label("내용 2"));
/// scrollView.Add(new Label("내용 3"));
///
/// // 스크롤 모드 설정
/// var verticalScroll = new UTKScrollView(ScrollViewMode.Vertical);
/// var horizontalScroll = new UTKScrollView(ScrollViewMode.Horizontal);
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 스크롤 뷰 -->
/// <utk:UTKScrollView style="height: 200px;">
/// <ui:Label text="항목 1" />
/// <ui:Label text="항목 2" />
/// <ui:Label text="항목 3" />
/// </utk:UTKScrollView>
///
/// <!-- 수직 스크롤만 -->
/// <utk:UTKScrollView mode="Vertical" />
///
/// <!-- 수평 스크롤만 -->
/// <utk:UTKScrollView mode="Horizontal" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKScrollView : ScrollView, IDisposable
{

View File

@@ -10,6 +10,33 @@ namespace UVC.UIToolkit
/// 드롭다운 메뉴 컴포넌트.
/// Unity DropdownField를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 드롭다운
/// var dropdown = new UTKDropdown();
/// dropdown.Label = "국가 선택";
/// dropdown.SetChoices(new List<string> { "한국", "미국", "일본" });
/// dropdown.OnSelectionChanged += (index, value) => Debug.Log($"선택: {value}");
///
/// // 기본값 설정
/// dropdown.value = "한국";
/// dropdown.index = 0;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 드롭다운 -->
/// <utk:UTKDropdown label="정렬" choices="이름,날짜,크기" />
///
/// <!-- 기본값 지정 -->
/// <utk:UTKDropdown label="언어" choices="한국어,English,日本語" index="0" />
///
/// <!-- 비활성화 -->
/// <utk:UTKDropdown label="선택" IsEnabled="false" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKDropdown : DropdownField, IDisposable
{

View File

@@ -9,6 +9,38 @@ namespace UVC.UIToolkit
/// Enum 선택 드롭다운 컴포넌트.
/// Unity EnumField를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // Enum 정의
/// public enum MyOption { Option1, Option2, Option3 }
///
/// // 기본 Enum 드롭다운
/// var enumDropdown = new UTKEnumDropDown();
/// enumDropdown.label = "옵션 선택";
/// enumDropdown.Init(MyOption.Option1);
///
/// // 값 변경 이벤트
/// enumDropdown.OnValueChanged += (value) => {
/// Debug.Log($"선택됨: {value}");
/// };
///
/// // 현재 값 가져오기
/// var current = (MyOption)enumDropdown.Value;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- UXML에서는 C#에서 Init() 호출 필요 -->
/// <utk:UTKEnumDropDown name="my-enum" label="옵션" />
/// </ui:UXML>
/// </code>
/// <para><b>UXML 로드 후 초기화:</b></para>
/// <code>
/// var enumField = root.Q<UTKEnumDropDown>("my-enum");
/// enumField.Init(MyOption.Option1);
/// </code>
/// </example>
[UxmlElement]
public partial class UTKEnumDropDown : EnumField, IDisposable
{

View File

@@ -9,6 +9,27 @@ namespace UVC.UIToolkit
/// 실수 입력 필드 컴포넌트.
/// Unity FloatField를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 실수 필드
/// var floatField = new UTKFloatField();
/// floatField.label = "가격";
/// floatField.value = 99.99f;
///
/// // 값 변경 이벤트
/// floatField.OnValueChanged += (value) => Debug.Log($"가격: {value}");
///
/// // 현재 값 접근
/// float current = floatField.Value;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKFloatField label="가격" value="99.99" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKFloatField : FloatField, IDisposable
{

View File

@@ -9,6 +9,47 @@ namespace UVC.UIToolkit
/// 입력 필드 컴포넌트.
/// Unity TextField를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 입력 필드
/// var input = new UTKInputField();
/// input.Label = "이름";
/// input.Placeholder = "이름을 입력하세요";
/// input.OnValueChanged += (value) => Debug.Log($"입력값: {value}");
///
/// // 비밀번호 입력 필드
/// var password = new UTKInputField();
/// password.Label = "비밀번호";
/// password.isPasswordField = true;
///
/// // 검증 오류 표시
/// input.SetError("이름은 필수입니다.");
/// input.ClearError();
///
/// // 변형 스타일
/// input.Variant = UTKInputField.InputFieldVariant.Outlined;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 입력 필드 -->
/// <utk:UTKInputField label="이름" />
///
/// <!-- 플레이스홀더 -->
/// <utk:UTKInputField label="이메일" Placeholder="example@email.com" />
///
/// <!-- 비밀번호 필드 -->
/// <utk:UTKInputField label="비밀번호" is-password-field="true" />
///
/// <!-- 여러 줄 입력 -->
/// <utk:UTKInputField label="설명" multiline="true" />
///
/// <!-- 비활성화 -->
/// <utk:UTKInputField label="읽기전용" IsEnabled="false" value="수정 불가" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKInputField : TextField, IDisposable
{
@@ -133,6 +174,12 @@ namespace UVC.UIToolkit
SetupStyles();
SetupEvents();
SubscribeToThemeChanges();
// UXML에서 로드될 때 속성이 설정된 후 UI 갱신
RegisterCallback<AttachToPanelEvent>(_ =>
{
UpdateVariant();
});
}
public UTKInputField(string label, string placeholder = "") : this()

View File

@@ -9,6 +9,27 @@ namespace UVC.UIToolkit
/// 정수 입력 필드 컴포넌트.
/// Unity IntegerField를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 정수 필드
/// var intField = new UTKIntegerField();
/// intField.label = "수량";
/// intField.value = 10;
///
/// // 값 변경 이벤트
/// intField.OnValueChanged += (value) => Debug.Log($"수량: {value}");
///
/// // 현재 값 접근
/// int current = intField.Value;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKIntegerField label="수량" value="10" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKIntegerField : IntegerField, IDisposable
{

View File

@@ -2,6 +2,7 @@
using System;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit.Common;
namespace UVC.UIToolkit.Input
{
@@ -217,15 +218,17 @@ namespace UVC.UIToolkit.Input
buttonContainer.AddToClassList("utk-number-stepper__buttons");
// Up Button
_upButton = new Button { name = "stepper-up", text = "\u25B2" }; // ▲
_upButton = new Button { name = "stepper-up", text = UTKMaterialIcons.KeyboardArrowUp };
_upButton.AddToClassList("utk-number-stepper__btn");
_upButton.AddToClassList("utk-number-stepper__btn--up");
UTKMaterialIcons.ApplyIconStyle(_upButton, 14);
buttonContainer.Add(_upButton);
// Down Button
_downButton = new Button { name = "stepper-down", text = "\u25BC" }; // ▼
_downButton = new Button { name = "stepper-down", text = UTKMaterialIcons.KeyboardArrowDown };
_downButton.AddToClassList("utk-number-stepper__btn");
_downButton.AddToClassList("utk-number-stepper__btn--down");
UTKMaterialIcons.ApplyIconStyle(_downButton, 14);
buttonContainer.Add(_downButton);
Add(buttonContainer);

View File

@@ -9,6 +9,29 @@ namespace UVC.UIToolkit
/// Vector3 입력 필드 컴포넌트.
/// Unity Vector3Field를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 Vector3 필드
/// var vec3Field = new UTKVector3Field();
/// vec3Field.label = "위치";
/// vec3Field.Value = new Vector3(1, 2, 3);
///
/// // 값 변경 이벤트
/// vec3Field.OnValueChanged += (vec) => Debug.Log($"위치: {vec}");
///
/// // 라벨 커스터마이징
/// vec3Field.XLabel = "X위치";
/// vec3Field.YLabel = "Y위치";
/// vec3Field.ZLabel = "Z위치";
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKVector3Field label="위치" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKVector3Field : Vector3Field, IDisposable
{

View File

@@ -1,5 +1,7 @@
#nullable enable
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
@@ -8,7 +10,72 @@ namespace UVC.UIToolkit
/// <summary>
/// 텍스트 라벨 컴포넌트.
/// 다양한 스타일과 크기의 텍스트를 표시합니다.
/// Material Icon 또는 Image Icon을 텍스트와 함께 또는 단독으로 표시할 수 있습니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 라벨
/// var label = new UTKLabel();
/// label.Text = "안녕하세요";
/// label.Size = UTKLabel.LabelSize.Body1;
/// label.Variant = UTKLabel.LabelVariant.Primary;
///
/// // 제목 스타일
/// var title = new UTKLabel();
/// title.Text = "제목";
/// title.Size = UTKLabel.LabelSize.Heading1;
/// title.IsBold = true;
///
/// // 스타일 적용
/// label.IsItalic = true;
/// label.TextAlign = UTKLabel.TextAlign.Center;
///
/// // Material Icon과 텍스트 함께 사용
/// var iconLabel = new UTKLabel("설정", UTKMaterialIcons.Settings);
///
/// // Image Icon과 텍스트 함께 사용
/// var imgLabel = new UTKLabel("닫기", UTKImageIcons.BtnClose16, isImageIcon: true);
///
/// // Material Icon만 사용
/// var iconOnly = new UTKLabel(UTKMaterialIcons.Home);
///
/// // Image Icon만 사용
/// var imgOnly = new UTKLabel(UTKImageIcons.IconSetting22, isImageIcon: true);
///
/// // 메서드로 아이콘 설정
/// label.SetMaterialIcon(UTKMaterialIcons.Search);
/// label.SetImageIcon(UTKImageIcons.IconSetting22);
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 라벨 -->
/// <utk:UTKLabel Text="일반 텍스트" />
///
/// <!-- 제목 -->
/// <utk:UTKLabel Text="제목" Size="H1" IsBold="true" />
///
/// <!-- 보조 텍스트 -->
/// <utk:UTKLabel Text="설명" Size="Caption" Variant="Secondary" />
///
/// <!-- Material Icon과 텍스트 -->
/// <utk:UTKLabel Text="설정" MaterialIcon="settings" />
///
/// <!-- Image Icon과 텍스트 -->
/// <utk:UTKLabel Text="닫기" ImageIcon="btn_close_16" />
///
/// <!-- 아이콘만 (Material) -->
/// <utk:UTKLabel MaterialIcon="home" IconSize="24" />
///
/// <!-- 아이콘만 (Image) -->
/// <utk:UTKLabel ImageIcon="icon_setting_22" IconSize="22" />
///
/// <!-- 아이콘 오른쪽 배치 -->
/// <utk:UTKLabel Text="다음" MaterialIcon="arrow_forward" IconPlacement="Right" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKLabel : VisualElement, IDisposable
{
@@ -19,6 +86,8 @@ namespace UVC.UIToolkit
#region Fields
private bool _disposed;
private Label? _label;
private Label? _iconLabel;
private VisualElement? _imageIcon;
private string _text = "";
private LabelSize _size = LabelSize.Body1;
@@ -27,6 +96,11 @@ namespace UVC.UIToolkit
private bool _isItalic;
private TextAlign _textAlign = TextAlign.Left;
private bool _isSelectable;
private IconPosition _iconPosition = IconPosition.Left;
private int _iconSize;
private int _gap = 4;
private string _materialIconName = "";
private string _imageIconName = "";
#endregion
#region Properties
@@ -116,6 +190,80 @@ namespace UVC.UIToolkit
}
}
}
/// <summary>아이콘 위치 (텍스트 기준 왼쪽/오른쪽)</summary>
[UxmlAttribute]
public IconPosition IconPlacement
{
get => _iconPosition;
set
{
_iconPosition = value;
UpdateIconPosition();
}
}
/// <summary>아이콘 크기 (0이면 텍스트 크기에 맞춤)</summary>
[UxmlAttribute]
public int IconSize
{
get => _iconSize;
set
{
_iconSize = value;
UpdateIconSize();
}
}
/// <summary>아이콘과 텍스트 사이 간격 (px)</summary>
[UxmlAttribute]
public int Gap
{
get => _gap;
set
{
_gap = value;
UpdateGap();
}
}
/// <summary>Material Icon 이름 (예: "settings", "home")</summary>
[UxmlAttribute]
public string MaterialIcon
{
get => _materialIconName;
set
{
_materialIconName = value;
if (!string.IsNullOrEmpty(value))
{
SetMaterialIconByName(value);
}
else
{
ClearIcon();
}
}
}
/// <summary>Image Icon 이름 (예: "icon_setting_22", "btn_close_16")</summary>
[UxmlAttribute]
public string ImageIcon
{
get => _imageIconName;
set
{
_imageIconName = value;
if (!string.IsNullOrEmpty(value))
{
SetImageIconByName(value);
}
else
{
ClearIcon();
}
}
}
#endregion
#region Enums
@@ -149,6 +297,12 @@ namespace UVC.UIToolkit
Center,
Right
}
public enum IconPosition
{
Left,
Right
}
#endregion
#region Constructor
@@ -164,13 +318,95 @@ namespace UVC.UIToolkit
CreateUI();
SubscribeToThemeChanges();
// UXML에서 로드될 때 속성이 설정된 후 UI 갱신
RegisterCallback<AttachToPanelEvent>(_ =>
{
if (_label != null) _label.text = _text;
UpdateSize();
UpdateVariant();
UpdateTextAlign();
});
}
/// <summary>
/// 텍스트와 크기를 지정하여 라벨을 생성합니다.
/// </summary>
/// <param name="text">표시할 텍스트</param>
/// <param name="size">텍스트 크기</param>
public UTKLabel(string text, LabelSize size = LabelSize.Body1) : this()
{
Text = text;
Size = size;
}
/// <summary>
/// Material Icon과 텍스트를 함께 표시하는 라벨을 생성합니다.
/// </summary>
/// <param name="text">표시할 텍스트</param>
/// <param name="materialIcon">Material Icon 유니코드 문자 (예: UTKMaterialIcons.Settings)</param>
/// <param name="iconPosition">아이콘 위치 (기본: 왼쪽)</param>
/// <param name="size">텍스트 크기</param>
public UTKLabel(string text, string materialIcon, IconPosition iconPosition = IconPosition.Left, LabelSize size = LabelSize.Body1) : this()
{
Text = text;
Size = size;
IconPlacement = iconPosition;
SetMaterialIcon(materialIcon);
}
/// <summary>
/// Image Icon과 텍스트를 함께 표시하는 라벨을 생성합니다.
/// </summary>
/// <param name="text">표시할 텍스트</param>
/// <param name="imageIconPath">Image Icon 리소스 경로 (예: UTKImageIcons.IconSetting22)</param>
/// <param name="isImageIcon">true로 설정해야 합니다 (Image Icon 생성자 구분용)</param>
/// <param name="iconPosition">아이콘 위치 (기본: 왼쪽)</param>
/// <param name="size">텍스트 크기</param>
public UTKLabel(string text, string imageIconPath, bool isImageIcon, IconPosition iconPosition = IconPosition.Left, LabelSize size = LabelSize.Body1) : this()
{
Text = text;
Size = size;
IconPlacement = iconPosition;
if (isImageIcon)
{
SetImageIcon(imageIconPath);
}
else
{
SetMaterialIcon(imageIconPath);
}
}
/// <summary>
/// Material Icon만 표시하는 라벨을 생성합니다.
/// </summary>
/// <param name="materialIcon">Material Icon 유니코드 문자 (예: UTKMaterialIcons.Home)</param>
/// <param name="iconSize">아이콘 크기 (0이면 기본 크기)</param>
public UTKLabel(string materialIcon, int iconSize = 0) : this()
{
IconSize = iconSize;
SetMaterialIcon(materialIcon);
}
/// <summary>
/// Image Icon만 표시하는 라벨을 생성합니다.
/// </summary>
/// <param name="imageIconPath">Image Icon 리소스 경로 (예: UTKImageIcons.IconSetting22)</param>
/// <param name="isImageIcon">true로 설정해야 합니다 (Image Icon 생성자 구분용)</param>
/// <param name="iconSize">아이콘 크기 (0이면 기본 크기)</param>
public UTKLabel(string imageIconPath, bool isImageIcon, int iconSize = 0) : this()
{
IconSize = iconSize;
if (isImageIcon)
{
SetImageIcon(imageIconPath);
}
else
{
SetMaterialIcon(imageIconPath);
}
}
#endregion
#region UI Creation
@@ -267,6 +503,266 @@ namespace UVC.UIToolkit
};
AddToClassList(alignClass);
}
private void UpdateIconPosition()
{
if (_iconLabel != null)
{
_iconLabel.SendToBack();
if (_iconPosition == IconPosition.Right)
{
_iconLabel.BringToFront();
}
}
if (_imageIcon != null)
{
_imageIcon.SendToBack();
if (_iconPosition == IconPosition.Right)
{
_imageIcon.BringToFront();
}
}
}
private void UpdateIconSize()
{
var size = GetEffectiveIconSize();
if (_iconLabel != null && _iconLabel.style.display == DisplayStyle.Flex)
{
_iconLabel.style.fontSize = size;
}
if (_imageIcon != null && _imageIcon.style.display == DisplayStyle.Flex)
{
_imageIcon.style.width = size;
_imageIcon.style.height = size;
}
}
private void UpdateGap()
{
// 아이콘과 텍스트 사이의 간격을 적용
if (_iconLabel != null)
{
_iconLabel.style.marginRight = _iconPosition == IconPosition.Left ? _gap : 0;
_iconLabel.style.marginLeft = _iconPosition == IconPosition.Right ? _gap : 0;
}
if (_imageIcon != null)
{
_imageIcon.style.marginRight = _iconPosition == IconPosition.Left ? _gap : 0;
_imageIcon.style.marginLeft = _iconPosition == IconPosition.Right ? _gap : 0;
}
}
private int GetEffectiveIconSize()
{
if (_iconSize > 0) return _iconSize;
return _size switch
{
LabelSize.H1 => 28,
LabelSize.H2 => 24,
LabelSize.H3 => 20,
LabelSize.Body1 => 16,
LabelSize.Body2 => 14,
LabelSize.Label1 => 14,
LabelSize.Label2 => 12,
LabelSize.Label3 => 12,
LabelSize.Caption => 12,
_ => 16
};
}
#endregion
#region Icon Methods
/// <summary>
/// Material Icon을 설정합니다.
/// </summary>
/// <param name="icon">Material Icon 유니코드 문자 (예: UTKMaterialIcons.Settings)</param>
/// <param name="fontSize">아이콘 폰트 크기 (null이면 텍스트 크기에 맞춤)</param>
public void SetMaterialIcon(string icon, int? fontSize = null)
{
ClearImageIcon();
EnsureIconLabel();
if (_iconLabel != null)
{
_iconLabel.text = icon;
_iconLabel.style.display = DisplayStyle.Flex;
UTKMaterialIcons.ApplyIconStyle(_iconLabel, fontSize ?? GetEffectiveIconSize());
}
EnableInClassList("utk-label--has-icon", true);
UpdateIconPosition();
UpdateGap();
}
/// <summary>
/// Material Icon을 비동기로 설정합니다.
/// </summary>
/// <param name="icon">Material Icon 유니코드 문자 (예: UTKMaterialIcons.Settings)</param>
/// <param name="ct">취소 토큰</param>
/// <param name="fontSize">아이콘 폰트 크기 (null이면 텍스트 크기에 맞춤)</param>
public async UniTask SetMaterialIconAsync(string icon, CancellationToken ct = default, int? fontSize = null)
{
ClearImageIcon();
EnsureIconLabel();
if (_iconLabel != null)
{
_iconLabel.text = icon;
_iconLabel.style.display = DisplayStyle.Flex;
await UTKMaterialIcons.ApplyIconStyleAsync(_iconLabel, ct, fontSize ?? GetEffectiveIconSize());
}
EnableInClassList("utk-label--has-icon", true);
UpdateIconPosition();
UpdateGap();
}
/// <summary>
/// 아이콘 이름으로 Material Icon을 설정합니다.
/// </summary>
/// <param name="iconName">아이콘 이름 (예: "settings", "home")</param>
/// <param name="fontSize">아이콘 폰트 크기 (null이면 텍스트 크기에 맞춤)</param>
public void SetMaterialIconByName(string iconName, int? fontSize = null)
{
var iconChar = UTKMaterialIcons.GetIcon(iconName);
if (!string.IsNullOrEmpty(iconChar))
{
SetMaterialIcon(iconChar, fontSize);
}
else
{
Debug.LogWarning($"[UTKLabel] Material icon '{iconName}'을(를) 찾을 수 없습니다.");
}
}
/// <summary>
/// Image Icon을 설정합니다.
/// </summary>
/// <param name="resourcePath">이미지 리소스 경로 (예: UTKImageIcons.IconSetting22)</param>
/// <param name="iconSize">아이콘 크기 (null이면 텍스트 크기에 맞춤)</param>
public void SetImageIcon(string resourcePath, int? iconSize = null)
{
var texture = UTKImageIcons.LoadTexture(resourcePath);
if (texture != null)
{
ApplyImageIcon(texture, iconSize);
}
else
{
Debug.LogWarning($"[UTKLabel] Image icon '{resourcePath}'을(를) 로드할 수 없습니다.");
}
}
/// <summary>
/// Image Icon을 비동기로 설정합니다.
/// </summary>
/// <param name="resourcePath">이미지 리소스 경로 (예: UTKImageIcons.IconSetting22)</param>
/// <param name="ct">취소 토큰</param>
/// <param name="iconSize">아이콘 크기 (null이면 텍스트 크기에 맞춤)</param>
public async UniTask SetImageIconAsync(string resourcePath, CancellationToken ct = default, int? iconSize = null)
{
var texture = await UTKImageIcons.LoadTextureAsync(resourcePath, ct);
if (texture != null)
{
ApplyImageIcon(texture, iconSize);
}
else
{
Debug.LogWarning($"[UTKLabel] Image icon '{resourcePath}'을(를) 로드할 수 없습니다.");
}
}
/// <summary>
/// 아이콘 이름으로 Image Icon을 설정합니다.
/// </summary>
/// <param name="iconName">아이콘 이름 (예: "icon_setting_22", "btn_close_16")</param>
/// <param name="iconSize">아이콘 크기 (null이면 텍스트 크기에 맞춤)</param>
public void SetImageIconByName(string iconName, int? iconSize = null)
{
var texture = UTKImageIcons.LoadTextureByName(iconName);
if (texture != null)
{
ApplyImageIcon(texture, iconSize);
}
else
{
Debug.LogWarning($"[UTKLabel] Image icon '{iconName}'을(를) 찾을 수 없습니다.");
}
}
/// <summary>
/// 모든 아이콘을 제거합니다.
/// </summary>
public void ClearIcon()
{
ClearMaterialIcon();
ClearImageIcon();
EnableInClassList("utk-label--has-icon", false);
}
private void EnsureIconLabel()
{
if (_iconLabel != null) return;
_iconLabel = new Label
{
name = "icon",
pickingMode = PickingMode.Ignore
};
_iconLabel.AddToClassList("utk-label__icon");
_iconLabel.style.display = DisplayStyle.None;
Insert(0, _iconLabel);
}
private void ApplyImageIcon(Texture2D texture, int? iconSize)
{
ClearMaterialIcon();
if (_imageIcon == null)
{
_imageIcon = new VisualElement
{
name = "image-icon",
pickingMode = PickingMode.Ignore
};
_imageIcon.AddToClassList("utk-label__image-icon");
Insert(0, _imageIcon);
}
var size = iconSize ?? GetEffectiveIconSize();
_imageIcon.style.width = size;
_imageIcon.style.height = size;
_imageIcon.style.backgroundImage = new StyleBackground(texture);
_imageIcon.style.display = DisplayStyle.Flex;
EnableInClassList("utk-label--has-icon", true);
UpdateIconPosition();
UpdateGap();
}
private void ClearMaterialIcon()
{
if (_iconLabel != null)
{
_iconLabel.text = "";
_iconLabel.style.display = DisplayStyle.None;
}
}
private void ClearImageIcon()
{
if (_imageIcon != null)
{
_imageIcon.style.backgroundImage = StyleKeyword.None;
_imageIcon.style.display = DisplayStyle.None;
}
}
#endregion
#region IDisposable

View File

@@ -1388,8 +1388,8 @@ namespace UVC.UIToolkit.List
// 검색어가 없으면 원본 데이터 복원
if (string.IsNullOrEmpty(query))
{
_treeView.SetRootItems<UTKComponentListItemDataBase>(_rootData);
_treeView.Rebuild();
_treeView?.SetRootItems<UTKComponentListItemDataBase>(_rootData);
_treeView?.Rebuild();
ExpandByData(_originalRoots);
return;
}
@@ -1399,8 +1399,8 @@ namespace UVC.UIToolkit.List
var filteredWrappers = FilterTree(qLower);
// 필터링된 결과로 TreeView 갱신
_treeView.SetRootItems<UTKComponentListItemDataBase>(filteredWrappers);
_treeView.Rebuild();
_treeView?.SetRootItems<UTKComponentListItemDataBase>(filteredWrappers);
_treeView?.Rebuild();
// 검색 결과 모두 펼치기
ExpandAll(filteredWrappers);

View File

@@ -10,6 +10,33 @@ namespace UVC.UIToolkit
/// 리스트 뷰 컴포넌트.
/// Unity ListView를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 리스트 뷰 생성
/// var listView = new UTKListView();
///
/// // 데이터 소스 설정
/// var items = new List<string> { "항목 1", "항목 2", "항목 3" };
/// listView.itemsSource = items;
///
/// // 아이템 렌더링
/// listView.makeItem = () => new Label();
/// listView.bindItem = (element, index) => {
/// (element as Label).text = items[index];
/// };
///
/// // 선택 이벤트
/// listView.OnItemSelected += (index) => Debug.Log($"선택: {items[index]}");
/// listView.OnItemDoubleClicked += (index) => Debug.Log($"더블클릭: {items[index]}");
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKListView fixed-item-height="30" selection-type="Single" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKListView : ListView, IDisposable
{

View File

@@ -10,6 +10,39 @@ namespace UVC.UIToolkit
/// 트리 뷰 컴포넌트.
/// Unity TreeView를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 트리 뷰 생성
/// var treeView = new UTKTreeView();
///
/// // 데이터 구조 설정
/// var rootItems = new List<TreeViewItemData<string>> {
/// new TreeViewItemData<string>(0, "루트 1", new List<TreeViewItemData<string>> {
/// new TreeViewItemData<string>(1, "자식 1-1"),
/// new TreeViewItemData<string>(2, "자식 1-2")
/// }),
/// new TreeViewItemData<string>(3, "루트 2")
/// };
/// treeView.SetRootItems(rootItems);
///
/// // 아이템 렌더링
/// treeView.makeItem = () => new Label();
/// treeView.bindItem = (element, index) => {
/// var item = treeView.GetItemDataForIndex<string>(index);
/// (element as Label).text = item;
/// };
///
/// // 선택 이벤트
/// treeView.OnItemSelected += (index) => Debug.Log($"선택: {index}");
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKTreeView fixed-item-height="24" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKTreeView : TreeView, IDisposable
{

View File

@@ -11,6 +11,34 @@ namespace UVC.UIToolkit
/// Alert 팝업 컴포넌트.
/// 사용자에게 중요한 정보를 알립니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 초기화 (root 설정 필요)
/// UTKAlert.Initialize(rootVisualElement);
///
/// // 기본 알림
/// await UTKAlert.Show("알림", "작업이 완료되었습니다.");
///
/// // 확인 대화상자
/// bool confirmed = await UTKAlert.ShowConfirm("삭제 확인", "정말 삭제하시겠습니까?");
/// if (confirmed) {
/// // 삭제 실행
/// }
///
/// // 타입별 알림
/// await UTKAlert.ShowSuccess("성공", "저장되었습니다.");
/// await UTKAlert.ShowError("오류", "파일을 찾을 수 없습니다.");
/// await UTKAlert.ShowWarning("경고", "변경사항이 저장되지 않았습니다.");
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- Alert는 주로 C# 코드로 동적 생성합니다 -->
/// <utk:UTKAlert Title="알림" Message="메시지" AlertType="Info" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKAlert : VisualElement, IDisposable
{

View File

@@ -4,6 +4,7 @@ using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit.Common;
namespace UVC.UIToolkit.Modal
{
@@ -217,6 +218,9 @@ namespace UVC.UIToolkit.Modal
picker._blocker = UTKModalBlocker.Show(parent, 0.5f, false);
picker._blocker.OnBlockerClicked += picker.Cancel;
// 위치 계산 전까지 숨김 (깜빡임 방지)
picker.style.visibility = Visibility.Hidden;
// 피커 추가
parent.Add(picker);
@@ -316,8 +320,9 @@ namespace UVC.UIToolkit.Modal
_titleLabel = new Label("Color Picker") { name = "title" };
_titleLabel.AddToClassList("utk-color-picker__title");
_closeButton = new Button { name = "close-btn", text = "\u2715" }; // ✕
_closeButton = new Button { name = "close-btn", text = UTKMaterialIcons.Close };
_closeButton.AddToClassList("utk-color-picker__close-btn");
UTKMaterialIcons.ApplyIconStyle(_closeButton, 18);
_header.Add(_titleLabel);
_header.Add(_closeButton);
@@ -453,6 +458,7 @@ namespace UVC.UIToolkit.Modal
_cancelButton ??= this.Q<UTKButton>("cancel-btn");
_confirmButton ??= this.Q<UTKButton>("confirm-btn");
_alphaRow ??= this.Q<VisualElement>("row-a");
_header ??= this.Q<VisualElement>("header");
}
private void SetupEvents()
@@ -1004,6 +1010,9 @@ namespace UVC.UIToolkit.Modal
private void CenterOnScreen()
{
// position absolute 강제 적용
style.position = Position.Absolute;
schedule.Execute(() =>
{
var parent = this.parent;
@@ -1014,8 +1023,20 @@ namespace UVC.UIToolkit.Modal
float selfWidth = resolvedStyle.width;
float selfHeight = resolvedStyle.height;
// NaN 체크
if (float.IsNaN(parentWidth) || float.IsNaN(parentHeight) ||
float.IsNaN(selfWidth) || float.IsNaN(selfHeight))
{
// 다음 프레임에 다시 시도
schedule.Execute(() => CenterOnScreen());
return;
}
style.left = (parentWidth - selfWidth) / 2;
style.top = (parentHeight - selfHeight) / 2;
// 위치 계산 완료 후 표시 (깜빡임 방지)
style.visibility = Visibility.Visible;
});
}

View File

@@ -5,6 +5,7 @@ using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.Locale;
using UVC.UIToolkit.Common;
using UVC.UIToolkit.Input;
namespace UVC.UIToolkit.Modal
@@ -58,6 +59,16 @@ namespace UVC.UIToolkit.Modal
/// Debug.Log("Cancelled");
/// }
///
/// // 요일 이름 커스터마이징 (static - 모든 인스턴스에 적용)
/// UTKDatePicker.SetDayNames(new[] { "일", "월", "화", "수", "목", "금", "토" });
/// UTKDatePicker.SetDayNames(new[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" });
///
/// // 요일 이름 로컬라이제이션 키 설정
/// UTKDatePicker.SetDayNameKeys(new[] { "day_sun", "day_mon", "day_tue", "day_wed", "day_thu", "day_fri", "day_sat" });
///
/// // 요일 이름 기본값으로 초기화
/// UTKDatePicker.ResetDayNames();
///
/// // 인스턴스 직접 생성
/// var datePicker = new UTKDatePicker();
/// datePicker.SetDate(DateTime.Today);
@@ -90,7 +101,12 @@ namespace UVC.UIToolkit.Modal
private const string UXML_PATH = "UIToolkit/Modal/UTKDatePicker";
private const string USS_PATH = "UIToolkit/Modal/UTKDatePicker";
private const int DAYS_IN_GRID = 42; // 6 rows x 7 columns
private static readonly string[] DAY_NAME_KEYS = { "day_sun", "day_mon", "day_tue", "day_wed", "day_thu", "day_fri", "day_sat" };
private static readonly string[] DEFAULT_DAY_NAME_KEYS = { "day_sun", "day_mon", "day_tue", "day_wed", "day_thu", "day_fri", "day_sat" };
#endregion
#region Static Fields
private static string[] s_dayNameKeys = DEFAULT_DAY_NAME_KEYS;
private static string[]? s_customDayNames;
#endregion
#region Fields
@@ -124,6 +140,12 @@ namespace UVC.UIToolkit.Modal
private UTKNumberStepper? _minuteStepper;
private UTKButton? _cancelButton;
private UTKButton? _confirmButton;
private VisualElement? _header;
// 드래그 관련 필드
private bool _isDragging;
private Vector2 _dragStartPosition;
private Vector2 _dragStartMousePosition;
#endregion
#region Events
@@ -204,6 +226,9 @@ namespace UVC.UIToolkit.Modal
picker._blocker = UTKModalBlocker.Show(parent, 0.5f, false);
picker._blocker.OnBlockerClicked += picker.Close;
// 위치 계산 전까지 숨김 (깜빡임 방지)
picker.style.visibility = Visibility.Hidden;
// 피커 추가
parent.Add(picker);
@@ -302,6 +327,9 @@ namespace UVC.UIToolkit.Modal
picker._blocker = UTKModalBlocker.Show(parent, 0.5f, false);
picker._blocker.OnBlockerClicked += picker.Close;
// 위치 계산 전까지 숨김 (깜빡임 방지)
picker.style.visibility = Visibility.Hidden;
// 피커 추가
parent.Add(picker);
@@ -399,6 +427,60 @@ namespace UVC.UIToolkit.Modal
public void PreviousMonth() => NavigateMonth(-1);
public void NextMonth() => NavigateMonth(1);
/// <summary>
/// 요일 이름을 직접 설정합니다. (일, 월, 화, 수, 목, 금, 토 순서로 7개)
/// 설정 후 새로 생성되는 피커부터 적용됩니다.
/// </summary>
/// <param name="dayNames">요일 이름 배열 (7개)</param>
/// <example>
/// <code>
/// UTKDatePicker.SetDayNames(new[] { "일", "월", "화", "수", "목", "금", "토" });
/// UTKDatePicker.SetDayNames(new[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" });
/// </code>
/// </example>
public static void SetDayNames(string[] dayNames)
{
if (dayNames == null || dayNames.Length != 7)
{
Debug.LogWarning("SetDayNames requires exactly 7 day names (Sun, Mon, Tue, Wed, Thu, Fri, Sat)");
return;
}
s_customDayNames = dayNames;
}
/// <summary>
/// 요일 이름의 로컬라이제이션 키를 설정합니다. (일, 월, 화, 수, 목, 금, 토 순서로 7개)
/// 설정 후 새로 생성되는 피커부터 적용됩니다.
/// </summary>
/// <param name="dayNameKeys">로컬라이제이션 키 배열 (7개)</param>
/// <example>
/// <code>
/// UTKDatePicker.SetDayNameKeys(new[] { "day_sun", "day_mon", "day_tue", "day_wed", "day_thu", "day_fri", "day_sat" });
/// </code>
/// </example>
public static void SetDayNameKeys(string[] dayNameKeys)
{
if (dayNameKeys == null || dayNameKeys.Length != 7)
{
Debug.LogWarning("SetDayNameKeys requires exactly 7 localization keys");
return;
}
s_customDayNames = null; // 커스텀 이름 초기화
s_dayNameKeys = dayNameKeys;
}
/// <summary>
/// 요일 이름을 기본값(로컬라이제이션)으로 초기화합니다.
/// 설정 후 새로 생성되는 피커부터 적용됩니다.
/// </summary>
public static void ResetDayNames()
{
s_customDayNames = null;
s_dayNameKeys = DEFAULT_DAY_NAME_KEYS;
}
public void Close()
{
OnClosed?.Invoke();
@@ -420,8 +502,9 @@ namespace UVC.UIToolkit.Modal
_titleLabel = new Label("Select Date") { name = "title" };
_titleLabel.AddToClassList("utk-date-picker__title");
_closeButton = new Button { name = "close-btn", text = "\u2715" }; // X
_closeButton = new Button { name = "close-btn", text = UTKMaterialIcons.Close };
_closeButton.AddToClassList("utk-date-picker__close-btn");
UTKMaterialIcons.ApplyIconStyle(_closeButton, UTKStyleGuide.FontSizeBody2);
header.Add(_titleLabel);
header.Add(_closeButton);
@@ -434,10 +517,12 @@ namespace UVC.UIToolkit.Modal
var leftNav = new VisualElement();
leftNav.AddToClassList("utk-date-picker__nav-group");
_prevYearButton = CreateNavButton("prev-year", "\u00AB"); // <<
_prevYearButton = CreateNavButton("prev-year", UTKMaterialIcons.FirstPage);
_prevYearButton.AddToClassList("utk-date-picker__nav-btn--prev-year");
UTKMaterialIcons.ApplyIconStyle(_prevYearButton, 18);
_prevMonthButton = CreateNavButton("prev-month", "\u2039"); // <
_prevMonthButton = CreateNavButton("prev-month", UTKMaterialIcons.ChevronLeft);
UTKMaterialIcons.ApplyIconStyle(_prevMonthButton, 18);
leftNav.Add(_prevYearButton);
leftNav.Add(_prevMonthButton);
@@ -448,10 +533,12 @@ namespace UVC.UIToolkit.Modal
var rightNav = new VisualElement();
rightNav.AddToClassList("utk-date-picker__nav-group");
_nextMonthButton = CreateNavButton("next-month", "\u203A"); // >
_nextMonthButton = CreateNavButton("next-month", UTKMaterialIcons.ChevronRight);
UTKMaterialIcons.ApplyIconStyle(_nextMonthButton, 18);
_nextYearButton = CreateNavButton("next-year", "\u00BB"); // >>
_nextYearButton = CreateNavButton("next-year", UTKMaterialIcons.LastPage);
_nextYearButton.AddToClassList("utk-date-picker__nav-btn--next-year");
UTKMaterialIcons.ApplyIconStyle(_nextYearButton, 18);
rightNav.Add(_nextMonthButton);
rightNav.Add(_nextYearButton);
@@ -465,9 +552,9 @@ namespace UVC.UIToolkit.Modal
_dayNamesRow = new VisualElement { name = "day-names" };
_dayNamesRow.AddToClassList("utk-date-picker__day-names");
for (int i = 0; i < DAY_NAME_KEYS.Length; i++)
for (int i = 0; i < s_dayNameKeys.Length; i++)
{
var dayText = LocalizationManager.Instance.GetString(DAY_NAME_KEYS[i]);
var dayText = GetDayName(i);
var label = new Label(dayText) { name = $"day-name-{i}" };
label.AddToClassList("utk-date-picker__day-name");
@@ -503,7 +590,7 @@ namespace UVC.UIToolkit.Modal
_timeRow = new VisualElement { name = "time-row" };
_timeRow.AddToClassList("utk-date-picker__time-row");
var timeLabel = new Label("시간:");
var timeLabel = new Label("Time:");
timeLabel.AddToClassList("utk-date-picker__time-label");
_hourStepper = new UTKNumberStepper(0, 23, 0, 1) { name = "hour-stepper" };
@@ -557,6 +644,7 @@ namespace UVC.UIToolkit.Modal
private void QueryElements()
{
_header ??= this.Q<VisualElement>("header");
_titleLabel ??= this.Q<Label>("title");
_closeButton ??= this.Q<Button>("close-btn");
_yearMonthLabel ??= this.Q<Label>("year-month");
@@ -599,10 +687,43 @@ namespace UVC.UIToolkit.Modal
_nextMonthButton?.RegisterCallback<ClickEvent>(_ => NextMonth());
_nextYearButton?.RegisterCallback<ClickEvent>(_ => NextYear());
// 헤더 드래그 이벤트
_header?.RegisterCallback<PointerDownEvent>(OnHeaderPointerDown);
_header?.RegisterCallback<PointerMoveEvent>(OnHeaderPointerMove);
_header?.RegisterCallback<PointerUpEvent>(OnHeaderPointerUp);
// 언어 변경 이벤트 구독
LocalizationManager.Instance.OnLanguageChanged += OnLanguageChanged;
}
private void OnHeaderPointerDown(PointerDownEvent evt)
{
if (evt.target == _closeButton) return;
_isDragging = true;
_dragStartMousePosition = evt.position;
_dragStartPosition = new Vector2(resolvedStyle.left, resolvedStyle.top);
_header?.CapturePointer(evt.pointerId);
evt.StopPropagation();
}
private void OnHeaderPointerMove(PointerMoveEvent evt)
{
if (!_isDragging) return;
Vector2 delta = (Vector2)evt.position - _dragStartMousePosition;
style.left = _dragStartPosition.x + delta.x;
style.top = _dragStartPosition.y + delta.y;
}
private void OnHeaderPointerUp(PointerUpEvent evt)
{
if (!_isDragging) return;
_isDragging = false;
_header?.ReleasePointer(evt.pointerId);
}
private void OnLanguageChanged(string newLanguage)
{
UpdateDayNameLabels();
@@ -612,15 +733,28 @@ namespace UVC.UIToolkit.Modal
{
if (_dayNamesRow == null) return;
for (int i = 0; i < DAY_NAME_KEYS.Length; i++)
for (int i = 0; i < s_dayNameKeys.Length; i++)
{
var label = _dayNamesRow.Q<Label>($"day-name-{i}");
if (label != null)
{
label.text = LocalizationManager.Instance.GetString(DAY_NAME_KEYS[i]);
label.text = GetDayName(i);
}
}
}
/// <summary>
/// 인덱스에 해당하는 요일 이름을 반환합니다.
/// 커스텀 이름이 설정되어 있으면 커스텀 이름을, 아니면 로컬라이제이션 키를 사용합니다.
/// </summary>
private string GetDayName(int index)
{
if (s_customDayNames != null && index < s_customDayNames.Length)
{
return s_customDayNames[index];
}
return LocalizationManager.Instance.GetString(s_dayNameKeys[index]);
}
#endregion
#region Private Methods - Logic
@@ -860,6 +994,9 @@ namespace UVC.UIToolkit.Modal
private void CenterOnScreen()
{
// position absolute 강제 적용
style.position = Position.Absolute;
schedule.Execute(() =>
{
var parent = this.parent;
@@ -870,8 +1007,20 @@ namespace UVC.UIToolkit.Modal
float selfWidth = resolvedStyle.width;
float selfHeight = resolvedStyle.height;
// NaN 체크
if (float.IsNaN(parentWidth) || float.IsNaN(parentHeight) ||
float.IsNaN(selfWidth) || float.IsNaN(selfHeight))
{
// 다음 프레임에 다시 시도
schedule.Execute(() => CenterOnScreen());
return;
}
style.left = (parentWidth - selfWidth) / 2;
style.top = (parentHeight - selfHeight) / 2;
// 위치 계산 완료 후 표시 (깜빡임 방지)
style.visibility = Visibility.Visible;
});
}
@@ -890,10 +1039,10 @@ namespace UVC.UIToolkit.Modal
string startText = _rangeStartDate?.ToString("yyyy-MM-dd") ?? "---";
string endText = _rangeEndDate?.ToString("yyyy-MM-dd") ?? "---";
string stateIndicator = _rangeState == RangeSelectionState.SelectingStart ? "" : " ";
string endStateIndicator = _rangeState == RangeSelectionState.SelectingEnd ? "" : " ";
string stateIndicator = _rangeState == RangeSelectionState.SelectingStart ? "" : " ";
string endStateIndicator = _rangeState == RangeSelectionState.SelectingEnd ? "" : " ";
_rangeInfoLabel.text = $"{stateIndicator}시작: {startText} {endStateIndicator}종료: {endText}";
_rangeInfoLabel.text = $"{stateIndicator}Start: {startText} {endStateIndicator}End: {endText}";
}
/// <summary>

View File

@@ -10,6 +10,35 @@ namespace UVC.UIToolkit
/// 모달 창 컴포넌트.
/// 사용자 정의 콘텐츠를 포함할 수 있는 모달 대화상자입니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 모달 생성 및 표시
/// var modal = new UTKModal();
/// modal.Title = "설정";
/// modal.Size = UTKModal.ModalSize.Medium;
/// modal.OnClosed += () => Debug.Log("모달 닫힘");
///
/// // 콘텐츠 추가
/// var content = new Label("모달 내용");
/// modal.AddContent(content);
///
/// // 푸터 버튼 추가
/// modal.AddFooterButton("확인", UTKButton.ButtonVariant.Primary, () => modal.Close());
/// modal.AddFooterButton("취소", UTKButton.ButtonVariant.Normal, () => modal.Close());
///
/// // 표시
/// modal.Show(rootElement);
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKModal Title="설정" Size="Medium" ShowCloseButton="true">
/// <ui:Label text="모달 내용을 여기에 추가하세요" />
/// </utk:UTKModal>
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKModal : VisualElement, IDisposable
{

View File

@@ -3,6 +3,7 @@ using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit.Common;
namespace UVC.UIToolkit
{
@@ -10,6 +11,37 @@ namespace UVC.UIToolkit
/// 알림 창 컴포넌트.
/// 화면 모서리에 표시되는 알림 메시지입니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 초기화 (root 설정 필요)
/// UTKNotification.Initialize(rootVisualElement);
///
/// // 기본 알림
/// UTKNotification.Show("알림", "새로운 메시지가 있습니다.");
///
/// // 타입별 알림
/// UTKNotification.ShowSuccess("성공", "저장되었습니다.");
/// UTKNotification.ShowError("오류", "실패했습니다.");
/// UTKNotification.ShowWarning("경고", "주의가 필요합니다.");
///
/// // 위치 설정
/// UTKNotification.Show("알림", "메시지", position: NotificationPosition.BottomRight);
///
/// // 액션 버튼 있는 알림
/// UTKNotification.Show("알림", "메시지", actions: new[] {
/// ("확인", () => Debug.Log("확인")),
/// ("취소", () => Debug.Log("취소"))
/// });
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- Notification은 주로 C# 코드로 동적 생성합니다 -->
/// <utk:UTKNotification Title="알림" Message="메시지" Type="Info" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKNotification : VisualElement, IDisposable
{
@@ -129,6 +161,14 @@ namespace UVC.UIToolkit
CreateUI();
SubscribeToThemeChanges();
// UXML에서 로드될 때 속성이 설정된 후 UI 갱신
RegisterCallback<AttachToPanelEvent>(_ =>
{
if (_titleLabel != null) _titleLabel.text = _title;
if (_messageLabel != null) _messageLabel.text = _message;
UpdateType();
});
}
public UTKNotification(string title, string message, NotificationType type = NotificationType.Info) : this()
@@ -210,14 +250,16 @@ namespace UVC.UIToolkit
_iconLabel = new Label { name = "icon" };
_iconLabel.AddToClassList("utk-notification__icon");
UTKMaterialIcons.ApplyIconStyle(_iconLabel, 20);
_header.Add(_iconLabel);
_titleLabel = new Label { name = "title" };
_titleLabel.AddToClassList("utk-notification__title");
_header.Add(_titleLabel);
_closeButton = new Button { name = "close-btn", text = "✕" };
_closeButton = new Button { name = "close-btn", text = UTKMaterialIcons.Close };
_closeButton.AddToClassList("utk-notification__close-btn");
UTKMaterialIcons.ApplyIconStyle(_closeButton, 16);
_closeButton.RegisterCallback<ClickEvent>(_ => Close());
_header.Add(_closeButton);
@@ -270,10 +312,10 @@ namespace UVC.UIToolkit
{
_iconLabel.text = _type switch
{
NotificationType.Success => "✓",
NotificationType.Warning => "⚠",
NotificationType.Error => "✕",
_ => ""
NotificationType.Success => UTKMaterialIcons.CheckCircle,
NotificationType.Warning => UTKMaterialIcons.Warning,
NotificationType.Error => UTKMaterialIcons.Error,
_ => UTKMaterialIcons.Info
};
}
}

View File

@@ -9,6 +9,46 @@ namespace UVC.UIToolkit
/// 패널 컨테이너 컴포넌트.
/// 콘텐츠를 그룹화하고 시각적으로 구분합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 패널
/// var panel = new UTKPanel();
/// panel.Title = "설정";
/// panel.AddContent(new Label("패널 내용"));
///
/// // 접을 수 있는 패널
/// panel.IsCollapsible = true;
/// panel.IsCollapsed = false;
///
/// // 헤더 액션 버튼 추가
/// panel.AddHeaderAction(UTKMaterialIcons.Settings, () => Debug.Log("설정"));
///
/// // 푸터 표시
/// panel.ShowFooter = true;
/// panel.AddFooterContent(new Label("푸터 내용"));
///
/// // 변형 스타일
/// panel.Variant = UTKPanel.PanelVariant.Elevated;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 패널 -->
/// <utk:UTKPanel Title="제목">
/// <ui:Label text="패널 내용" />
/// </utk:UTKPanel>
///
/// <!-- 접을 수 있는 패널 -->
/// <utk:UTKPanel Title="고급 설정" IsCollapsible="true">
/// <ui:Label text="내용" />
/// </utk:UTKPanel>
///
/// <!-- 외곽선 스타일 -->
/// <utk:UTKPanel Title="외곽선" Variant="Outlined" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKPanel : VisualElement, IDisposable
{

View File

@@ -10,6 +10,35 @@ namespace UVC.UIToolkit
/// 토스트 알림 컴포넌트.
/// 화면 하단에 일시적으로 표시되는 알림입니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 초기화 (root 설정 필요)
/// UTKToast.Initialize(rootVisualElement);
///
/// // 기본 토스트
/// UTKToast.Show("저장되었습니다.");
///
/// // 타입별 토스트
/// UTKToast.ShowSuccess("성공적으로 완료되었습니다.");
/// UTKToast.ShowError("오류가 발생했습니다.");
/// UTKToast.ShowWarning("주의가 필요합니다.");
/// UTKToast.ShowInfo("정보 메시지");
///
/// // 지속시간 설정 (ms)
/// UTKToast.Show("잠시 표시", duration: 2000);
///
/// // 닫기 버튼 표시
/// UTKToast.Show("메시지", showCloseButton: true);
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 토스트는 주로 C# 코드로 동적 생성합니다 -->
/// <utk:UTKToast Message="저장됨" Type="Success" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKToast : VisualElement, IDisposable
{

View File

@@ -9,6 +9,29 @@ namespace UVC.UIToolkit
/// 최소-최대 범위 슬라이더 컴포넌트.
/// Unity MinMaxSlider를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 범위 슬라이더
/// var slider = new UTKMinMaxSlider();
/// slider.label = "가격 범위";
/// slider.lowLimit = 0;
/// slider.highLimit = 1000;
/// slider.MinValue = 100;
/// slider.MaxValue = 500;
///
/// // 값 변경 이벤트
/// slider.OnValueChanged += (range) => {
/// Debug.Log($"범위: {range.x} ~ {range.y}");
/// };
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKMinMaxSlider label="범위" low-limit="0" high-limit="100" min-value="20" max-value="80" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKMinMaxSlider : MinMaxSlider, IDisposable
{

View File

@@ -9,6 +9,40 @@ namespace UVC.UIToolkit
/// 프로그레스 바 컴포넌트.
/// Unity ProgressBar를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 프로그레스 바
/// var progressBar = new UTKProgressBar();
/// progressBar.title = "다운로드 중...";
/// progressBar.MinValue = 0;
/// progressBar.MaxValue = 100;
/// progressBar.Value = 50;
///
/// // 값 표시 설정
/// progressBar.ShowValue = true;
/// progressBar.ShowPercentage = true;
///
/// // 무한 로딩 (불확정 상태)
/// progressBar.IsIndeterminate = true;
///
/// // 변형 스타일
/// progressBar.Variant = UTKProgressBar.ProgressBarVariant.Success;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 프로그레스 바 -->
/// <utk:UTKProgressBar title="진행률" low-value="0" high-value="100" value="30" />
///
/// <!-- 퍼센트 표시 -->
/// <utk:UTKProgressBar ShowPercentage="true" value="75" />
///
/// <!-- 성공 스타일 -->
/// <utk:UTKProgressBar Variant="Success" value="100" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKProgressBar : ProgressBar, IDisposable
{

View File

@@ -9,6 +9,31 @@ namespace UVC.UIToolkit
/// 슬라이더 컴포넌트.
/// Unity Slider를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 슬라이더
/// var slider = new UTKSlider();
/// slider.label = "볼륨";
/// slider.lowValue = 0;
/// slider.highValue = 100;
/// slider.value = 50;
/// slider.OnValueChanged += (value) => Debug.Log($"볼륨: {value}");
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 슬라이더 -->
/// <utk:UTKSlider label="밝기" low-value="0" high-value="100" value="50" />
///
/// <!-- 값 표시 -->
/// <utk:UTKSlider label="투명도" low-value="0" high-value="1" value="0.5" show-input-field="true" />
///
/// <!-- 비활성화 -->
/// <utk:UTKSlider label="잠금" IsEnabled="false" value="75" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKSlider : Slider, IDisposable
{

View File

@@ -9,6 +9,36 @@ namespace UVC.UIToolkit
/// 탭 컴포넌트.
/// Unity Tab을 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // UTKTabView와 함께 사용
/// var tabView = new UTKTabView();
///
/// var tab1 = new UTKTab();
/// tab1.Text = "일반";
/// tab1.Add(new Label("일반 탭 내용"));
/// tabView.Add(tab1);
///
/// var tab2 = new UTKTab();
/// tab2.Text = "고급";
/// tab2.IsEnabled = false; // 비활성화
/// tabView.Add(tab2);
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKTabView>
/// <utk:UTKTab label="일반">
/// <ui:Label text="일반 내용" />
/// </utk:UTKTab>
/// <utk:UTKTab label="고급">
/// <ui:Label text="고급 내용" />
/// </utk:UTKTab>
/// </utk:UTKTabView>
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKTab : Tab, IDisposable
{

View File

@@ -10,6 +10,39 @@ namespace UVC.UIToolkit
/// 탭 뷰 컴포넌트.
/// Unity TabView를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 탭 뷰 생성
/// var tabView = new UTKTabView();
///
/// // 탭 추가
/// var tab1 = tabView.AddTab("일반", UTKMaterialIcons.Settings);
/// tab1.Add(new Label("일반 설정 내용"));
///
/// var tab2 = tabView.AddTab("고급", UTKMaterialIcons.Build);
/// tab2.Add(new Label("고급 설정 내용"));
///
/// // 탭 변경 이벤트
/// tabView.OnTabChanged += (index, tab) => Debug.Log($"탭 {index} 선택됨");
///
/// // 탭 선택
/// tabView.SelectedIndex = 0;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKTabView>
/// <utk:UTKTab label="일반">
/// <ui:Label text="일반 탭 내용" />
/// </utk:UTKTab>
/// <utk:UTKTab label="고급">
/// <ui:Label text="고급 탭 내용" />
/// </utk:UTKTab>
/// </utk:UTKTabView>
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKTabView : TabView, IDisposable
{