Files
EnglewoodLAB/Assets/Scripts/UVC/UIToolkit/ToolBar/Items/UTKToolBarButtonBase.cs

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
}
}