Files
EnglewoodLAB/Assets/Sample/UIToolkit/UTKStyleGuideSample.Icon.cs
2026-03-10 11:35:30 +09:00

666 lines
24 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
/// <summary>
/// UTKStyleGuideSample의 Icon 카테고리 Initialize 메서드들
/// </summary>
public partial class UTKStyleGuideSample
{
#region Icon Initializers
private void InitializeUTKMaterialIconsSample(VisualElement root)
{
var font = UTKMaterialIcons.LoadFont();
if (font == null)
{
root.Add(new Label("Error: UTKMaterialIcons 폰트를 로드할 수 없습니다."));
return;
}
// Icon 코드 블록 참조 저장
_materialIconCodeBlock = root.Q<UTKCodeBlock>();
if (_materialIconCodeBlock != null && _materialIconCodeBlock.Title == "Icon")
{
UpdateMaterialIconCodeBlock("Home");
}
_iconNameList = UTKMaterialIcons.GetAllIconNames().ToList();
_filteredIconNameList = _iconNameList;
_iconCountLabel = root.Q<Label>("icon-count-label");
if (_iconCountLabel != null)
{
_iconCountLabel.text = $"총 {UTKMaterialIcons.Count}개의 아이콘 (가상화 적용)";
}
var searchField = root.Q<UTKInputField>("icon-search");
var gridContainer = root.Q<VisualElement>("icon-grid-container");
if (gridContainer != null)
{
var rowData = CreateIconRowData(_filteredIconNameList);
_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;
gridContainer.Add(_iconListView);
}
if (searchField != null)
{
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}개 아이콘 (가상화 적용)";
}
searchField.OnSubmit += PerformSearch;
searchField.OnBlurred += () => PerformSearch(searchField.Value);
}
SetCodeSamples(root,
csharpCode: @"// 폰트 로드
Font font = UTKMaterialIcons.LoadFont();
// Label에 아이콘 적용
var label = new Label(UTKMaterialIcons.Home);
UTKMaterialIcons.ApplyIconStyle(label);
// 또는 직접 스타일 정의 사용
label.style.unityFontDefinition = UTKMaterialIcons.GetFontDefinition();
// 이름으로 아이콘 조회
string icon = UTKMaterialIcons.GetIcon(""settings"");
// 아이콘 존재 여부 확인
if (UTKMaterialIcons.HasIcon(""search"")) { }
// 전체 아이콘 이름 순회
foreach (var name in UTKMaterialIcons.GetAllIconNames())
{
Debug.Log(name);
}
// 아이콘 총 개수
int count = UTKMaterialIcons.Count;
// 비동기 폰트 로드
Font? fontAsync = await UTKMaterialIcons.LoadFontAsync(cancellationToken);
// 비동기로 아이콘 스타일 적용
await UTKMaterialIcons.ApplyIconStyleAsync(label, cancellationToken);",
uxmlCode: @"<!-- USS 파일에서 폰트 설정 -->
.material-icon {
-unity-font-definition: resource('Fonts/Icons/MaterialSymbolsOutlined');
font-size: 24px;
-unity-text-align: middle-center;
}
<!-- UXML 파일에서 사용 -->
<ui:Label class=""material-icon"" text="""" /> <!-- home 아이콘 -->
<ui:Label class=""material-icon"" text="""" /> <!-- settings 아이콘 -->
<!-- C# 코드에서 UXML Label에 아이콘 적용 -->
<!-- var iconLabel = root.Q<Label>(""my-icon""); -->
<!-- iconLabel.text = UTKMaterialIcons.Settings; -->
<!-- UTKMaterialIcons.ApplyIconStyle(iconLabel); -->");
}
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;
}
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;
}
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.borderTopLeftRadius = 4;
item.style.borderTopRightRadius = 4;
item.style.borderBottomLeftRadius = 4;
item.style.borderBottomRightRadius = 4;
item.AddToClassList("utk-icon-slot");
var iconLabel = new Label();
iconLabel.name = "icon-label";
UTKMaterialIcons.ApplyIconStyle(iconLabel, 28);
iconLabel.AddToClassList("utk-icon-slot__icon");
iconLabel.style.marginBottom = 4;
item.Add(iconLabel);
var nameLabel = new Label();
nameLabel.name = "name-label";
nameLabel.style.fontSize = 8;
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;
nameLabel.AddToClassList("utk-icon-slot__name");
item.Add(nameLabel);
return item;
}
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;
slot.userData = iconName;
slot.UnregisterCallback<ClickEvent>(OnIconSlotClicked);
slot.RegisterCallback<ClickEvent>(OnIconSlotClicked);
}
else
{
slot.style.display = DisplayStyle.None;
}
}
}
private void OnIconSlotClicked(ClickEvent evt)
{
if (evt.currentTarget is VisualElement slot && slot.userData is string iconName)
{
UpdateMaterialIconCodeBlock(iconName);
var csharpCode = $"UTKMaterialIcons.{iconName}";
GUIUtility.systemCopyBuffer = csharpCode;
UTKToast.Show($"복사됨: {csharpCode}");
}
}
private void UpdateMaterialIconCodeBlock(string iconName)
{
if (_materialIconCodeBlock == null) return;
var iconChar = UTKMaterialIcons.GetIcon(iconName);
if (string.IsNullOrEmpty(iconChar))
{
_materialIconCodeBlock.Code = $"// 아이콘을 찾을 수 없습니다: {iconName}";
return;
}
var iconDisplayName = UTKMaterialIcons.GetIconNameByChar(iconChar);
//첫글자만 대문자로 바꾸기
var iconDisplayNameCapitalized = char.ToUpper(iconDisplayName[0]) + iconDisplayName.Substring(1);
var unicodeHex = $"\\u{((int)iconChar[0]):x4}";
var code = $"// C# 코드\n" +
$"// Material Icon과 텍스트 함께 사용\n" +
$"var iconLabel = new UTKLabel(\"\", UTKMaterialIcons.{iconDisplayNameCapitalized});\n" +
$"\n" +
$"// Material Icon만 사용\n" +
$"var iconOnly = new UTKLabel(UTKMaterialIcons.{iconDisplayNameCapitalized}, 12);\n" +
$"<utk:UTKLabel material-icon=\"{iconDisplayName}\" icon-size=\"12\" />\n" +
$"var iconLabel = new UTKLabel(\"\", UTKMaterialIcons.{iconDisplayNameCapitalized});\n" +
$"<utk:UTKLabel text=\"\" material-icon=\"{iconDisplayName}\" />\n" +
$"\n" +
$"var circleBtn1 = new UTKButton(\"\", UTKMaterialIcons.{iconDisplayNameCapitalized}, ButtonVariant.Text, 12) {{ IconOnly = true }};\n" +
$"<utk:UTKButton icon=\"{iconDisplayName}\" icon-only=\"true\" icon-size=\"12\" variant=\"Text\" />\n" +
$"\n" +
$"var label = new Label(UTKMaterialIcons.{iconDisplayNameCapitalized});\n" +
$"UTKMaterialIcons.ApplyIconStyle(label);\n" +
$"\n" +
$"/* 유니코드: {unicodeHex} */";
_materialIconCodeBlock.Code = code;
}
private void InitializeImageIconsSample(VisualElement root)
{
// Icon 코드 블록 참조 저장
var codeBlocks = root.Query<UTKCodeBlock>().ToList();
_imageIconCodeBlock = codeBlocks.FirstOrDefault(cb => cb.Title == "Icon");
if (_imageIconCodeBlock != null && _imageIconNameList != null && _imageIconNameList.Count > 0)
{
UpdateImageIconCodeBlock(_imageIconNameList[0]);
}
_imageIconNameList = UTKImageIcons.GetAllIconNames().ToList();
_filteredImageIconNameList = _imageIconNameList;
_imageIconCountLabel = root.Q<Label>("image-icon-count-label");
if (_imageIconCountLabel != null)
{
_imageIconCountLabel.text = $"총 {UTKImageIcons.Count}개의 이미지 아이콘 (가상화 적용)";
}
var searchField = root.Q<UTKInputField>("image-icon-search");
var gridContainer = root.Q<VisualElement>("image-icon-grid-container");
if (gridContainer != null)
{
var rowData = CreateImageIconRowData(_filteredImageIconNameList);
_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;
gridContainer.Add(_imageIconListView);
}
if (searchField != null)
{
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);
}
SetCodeSamples(root,
csharpCode: @"// 상수로 리소스 경로 사용
string path = UTKImageIcons.BtnClose16;
// 동기 Sprite 로드 (캐싱됨)
Sprite sprite = UTKImageIcons.LoadSprite(UTKImageIcons.BtnClose16);
// 비동기 Sprite 로드 (캐싱됨)
Sprite? spriteAsync = await UTKImageIcons.LoadSpriteAsync(UTKImageIcons.IconSetting22, cancellationToken);
// 동기 Texture2D 로드 (캐싱됨)
Texture2D texture = UTKImageIcons.LoadTexture(UTKImageIcons.IconSetting22);
// 비동기 Texture2D 로드 (캐싱됨)
Texture2D? textureAsync = await UTKImageIcons.LoadTextureAsync(UTKImageIcons.IconSetting22, cancellationToken);
// 이름으로 Sprite 로드
Sprite icon = UTKImageIcons.LoadSpriteByName(""btn_close_16"");
// 이름으로 비동기 Sprite 로드
Sprite? iconAsync = await UTKImageIcons.LoadSpriteByNameAsync(""btn_close_16"", cancellationToken);
// 이름으로 경로 조회
string iconPath = UTKImageIcons.GetPath(""icon_setting_22"");
// 아이콘 존재 여부 확인
if (UTKImageIcons.HasIcon(""btn_close_16"")) { }
// 전체 아이콘 이름 순회
foreach (var name in UTKImageIcons.GetAllIconNames())
{
Debug.Log(name);
}
// 캐시 클리어
UTKImageIcons.ClearCache();",
uxmlCode: @"<!-- USS 파일에서 이미지 아이콘 설정 -->
.my-icon {
width: 24px;
height: 24px;
background-image: resource('UIToolkit/Images/icon_setting_22');
}
<!-- UXML 파일에서 사용 -->
<VisualElement class=""my-icon"" />
<!-- C# 코드에서 UXML 요소에 이미지 적용 -->
<!-- var iconElement = root.Q<VisualElement>(""my-icon""); -->
<!-- var texture = UTKImageIcons.LoadTextureByName(""icon_setting_22""); -->
<!-- iconElement.style.backgroundImage = new StyleBackground(texture); -->
<!-- Image 요소에서 사용 -->
<!-- var image = root.Q<Image>(""my-image""); -->
<!-- image.sprite = UTKImageIcons.LoadSpriteByName(""btn_close_16""); -->");
}
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;
}
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;
}
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.borderTopLeftRadius = 4;
item.style.borderTopRightRadius = 4;
item.style.borderBottomLeftRadius = 4;
item.style.borderBottomRightRadius = 4;
item.AddToClassList("utk-icon-slot");
var iconImage = new VisualElement();
iconImage.name = "icon-image";
iconImage.style.width = 32;
iconImage.style.height = 32;
iconImage.style.marginBottom = 4;
iconImage.AddToClassList("utk-icon-slot__image");
item.Add(iconImage);
var nameLabel = new Label();
nameLabel.name = "name-label";
nameLabel.style.fontSize = 8;
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;
nameLabel.AddToClassList("utk-icon-slot__name");
item.Add(nameLabel);
return item;
}
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;
}
}
}
private void OnImageIconSlotClicked(ClickEvent evt)
{
if (evt.currentTarget is VisualElement slot && slot.userData is string iconName)
{
UpdateImageIconCodeBlock(iconName);
var csharpCode = $"UTKImageIcons.{iconName}";
GUIUtility.systemCopyBuffer = csharpCode;
UTKToast.Show($"복사됨: {csharpCode}");
}
}
private void UpdateImageIconCodeBlock(string iconName)
{
if (_imageIconCodeBlock == null) return;
var iconPath = UTKImageIcons.GetPath(iconName);
if (string.IsNullOrEmpty(iconPath))
{
_imageIconCodeBlock.Code = $"// 아이콘을 찾을 수 없습니다: {iconName}";
return;
}
//PascalCase을 camelCase로 변환 btn_cancel_64 -> BtnCancel64
var iconDisplayNameCapitalized = "";
var words = iconName.Split('_');
for (int i = 0; i < words.Length; i++)
{
words[i] = char.ToUpper(words[i][0]) + words[i].Substring(1);
}
iconDisplayNameCapitalized = string.Join("", words);
var code = $"// C# 코드\n" +
$"// Sprite 로드\n" +
$"Sprite sprite = UTKImageIcons.{iconDisplayNameCapitalized};\n" +
$"var image = new Image {{ sprite = sprite }};\n" +
$"\n" +
$"// 또는 이름으로 로드\n" +
$"Sprite sprite2 = UTKImageIcons.LoadSpriteByName(\"{iconName}\");\n" +
$"\n" +
$"// Texture2D 로드\n" +
$"Texture2D texture = UTKImageIcons.LoadTextureByName(\"{iconName}\");\n" +
$"var image2 = new Image();\n" +
$"image2.style.backgroundImage = new StyleBackground(texture);\n" +
$"\n" +
$"// Image Icon만 사용\n" +
$"var imgOnly = new UTKLabel(UTKImageIcons.{iconDisplayNameCapitalized}, isImageIcon: true);\n" +
$"<utk:UTKLabel text=\"\" image-icon=\"{iconName}\" />\n" +
$"<utk:UTKLabel image-icon=\"{iconName}\" icon-size=\"22\" />\n" +
$"\n" +
$"var circleBtn1 = new UTKButton(\"\", UTKImageIcons.{iconDisplayNameCapitalized}, ButtonVariant.Text, 12) {{ IconOnly = true }};\n" +
$"<utk:UTKButton icon=\"{iconName}\" icon-only=\"true\" icon-size=\"12\" variant=\"Text\" />\n" +
$"\n" +
$"/* 경로: {iconPath} */\n" +
$"\n" +
$"/* UXML에서 사용 */\n" +
$"<utk:UTKLabel imageIcon=\"{iconPath}\" iconSize=\"22\" />";
_imageIconCodeBlock.Code = code;
}
private void InitializeUTKImageSample(VisualElement root)
{
// 외부 이미지 로드 버튼
var btnLoadExternal = root.Q<UTKButton>("btn-load-external");
var imgExternal = root.Q<UTKImage>("img-external");
var imgExternalStatus = root.Q<Label>("img-external-status");
if (btnLoadExternal != null && imgExternal != null)
{
btnLoadExternal.RegisterCallback<ClickEvent>(_ =>
{
if (imgExternalStatus != null)
imgExternalStatus.text = "로드 중...";
// 샘플 외부 이미지 URL (placeholder 이미지)
const string sampleUrl = "https://picsum.photos/200";
imgExternal.OnImageLoaded += (texture) =>
{
if (imgExternalStatus != null)
imgExternalStatus.text = $"로드 완료 ({texture.width}x{texture.height})";
};
imgExternal.OnImageFailed += (error) =>
{
if (imgExternalStatus != null)
imgExternalStatus.text = $"로드 실패: {error}";
};
imgExternal.Src = sampleUrl;
});
}
SetCodeSamples(root,
csharpCode: @"// 내부 리소스 이미지 로드
var image = new UTKImage();
image.Src = ""UIToolkit/Images/icon_setting_22"";
image.style.width = 100;
image.style.height = 100;
// 외부 URL 이미지 로드
var webImage = new UTKImage();
webImage.Src = ""https://picsum.photos/200"";
webImage.ScaleMode = ScaleMode.ScaleToFit;
// 생성자에서 소스 지정
var profileImage = new UTKImage(""https://api.example.com/avatar/123"");
profileImage.style.width = 64;
profileImage.style.height = 64;
profileImage.style.borderTopLeftRadius = 32;
profileImage.style.borderTopRightRadius = 32;
profileImage.style.borderBottomLeftRadius = 32;
profileImage.style.borderBottomRightRadius = 32;
// 이미지 로드 이벤트 핸들링
image.OnImageLoaded += (texture) => Debug.Log(""이미지 로드 완료"");
image.OnImageFailed += (error) => Debug.LogError($""로드 실패: {error}"");
// 틴트 색상 적용
image.TintColor = Color.red;
// 비동기 로드
await image.LoadAsync(""https://example.com/image.png"", cancellationToken);",
uxmlCode: @"<!-- 네임스페이스 선언 -->
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
<!-- 내부 리소스 이미지 -->
<utk:UTKImage src=""UIToolkit/Images/icon_setting_22""
style=""width: 48px; height: 48px;"" />
<!-- 외부 URL 이미지 -->
<utk:UTKImage src=""https://picsum.photos/200""
style=""width: 200px; height: 150px;""
scale-mode=""ScaleToFit"" />
<!-- 둥근 프로필 이미지 -->
<utk:UTKImage src=""https://api.example.com/avatar""
class=""profile-avatar"" />
<!-- 틴트 색상 적용 -->
<utk:UTKImage src=""UIToolkit/Images/icon_home""
tint-color=""#FF5500"" />
</ui:UXML>");
}
#endregion
}