StyleGuide Sample 완료
This commit is contained in:
2
.github/copilot-instructions.md
vendored
Normal file
2
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
프로젝트 루트 폴더의 CLAUDE.md를 참조해
|
||||
답변은 항상 한국어로 작성해
|
||||
578
Assets/Editor/UIToolkit/UTKMaterialIconsGenerator.cs
Normal file
578
Assets/Editor/UIToolkit/UTKMaterialIconsGenerator.cs
Normal file
@@ -0,0 +1,578 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UIToolkit.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// MaterialSymbolsOutlinedCodepoints.txt 파일을 파싱하여
|
||||
/// UTKMaterialIcons.cs 정적 클래스를 자동 생성하는 에디터 도구입니다.
|
||||
/// </summary>
|
||||
public class UTKMaterialIconsGenerator : EditorWindow
|
||||
{
|
||||
#region EditorPrefs Keys
|
||||
|
||||
private const string PrefKeyCodepointsPath = "UTK_MaterialIcons_CodepointsPath";
|
||||
private const string PrefKeyOutputPath = "UTK_MaterialIcons_OutputPath";
|
||||
private const string PrefKeyClassName = "UTK_MaterialIcons_ClassName";
|
||||
private const string PrefKeyNamespace = "UTK_MaterialIcons_Namespace";
|
||||
private const string PrefKeyFontPath = "UTK_MaterialIcons_FontPath";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Default Values
|
||||
|
||||
private const string DefaultCodepointsPath = "Assets/Resources/Fonts/Icons/MaterialSymbolsOutlinedCodepoints.txt";
|
||||
private const string DefaultOutputPath = "Assets/Scripts/UVC/UIToolkit/Common/UTKMaterialIcons.cs";
|
||||
private const string DefaultClassName = "UTKMaterialIcons";
|
||||
private const string DefaultNamespace = "UVC.UIToolkit";
|
||||
private const string DefaultFontPath = "Fonts/Icons/MaterialSymbolsOutlined";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private string _codepointsPath = DefaultCodepointsPath;
|
||||
private string _outputPath = DefaultOutputPath;
|
||||
private string _className = DefaultClassName;
|
||||
private string _namespace = DefaultNamespace;
|
||||
private string _fontPath = DefaultFontPath;
|
||||
|
||||
#endregion
|
||||
|
||||
[MenuItem("Tools/UTK/Material Icons Generator")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<UTKMaterialIconsGenerator>("Material Icons Generator");
|
||||
window.minSize = new Vector2(450, 250);
|
||||
window.LoadSettings();
|
||||
}
|
||||
|
||||
[MenuItem("Tools/UTK/Generate Material Icons Class (Quick)")]
|
||||
public static void GenerateQuick()
|
||||
{
|
||||
var codepointsPath = EditorPrefs.GetString(PrefKeyCodepointsPath, DefaultCodepointsPath);
|
||||
var outputPath = EditorPrefs.GetString(PrefKeyOutputPath, DefaultOutputPath);
|
||||
var className = EditorPrefs.GetString(PrefKeyClassName, DefaultClassName);
|
||||
var namespaceName = EditorPrefs.GetString(PrefKeyNamespace, DefaultNamespace);
|
||||
var fontPath = EditorPrefs.GetString(PrefKeyFontPath, DefaultFontPath);
|
||||
|
||||
Generate(codepointsPath, outputPath, className, namespaceName, fontPath);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
_codepointsPath = EditorPrefs.GetString(PrefKeyCodepointsPath, DefaultCodepointsPath);
|
||||
_outputPath = EditorPrefs.GetString(PrefKeyOutputPath, DefaultOutputPath);
|
||||
_className = EditorPrefs.GetString(PrefKeyClassName, DefaultClassName);
|
||||
_namespace = EditorPrefs.GetString(PrefKeyNamespace, DefaultNamespace);
|
||||
_fontPath = EditorPrefs.GetString(PrefKeyFontPath, DefaultFontPath);
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
EditorPrefs.SetString(PrefKeyCodepointsPath, _codepointsPath);
|
||||
EditorPrefs.SetString(PrefKeyOutputPath, _outputPath);
|
||||
EditorPrefs.SetString(PrefKeyClassName, _className);
|
||||
EditorPrefs.SetString(PrefKeyNamespace, _namespace);
|
||||
EditorPrefs.SetString(PrefKeyFontPath, _fontPath);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.Space(10);
|
||||
EditorGUILayout.LabelField("Material Icons Class Generator", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.HelpBox("Codepoints 파일을 파싱하여 C# 정적 클래스를 생성합니다.", MessageType.Info);
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// Codepoints 파일 경로
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
_codepointsPath = EditorGUILayout.TextField("Codepoints File", _codepointsPath);
|
||||
if (GUILayout.Button("...", GUILayout.Width(30)))
|
||||
{
|
||||
var path = EditorUtility.OpenFilePanel("Select Codepoints File", "Assets", "txt");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
// 상대 경로로 변환
|
||||
if (path.StartsWith(Application.dataPath))
|
||||
{
|
||||
path = "Assets" + path.Substring(Application.dataPath.Length);
|
||||
}
|
||||
_codepointsPath = path;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// 출력 파일 경로
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
_outputPath = EditorGUILayout.TextField("Output File", _outputPath);
|
||||
if (GUILayout.Button("...", GUILayout.Width(30)))
|
||||
{
|
||||
var directory = Path.GetDirectoryName(_outputPath) ?? "Assets";
|
||||
var filename = Path.GetFileName(_outputPath);
|
||||
var path = EditorUtility.SaveFilePanel("Save Generated Class", directory, filename, "cs");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
// 상대 경로로 변환
|
||||
if (path.StartsWith(Application.dataPath))
|
||||
{
|
||||
path = "Assets" + path.Substring(Application.dataPath.Length);
|
||||
}
|
||||
_outputPath = path;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// 클래스명
|
||||
_className = EditorGUILayout.TextField("Class Name", _className);
|
||||
|
||||
// 네임스페이스
|
||||
_namespace = EditorGUILayout.TextField("Namespace", _namespace);
|
||||
|
||||
// 폰트 경로 (Resources 기준)
|
||||
_fontPath = EditorGUILayout.TextField("Font Path (Resources)", _fontPath);
|
||||
EditorGUILayout.HelpBox("Resources.Load에 사용할 폰트 경로 (확장자 제외)", MessageType.None);
|
||||
|
||||
EditorGUILayout.Space(15);
|
||||
|
||||
// 파일 존재 여부 표시
|
||||
var codepointsExists = File.Exists(_codepointsPath);
|
||||
var outputExists = File.Exists(_outputPath);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Codepoints File:", GUILayout.Width(100));
|
||||
EditorGUILayout.LabelField(codepointsExists ? "Found" : "Not Found",
|
||||
codepointsExists ? EditorStyles.label : EditorStyles.boldLabel);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Output File:", GUILayout.Width(100));
|
||||
EditorGUILayout.LabelField(outputExists ? "Exists (will overwrite)" : "New file",
|
||||
EditorStyles.label);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(15);
|
||||
|
||||
// 버튼들
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
GUI.enabled = codepointsExists;
|
||||
if (GUILayout.Button("Generate", GUILayout.Height(30)))
|
||||
{
|
||||
SaveSettings();
|
||||
Generate(_codepointsPath, _outputPath, _className, _namespace, _fontPath);
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
if (GUILayout.Button("Reset to Defaults", GUILayout.Height(30)))
|
||||
{
|
||||
_codepointsPath = DefaultCodepointsPath;
|
||||
_outputPath = DefaultOutputPath;
|
||||
_className = DefaultClassName;
|
||||
_namespace = DefaultNamespace;
|
||||
_fontPath = DefaultFontPath;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public static void Generate(string codepointsPath, string outputPath, string className, string namespaceName, string fontPath = DefaultFontPath)
|
||||
{
|
||||
if (!File.Exists(codepointsPath))
|
||||
{
|
||||
Debug.LogError($"Codepoints 파일을 찾을 수 없습니다: {codepointsPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
var icons = ParseCodepoints(codepointsPath);
|
||||
if (icons.Count == 0)
|
||||
{
|
||||
Debug.LogError("파싱된 아이콘이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
var code = GenerateCode(icons, className, namespaceName, fontPath);
|
||||
|
||||
// 디렉토리 생성
|
||||
var directory = Path.GetDirectoryName(outputPath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.WriteAllText(outputPath, code, Encoding.UTF8);
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
Debug.Log($"{className}.cs 생성 완료: {icons.Count}개 아이콘 ({outputPath})");
|
||||
}
|
||||
|
||||
private static List<(string name, string unicode)> ParseCodepoints(string path)
|
||||
{
|
||||
var result = new List<(string, string)>();
|
||||
var lines = File.ReadAllLines(path);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
result.Add((parts[0], parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GenerateCode(List<(string name, string unicode)> icons, string className, string namespaceName, string fontPath)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var usedNames = new HashSet<string>();
|
||||
|
||||
sb.AppendLine("// <auto-generated>");
|
||||
sb.AppendLine("// 이 파일은 UTKMaterialIconsGenerator에 의해 자동 생성되었습니다.");
|
||||
sb.AppendLine("// 직접 수정하지 마세요. Tools > UTK > Material Icons Generator 메뉴로 재생성하세요.");
|
||||
sb.AppendLine("// </auto-generated>");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("#nullable enable");
|
||||
sb.AppendLine("using System;");
|
||||
sb.AppendLine("using System.Collections.Generic;");
|
||||
sb.AppendLine("using System.Threading;");
|
||||
sb.AppendLine("using Cysharp.Threading.Tasks;");
|
||||
sb.AppendLine("using UnityEngine;");
|
||||
sb.AppendLine("using UnityEngine.UIElements;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"namespace {namespaceName}");
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// Material Symbols Outlined 아이콘 유니코드 문자 상수 클래스입니다.");
|
||||
sb.AppendLine($" /// 총 {icons.Count}개의 아이콘을 포함합니다.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" /// <example>");
|
||||
sb.AppendLine(" /// <code>");
|
||||
sb.AppendLine($" /// // 동기 폰트 로드 (캐싱됨)");
|
||||
sb.AppendLine($" /// Font font = {className}.LoadFont();");
|
||||
sb.AppendLine(" /// ");
|
||||
sb.AppendLine($" /// // 비동기 폰트 로드 (UniTask)");
|
||||
sb.AppendLine($" /// Font? font = await {className}.LoadFontAsync(cancellationToken);");
|
||||
sb.AppendLine(" /// ");
|
||||
sb.AppendLine($" /// // Label에 아이콘 적용");
|
||||
sb.AppendLine($" /// var label = new Label({className}.Home);");
|
||||
sb.AppendLine($" /// {className}.ApplyIconStyle(label);");
|
||||
sb.AppendLine(" /// ");
|
||||
sb.AppendLine($" /// // 비동기로 아이콘 스타일 적용");
|
||||
sb.AppendLine($" /// await {className}.ApplyIconStyleAsync(label, cancellationToken);");
|
||||
sb.AppendLine(" /// ");
|
||||
sb.AppendLine($" /// // 또는 StyleFontDefinition 직접 사용");
|
||||
sb.AppendLine($" /// label.style.unityFontDefinition = {className}.GetFontDefinition();");
|
||||
sb.AppendLine(" /// ");
|
||||
sb.AppendLine($" /// // 이름으로 아이콘 조회");
|
||||
sb.AppendLine($" /// string icon = {className}.GetIcon(\"settings\");");
|
||||
sb.AppendLine(" /// ");
|
||||
sb.AppendLine($" /// // 아이콘 존재 여부 확인");
|
||||
sb.AppendLine($" /// if ({className}.HasIcon(\"search\")) {{ }}");
|
||||
sb.AppendLine(" /// ");
|
||||
sb.AppendLine($" /// // 전체 아이콘 이름 순회");
|
||||
sb.AppendLine($" /// foreach (var name in {className}.GetAllIconNames()) {{ }}");
|
||||
sb.AppendLine(" /// ");
|
||||
sb.AppendLine($" /// // 아이콘 총 개수");
|
||||
sb.AppendLine($" /// int count = {className}.Count;");
|
||||
sb.AppendLine(" /// </code>");
|
||||
sb.AppendLine(" /// </example>");
|
||||
sb.AppendLine(" /// <remarks>");
|
||||
sb.AppendLine(" /// <para><b>UXML에서 사용하기:</b></para>");
|
||||
sb.AppendLine(" /// <para>UXML에서 Material Icons를 사용하려면 USS에서 폰트를 설정하고, Label의 text에 유니코드 문자를 직접 입력합니다.</para>");
|
||||
sb.AppendLine(" /// <code>");
|
||||
sb.AppendLine(" /// /* USS 파일 */");
|
||||
sb.AppendLine(" /// .material-icon {");
|
||||
sb.AppendLine(" /// -unity-font: resource('Fonts/Icons/MaterialSymbolsOutlined');");
|
||||
sb.AppendLine(" /// font-size: 24px;");
|
||||
sb.AppendLine(" /// }");
|
||||
sb.AppendLine(" /// </code>");
|
||||
sb.AppendLine(" /// <code>");
|
||||
sb.AppendLine(" /// <!-- UXML 파일 -->");
|
||||
sb.AppendLine(" /// <ui:Label class=\"material-icon\" text=\"\" /> <!-- home 아이콘 -->");
|
||||
sb.AppendLine(" /// <ui:Label class=\"material-icon\" text=\"\" /> <!-- settings 아이콘 -->");
|
||||
sb.AppendLine(" /// </code>");
|
||||
sb.AppendLine(" /// <para><b>C# 코드에서 UXML Label에 아이콘 적용:</b></para>");
|
||||
sb.AppendLine(" /// <code>");
|
||||
sb.AppendLine($" /// var iconLabel = root.Q<Label>(\"my-icon\");");
|
||||
sb.AppendLine($" /// iconLabel.text = {className}.Settings;");
|
||||
sb.AppendLine($" /// {className}.ApplyIconStyle(iconLabel);");
|
||||
sb.AppendLine(" /// </code>");
|
||||
sb.AppendLine(" /// </remarks>");
|
||||
sb.AppendLine($" public static class {className}");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" #region Font");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($" private const string FontResourcePath = \"{fontPath}\";");
|
||||
sb.AppendLine(" private static Font? _cachedFont;");
|
||||
sb.AppendLine(" private static StyleFontDefinition? _cachedFontDefinition;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// 아이콘 폰트를 동기로 로드합니다. (캐싱됨)");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" /// <returns>로드된 Font, 실패 시 null</returns>");
|
||||
sb.AppendLine(" public static Font? LoadFont()");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (_cachedFont == null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" _cachedFont = Resources.Load<Font>(FontResourcePath);");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" return _cachedFont;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// 아이콘 폰트를 비동기로 로드합니다. (캐싱됨)");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" /// <param name=\"ct\">취소 토큰</param>");
|
||||
sb.AppendLine(" /// <returns>로드된 Font, 실패 시 null</returns>");
|
||||
sb.AppendLine(" public static async UniTask<Font?> LoadFontAsync(CancellationToken ct = default)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (_cachedFont != null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" return _cachedFont;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" var request = Resources.LoadAsync<Font>(FontResourcePath);");
|
||||
sb.AppendLine(" await request.ToUniTask(cancellationToken: ct);");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" _cachedFont = request.asset as Font;");
|
||||
sb.AppendLine(" return _cachedFont;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// UI Toolkit용 StyleFontDefinition을 동기로 반환합니다. (캐싱됨)");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" /// <returns>StyleFontDefinition, 폰트 로드 실패 시 기본값</returns>");
|
||||
sb.AppendLine(" public static StyleFontDefinition GetFontDefinition()");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (_cachedFontDefinition == null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var font = LoadFont();");
|
||||
sb.AppendLine(" if (font != null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" _cachedFontDefinition = new StyleFontDefinition(font);");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" return _cachedFontDefinition ?? default;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// UI Toolkit용 StyleFontDefinition을 비동기로 반환합니다. (캐싱됨)");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" /// <param name=\"ct\">취소 토큰</param>");
|
||||
sb.AppendLine(" /// <returns>StyleFontDefinition, 폰트 로드 실패 시 기본값</returns>");
|
||||
sb.AppendLine(" public static async UniTask<StyleFontDefinition> GetFontDefinitionAsync(CancellationToken ct = default)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (_cachedFontDefinition == null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var font = await LoadFontAsync(ct);");
|
||||
sb.AppendLine(" if (font != null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" _cachedFontDefinition = new StyleFontDefinition(font);");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" return _cachedFontDefinition ?? default;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// VisualElement에 아이콘 폰트 스타일을 동기로 적용합니다.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" /// <param name=\"element\">스타일을 적용할 요소</param>");
|
||||
sb.AppendLine(" /// <param name=\"fontSize\">폰트 크기 (기본값: 24)</param>");
|
||||
sb.AppendLine(" public static void ApplyIconStyle(VisualElement element, int fontSize = 24)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" element.style.unityFontDefinition = GetFontDefinition();");
|
||||
sb.AppendLine(" element.style.fontSize = fontSize;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// VisualElement에 아이콘 폰트 스타일을 비동기로 적용합니다.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" /// <param name=\"element\">스타일을 적용할 요소</param>");
|
||||
sb.AppendLine(" /// <param name=\"ct\">취소 토큰</param>");
|
||||
sb.AppendLine(" /// <param name=\"fontSize\">폰트 크기 (기본값: 24)</param>");
|
||||
sb.AppendLine(" public static async UniTask ApplyIconStyleAsync(VisualElement element, CancellationToken ct = default, int fontSize = 24)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" element.style.unityFontDefinition = await GetFontDefinitionAsync(ct);");
|
||||
sb.AppendLine(" element.style.fontSize = fontSize;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// 폰트 캐시를 클리어합니다.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" public static void ClearFontCache()");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" _cachedFont = null;");
|
||||
sb.AppendLine(" _cachedFontDefinition = null;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" #endregion");
|
||||
sb.AppendLine();
|
||||
|
||||
// 아이콘 상수 생성
|
||||
foreach (var (name, unicode) in icons)
|
||||
{
|
||||
var fieldName = ConvertToFieldName(name, usedNames);
|
||||
var character = GetCharacterFromUnicode(unicode);
|
||||
var escapedChar = EscapeForCSharp(character);
|
||||
|
||||
sb.AppendLine($" /// <summary>{name} (U+{unicode.ToUpper()})</summary>");
|
||||
sb.AppendLine($" public const string {fieldName} = \"{escapedChar}\";");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" #region Lookup");
|
||||
sb.AppendLine();
|
||||
|
||||
// 이름으로 아이콘 조회 딕셔너리
|
||||
sb.AppendLine(" private static readonly Dictionary<string, string> _iconsByName = new()");
|
||||
sb.AppendLine(" {");
|
||||
foreach (var (name, unicode) in icons)
|
||||
{
|
||||
var character = GetCharacterFromUnicode(unicode);
|
||||
var escapedChar = EscapeForCSharp(character);
|
||||
sb.AppendLine($" [\"{name}\"] = \"{escapedChar}\",");
|
||||
}
|
||||
sb.AppendLine(" };");
|
||||
sb.AppendLine();
|
||||
|
||||
// 조회 메서드
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// 아이콘 이름으로 유니코드 문자를 조회합니다.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" /// <param name=\"iconName\">아이콘 이름 (예: \"home\", \"settings\")</param>");
|
||||
sb.AppendLine(" /// <returns>아이콘 문자, 없으면 빈 문자열</returns>");
|
||||
sb.AppendLine(" public static string GetIcon(string iconName)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" return _iconsByName.TryGetValue(iconName, out var icon) ? icon : string.Empty;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// 아이콘이 존재하는지 확인합니다.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" public static bool HasIcon(string iconName) => _iconsByName.ContainsKey(iconName);");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// 모든 아이콘 이름 목록을 반환합니다.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" public static IEnumerable<string> GetAllIconNames() => _iconsByName.Keys;");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// 전체 아이콘 수를 반환합니다.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine($" public static int Count => {icons.Count};");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" #endregion");
|
||||
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string ConvertToFieldName(string iconName, HashSet<string> usedNames)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var words = iconName.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var word in words)
|
||||
{
|
||||
if (word.Length == 0) continue;
|
||||
|
||||
// 첫 글자 대문자, 나머지 소문자 (PascalCase)
|
||||
sb.Append(char.ToUpper(word[0]));
|
||||
if (word.Length > 1)
|
||||
{
|
||||
sb.Append(word.Substring(1).ToLower());
|
||||
}
|
||||
}
|
||||
|
||||
var result = sb.ToString();
|
||||
|
||||
// 숫자로 시작하면 앞에 _ 추가
|
||||
if (result.Length > 0 && char.IsDigit(result[0]))
|
||||
{
|
||||
result = "_" + result;
|
||||
}
|
||||
|
||||
// 빈 문자열이면 기본값
|
||||
if (string.IsNullOrEmpty(result))
|
||||
{
|
||||
result = "_Icon";
|
||||
}
|
||||
|
||||
// 중복 처리
|
||||
var originalResult = result;
|
||||
var counter = 2;
|
||||
while (usedNames.Contains(result))
|
||||
{
|
||||
result = $"{originalResult}_{counter}";
|
||||
counter++;
|
||||
}
|
||||
|
||||
usedNames.Add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetCharacterFromUnicode(string hexCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var codepoint = int.Parse(hexCode, NumberStyles.HexNumber);
|
||||
return char.ConvertFromUtf32(codepoint);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
private static string EscapeForCSharp(string str)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var c in str)
|
||||
{
|
||||
// 유니코드 이스케이프 시퀀스로 변환
|
||||
if (c > 127 || c < 32)
|
||||
{
|
||||
// Surrogate pair 처리
|
||||
sb.Append($"\\u{(int)c:X4}");
|
||||
}
|
||||
else if (c == '"')
|
||||
{
|
||||
sb.Append("\\\"");
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
sb.Append("\\\\");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Assets/Resources/Fonts/Icons/MaterialSymbolsOutlined.ttf
Normal file
BIN
Assets/Resources/Fonts/Icons/MaterialSymbolsOutlined.ttf
Normal file
Binary file not shown.
4102
Assets/Resources/Fonts/Icons/MaterialSymbolsOutlinedCodepoints.txt
Normal file
4102
Assets/Resources/Fonts/Icons/MaterialSymbolsOutlinedCodepoints.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -292,6 +292,70 @@
|
||||
color: var(--color-text-on-primary);
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Text Variant (Label/Icon only, no background/border)
|
||||
=================================== */
|
||||
|
||||
/* Text - 배경과 외곽선이 투명하고 텍스트/아이콘만 표시 */
|
||||
.utk-button--text {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
border-width: 0;
|
||||
color: var(--color-text-primary);
|
||||
padding-left: var(--space-s);
|
||||
padding-right: var(--space-s);
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.utk-button--text:hover {
|
||||
background-color: transparent;
|
||||
color: var(--color-btn-primary);
|
||||
}
|
||||
|
||||
.utk-button--text:active {
|
||||
background-color: transparent;
|
||||
color: var(--color-btn-primary-hover);
|
||||
}
|
||||
|
||||
.utk-button--text .utk-button__text,
|
||||
.utk-button--text .utk-button__icon {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.utk-button--text:hover .utk-button__text,
|
||||
.utk-button--text:hover .utk-button__icon {
|
||||
color: var(--color-btn-primary);
|
||||
}
|
||||
|
||||
.utk-button--text:active .utk-button__text,
|
||||
.utk-button--text:active .utk-button__icon {
|
||||
color: var(--color-btn-primary-hover);
|
||||
}
|
||||
|
||||
/* Text Icon Only - 원형 아이콘 버튼 */
|
||||
.utk-button--text.utk-button--icon-only {
|
||||
width: var(--size-icon-btn);
|
||||
height: var(--size-icon-btn);
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.utk-button--text.utk-button--icon-only:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.utk-button--text.utk-button--icon-only:hover .utk-button__icon {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.utk-button--text.utk-button--icon-only:active {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.utk-button--text.utk-button--icon-only:active .utk-button__icon {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Disabled State
|
||||
=================================== */
|
||||
@@ -321,3 +385,53 @@
|
||||
.utk-button--disabled .utk-button__icon {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
/* Text Variant Disabled - hover/active 시에도 색상 유지 */
|
||||
.utk-button--text.utk-button--disabled {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.utk-button--text.utk-button--disabled:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.utk-button--text.utk-button--disabled:hover .utk-button__text,
|
||||
.utk-button--text.utk-button--disabled:hover .utk-button__icon {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.utk-button--text.utk-button--disabled:active .utk-button__text,
|
||||
.utk-button--text.utk-button--disabled:active .utk-button__icon {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Image Icon Support
|
||||
=================================== */
|
||||
|
||||
.utk-button__image-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
-unity-background-scale-mode: scale-to-fit;
|
||||
}
|
||||
|
||||
.utk-button--has-image-icon .utk-button__image-icon {
|
||||
margin-right: var(--space-s);
|
||||
}
|
||||
|
||||
.utk-button--icon-only .utk-button__image-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* Size Variants for Image Icon */
|
||||
.utk-button--small .utk-button__image-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.utk-button--large .utk-button__image-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
@@ -136,3 +136,85 @@
|
||||
.utk-label--right .utk-label__text {
|
||||
-unity-text-align: middle-right;
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Icon Styles
|
||||
=================================== */
|
||||
|
||||
.utk-label--has-icon {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.utk-label__icon {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-unity-text-align: middle-center;
|
||||
flex-shrink: 0;
|
||||
-unity-font-definition: resource('Fonts/Icons/MaterialSymbolsOutlined');
|
||||
font-size: 16px;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Icon Color Variants */
|
||||
.utk-label--primary .utk-label__icon {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.utk-label--secondary .utk-label__icon {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.utk-label--disabled .utk-label__icon {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.utk-label--success .utk-label__icon {
|
||||
color: var(--color-state-success);
|
||||
}
|
||||
|
||||
.utk-label--warning .utk-label__icon {
|
||||
color: var(--color-state-warning);
|
||||
}
|
||||
|
||||
.utk-label--error .utk-label__icon {
|
||||
color: var(--color-state-error);
|
||||
}
|
||||
|
||||
.utk-label--info .utk-label__icon {
|
||||
color: var(--color-state-info);
|
||||
}
|
||||
|
||||
.utk-label__image-icon {
|
||||
flex-shrink: 0;
|
||||
-unity-background-scale-mode: scale-to-fit;
|
||||
-unity-background-image-tint-color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Image Icon Color Variants */
|
||||
.utk-label--primary .utk-label__image-icon {
|
||||
-unity-background-image-tint-color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.utk-label--secondary .utk-label__image-icon {
|
||||
-unity-background-image-tint-color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.utk-label--disabled .utk-label__image-icon {
|
||||
-unity-background-image-tint-color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.utk-label--success .utk-label__image-icon {
|
||||
-unity-background-image-tint-color: var(--color-state-success);
|
||||
}
|
||||
|
||||
.utk-label--warning .utk-label__image-icon {
|
||||
-unity-background-image-tint-color: var(--color-state-warning);
|
||||
}
|
||||
|
||||
.utk-label--error .utk-label__image-icon {
|
||||
-unity-background-image-tint-color: var(--color-state-error);
|
||||
}
|
||||
|
||||
.utk-label--info .utk-label__image-icon {
|
||||
-unity-background-image-tint-color: var(--color-state-info);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
border-radius: var(--radius-m);
|
||||
border-width: var(--border-width);
|
||||
border-color: var(--color-border);
|
||||
padding: var(--space-l);
|
||||
padding-top: var(--space-s);
|
||||
padding-left: var(--space-l);
|
||||
padding-right: var(--space-l);
|
||||
padding-bottom: var(--space-l);
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
@@ -37,7 +40,9 @@
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
color: var(--color-text-secondary);
|
||||
border-radius: var(--radius-s);
|
||||
border-radius: var(--radius-full);
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.utk-color-picker__close-btn:hover {
|
||||
@@ -222,32 +227,11 @@
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/* UTKButton은 자체 스타일을 가지므로 마진/크기만 설정 */
|
||||
.utk-color-picker__cancel-btn {
|
||||
margin-right: var(--space-m);
|
||||
width: var(--size-btn-min-width);
|
||||
height: var(--size-btn-height);
|
||||
font-size: var(--font-size-body2);
|
||||
background-color: var(--color-btn-normal);
|
||||
color: var(--color-text-primary);
|
||||
border-width: var(--border-width);
|
||||
border-color: var(--color-border);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.utk-color-picker__cancel-btn:hover {
|
||||
background-color: var(--color-btn-hover);
|
||||
}
|
||||
|
||||
.utk-color-picker__confirm-btn {
|
||||
width: var(--size-btn-min-width);
|
||||
height: var(--size-btn-height);
|
||||
font-size: var(--font-size-body2);
|
||||
background-color: var(--color-btn-primary);
|
||||
color: var(--color-text-on-primary);
|
||||
border-width: 0;
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.utk-color-picker__confirm-btn:hover {
|
||||
background-color: var(--color-btn-primary-hover);
|
||||
/* UTKButton Primary 스타일 사용 */
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
|
||||
<Style src="project://database/Assets/Resources/UIToolkit/Modal/UTKColorPicker.uss?fileID=7433441132597879392&guid=7ae6523e61765d84caaec92e84dcc017&type=3#UTKColorPicker" />
|
||||
<ui:VisualElement name="root" class="utk-color-picker">
|
||||
<ui:VisualElement name="header" class="utk-color-picker__header">
|
||||
<ui:Label name="title" text="Color Picker" class="utk-color-picker__title" />
|
||||
<ui:Button name="close-btn" text="✕" class="utk-color-picker__close-btn" />
|
||||
<ui:Button name="close-btn" text="" class="utk-color-picker__close-btn material-icon" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="color-area" class="utk-color-picker__color-area">
|
||||
<ui:VisualElement name="sv-box" class="utk-color-picker__sv-box">
|
||||
@@ -44,8 +44,8 @@
|
||||
<ui:TextField name="hex-field" class="utk-color-picker__hex-field" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="button-row" class="utk-color-picker__button-row">
|
||||
<ui:Button name="cancel-btn" text="Cancel" class="utk-color-picker__cancel-btn" />
|
||||
<ui:Button name="confirm-btn" text="OK" class="utk-color-picker__confirm-btn" />
|
||||
<utk:UTKButton name="cancel-btn" text="Cancel" variant="Normal" class="utk-color-picker__cancel-btn" />
|
||||
<utk:UTKButton name="confirm-btn" text="OK" variant="Primary" class="utk-color-picker__confirm-btn" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
border-radius: var(--radius-l);
|
||||
border-width: var(--border-width);
|
||||
border-color: var(--color-border);
|
||||
padding: var(--space-l);
|
||||
padding-top: var(--space-s);
|
||||
padding-left: var(--space-l);
|
||||
padding-right: var(--space-l);
|
||||
padding-bottom: var(--space-l);
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
@@ -31,6 +34,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-l);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.utk-date-picker__title {
|
||||
@@ -42,16 +46,18 @@
|
||||
.utk-date-picker__close-btn {
|
||||
width: var(--size-icon-btn);
|
||||
height: var(--size-icon-btn);
|
||||
font-size: var(--font-size-body2);
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-body2);
|
||||
border-radius: var(--radius-full);
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.utk-date-picker__close-btn:hover {
|
||||
color: var(--color-text-primary);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
@@ -239,8 +245,10 @@
|
||||
=================================== */
|
||||
|
||||
.utk-date-picker__range-info {
|
||||
margin-top: var(--space-m);
|
||||
padding: var(--space-s) var(--space-m);
|
||||
margin-top: var(--space-l);
|
||||
padding-top: var(--space-l);
|
||||
border-top-width: var(--border-width);
|
||||
border-top-color: var(--color-border);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-body2);
|
||||
@@ -255,7 +263,7 @@
|
||||
.utk-date-picker__time-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
justify-content: center;
|
||||
margin-top: var(--space-l);
|
||||
padding-top: var(--space-l);
|
||||
border-top-width: var(--border-width);
|
||||
|
||||
@@ -661,3 +661,9 @@ SetupDraggerEvents() 메서드처럼 코드로 MouseEnterEvent/MouseLeaveEvent
|
||||
}
|
||||
|
||||
|
||||
/* USS 파일 */
|
||||
.material-icon {
|
||||
-unity-font-definition: resource('Fonts/Icons/MaterialSymbolsOutlined');
|
||||
font-size: 24px;
|
||||
-unity-text-align: middle-center;
|
||||
}
|
||||
@@ -124,6 +124,61 @@ namespace UVC.Sample.UIToolkit
|
||||
openPickerAsyncBtn.style.marginBottom = 10;
|
||||
container.Add(openPickerAsyncBtn);
|
||||
|
||||
// Alpha 프리셋 버튼들
|
||||
var alphaPresetLabel = new Label("Alpha Presets:");
|
||||
alphaPresetLabel.style.color = Color.white;
|
||||
alphaPresetLabel.style.marginTop = 10;
|
||||
alphaPresetLabel.style.marginBottom = 5;
|
||||
container.Add(alphaPresetLabel);
|
||||
|
||||
var alphaPresetRow = new VisualElement();
|
||||
alphaPresetRow.style.flexDirection = FlexDirection.Row;
|
||||
alphaPresetRow.style.marginBottom = 10;
|
||||
|
||||
// Alpha 값이 다른 프리셋 버튼들
|
||||
float[] alphaValues = { 1.0f, 0.75f, 0.5f, 0.25f };
|
||||
foreach (var alpha in alphaValues)
|
||||
{
|
||||
var alphaBtn = new Button(() => SetColorWithAlpha(alpha));
|
||||
alphaBtn.style.width = 50;
|
||||
alphaBtn.style.height = 28;
|
||||
alphaBtn.style.marginRight = 5;
|
||||
alphaBtn.style.backgroundColor = new Color(0.3f, 0.3f, 0.3f);
|
||||
alphaBtn.style.borderTopLeftRadius = 4;
|
||||
alphaBtn.style.borderTopRightRadius = 4;
|
||||
alphaBtn.style.borderBottomLeftRadius = 4;
|
||||
alphaBtn.style.borderBottomRightRadius = 4;
|
||||
alphaBtn.text = $"{(int)(alpha * 100)}%";
|
||||
alphaBtn.style.fontSize = 11;
|
||||
alphaPresetRow.Add(alphaBtn);
|
||||
}
|
||||
|
||||
container.Add(alphaPresetRow);
|
||||
|
||||
// Alpha 활성화/비활성화 직접 호출 버튼들
|
||||
var alphaControlLabel = new Label("Alpha Control Examples:");
|
||||
alphaControlLabel.style.color = Color.white;
|
||||
alphaControlLabel.style.marginTop = 5;
|
||||
alphaControlLabel.style.marginBottom = 5;
|
||||
container.Add(alphaControlLabel);
|
||||
|
||||
var alphaControlRow = new VisualElement();
|
||||
alphaControlRow.style.flexDirection = FlexDirection.Row;
|
||||
alphaControlRow.style.marginBottom = 10;
|
||||
|
||||
var withAlphaBtn = new Button(OpenColorPickerWithAlpha) { text = "With Alpha" };
|
||||
withAlphaBtn.style.height = 28;
|
||||
withAlphaBtn.style.flexGrow = 1;
|
||||
withAlphaBtn.style.marginRight = 5;
|
||||
alphaControlRow.Add(withAlphaBtn);
|
||||
|
||||
var withoutAlphaBtn = new Button(OpenColorPickerWithoutAlpha) { text = "Without Alpha" };
|
||||
withoutAlphaBtn.style.height = 28;
|
||||
withoutAlphaBtn.style.flexGrow = 1;
|
||||
alphaControlRow.Add(withoutAlphaBtn);
|
||||
|
||||
container.Add(alphaControlRow);
|
||||
|
||||
// 프리셋 색상 버튼들
|
||||
var presetLabel = new Label("Preset Colors:");
|
||||
presetLabel.style.color = Color.white;
|
||||
@@ -169,6 +224,58 @@ namespace UVC.Sample.UIToolkit
|
||||
_currentPicker.OnColorSelected += OnColorSelected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alpha 채널 활성화 상태로 컬러 피커 열기
|
||||
/// </summary>
|
||||
private void OpenColorPickerWithAlpha()
|
||||
{
|
||||
if (_root == null || _currentPicker != null) return;
|
||||
|
||||
// useAlpha = true로 명시적 호출
|
||||
_currentPicker = UTKColorPicker.Show(_root, _currentColor, "Select Color (Alpha ON)", useAlpha: true);
|
||||
|
||||
_currentPicker.OnColorChanged += OnColorChanged;
|
||||
_currentPicker.OnColorSelected += OnColorSelected;
|
||||
|
||||
Debug.Log("[Sample] ColorPicker opened with Alpha channel enabled");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alpha 채널 비활성화 상태로 컬러 피커 열기
|
||||
/// </summary>
|
||||
private void OpenColorPickerWithoutAlpha()
|
||||
{
|
||||
if (_root == null || _currentPicker != null) return;
|
||||
|
||||
// useAlpha = false로 명시적 호출
|
||||
_currentPicker = UTKColorPicker.Show(_root, _currentColor, "Select Color (Alpha OFF)", useAlpha: false);
|
||||
|
||||
_currentPicker.OnColorChanged += OnColorChanged;
|
||||
_currentPicker.OnColorSelected += OnColorSelected;
|
||||
|
||||
Debug.Log("[Sample] ColorPicker opened without Alpha channel");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 색상의 Alpha 값을 변경
|
||||
/// </summary>
|
||||
private void SetColorWithAlpha(float alpha)
|
||||
{
|
||||
_currentColor = new Color(_currentColor.r, _currentColor.g, _currentColor.b, alpha);
|
||||
|
||||
if (_colorPreview != null)
|
||||
{
|
||||
_colorPreview.style.backgroundColor = _currentColor;
|
||||
}
|
||||
|
||||
if (_colorLabel != null)
|
||||
{
|
||||
_colorLabel.text = ColorToHex(_currentColor);
|
||||
}
|
||||
|
||||
Debug.Log($"[Sample] Alpha set to {(int)(alpha * 100)}% - Color: {ColorToHex(_currentColor)}");
|
||||
}
|
||||
|
||||
private async UniTaskVoid OpenColorPickerAsync()
|
||||
{
|
||||
if (_root == null) return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
@@ -48,6 +49,7 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
// 카테고리별 컨트롤 정의
|
||||
private static readonly Dictionary<string, string[]> ControlCategories = new()
|
||||
{
|
||||
["Icon"] = new[] { "MaterialSymbolsOutlined", "UTKImageIcons" },
|
||||
["Button"] = new[] { "UTKButton", "UTKCheckBox", "UTKToggle", "UTKRadioButton", "UTKToggleButtonGroup" },
|
||||
["Input"] = new[] { "UTKInputField", "UTKIntegerField", "UTKLongField", "UTKFloatField", "UTKDoubleField", "UTKVector2Field", "UTKVector3Field", "UTKVector4Field", "UTKRectField", "UTKBoundsField", "UTKNumberStepper" },
|
||||
["Slider"] = new[] { "UTKSlider", "UTKMinMaxSlider", "UTKProgressBar" },
|
||||
@@ -57,7 +59,7 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
["Card"] = new[] { "UTKCard", "UTKPanel" },
|
||||
["Tab"] = new[] { "UTKTabView" },
|
||||
["Modal"] = new[] { "UTKAlert", "UTKToast", "UTKTooltip" },
|
||||
["Picker"] = new[] { "UTKColorPicker", "UTKDatePicker" }
|
||||
["Picker"] = new[] { "UTKColorPicker", "UTKDatePicker" },
|
||||
};
|
||||
|
||||
// UI 요소들
|
||||
@@ -73,6 +75,8 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
uiDocument ??= GetComponent<UIDocument>();
|
||||
_root = uiDocument.rootVisualElement;
|
||||
|
||||
UTKToast.SetRoot(_root);
|
||||
|
||||
// 테마 매니저에 루트 등록
|
||||
UTKThemeManager.Instance.RegisterRoot(_root);
|
||||
|
||||
@@ -344,6 +348,12 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
case "UTKDatePicker":
|
||||
CreateDatePickerPreview(container);
|
||||
break;
|
||||
case "MaterialSymbolsOutlined":
|
||||
CreateIconPreview(container);
|
||||
break;
|
||||
case "UTKImageIcons":
|
||||
CreateImageIconPreview(container);
|
||||
break;
|
||||
default:
|
||||
container.Add(new Label($"Preview for {controlName} not implemented"));
|
||||
break;
|
||||
@@ -370,15 +380,33 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
|
||||
// Icon Only
|
||||
var row3 = CreateRow(container, "Icon Only");
|
||||
row3.Add(new UTKButton("", "✚", UTKButton.ButtonVariant.Primary) { IconOnly = true });
|
||||
row3.Add(new UTKButton("", "✎", UTKButton.ButtonVariant.Normal) { IconOnly = true });
|
||||
row3.Add(new UTKButton("", "✖", UTKButton.ButtonVariant.Danger) { IconOnly = true });
|
||||
row3.Add(new UTKButton("", "⚙", UTKButton.ButtonVariant.OutlinePrimary) { IconOnly = true });
|
||||
row3.Add(new UTKButton("", UTKMaterialIcons.PlusOne, UTKButton.ButtonVariant.Primary) { IconOnly = true });
|
||||
row3.Add(new UTKButton("", UTKMaterialIcons.Edit, UTKButton.ButtonVariant.Normal) { IconOnly = true });
|
||||
row3.Add(new UTKButton("", UTKMaterialIcons.Close, UTKButton.ButtonVariant.Danger) { IconOnly = true });
|
||||
row3.Add(new UTKButton("", UTKMaterialIcons.Settings, UTKButton.ButtonVariant.OutlinePrimary) { IconOnly = true });
|
||||
row3.Add(new UTKButton("", UTKMaterialIcons.Cancel, UTKButton.ButtonVariant.Normal) { IconOnly = true });
|
||||
// Ghost
|
||||
var row4 = CreateRow(container, "Ghost");
|
||||
row4.Add(new UTKButton("Ghost", "", UTKButton.ButtonVariant.Ghost));
|
||||
row4.Add(new UTKButton("", UTKMaterialIcons.Settings, UTKButton.ButtonVariant.Ghost) { IconOnly = true });
|
||||
// Text (배경/외곽선 없이 텍스트/아이콘만)
|
||||
var row5 = CreateRow(container, "Text (Label/Icon Only)");
|
||||
row5.Add(new UTKButton("Text Only", "", UTKButton.ButtonVariant.Text));
|
||||
row5.Add(new UTKButton("With Icon", UTKMaterialIcons.FlashOn, UTKButton.ButtonVariant.Text));
|
||||
row5.Add(new UTKButton("Link Style", "", UTKButton.ButtonVariant.Text));
|
||||
|
||||
// Text Icon Only (원형 아이콘 버튼 - UTKColorPicker 닫기 버튼 스타일)
|
||||
var row6 = CreateRow(container, "Text Icon Only (Circle)");
|
||||
row6.Add(new UTKButton("", UTKMaterialIcons.Close, UTKButton.ButtonVariant.Text, 12) { IconOnly = true });
|
||||
row6.Add(new UTKButton("", UTKMaterialIcons.Check, UTKButton.ButtonVariant.Text, 12) { IconOnly = true });
|
||||
row6.Add(new UTKButton("", UTKMaterialIcons.Settings, UTKButton.ButtonVariant.Text, 12) { IconOnly = true });
|
||||
row6.Add(new UTKButton("", UTKMaterialIcons.Edit, UTKButton.ButtonVariant.Text, 12) { IconOnly = true });
|
||||
row6.Add(new UTKButton("", UTKMaterialIcons.Search, UTKButton.ButtonVariant.Text, 12) { IconOnly = true });
|
||||
// Disabled
|
||||
var row4 = CreateRow(container, "Disabled");
|
||||
row4.Add(new UTKButton("Disabled", "", UTKButton.ButtonVariant.Primary) { IsEnabled = false });
|
||||
row4.Add(new UTKButton("Disabled", "", UTKButton.ButtonVariant.Normal) { IsEnabled = false });
|
||||
var row7 = CreateRow(container, "Disabled");
|
||||
row7.Add(new UTKButton("Disabled", "", UTKButton.ButtonVariant.Primary) { IsEnabled = false });
|
||||
row7.Add(new UTKButton("Disabled", "", UTKButton.ButtonVariant.Normal) { IsEnabled = false });
|
||||
row7.Add(new UTKButton("Disabled", "", UTKButton.ButtonVariant.Text) { IsEnabled = false });
|
||||
}
|
||||
|
||||
private void CreateCheckBoxPreview(VisualElement container)
|
||||
@@ -718,14 +746,68 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
|
||||
private void CreateLabelPreview(VisualElement container)
|
||||
{
|
||||
AddDescription(container, "다양한 크기의 라벨 컴포넌트");
|
||||
AddDescription(container, "다양한 크기와 아이콘을 지원하는 라벨 컴포넌트");
|
||||
|
||||
container.Add(new UTKLabel("H1 Heading", UTKLabel.LabelSize.H1));
|
||||
container.Add(new UTKLabel("H2 Heading", UTKLabel.LabelSize.H2));
|
||||
container.Add(new UTKLabel("H3 Heading", UTKLabel.LabelSize.H3));
|
||||
container.Add(new UTKLabel("Body1 Text", UTKLabel.LabelSize.Body1));
|
||||
container.Add(new UTKLabel("Body2 Text", UTKLabel.LabelSize.Body2));
|
||||
container.Add(new UTKLabel("Caption Text", UTKLabel.LabelSize.Caption));
|
||||
// 텍스트 크기
|
||||
var row1 = CreateRow(container, "Text Sizes");
|
||||
row1.style.flexDirection = FlexDirection.Column;
|
||||
row1.style.alignItems = Align.FlexStart;
|
||||
row1.Add(new UTKLabel("H1 Heading", UTKLabel.LabelSize.H1));
|
||||
row1.Add(new UTKLabel("H2 Heading", UTKLabel.LabelSize.H2));
|
||||
row1.Add(new UTKLabel("H3 Heading", UTKLabel.LabelSize.H3));
|
||||
row1.Add(new UTKLabel("Body1 Text", UTKLabel.LabelSize.Body1));
|
||||
row1.Add(new UTKLabel("Body2 Text", UTKLabel.LabelSize.Body2));
|
||||
row1.Add(new UTKLabel("Caption Text", UTKLabel.LabelSize.Caption));
|
||||
|
||||
// Material Icon + 텍스트
|
||||
var row2 = CreateRow(container, "Material Icon + Text");
|
||||
row2.Add(new UTKLabel("Settings", UTKMaterialIcons.Settings));
|
||||
row2.Add(new UTKLabel("Home", UTKMaterialIcons.Home));
|
||||
row2.Add(new UTKLabel("Search", UTKMaterialIcons.Search));
|
||||
row2.Add(new UTKLabel("Edit", UTKMaterialIcons.Edit));
|
||||
|
||||
// Material Icon 오른쪽 배치
|
||||
var row3 = CreateRow(container, "Icon Right");
|
||||
row3.Add(new UTKLabel("Next", UTKMaterialIcons.ArrowForward, UTKLabel.IconPosition.Right));
|
||||
row3.Add(new UTKLabel("Download", UTKMaterialIcons.Download, UTKLabel.IconPosition.Right));
|
||||
row3.Add(new UTKLabel("External", UTKMaterialIcons.OpenInNew, UTKLabel.IconPosition.Right));
|
||||
|
||||
// Image Icon + 텍스트
|
||||
var row4 = CreateRow(container, "Image Icon + Text");
|
||||
row4.Add(new UTKLabel("Close", UTKImageIcons.BtnClose16, isImageIcon: true));
|
||||
row4.Add(new UTKLabel("Settings", UTKImageIcons.IconSetting22, isImageIcon: true));
|
||||
|
||||
// Material Icon만
|
||||
var row5 = CreateRow(container, "Material Icon Only");
|
||||
row5.Add(new UTKLabel(UTKMaterialIcons.Home, 24));
|
||||
row5.Add(new UTKLabel(UTKMaterialIcons.Settings, 24));
|
||||
row5.Add(new UTKLabel(UTKMaterialIcons.Search, 24));
|
||||
row5.Add(new UTKLabel(UTKMaterialIcons.Edit, 24));
|
||||
row5.Add(new UTKLabel(UTKMaterialIcons.Delete, 24));
|
||||
|
||||
// Image Icon만
|
||||
var row6 = CreateRow(container, "Image Icon Only");
|
||||
row6.Add(new UTKLabel(UTKImageIcons.BtnClose22, isImageIcon: true, iconSize: 22));
|
||||
row6.Add(new UTKLabel(UTKImageIcons.IconSetting22, isImageIcon: true, iconSize: 22));
|
||||
|
||||
// 메서드로 아이콘 설정
|
||||
var row7 = CreateRow(container, "Set Icon via Method");
|
||||
var label1 = new UTKLabel("Dynamic Icon", UTKLabel.LabelSize.Body1);
|
||||
label1.SetMaterialIcon(UTKMaterialIcons.Star);
|
||||
row7.Add(label1);
|
||||
|
||||
var label2 = new UTKLabel("Image Icon", UTKLabel.LabelSize.Body1);
|
||||
label2.SetImageIconByName("icon_setting_22");
|
||||
row7.Add(label2);
|
||||
|
||||
// 텍스트 변형
|
||||
var row8 = CreateRow(container, "Variants");
|
||||
row8.Add(new UTKLabel("Primary", UTKLabel.LabelSize.Body1) { Variant = UTKLabel.LabelVariant.Primary });
|
||||
row8.Add(new UTKLabel("Secondary", UTKLabel.LabelSize.Body1) { Variant = UTKLabel.LabelVariant.Secondary });
|
||||
row8.Add(new UTKLabel("Success", UTKLabel.LabelSize.Body1) { Variant = UTKLabel.LabelVariant.Success });
|
||||
row8.Add(new UTKLabel("Warning", UTKLabel.LabelSize.Body1) { Variant = UTKLabel.LabelVariant.Warning });
|
||||
row8.Add(new UTKLabel("Error", UTKLabel.LabelSize.Body1) { Variant = UTKLabel.LabelVariant.Error });
|
||||
row8.Add(new UTKLabel("Disabled", UTKLabel.LabelSize.Body1) { Variant = UTKLabel.LabelVariant.Disabled });
|
||||
}
|
||||
|
||||
private void CreateHelpBoxPreview(VisualElement container)
|
||||
@@ -1172,8 +1254,6 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
|
||||
var row = CreateRow(container, "Toasts");
|
||||
|
||||
UTKToast.SetRoot(_root);
|
||||
|
||||
var infoBtn = new UTKButton("Info Toast", "", UTKButton.ButtonVariant.Primary);
|
||||
infoBtn.OnClicked += () => UTKToast.Show("This is an info toast!This is an info toast!This is an info toast!This is an info toast!This is an info toast!");
|
||||
row.Add(infoBtn);
|
||||
@@ -1315,6 +1395,8 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
|
||||
AddDescription(container, "날짜 선택 컴포넌트 (버튼 클릭으로 모달 표시)");
|
||||
|
||||
UTKDatePicker.SetDayNames(new[] { "일", "월", "화", "수", "목", "금", "토" });
|
||||
|
||||
// 현재 선택된 날짜 표시
|
||||
var previewRow = CreateRow(container, "Current Date");
|
||||
_dateLabel = new Label($"Selected: {_selectedDate:yyyy-MM-dd}");
|
||||
@@ -1435,6 +1517,472 @@ public class UTKStyleGuideSample : MonoBehaviour
|
||||
|
||||
#endregion
|
||||
|
||||
#region Icon Previews
|
||||
|
||||
private List<string>? _iconNameList;
|
||||
private List<string>? _filteredIconNameList;
|
||||
private ListView? _iconListView;
|
||||
private Label? _iconCountLabel;
|
||||
|
||||
// 그리드 레이아웃 상수
|
||||
private const int IconItemWidth = 80;
|
||||
private const int IconItemHeight = 80;
|
||||
private const int IconItemMargin = 4;
|
||||
private const int IconsPerRow = 10; // 한 행에 표시할 아이콘 수
|
||||
|
||||
private void CreateIconPreview(VisualElement container)
|
||||
{
|
||||
AddDescription(container, $"Material Symbols Outlined 아이콘 폰트 (UTKMaterialIcons 사용, ListView 가상화 적용)");
|
||||
|
||||
// UTKMaterialIcons에서 폰트 로드
|
||||
var font = UTKMaterialIcons.LoadFont();
|
||||
if (font == null)
|
||||
{
|
||||
container.Add(new Label("Error: UTKMaterialIcons 폰트를 로드할 수 없습니다."));
|
||||
return;
|
||||
}
|
||||
|
||||
// UTKMaterialIcons에서 모든 아이콘 이름 가져오기
|
||||
_iconNameList = UTKMaterialIcons.GetAllIconNames().ToList();
|
||||
_filteredIconNameList = _iconNameList;
|
||||
|
||||
// 아이콘 개수 표시
|
||||
_iconCountLabel = new Label($"총 {UTKMaterialIcons.Count}개의 아이콘 (가상화 적용)");
|
||||
_iconCountLabel.style.marginBottom = 10;
|
||||
_iconCountLabel.style.color = new Color(0.6f, 0.6f, 0.6f);
|
||||
container.Add(_iconCountLabel);
|
||||
|
||||
// 검색 필드
|
||||
var searchRow = CreateRow(container, "Search");
|
||||
var searchField = new UTKInputField("", "Search icons...");
|
||||
searchField.style.width = 300;
|
||||
searchRow.Add(searchField);
|
||||
|
||||
// 행 데이터 생성 (아이콘 이름을 행 단위로 그룹화)
|
||||
var rowData = CreateIconRowData(_filteredIconNameList);
|
||||
|
||||
// ListView 생성 (가상화 적용)
|
||||
_iconListView = new ListView();
|
||||
_iconListView.style.flexGrow = 1;
|
||||
_iconListView.style.maxHeight = 500;
|
||||
_iconListView.fixedItemHeight = IconItemHeight + IconItemMargin;
|
||||
_iconListView.itemsSource = rowData;
|
||||
_iconListView.makeItem = MakeIconRow;
|
||||
_iconListView.bindItem = BindIconRow;
|
||||
_iconListView.selectionType = SelectionType.None;
|
||||
_iconListView.virtualizationMethod = CollectionVirtualizationMethod.FixedHeight;
|
||||
_iconListView.AddToClassList("utk-icon-listview");
|
||||
container.Add(_iconListView);
|
||||
|
||||
// 검색 기능 (Enter 키 또는 포커스 해제 시 검색)
|
||||
void PerformSearch(string searchValue)
|
||||
{
|
||||
if (_iconNameList == null || _iconListView == null || _iconCountLabel == null) return;
|
||||
|
||||
_filteredIconNameList = string.IsNullOrEmpty(searchValue)
|
||||
? _iconNameList
|
||||
: _iconNameList.FindAll(name => name.Contains(searchValue, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var newRowData = CreateIconRowData(_filteredIconNameList);
|
||||
_iconListView.itemsSource = newRowData;
|
||||
_iconListView.Rebuild();
|
||||
|
||||
// 필터링 결과 개수 업데이트
|
||||
_iconCountLabel.text = string.IsNullOrEmpty(searchValue)
|
||||
? $"총 {UTKMaterialIcons.Count}개의 아이콘 (가상화 적용)"
|
||||
: $"{_filteredIconNameList.Count}개 / {UTKMaterialIcons.Count}개 아이콘 (가상화 적용)";
|
||||
}
|
||||
|
||||
// Enter 키로 검색
|
||||
searchField.OnSubmit += PerformSearch;
|
||||
|
||||
// 포커스 해제 시 검색
|
||||
searchField.OnBlurred += () => PerformSearch(searchField.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이콘 이름 목록을 행 단위로 그룹화합니다.
|
||||
/// </summary>
|
||||
private List<List<string>> CreateIconRowData(List<string> iconNames)
|
||||
{
|
||||
var rows = new List<List<string>>();
|
||||
for (int i = 0; i < iconNames.Count; i += IconsPerRow)
|
||||
{
|
||||
var row = new List<string>();
|
||||
for (int j = 0; j < IconsPerRow && i + j < iconNames.Count; j++)
|
||||
{
|
||||
row.Add(iconNames[i + j]);
|
||||
}
|
||||
rows.Add(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ListView 행 아이템을 생성합니다.
|
||||
/// </summary>
|
||||
private VisualElement MakeIconRow()
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
row.style.height = IconItemHeight;
|
||||
|
||||
// 행에 아이콘 슬롯 미리 생성
|
||||
for (int i = 0; i < IconsPerRow; i++)
|
||||
{
|
||||
var iconSlot = CreateIconSlot();
|
||||
row.Add(iconSlot);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이콘 슬롯 (재사용 가능한 컨테이너)을 생성합니다.
|
||||
/// </summary>
|
||||
private VisualElement CreateIconSlot()
|
||||
{
|
||||
var item = new VisualElement();
|
||||
item.name = "icon-slot";
|
||||
item.style.width = IconItemWidth;
|
||||
item.style.height = IconItemHeight;
|
||||
item.style.marginRight = IconItemMargin;
|
||||
item.style.alignItems = Align.Center;
|
||||
item.style.justifyContent = Justify.Center;
|
||||
item.style.backgroundColor = new Color(0.15f, 0.15f, 0.15f, 0.5f);
|
||||
item.style.borderTopLeftRadius = 4;
|
||||
item.style.borderTopRightRadius = 4;
|
||||
item.style.borderBottomLeftRadius = 4;
|
||||
item.style.borderBottomRightRadius = 4;
|
||||
|
||||
// 호버 효과
|
||||
item.RegisterCallback<MouseEnterEvent>(_ =>
|
||||
{
|
||||
if (item.style.display != DisplayStyle.None)
|
||||
item.style.backgroundColor = new Color(0.25f, 0.25f, 0.25f, 0.8f);
|
||||
});
|
||||
item.RegisterCallback<MouseLeaveEvent>(_ =>
|
||||
{
|
||||
if (item.style.display != DisplayStyle.None)
|
||||
item.style.backgroundColor = new Color(0.15f, 0.15f, 0.15f, 0.5f);
|
||||
});
|
||||
|
||||
// 아이콘 라벨 (UTKMaterialIcons.ApplyIconStyle 사용)
|
||||
var iconLabel = new Label();
|
||||
iconLabel.name = "icon-label";
|
||||
UTKMaterialIcons.ApplyIconStyle(iconLabel, 28);
|
||||
iconLabel.style.color = Color.white;
|
||||
iconLabel.style.marginBottom = 4;
|
||||
item.Add(iconLabel);
|
||||
|
||||
// 아이콘 이름
|
||||
var nameLabel = new Label();
|
||||
nameLabel.name = "name-label";
|
||||
nameLabel.style.fontSize = 8;
|
||||
nameLabel.style.color = new Color(0.7f, 0.7f, 0.7f);
|
||||
nameLabel.style.whiteSpace = WhiteSpace.NoWrap;
|
||||
nameLabel.style.overflow = Overflow.Hidden;
|
||||
nameLabel.style.textOverflow = TextOverflow.Ellipsis;
|
||||
nameLabel.style.maxWidth = IconItemWidth - 4;
|
||||
nameLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||||
item.Add(nameLabel);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ListView 행에 데이터를 바인딩합니다.
|
||||
/// </summary>
|
||||
private void BindIconRow(VisualElement row, int index)
|
||||
{
|
||||
if (_iconListView?.itemsSource is not List<List<string>> rowData) return;
|
||||
if (index < 0 || index >= rowData.Count) return;
|
||||
|
||||
var iconNames = rowData[index];
|
||||
var slots = row.Query<VisualElement>("icon-slot").ToList();
|
||||
|
||||
for (int i = 0; i < slots.Count; i++)
|
||||
{
|
||||
var slot = slots[i];
|
||||
if (i < iconNames.Count)
|
||||
{
|
||||
var iconName = iconNames[i];
|
||||
var iconChar = UTKMaterialIcons.GetIcon(iconName);
|
||||
|
||||
slot.style.display = DisplayStyle.Flex;
|
||||
|
||||
// 아이콘 라벨 업데이트
|
||||
var iconLabel = slot.Q<Label>("icon-label");
|
||||
if (iconLabel != null)
|
||||
iconLabel.text = iconChar;
|
||||
|
||||
// 이름 라벨 업데이트
|
||||
var nameLabel = slot.Q<Label>("name-label");
|
||||
if (nameLabel != null)
|
||||
nameLabel.text = iconName;
|
||||
|
||||
// 툴팁 갱신
|
||||
slot.tooltip = iconName;
|
||||
|
||||
// 클릭 이벤트 재등록 (userData로 아이콘 이름 저장)
|
||||
slot.userData = iconName;
|
||||
slot.UnregisterCallback<ClickEvent>(OnIconSlotClicked);
|
||||
slot.RegisterCallback<ClickEvent>(OnIconSlotClicked);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 빈 슬롯 숨기기
|
||||
slot.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이콘 슬롯 클릭 이벤트 핸들러
|
||||
/// </summary>
|
||||
private void OnIconSlotClicked(ClickEvent evt)
|
||||
{
|
||||
if (evt.currentTarget is VisualElement slot && slot.userData is string iconName)
|
||||
{
|
||||
var iconChar = UTKMaterialIcons.GetIcon(iconName);
|
||||
GUIUtility.systemCopyBuffer = iconChar;
|
||||
UTKToast.Show($"'{iconName}' 아이콘이 클립보드에 복사되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image Icon Previews
|
||||
|
||||
private List<string>? _imageIconNameList;
|
||||
private List<string>? _filteredImageIconNameList;
|
||||
private ListView? _imageIconListView;
|
||||
private Label? _imageIconCountLabel;
|
||||
|
||||
// 이미지 아이콘 그리드 레이아웃 상수
|
||||
private const int ImageIconItemWidth = 100;
|
||||
private const int ImageIconItemHeight = 100;
|
||||
private const int ImageIconItemMargin = 8;
|
||||
private const int ImageIconsPerRow = 8;
|
||||
|
||||
private void CreateImageIconPreview(VisualElement container)
|
||||
{
|
||||
AddDescription(container, $"이미지 기반 아이콘 (UTKImageIcons 사용, ListView 가상화 적용)");
|
||||
|
||||
// UTKImageIcons에서 모든 아이콘 이름 가져오기
|
||||
_imageIconNameList = UTKImageIcons.GetAllIconNames().ToList();
|
||||
_filteredImageIconNameList = _imageIconNameList;
|
||||
|
||||
// 아이콘 개수 표시
|
||||
_imageIconCountLabel = new Label($"총 {UTKImageIcons.Count}개의 이미지 아이콘 (가상화 적용)");
|
||||
_imageIconCountLabel.style.marginBottom = 10;
|
||||
_imageIconCountLabel.style.color = new Color(0.6f, 0.6f, 0.6f);
|
||||
container.Add(_imageIconCountLabel);
|
||||
|
||||
// 검색 필드
|
||||
var searchRow = CreateRow(container, "Search");
|
||||
var searchField = new UTKInputField("", "Search image icons...");
|
||||
searchField.style.width = 300;
|
||||
searchRow.Add(searchField);
|
||||
|
||||
// 행 데이터 생성 (아이콘 이름을 행 단위로 그룹화)
|
||||
var rowData = CreateImageIconRowData(_filteredImageIconNameList);
|
||||
|
||||
// ListView 생성 (가상화 적용)
|
||||
_imageIconListView = new ListView();
|
||||
_imageIconListView.style.flexGrow = 1;
|
||||
_imageIconListView.style.maxHeight = 500;
|
||||
_imageIconListView.fixedItemHeight = ImageIconItemHeight + ImageIconItemMargin;
|
||||
_imageIconListView.itemsSource = rowData;
|
||||
_imageIconListView.makeItem = MakeImageIconRow;
|
||||
_imageIconListView.bindItem = BindImageIconRow;
|
||||
_imageIconListView.selectionType = SelectionType.None;
|
||||
_imageIconListView.virtualizationMethod = CollectionVirtualizationMethod.FixedHeight;
|
||||
_imageIconListView.AddToClassList("utk-image-icon-listview");
|
||||
container.Add(_imageIconListView);
|
||||
|
||||
// 검색 기능
|
||||
void PerformSearch(string searchValue)
|
||||
{
|
||||
if (_imageIconNameList == null || _imageIconListView == null || _imageIconCountLabel == null) return;
|
||||
|
||||
_filteredImageIconNameList = string.IsNullOrEmpty(searchValue)
|
||||
? _imageIconNameList
|
||||
: _imageIconNameList.FindAll(name => name.Contains(searchValue, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var newRowData = CreateImageIconRowData(_filteredImageIconNameList);
|
||||
_imageIconListView.itemsSource = newRowData;
|
||||
_imageIconListView.Rebuild();
|
||||
|
||||
_imageIconCountLabel.text = string.IsNullOrEmpty(searchValue)
|
||||
? $"총 {UTKImageIcons.Count}개의 이미지 아이콘 (가상화 적용)"
|
||||
: $"{_filteredImageIconNameList.Count}개 / {UTKImageIcons.Count}개 이미지 아이콘 (가상화 적용)";
|
||||
}
|
||||
|
||||
searchField.OnSubmit += PerformSearch;
|
||||
searchField.OnBlurred += () => PerformSearch(searchField.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 아이콘 이름 목록을 행 단위로 그룹화합니다.
|
||||
/// </summary>
|
||||
private List<List<string>> CreateImageIconRowData(List<string> iconNames)
|
||||
{
|
||||
var rows = new List<List<string>>();
|
||||
for (int i = 0; i < iconNames.Count; i += ImageIconsPerRow)
|
||||
{
|
||||
var row = new List<string>();
|
||||
for (int j = 0; j < ImageIconsPerRow && i + j < iconNames.Count; j++)
|
||||
{
|
||||
row.Add(iconNames[i + j]);
|
||||
}
|
||||
rows.Add(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 아이콘 ListView 행 아이템을 생성합니다.
|
||||
/// </summary>
|
||||
private VisualElement MakeImageIconRow()
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
row.style.height = ImageIconItemHeight;
|
||||
|
||||
for (int i = 0; i < ImageIconsPerRow; i++)
|
||||
{
|
||||
var iconSlot = CreateImageIconSlot();
|
||||
row.Add(iconSlot);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 아이콘 슬롯을 생성합니다.
|
||||
/// </summary>
|
||||
private VisualElement CreateImageIconSlot()
|
||||
{
|
||||
var item = new VisualElement();
|
||||
item.name = "image-icon-slot";
|
||||
item.style.width = ImageIconItemWidth;
|
||||
item.style.height = ImageIconItemHeight;
|
||||
item.style.marginRight = ImageIconItemMargin;
|
||||
item.style.alignItems = Align.Center;
|
||||
item.style.justifyContent = Justify.Center;
|
||||
item.style.backgroundColor = new Color(0.15f, 0.15f, 0.15f, 0.5f);
|
||||
item.style.borderTopLeftRadius = 4;
|
||||
item.style.borderTopRightRadius = 4;
|
||||
item.style.borderBottomLeftRadius = 4;
|
||||
item.style.borderBottomRightRadius = 4;
|
||||
|
||||
// 호버 효과
|
||||
item.RegisterCallback<MouseEnterEvent>(_ =>
|
||||
{
|
||||
if (item.style.display != DisplayStyle.None)
|
||||
item.style.backgroundColor = new Color(0.25f, 0.25f, 0.25f, 0.8f);
|
||||
});
|
||||
item.RegisterCallback<MouseLeaveEvent>(_ =>
|
||||
{
|
||||
if (item.style.display != DisplayStyle.None)
|
||||
item.style.backgroundColor = new Color(0.15f, 0.15f, 0.15f, 0.5f);
|
||||
});
|
||||
|
||||
// 이미지 아이콘
|
||||
var iconImage = new VisualElement();
|
||||
iconImage.name = "icon-image";
|
||||
iconImage.style.width = 32;
|
||||
iconImage.style.height = 32;
|
||||
iconImage.style.marginBottom = 4;
|
||||
item.Add(iconImage);
|
||||
|
||||
// 아이콘 이름
|
||||
var nameLabel = new Label();
|
||||
nameLabel.name = "name-label";
|
||||
nameLabel.style.fontSize = 8;
|
||||
nameLabel.style.color = new Color(0.7f, 0.7f, 0.7f);
|
||||
nameLabel.style.whiteSpace = WhiteSpace.NoWrap;
|
||||
nameLabel.style.overflow = Overflow.Hidden;
|
||||
nameLabel.style.textOverflow = TextOverflow.Ellipsis;
|
||||
nameLabel.style.maxWidth = ImageIconItemWidth - 4;
|
||||
nameLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||||
item.Add(nameLabel);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 아이콘 ListView 행에 데이터를 바인딩합니다.
|
||||
/// </summary>
|
||||
private void BindImageIconRow(VisualElement row, int index)
|
||||
{
|
||||
if (_imageIconListView?.itemsSource is not List<List<string>> rowData) return;
|
||||
if (index < 0 || index >= rowData.Count) return;
|
||||
|
||||
var iconNames = rowData[index];
|
||||
var slots = row.Query<VisualElement>("image-icon-slot").ToList();
|
||||
|
||||
for (int i = 0; i < slots.Count; i++)
|
||||
{
|
||||
var slot = slots[i];
|
||||
if (i < iconNames.Count)
|
||||
{
|
||||
var iconName = iconNames[i];
|
||||
var iconPath = UTKImageIcons.GetPath(iconName);
|
||||
|
||||
slot.style.display = DisplayStyle.Flex;
|
||||
|
||||
// 이미지 아이콘 로드 및 표시
|
||||
var iconImage = slot.Q<VisualElement>("icon-image");
|
||||
if (iconImage != null)
|
||||
{
|
||||
var texture = UTKImageIcons.LoadTextureByName(iconName);
|
||||
if (texture != null)
|
||||
{
|
||||
iconImage.style.backgroundImage = new StyleBackground(texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
iconImage.style.backgroundImage = StyleKeyword.None;
|
||||
}
|
||||
}
|
||||
|
||||
// 이름 라벨 업데이트
|
||||
var nameLabel = slot.Q<Label>("name-label");
|
||||
if (nameLabel != null)
|
||||
nameLabel.text = iconName;
|
||||
|
||||
// 툴팁 갱신
|
||||
slot.tooltip = $"{iconName}\n{iconPath}";
|
||||
|
||||
// 클릭 이벤트 재등록
|
||||
slot.userData = iconName;
|
||||
slot.UnregisterCallback<ClickEvent>(OnImageIconSlotClicked);
|
||||
slot.RegisterCallback<ClickEvent>(OnImageIconSlotClicked);
|
||||
}
|
||||
else
|
||||
{
|
||||
slot.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 아이콘 슬롯 클릭 이벤트 핸들러
|
||||
/// </summary>
|
||||
private void OnImageIconSlotClicked(ClickEvent evt)
|
||||
{
|
||||
if (evt.currentTarget is VisualElement slot && slot.userData is string iconName)
|
||||
{
|
||||
var iconCode = $"UTKImageIcons.LoadSpriteByName(\"{iconName}\")";
|
||||
GUIUtility.systemCopyBuffer = iconCode;
|
||||
UTKToast.Show($"'{iconName}' 코드가 클립보드에 복사되었습니다.\n{iconCode}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private void AddDescription(VisualElement container, string text)
|
||||
|
||||
@@ -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="" />
|
||||
///
|
||||
/// <!-- 버튼 변형 -->
|
||||
/// <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="" 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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
359
Assets/Scripts/UVC/UIToolkit/Common/UTKImageIcons.cs
Normal file
359
Assets/Scripts/UVC/UIToolkit/Common/UTKImageIcons.cs
Normal 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
|
||||
}
|
||||
}
|
||||
12524
Assets/Scripts/UVC/UIToolkit/Common/UTKMaterialIcons.cs
Normal file
12524
Assets/Scripts/UVC/UIToolkit/Common/UTKMaterialIcons.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
27
CLAUDE.md
27
CLAUDE.md
@@ -233,7 +233,24 @@ UI Toolkit 개발 시 다음 위치의 스타일 리소스를 참조하세요:
|
||||
|
||||
---
|
||||
|
||||
## 8) 주석 원칙 (C# XML)
|
||||
## 8) 아이콘 사용 가이드 (Icons)
|
||||
|
||||
### 아이콘 사용 우선순위
|
||||
UI 아이콘 적용 시 다음 순서를 반드시 준수해야 합니다.
|
||||
|
||||
1. **1순위 (Material Icons)**: `UTKMaterialIcons` 클래스 확인
|
||||
- 폰트 기반 아이콘(Unicode)을 우선 사용합니다.
|
||||
- 예: `UTKButton.SetMaterialIcon(UTKMaterialIcons.Home)`
|
||||
|
||||
2. **2순위 (Image Icons)**: `UTKImageIcons` 클래스 사용
|
||||
- 필요한 아이콘이 `UTKMaterialIcons`에 없는 경우에만 `UTKImageIcons`를 사용합니다.
|
||||
- 예: `UTKButton.SetImageIcon(UTKImageIcons.CustomIcon)`
|
||||
|
||||
> **권장**: 일관된 UI 스타일과 메모리 효율을 위해 가능한 Material Icons 사용을 권장합니다.
|
||||
|
||||
---
|
||||
|
||||
## 9) 주석 원칙 (C# XML)
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
@@ -251,7 +268,7 @@ public async UniTask<UserData?> LoadUserAsync(string userId, CancellationToken c
|
||||
|
||||
---
|
||||
|
||||
## 9) 디자인 패턴 요약
|
||||
## 10) 디자인 패턴 요약
|
||||
|
||||
| 패턴 | 사용 시점 |
|
||||
|------|-----------|
|
||||
@@ -266,7 +283,7 @@ public async UniTask<UserData?> LoadUserAsync(string userId, CancellationToken c
|
||||
|
||||
---
|
||||
|
||||
## 10) View 기본 패턴
|
||||
## 11) View 기본 패턴
|
||||
|
||||
```csharp
|
||||
#nullable enable
|
||||
@@ -307,7 +324,7 @@ public abstract class UIViewBase : IDisposable
|
||||
|
||||
---
|
||||
|
||||
## 11) Unity Nullable 주의
|
||||
## 12) Unity Nullable 주의
|
||||
|
||||
```csharp
|
||||
// Unity Object는 == null 오버로드됨
|
||||
@@ -321,7 +338,7 @@ if (ReferenceEquals(obj, null)) { }
|
||||
|
||||
---
|
||||
|
||||
## 12) 품질 자동화
|
||||
## 13) 품질 자동화
|
||||
|
||||
### .editorconfig 권장
|
||||
```ini
|
||||
|
||||
Reference in New Issue
Block a user