기본 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user