421 lines
13 KiB
C#
421 lines
13 KiB
C#
#nullable enable
|
|
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// 툴바 버튼 VisualElement의 추상 베이스 클래스입니다.
|
|
/// 아이콘(Material Icon / Image), 텍스트, 활성화 상태의 공통 UI를 제공합니다.
|
|
/// </summary>
|
|
public abstract partial class UTKToolBarButtonBase : VisualElement, IDisposable
|
|
{
|
|
#region Fields
|
|
|
|
/// <summary>아이콘 요소 (Material Icon Label 또는 Image)</summary>
|
|
protected UTKLabel? _iconLabel;
|
|
|
|
/// <summary>텍스트 라벨</summary>
|
|
protected UTKLabel? _textLabel;
|
|
|
|
/// <summary>루트 버튼 요소</summary>
|
|
protected VisualElement? _rootButton;
|
|
|
|
/// <summary>바인딩된 데이터</summary>
|
|
protected UTKToolBarButtonData? _data;
|
|
|
|
/// <summary>클릭 콜백</summary>
|
|
protected EventCallback<ClickEvent>? _onClickCallback;
|
|
|
|
|
|
/// <summary>정리 여부</summary>
|
|
protected bool _disposed;
|
|
|
|
/// <summary>UXML 리소스 경로</summary>
|
|
protected string _uxmlPath = "";
|
|
|
|
/// <summary>USS 리소스 경로</summary>
|
|
protected string _ussPath = "";
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// 텍스트 라벨의 표시 여부. false로 설정하면 텍스트가 있어도 라벨을 숨깁니다.
|
|
/// </summary>
|
|
public bool ShowLabel
|
|
{
|
|
get => _data?.ShowLabel ?? true;
|
|
set
|
|
{
|
|
if (_data == null || _data.ShowLabel == value) return;
|
|
_data.ShowLabel = value;
|
|
if (_textLabel != null)
|
|
{
|
|
_textLabel.style.display = value && !string.IsNullOrEmpty(_data.Text)
|
|
? DisplayStyle.Flex
|
|
: DisplayStyle.None;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
/// <summary>버튼 클릭 이벤트 (데이터 전달)</summary>
|
|
public event Action<UTKToolBarButtonData>? OnButtonClicked;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// UTKToolBarButtonBase의 새 인스턴스를 초기화합니다.
|
|
/// </summary>
|
|
protected UTKToolBarButtonBase()
|
|
{
|
|
// 1. 테마 적용
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
|
|
// 2. 테마 구독
|
|
SubscribeToThemeChanges();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Setup
|
|
|
|
/// <summary>
|
|
/// UI를 생성합니다. UXML 로드 또는 코드 Fallback.
|
|
/// </summary>
|
|
protected virtual void CreateUI()
|
|
{
|
|
// USS 로드
|
|
if (!string.IsNullOrEmpty(_ussPath))
|
|
{
|
|
var uss = Resources.Load<StyleSheet>(_ussPath);
|
|
if (uss != null)
|
|
{
|
|
styleSheets.Add(uss);
|
|
}
|
|
}
|
|
|
|
// UXML 로드
|
|
if (!string.IsNullOrEmpty(_uxmlPath))
|
|
{
|
|
var asset = Resources.Load<VisualTreeAsset>(_uxmlPath);
|
|
if (asset != null)
|
|
{
|
|
CreateUIFromUxml(asset);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fallback
|
|
CreateUIFallback();
|
|
}
|
|
|
|
/// <summary>
|
|
/// UXML에서 UI를 생성합니다.
|
|
/// </summary>
|
|
/// <param name="asset">UXML 에셋</param>
|
|
protected virtual void CreateUIFromUxml(VisualTreeAsset asset)
|
|
{
|
|
var root = asset.Instantiate();
|
|
_rootButton = root.Q<VisualElement>("button-root");
|
|
_iconLabel = root.Q<UTKLabel>("icon");
|
|
_textLabel = root.Q<UTKLabel>("label");
|
|
_textLabel.Size = UTKLabel.LabelSize.Caption; // UXML에서 기본 크기를 설정하므로 코드에서 다시 지정
|
|
|
|
// TemplateContainer가 아이콘 정렬을 방해하지 않도록 설정
|
|
root.style.flexGrow = 1;
|
|
root.style.alignItems = Align.Stretch;
|
|
root.style.justifyContent = Justify.Center;
|
|
|
|
Add(root);
|
|
|
|
// 클릭 이벤트 등록
|
|
if (_rootButton != null)
|
|
{
|
|
_onClickCallback = OnClick;
|
|
_rootButton.RegisterCallback(_onClickCallback);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 코드 Fallback으로 UI를 생성합니다.
|
|
/// </summary>
|
|
protected virtual void CreateUIFallback()
|
|
{
|
|
_rootButton = new VisualElement();
|
|
_rootButton.AddToClassList("utk-toolbar-btn");
|
|
|
|
_iconLabel = new UTKLabel();
|
|
_iconLabel.AddToClassList("utk-toolbar-btn__icon");
|
|
_rootButton.Add(_iconLabel);
|
|
|
|
_textLabel = new UTKLabel();
|
|
_textLabel.AddToClassList("utk-toolbar-btn__label");
|
|
_textLabel.Size = UTKLabel.LabelSize.Caption;
|
|
_rootButton.Add(_textLabel);
|
|
|
|
Add(_rootButton);
|
|
|
|
// 클릭 이벤트 등록
|
|
_onClickCallback = OnClick;
|
|
_rootButton.RegisterCallback(_onClickCallback);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Data Binding
|
|
|
|
/// <summary>
|
|
/// 데이터를 바인딩합니다. OnStateChanged 이벤트를 구독합니다.
|
|
/// </summary>
|
|
/// <param name="data">바인딩할 데이터</param>
|
|
public virtual void BindData(UTKToolBarButtonData data)
|
|
{
|
|
// 기존 바인딩 해제
|
|
UnbindData();
|
|
|
|
_data = data;
|
|
_data.OnStateChanged += OnDataStateChanged;
|
|
|
|
|
|
// 초기 UI 업데이트
|
|
UpdateIcon(_data.IconPath, _data.UseMaterialIcon);
|
|
UpdateText(_data.Text);
|
|
UpdateEnabled(_data.IsEnabled);
|
|
UpdateTooltip(_data.Tooltip);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 데이터 바인딩을 해제합니다.
|
|
/// </summary>
|
|
public virtual void UnbindData()
|
|
{
|
|
if (_data != null)
|
|
{
|
|
_data.OnStateChanged -= OnDataStateChanged;
|
|
_data = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>바인딩된 데이터 (읽기 전용)</summary>
|
|
public UTKToolBarButtonData? BoundData => _data;
|
|
|
|
/// <summary>
|
|
/// 바인딩된 데이터의 활성화 상태를 변경합니다.
|
|
/// </summary>
|
|
/// <param name="isEnabled">활성화 여부</param>
|
|
public void SetDataEnabled(bool isEnabled)
|
|
{
|
|
if (_data != null)
|
|
{
|
|
_data.IsEnabled = isEnabled;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region UI Update
|
|
|
|
/// <summary>
|
|
/// 아이콘을 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="iconPath">아이콘 경로 (Material Icon 유니코드 또는 Resources 경로)</param>
|
|
/// <param name="useMaterialIcon">Material Icon 사용 여부</param>
|
|
protected void UpdateIcon(string? iconPath, bool useMaterialIcon)
|
|
{
|
|
if (_iconLabel == null) return;
|
|
|
|
if (string.IsNullOrEmpty(iconPath))
|
|
{
|
|
_iconLabel.style.display = DisplayStyle.None;
|
|
return;
|
|
}
|
|
|
|
_iconLabel.style.display = DisplayStyle.Flex;
|
|
|
|
if (useMaterialIcon)
|
|
{
|
|
// Material Icon (폰트 기반) - UTKLabel의 SetMaterialIcon 사용
|
|
_iconLabel.RemoveFromClassList("utk-toolbar-btn__icon--image");
|
|
_iconLabel.SetMaterialIcon(iconPath);
|
|
}
|
|
else
|
|
{
|
|
// 이미지 아이콘 - UTKLabel의 SetImageIcon 사용
|
|
_iconLabel.AddToClassList("utk-toolbar-btn__icon--image");
|
|
_iconLabel.SetImageIcon(iconPath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 텍스트를 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="text">버튼 텍스트</param>
|
|
protected void UpdateText(string text)
|
|
{
|
|
if (_textLabel != null)
|
|
{
|
|
_textLabel.Text = text;
|
|
_textLabel.style.display = (_data?.ShowLabel ?? true) && !string.IsNullOrEmpty(text)
|
|
? DisplayStyle.Flex
|
|
: DisplayStyle.None;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 툴팁을 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="tooltipText">툴팁 텍스트</param>
|
|
protected void UpdateTooltip(string? tooltipText)
|
|
{
|
|
if (_rootButton == null) return;
|
|
|
|
if (string.IsNullOrEmpty(tooltipText))
|
|
{
|
|
UTKTooltipManager.Instance.DetachTooltip(_rootButton);
|
|
}
|
|
else
|
|
{
|
|
UTKTooltipManager.Instance.UpdateTooltip(_rootButton, tooltipText);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 활성화 상태를 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="isEnabled">활성화 여부</param>
|
|
protected void UpdateEnabled(bool isEnabled)
|
|
{
|
|
if (_rootButton != null)
|
|
{
|
|
if (isEnabled)
|
|
{
|
|
_rootButton.RemoveFromClassList("utk-toolbar-btn--disabled");
|
|
}
|
|
else
|
|
{
|
|
_rootButton.AddToClassList("utk-toolbar-btn--disabled");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모델 상태 변경 핸들러. 바인딩된 데이터의 시각 상태를 UI에 반영합니다.
|
|
/// </summary>
|
|
protected virtual void OnDataStateChanged()
|
|
{
|
|
if (_data == null) return;
|
|
UpdateIcon(_data.IconPath, _data.UseMaterialIcon);
|
|
UpdateText(_data.Text);
|
|
UpdateEnabled(_data.IsEnabled);
|
|
UpdateTooltip(_data.Tooltip);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Click Handler
|
|
|
|
/// <summary>
|
|
/// 클릭 이벤트 핸들러.
|
|
/// </summary>
|
|
/// <param name="evt">클릭 이벤트</param>
|
|
protected virtual void OnClick(ClickEvent evt)
|
|
{
|
|
if (_data == null || !_data.IsEnabled) return;
|
|
_data.ExecuteClick();
|
|
RaiseOnButtonClicked(_data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// OnButtonClicked 이벤트를 발생시킵니다. 파생 클래스에서 사용합니다.
|
|
/// </summary>
|
|
/// <param name="data">버튼 데이터</param>
|
|
protected void RaiseOnButtonClicked(UTKToolBarButtonData data)
|
|
{
|
|
OnButtonClicked?.Invoke(data);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Theme
|
|
|
|
/// <summary>
|
|
/// 테마 변경 이벤트를 구독합니다.
|
|
/// </summary>
|
|
private void SubscribeToThemeChanges()
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
}
|
|
|
|
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
}
|
|
|
|
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
}
|
|
|
|
private void OnThemeChanged(UTKTheme theme)
|
|
{
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
/// <summary>
|
|
/// 리소스를 정리합니다.
|
|
/// </summary>
|
|
public virtual void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
// 데이터 바인딩 해제
|
|
UnbindData();
|
|
|
|
// 툴팁 해제
|
|
if (_rootButton != null)
|
|
{
|
|
UTKTooltipManager.Instance.DetachTooltip(_rootButton);
|
|
}
|
|
|
|
// 테마 구독 해제
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
|
|
// 클릭 이벤트 해제
|
|
if (_rootButton != null && _onClickCallback != null)
|
|
{
|
|
_rootButton.UnregisterCallback(_onClickCallback);
|
|
}
|
|
|
|
// 참조 정리
|
|
OnButtonClicked = null;
|
|
_rootButton = null;
|
|
_iconLabel = null;
|
|
_textLabel = null;
|
|
_onClickCallback = null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|