StyleGuide Sample 완료

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

View File

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

View File

@@ -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)