기본 Styleguide 완료
This commit is contained in:
323
Assets/Scripts/UVC/UIToolkit/Common/UTKCodeBlock.cs
Normal file
323
Assets/Scripts/UVC/UIToolkit/Common/UTKCodeBlock.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
#nullable enable
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 코드 샘플을 표시하는 컴포넌트.
|
||||
/// 텍스트 선택 및 복사가 가능하며, 복사 버튼을 제공합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>CodeBlock(코드 블록)이란?</b></para>
|
||||
/// <para>
|
||||
/// CodeBlock은 코드 샘플을 읽기 쉽게 표시하는 컴포넌트입니다.
|
||||
/// 고정폭 폰트, 배경색, 복사 버튼 등으로 코드를 강조하여 표시합니다.
|
||||
/// 문서, 튜토리얼, 샘플 앱 등에서 코드 예제를 보여줄 때 사용합니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>주요 기능:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>고정폭 폰트로 코드 가독성 향상</description></item>
|
||||
/// <item><description>텍스트 선택 및 복사 가능</description></item>
|
||||
/// <item><description>복사 버튼으로 원클릭 복사</description></item>
|
||||
/// <item><description>제목 표시 (예: "C#", "UXML", "JSON")</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>UXML에서 코드 설정 방법:</b></para>
|
||||
/// <list type="number">
|
||||
/// <item><description>code 속성 사용: <c>code="var x = 1;\nvar y = 2;"</c> (줄바꿈은 \n)</description></item>
|
||||
/// <item><description>텍스트 콘텐츠 사용: 태그 사이에 직접 코드 작성 (줄바꿈 유지)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>API 문서 - 함수 사용법 예제</description></item>
|
||||
/// <item><description>튜토리얼 - 단계별 코드 설명</description></item>
|
||||
/// <item><description>StyleGuide 샘플 - C#/UXML 코드 예시</description></item>
|
||||
/// <item><description>오류 로그 - 스택 트레이스 표시</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para><b>UXML에서 사용 (code 속성):</b></para>
|
||||
/// <code>
|
||||
/// <utk:UTKCodeBlock title="C#" code="var x = 1; var y = 2;" />
|
||||
/// </code>
|
||||
/// <para><b>UXML에서 사용 (텍스트 콘텐츠):</b></para>
|
||||
/// <code>
|
||||
/// <utk:UTKCodeBlock title="C#">
|
||||
/// var x = 1;
|
||||
/// var y = 2;
|
||||
/// </utk:UTKCodeBlock>
|
||||
/// </code>
|
||||
/// <para><b>C# 코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// var codeBlock = new UTKCodeBlock
|
||||
/// {
|
||||
/// Title = "C#",
|
||||
/// Code = "var player = new Player();\nplayer.Move(10, 0);"
|
||||
/// };
|
||||
///
|
||||
/// // 여러 코드 블록 컨테이너 생성
|
||||
/// var container = UTKCodeBlock.CreateCodeSampleContainer(
|
||||
/// csharpCode: "var btn = new UTKButton();",
|
||||
/// uxmlCode: "<utk:UTKButton text=\"Click\" />"
|
||||
/// );
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlElement]
|
||||
public partial class UTKCodeBlock : VisualElement
|
||||
{
|
||||
private static class UssClasses
|
||||
{
|
||||
public const string Root = "utk-code-block";
|
||||
public const string Header = "utk-code-block__header";
|
||||
public const string Title = "utk-code-block__title";
|
||||
public const string CopyButton = "utk-code-block__copy-btn";
|
||||
public const string Content = "utk-code-block__content";
|
||||
public const string Code = "utk-code-block__code";
|
||||
}
|
||||
|
||||
private Label? _titleLabel;
|
||||
private UTKButton? _copyButton;
|
||||
private TextField? _codeField;
|
||||
private string _title = "";
|
||||
private string _code = "";
|
||||
private bool _codeSetFromAttribute;
|
||||
|
||||
/// <summary>
|
||||
/// 코드 블록 제목 (예: "C# Code", "UXML")
|
||||
/// </summary>
|
||||
[UxmlAttribute("title")]
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set
|
||||
{
|
||||
_title = value;
|
||||
if (_titleLabel != null)
|
||||
{
|
||||
_titleLabel.text = value;
|
||||
_titleLabel.style.display = string.IsNullOrEmpty(value) ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 표시할 코드
|
||||
/// UXML에서 줄바꿈은 \n 문자열로 표현하거나, 텍스트 콘텐츠로 직접 입력할 수 있습니다.
|
||||
/// </summary>
|
||||
[UxmlAttribute("code")]
|
||||
public string Code
|
||||
{
|
||||
get => _code;
|
||||
set
|
||||
{
|
||||
// UXML에서 "\n" 문자열 리터럴이 들어올 수 있으므로 실제 줄바꿈으로 변환
|
||||
_code = (value ?? "").Replace("\\n", "\n");
|
||||
_codeSetFromAttribute = !string.IsNullOrEmpty(_code);
|
||||
if (_codeField != null)
|
||||
{
|
||||
_codeField.value = _code;
|
||||
UpdateCodeFieldHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트 콘텐츠로 코드를 설정합니다.
|
||||
/// code 속성이 설정되지 않은 경우에만 적용됩니다.
|
||||
/// </summary>
|
||||
public void SetCodeFromContent(string content)
|
||||
{
|
||||
if (_codeSetFromAttribute) return;
|
||||
|
||||
// 앞뒤 빈 줄 제거하고 공통 들여쓰기 제거
|
||||
_code = TrimAndNormalizeIndent(content);
|
||||
if (_codeField != null)
|
||||
{
|
||||
_codeField.value = _code;
|
||||
UpdateCodeFieldHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트의 앞뒤 빈 줄을 제거하고 공통 들여쓰기를 정규화합니다.
|
||||
/// </summary>
|
||||
private static string TrimAndNormalizeIndent(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return "";
|
||||
|
||||
var lines = text.Split('\n');
|
||||
|
||||
// 앞뒤 빈 줄 제거
|
||||
int startIndex = 0;
|
||||
int endIndex = lines.Length - 1;
|
||||
|
||||
while (startIndex <= endIndex && string.IsNullOrWhiteSpace(lines[startIndex]))
|
||||
startIndex++;
|
||||
|
||||
while (endIndex >= startIndex && string.IsNullOrWhiteSpace(lines[endIndex]))
|
||||
endIndex--;
|
||||
|
||||
if (startIndex > endIndex) return "";
|
||||
|
||||
// 공통 들여쓰기 찾기
|
||||
int minIndent = int.MaxValue;
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
int indent = 0;
|
||||
foreach (char c in line)
|
||||
{
|
||||
if (c == ' ') indent++;
|
||||
else if (c == '\t') indent += 4;
|
||||
else break;
|
||||
}
|
||||
minIndent = Mathf.Min(minIndent, indent);
|
||||
}
|
||||
|
||||
if (minIndent == int.MaxValue) minIndent = 0;
|
||||
|
||||
// 공통 들여쓰기 제거하고 결과 생성
|
||||
var result = new System.Text.StringBuilder();
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
result.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
int removed = 0;
|
||||
int charIndex = 0;
|
||||
while (removed < minIndent && charIndex < line.Length)
|
||||
{
|
||||
if (line[charIndex] == ' ') { removed++; charIndex++; }
|
||||
else if (line[charIndex] == '\t') { removed += 4; charIndex++; }
|
||||
else break;
|
||||
}
|
||||
result.AppendLine(line[charIndex..]);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString().TrimEnd('\r', '\n');
|
||||
}
|
||||
|
||||
private void UpdateCodeFieldHeight()
|
||||
{
|
||||
if (_codeField == null) return;
|
||||
|
||||
// 줄 수에 따라 높이 조정
|
||||
var lineCount = string.IsNullOrEmpty(_code) ? 1 : _code.Split('\n').Length;
|
||||
var height = Mathf.Max(40, lineCount * 18 + 24);
|
||||
_codeField.style.height = height;
|
||||
}
|
||||
|
||||
public UTKCodeBlock()
|
||||
{
|
||||
AddToClassList(UssClasses.Root);
|
||||
|
||||
// Header
|
||||
var header = new VisualElement();
|
||||
header.AddToClassList(UssClasses.Header);
|
||||
Add(header);
|
||||
|
||||
_titleLabel = new Label();
|
||||
_titleLabel.AddToClassList(UssClasses.Title);
|
||||
header.Add(_titleLabel);
|
||||
|
||||
_copyButton = new UTKButton("Copy", UTKMaterialIcons.ContentCopy, UTKButton.ButtonVariant.Text, 12);
|
||||
_copyButton.AddToClassList(UssClasses.CopyButton);
|
||||
_copyButton.OnClicked += OnCopyClicked;
|
||||
header.Add(_copyButton);
|
||||
|
||||
// Content
|
||||
var content = new VisualElement();
|
||||
content.AddToClassList(UssClasses.Content);
|
||||
Add(content);
|
||||
|
||||
// TextField를 사용하여 텍스트 선택 가능하게
|
||||
_codeField = new TextField();
|
||||
_codeField.AddToClassList(UssClasses.Code);
|
||||
_codeField.multiline = true;
|
||||
_codeField.isReadOnly = true;
|
||||
content.Add(_codeField);
|
||||
|
||||
// 초기값 적용 (UXML에서 속성이 먼저 설정된 경우)
|
||||
if (!string.IsNullOrEmpty(_title))
|
||||
{
|
||||
_titleLabel.text = _title;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_code))
|
||||
{
|
||||
_codeField.value = _code;
|
||||
UpdateCodeFieldHeight();
|
||||
}
|
||||
|
||||
// UXML 텍스트 콘텐츠 처리를 위한 콜백
|
||||
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
}
|
||||
|
||||
private void OnAttachToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
// UXML에서 텍스트 콘텐츠가 있으면 처리
|
||||
// Unity는 텍스트 콘텐츠를 Label 자식으로 추가하므로 확인
|
||||
schedule.Execute(() =>
|
||||
{
|
||||
if (_codeSetFromAttribute) return;
|
||||
|
||||
// 자식 Label에서 텍스트 추출 시도
|
||||
var textLabel = this.Q<Label>(className: null);
|
||||
if (textLabel != null && textLabel != _titleLabel && !string.IsNullOrEmpty(textLabel.text))
|
||||
{
|
||||
SetCodeFromContent(textLabel.text);
|
||||
textLabel.RemoveFromHierarchy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnCopyClicked()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_code))
|
||||
{
|
||||
GUIUtility.systemCopyBuffer = _code;
|
||||
UTKToast.Show("코드가 클립보드에 복사되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C# 코드와 UXML 코드를 함께 표시하는 컨테이너를 생성합니다.
|
||||
/// </summary>
|
||||
public static VisualElement CreateCodeSampleContainer(string? csharpCode, string? uxmlCode)
|
||||
{
|
||||
var container = new VisualElement();
|
||||
container.AddToClassList("utk-code-sample-container");
|
||||
|
||||
if (!string.IsNullOrEmpty(csharpCode))
|
||||
{
|
||||
var csharpBlock = new UTKCodeBlock
|
||||
{
|
||||
Title = "C#",
|
||||
Code = csharpCode
|
||||
};
|
||||
container.Add(csharpBlock);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(uxmlCode))
|
||||
{
|
||||
var uxmlBlock = new UTKCodeBlock
|
||||
{
|
||||
Title = "UXML",
|
||||
Code = uxmlCode
|
||||
};
|
||||
container.Add(uxmlBlock);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UIToolkit/Common/UTKCodeBlock.cs.meta
Normal file
2
Assets/Scripts/UVC/UIToolkit/Common/UTKCodeBlock.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b725c3e7363d7004a955aeb90d587f84
|
||||
@@ -8,7 +8,36 @@ namespace UVC.UIToolkit
|
||||
/// <summary>
|
||||
/// 접을 수 있는 섹션 컴포넌트.
|
||||
/// Unity Foldout을 래핑하여 커스텀 스타일을 적용합니다.
|
||||
/// 헤더 클릭으로 내용을 펼치거나 접을 수 있습니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Foldout(폴드아웃)이란?</b></para>
|
||||
/// <para>
|
||||
/// Foldout은 헤더를 클릭하여 내용을 펼치거나 접을 수 있는 컨테이너입니다.
|
||||
/// 아코디언(Accordion)이라고도 불리며, 많은 내용을 정리할 때 유용합니다.
|
||||
/// Unity Inspector에서 컴포넌트 섹션을 접는 것과 같은 동작입니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>Foldout vs Panel 차이:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>Foldout</c> - 간단한 접기/펼치기, 토글 화살표 표시</description></item>
|
||||
/// <item><description><c>Panel</c> - 헤더/푸터/액션 등 풍부한 기능, 더 많은 커스터마이징</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>주요 속성:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>text</c> - 헤더에 표시되는 제목</description></item>
|
||||
/// <item><description><c>value</c> / <c>IsExpanded</c> - 펼침 상태 (true=펼침, false=접힘)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>설정 페이지 - 고급 설정 숨기기</description></item>
|
||||
/// <item><description>FAQ - 질문 클릭 시 답변 표시</description></item>
|
||||
/// <item><description>인스펙터 - 컴포넌트 섹션 접기</description></item>
|
||||
/// <item><description>필터 패널 - 상세 필터 옵션 숨기기</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para><b>C# 코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
|
||||
@@ -8,7 +8,38 @@ namespace UVC.UIToolkit
|
||||
/// <summary>
|
||||
/// 도움말 박스 컴포넌트.
|
||||
/// Unity HelpBox를 래핑하여 커스텀 스타일을 적용합니다.
|
||||
/// 정보, 경고, 오류 메시지를 아이콘과 함께 표시합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>HelpBox(도움말 박스)란?</b></para>
|
||||
/// <para>
|
||||
/// HelpBox는 사용자에게 중요한 정보, 경고, 오류 메시지를 눈에 띄게 표시하는 컴포넌트입니다.
|
||||
/// 메시지 유형에 따라 아이콘과 배경색이 달라져 시각적으로 구분됩니다.
|
||||
/// Unity Inspector에서 볼 수 있는 노란색/빨간색 경고 박스와 같은 역할입니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>메시지 유형 (HelpBoxMessageType):</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>None</c> - 아이콘 없이 텍스트만 표시</description></item>
|
||||
/// <item><description><c>Info</c> - 정보 아이콘 (파란색) - 일반 안내 메시지</description></item>
|
||||
/// <item><description><c>Warning</c> - 경고 아이콘 (노란색) - 주의 필요한 사항</description></item>
|
||||
/// <item><description><c>Error</c> - 오류 아이콘 (빨간색) - 에러, 문제 상황</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>HelpBox vs Alert 차이:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>HelpBox</c> - 화면에 고정 표시, 닫기 불가, 인라인 메시지</description></item>
|
||||
/// <item><description><c>Alert</c> - 모달 팝업, 버튼으로 닫기, 사용자 확인 필요</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>폼 유효성 검사 - 입력 오류 메시지</description></item>
|
||||
/// <item><description>설정 페이지 - "이 설정은 재시작 후 적용됩니다" 안내</description></item>
|
||||
/// <item><description>에디터 - "필수 필드가 비어 있습니다" 경고</description></item>
|
||||
/// <item><description>튜토리얼 - 사용법 힌트 표시</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para><b>C# 코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
|
||||
492
Assets/Scripts/UVC/UIToolkit/Common/UTKImage.cs
Normal file
492
Assets/Scripts/UVC/UIToolkit/Common/UTKImage.cs
Normal file
@@ -0,0 +1,492 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 이미지 컴포넌트.
|
||||
/// Resources 폴더 내부 경로 또는 HTTP/HTTPS URL에서 이미지를 로드하여 표시합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>UTKImage란?</b></para>
|
||||
/// <para>
|
||||
/// UTKImage는 UXML에서 직접 사용할 수 있는 이미지 표시 컴포넌트입니다.
|
||||
/// <c>src</c> 속성에 경로를 지정하면 자동으로 이미지를 로드하여 표시합니다.
|
||||
/// 내부 리소스(Resources 폴더)와 외부 URL(HTTP/HTTPS) 모두 지원합니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>지원하는 이미지 소스:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>내부 리소스</b> - Assets/Resources 폴더 내 이미지 경로 (확장자 제외)</description></item>
|
||||
/// <item><description><b>외부 URL</b> - http:// 또는 https://로 시작하는 이미지 URL</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>주요 속성:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>src</c> - 이미지 소스 경로 (Resources 경로 또는 HTTP URL)</description></item>
|
||||
/// <item><description><c>scale-mode</c> - 이미지 스케일 모드 (StretchToFill, ScaleAndCrop, ScaleToFit)</description></item>
|
||||
/// <item><description><c>tint-color</c> - 이미지 틴트 색상</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>이미지 스케일 모드:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>StretchToFill</c> - 요소 크기에 맞게 늘림 (비율 무시)</description></item>
|
||||
/// <item><description><c>ScaleAndCrop</c> - 비율 유지하며 요소를 채움 (잘릴 수 있음)</description></item>
|
||||
/// <item><description><c>ScaleToFit</c> - 비율 유지하며 요소 안에 맞춤 (여백 발생 가능)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>프로필 이미지, 아바타</description></item>
|
||||
/// <item><description>썸네일, 미리보기 이미지</description></item>
|
||||
/// <item><description>배너, 광고 이미지</description></item>
|
||||
/// <item><description>제품 이미지, 갤러리</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para><b>C# 코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// // 내부 리소스 이미지 로드
|
||||
/// 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://example.com/image.png";
|
||||
/// 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);
|
||||
/// </code>
|
||||
/// <para><b>UXML에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
|
||||
/// <!-- 내부 리소스 이미지 -->
|
||||
/// <utk:UTKImage src="UIToolkit/Images/icon_setting_22"
|
||||
/// style="width: 48px; height: 48px;" />
|
||||
///
|
||||
/// <!-- 외부 URL 이미지 -->
|
||||
/// <utk:UTKImage src="https://example.com/image.png"
|
||||
/// 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>
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlElement]
|
||||
public partial class UTKImage : VisualElement, IDisposable
|
||||
{
|
||||
#region Constants
|
||||
private const string USS_PATH = "UIToolkit/Common/UTKImage";
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
private bool _disposed;
|
||||
private string _src = "";
|
||||
private ScaleMode _scaleMode = ScaleMode.ScaleToFit;
|
||||
private Color _tintColor = Color.white;
|
||||
private Texture2D? _loadedTexture;
|
||||
private CancellationTokenSource? _loadCts;
|
||||
private bool _isExternalTexture;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
/// <summary>이미지 로드 완료 이벤트</summary>
|
||||
public event Action<Texture2D>? OnImageLoaded;
|
||||
/// <summary>이미지 로드 실패 이벤트</summary>
|
||||
public event Action<string>? OnImageFailed;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// 이미지 소스 경로.
|
||||
/// Resources 폴더 내 경로(확장자 제외) 또는 HTTP/HTTPS URL을 지정합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 내부 리소스
|
||||
/// image.Src = "UIToolkit/Images/icon_setting_22";
|
||||
///
|
||||
/// // 외부 URL
|
||||
/// image.Src = "https://example.com/image.png";
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlAttribute("src")]
|
||||
public string Src
|
||||
{
|
||||
get => _src;
|
||||
set
|
||||
{
|
||||
if (_src == value) return;
|
||||
_src = value;
|
||||
LoadImageFromSource();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>이미지 스케일 모드</summary>
|
||||
[UxmlAttribute("scale-mode")]
|
||||
public ScaleMode ScaleMode
|
||||
{
|
||||
get => _scaleMode;
|
||||
set
|
||||
{
|
||||
_scaleMode = value;
|
||||
UpdateBackgroundScaleMode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>이미지 틴트 색상</summary>
|
||||
[UxmlAttribute("tint-color")]
|
||||
public Color TintColor
|
||||
{
|
||||
get => _tintColor;
|
||||
set
|
||||
{
|
||||
_tintColor = value;
|
||||
style.unityBackgroundImageTintColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>현재 로드된 텍스처</summary>
|
||||
public Texture2D? LoadedTexture => _loadedTexture;
|
||||
|
||||
/// <summary>이미지 로드 중 여부</summary>
|
||||
public bool IsLoading => _loadCts != null && !_loadCts.IsCancellationRequested;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKImage()
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
|
||||
var uss = Resources.Load<StyleSheet>(USS_PATH);
|
||||
if (uss != null)
|
||||
{
|
||||
styleSheets.Add(uss);
|
||||
}
|
||||
|
||||
SetupStyles();
|
||||
SubscribeToThemeChanges();
|
||||
|
||||
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스를 지정하여 이미지를 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="src">이미지 소스 경로 (Resources 경로 또는 HTTP URL)</param>
|
||||
public UTKImage(string src) : this()
|
||||
{
|
||||
_src = src;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture2D로 직접 이미지를 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="texture">표시할 텍스처</param>
|
||||
public UTKImage(Texture2D texture) : this()
|
||||
{
|
||||
SetTexture(texture);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Setup
|
||||
private void SetupStyles()
|
||||
{
|
||||
AddToClassList("utk-image");
|
||||
style.unityBackgroundScaleMode = _scaleMode;
|
||||
}
|
||||
|
||||
private void SubscribeToThemeChanges()
|
||||
{
|
||||
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
||||
RegisterCallback<DetachFromPanelEvent>(_ =>
|
||||
{
|
||||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnThemeChanged(UTKTheme theme)
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
|
||||
private void OnAttachToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
|
||||
// UXML에서 설정된 src가 있으면 로드
|
||||
if (!string.IsNullOrEmpty(_src))
|
||||
{
|
||||
LoadImageFromSource();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Image Loading
|
||||
private void LoadImageFromSource()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_src))
|
||||
{
|
||||
ClearImage();
|
||||
return;
|
||||
}
|
||||
|
||||
// 이전 로드 취소
|
||||
CancelCurrentLoad();
|
||||
|
||||
if (IsExternalUrl(_src))
|
||||
{
|
||||
LoadFromUrlAsync(_src).Forget();
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadFromResources(_src);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resources 폴더에서 이미지를 동기로 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="resourcePath">Resources 폴더 내 경로 (확장자 제외)</param>
|
||||
public void LoadFromResources(string resourcePath)
|
||||
{
|
||||
var texture = Resources.Load<Texture2D>(resourcePath);
|
||||
if (texture != null)
|
||||
{
|
||||
_isExternalTexture = false;
|
||||
ApplyTexture(texture);
|
||||
OnImageLoaded?.Invoke(texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMsg = $"리소스를 찾을 수 없습니다: {resourcePath}";
|
||||
Debug.LogWarning($"[UTKImage] {errorMsg}");
|
||||
OnImageFailed?.Invoke(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resources 폴더에서 이미지를 비동기로 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="resourcePath">Resources 폴더 내 경로 (확장자 제외)</param>
|
||||
/// <param name="ct">취소 토큰</param>
|
||||
public async UniTask LoadFromResourcesAsync(string resourcePath, CancellationToken ct = default)
|
||||
{
|
||||
CancelCurrentLoad();
|
||||
_loadCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
|
||||
try
|
||||
{
|
||||
var request = Resources.LoadAsync<Texture2D>(resourcePath);
|
||||
await request.ToUniTask(cancellationToken: _loadCts.Token);
|
||||
|
||||
var texture = request.asset as Texture2D;
|
||||
if (texture != null)
|
||||
{
|
||||
_isExternalTexture = false;
|
||||
ApplyTexture(texture);
|
||||
OnImageLoaded?.Invoke(texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMsg = $"리소스를 찾을 수 없습니다: {resourcePath}";
|
||||
Debug.LogWarning($"[UTKImage] {errorMsg}");
|
||||
OnImageFailed?.Invoke(errorMsg);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 취소됨
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loadCts?.Dispose();
|
||||
_loadCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 외부 URL에서 이미지를 비동기로 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="url">이미지 URL (http:// 또는 https://)</param>
|
||||
/// <param name="ct">취소 토큰</param>
|
||||
public async UniTask LoadFromUrlAsync(string url, CancellationToken ct = default)
|
||||
{
|
||||
CancelCurrentLoad();
|
||||
_loadCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
|
||||
try
|
||||
{
|
||||
AddToClassList("utk-image--loading");
|
||||
|
||||
using var request = UnityWebRequestTexture.GetTexture(url);
|
||||
await request.SendWebRequest().ToUniTask(cancellationToken: _loadCts.Token);
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
var texture = DownloadHandlerTexture.GetContent(request);
|
||||
if (texture != null)
|
||||
{
|
||||
// 이전 외부 텍스처 정리
|
||||
CleanupExternalTexture();
|
||||
|
||||
_isExternalTexture = true;
|
||||
ApplyTexture(texture);
|
||||
OnImageLoaded?.Invoke(texture);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMsg = $"이미지 로드 실패: {request.error}";
|
||||
Debug.LogWarning($"[UTKImage] {errorMsg}");
|
||||
OnImageFailed?.Invoke(errorMsg);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 취소됨
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"이미지 로드 중 오류: {ex.Message}";
|
||||
Debug.LogError($"[UTKImage] {errorMsg}");
|
||||
OnImageFailed?.Invoke(errorMsg);
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemoveFromClassList("utk-image--loading");
|
||||
_loadCts?.Dispose();
|
||||
_loadCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스에서 이미지를 비동기로 로드합니다.
|
||||
/// Resources 경로 또는 HTTP URL을 자동으로 감지합니다.
|
||||
/// </summary>
|
||||
/// <param name="source">이미지 소스 (Resources 경로 또는 HTTP URL)</param>
|
||||
/// <param name="ct">취소 토큰</param>
|
||||
public async UniTask LoadAsync(string source, CancellationToken ct = default)
|
||||
{
|
||||
_src = source;
|
||||
|
||||
if (IsExternalUrl(source))
|
||||
{
|
||||
await LoadFromUrlAsync(source, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LoadFromResourcesAsync(source, ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture2D를 직접 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="texture">표시할 텍스처</param>
|
||||
public void SetTexture(Texture2D texture)
|
||||
{
|
||||
CleanupExternalTexture();
|
||||
_isExternalTexture = false;
|
||||
ApplyTexture(texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지를 제거합니다.
|
||||
/// </summary>
|
||||
public void ClearImage()
|
||||
{
|
||||
CleanupExternalTexture();
|
||||
_loadedTexture = null;
|
||||
style.backgroundImage = StyleKeyword.None;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
private void ApplyTexture(Texture2D texture)
|
||||
{
|
||||
_loadedTexture = texture;
|
||||
style.backgroundImage = new StyleBackground(texture);
|
||||
style.unityBackgroundScaleMode = _scaleMode;
|
||||
style.unityBackgroundImageTintColor = _tintColor;
|
||||
}
|
||||
|
||||
private void UpdateBackgroundScaleMode()
|
||||
{
|
||||
style.unityBackgroundScaleMode = _scaleMode;
|
||||
}
|
||||
|
||||
private void CancelCurrentLoad()
|
||||
{
|
||||
if (_loadCts != null)
|
||||
{
|
||||
_loadCts.Cancel();
|
||||
_loadCts.Dispose();
|
||||
_loadCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupExternalTexture()
|
||||
{
|
||||
if (_isExternalTexture && _loadedTexture != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(_loadedTexture);
|
||||
_loadedTexture = null;
|
||||
_isExternalTexture = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsExternalUrl(string source)
|
||||
{
|
||||
return source.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
source.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
CancelCurrentLoad();
|
||||
CleanupExternalTexture();
|
||||
|
||||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||||
OnImageLoaded = null;
|
||||
OnImageFailed = null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UIToolkit/Common/UTKImage.cs.meta
Normal file
2
Assets/Scripts/UVC/UIToolkit/Common/UTKImage.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bab8739301e5d104daef9ce9bc628b79
|
||||
@@ -65,7 +65,7 @@ namespace UVC.UIToolkit
|
||||
/// </code>
|
||||
/// <code>
|
||||
/// <!-- UXML 파일 -->
|
||||
/// <ui:VisualElement class="my-icon" />
|
||||
/// <VisualElement class="my-icon" />
|
||||
/// </code>
|
||||
/// <para><b>C# 코드에서 UXML 요소에 이미지 적용:</b></para>
|
||||
/// <code>
|
||||
|
||||
@@ -8,7 +8,38 @@ namespace UVC.UIToolkit
|
||||
/// <summary>
|
||||
/// 스크롤 뷰 컴포넌트.
|
||||
/// Unity ScrollView를 래핑하여 커스텀 스타일을 적용합니다.
|
||||
/// 내용이 컨테이너보다 클 때 스크롤하여 볼 수 있습니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>ScrollView(스크롤 뷰)란?</b></para>
|
||||
/// <para>
|
||||
/// ScrollView는 내용이 표시 영역보다 클 때 스크롤하여 나머지 내용을 볼 수 있게 하는 컨테이너입니다.
|
||||
/// 긴 목록, 대화 내용, 문서 등 많은 콘텐츠를 제한된 공간에 표시할 때 사용합니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>스크롤 모드 (ScrollViewMode):</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>Vertical</c> - 세로 스크롤만 (기본값, 대부분의 목록)</description></item>
|
||||
/// <item><description><c>Horizontal</c> - 가로 스크롤만 (가로 갤러리, 타임라인)</description></item>
|
||||
/// <item><description><c>VerticalAndHorizontal</c> - 양방향 스크롤 (큰 이미지, 스프레드시트)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>주요 속성:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>mode</c> - 스크롤 방향 설정</description></item>
|
||||
/// <item><description><c>scrollOffset</c> - 현재 스크롤 위치</description></item>
|
||||
/// <item><description><c>verticalScrollerVisibility</c> - 세로 스크롤바 표시 설정</description></item>
|
||||
/// <item><description><c>horizontalScrollerVisibility</c> - 가로 스크롤바 표시 설정</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>채팅 창 - 메시지 목록 스크롤</description></item>
|
||||
/// <item><description>설정 페이지 - 많은 옵션 스크롤</description></item>
|
||||
/// <item><description>갤러리 - 이미지 목록 스크롤</description></item>
|
||||
/// <item><description>코드 에디터 - 긴 코드 스크롤</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para><b>C# 코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
|
||||
@@ -10,9 +10,69 @@ using UVC.Locale;
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// UIToolkit 기반 툴팁 매니저
|
||||
/// VisualElement에 마우스 오버 시 툴팁 표시
|
||||
/// UIToolkit 기반 툴팁 매니저.
|
||||
/// VisualElement에 마우스 오버 시 툴팁을 표시하는 싱글톤 관리자입니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Tooltip(툴팁)이란?</b></para>
|
||||
/// <para>
|
||||
/// Tooltip은 UI 요소에 마우스를 올렸을 때 나타나는 작은 설명 텍스트입니다.
|
||||
/// 버튼이나 아이콘의 기능을 설명하거나 추가 정보를 제공할 때 사용합니다.
|
||||
/// 일반적으로 잠시 후(500ms) 나타나고, 마우스가 벗어나면 사라집니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>싱글톤 패턴:</b></para>
|
||||
/// <para>
|
||||
/// UTKTooltipManager는 싱글톤으로 구현되어 있습니다.
|
||||
/// <c>UTKTooltipManager.Instance</c>로 접근하며, 앱 전체에서 하나의 툴팁 UI를 공유합니다.
|
||||
/// 사용 전에 반드시 <c>Initialize(root)</c>를 호출해야 합니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>주요 기능:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>지연 표시 - 마우스 오버 후 일정 시간 뒤에 표시</description></item>
|
||||
/// <item><description>마우스 따라가기 - 마우스 이동에 따라 위치 업데이트</description></item>
|
||||
/// <item><description>화면 경계 처리 - 화면 밖으로 나가지 않도록 자동 조정</description></item>
|
||||
/// <item><description>다국어 지원 - 로컬라이제이션 키 자동 변환</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>주요 메서드:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>Initialize(root)</c> - 초기화 (루트 요소 지정)</description></item>
|
||||
/// <item><description><c>AttachTooltip(element, text)</c> - 요소에 툴팁 연결</description></item>
|
||||
/// <item><description><c>DetachTooltip(element)</c> - 툴팁 제거</description></item>
|
||||
/// <item><description><c>Show(text, position)</c> - 즉시 표시</description></item>
|
||||
/// <item><description><c>Hide()</c> - 숨기기</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>아이콘 버튼 - "저장", "삭제", "설정" 등 기능 설명</description></item>
|
||||
/// <item><description>복잡한 옵션 - 설정 항목의 상세 설명</description></item>
|
||||
/// <item><description>잘린 텍스트 - 전체 내용 표시</description></item>
|
||||
/// <item><description>단축키 안내 - "Ctrl+S" 등 키보드 힌트</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para><b>C# 코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// // 1. 초기화 (앱 시작 시 한 번)
|
||||
/// UTKTooltipManager.Instance.Initialize(rootVisualElement);
|
||||
///
|
||||
/// // 2. 버튼에 툴팁 연결
|
||||
/// var saveButton = new UTKButton("", UTKMaterialIcons.Save);
|
||||
/// UTKTooltipManager.Instance.AttachTooltip(saveButton, "저장 (Ctrl+S)");
|
||||
///
|
||||
/// // 3. 다국어 키로 툴팁 연결
|
||||
/// UTKTooltipManager.Instance.AttachTooltip(settingsButton, "tooltip_settings");
|
||||
///
|
||||
/// // 4. 툴팁 업데이트
|
||||
/// UTKTooltipManager.Instance.UpdateTooltip(button, "새로운 설명");
|
||||
///
|
||||
/// // 5. 툴팁 제거
|
||||
/// UTKTooltipManager.Instance.DetachTooltip(button);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class UTKTooltipManager : IDisposable
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
Reference in New Issue
Block a user