#nullable enable using System; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// 툴바 버튼 VisualElement의 추상 베이스 클래스입니다. /// 아이콘(Material Icon / Image), 텍스트, 활성화 상태의 공통 UI를 제공합니다. /// public abstract partial class UTKToolBarButtonBase : VisualElement, IDisposable { #region Fields /// 아이콘 요소 (Material Icon Label 또는 Image) protected UTKLabel? _iconLabel; /// 텍스트 라벨 protected UTKLabel? _textLabel; /// 루트 버튼 요소 protected VisualElement? _rootButton; /// 바인딩된 데이터 protected UTKToolBarButtonData? _data; /// 클릭 콜백 protected EventCallback? _onClickCallback; /// 정리 여부 protected bool _disposed; /// UXML 리소스 경로 protected string _uxmlPath = ""; /// USS 리소스 경로 protected string _ussPath = ""; #endregion #region Events /// 버튼 클릭 이벤트 (데이터 전달) public event Action? OnButtonClicked; #endregion #region Constructor /// /// UTKToolBarButtonBase의 새 인스턴스를 초기화합니다. /// protected UTKToolBarButtonBase() { // 1. 테마 적용 UTKThemeManager.Instance.ApplyThemeToElement(this); // 2. 테마 구독 SubscribeToThemeChanges(); } #endregion #region Setup /// /// UI를 생성합니다. UXML 로드 또는 코드 Fallback. /// protected virtual void CreateUI() { // USS 로드 if (!string.IsNullOrEmpty(_ussPath)) { var uss = Resources.Load(_ussPath); if (uss != null) { styleSheets.Add(uss); } } // UXML 로드 if (!string.IsNullOrEmpty(_uxmlPath)) { var asset = Resources.Load(_uxmlPath); if (asset != null) { CreateUIFromUxml(asset); return; } } // Fallback CreateUIFallback(); } /// /// UXML에서 UI를 생성합니다. /// /// UXML 에셋 protected virtual void CreateUIFromUxml(VisualTreeAsset asset) { var root = asset.Instantiate(); _rootButton = root.Q("button-root"); _iconLabel = root.Q("icon"); _textLabel = root.Q("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); } } /// /// 코드 Fallback으로 UI를 생성합니다. /// 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 /// /// 데이터를 바인딩합니다. OnStateChanged 이벤트를 구독합니다. /// /// 바인딩할 데이터 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); } /// /// 데이터 바인딩을 해제합니다. /// public virtual void UnbindData() { if (_data != null) { _data.OnStateChanged -= OnDataStateChanged; _data = null; } } /// 바인딩된 데이터 (읽기 전용) public UTKToolBarButtonData? BoundData => _data; /// /// 바인딩된 데이터의 활성화 상태를 변경합니다. /// /// 활성화 여부 public void SetDataEnabled(bool isEnabled) { if (_data != null) { _data.IsEnabled = isEnabled; } } #endregion #region UI Update /// /// 아이콘을 업데이트합니다. /// /// 아이콘 경로 (Material Icon 유니코드 또는 Resources 경로) /// Material Icon 사용 여부 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); } } /// /// 텍스트를 업데이트합니다. /// /// 버튼 텍스트 protected void UpdateText(string text) { if (_textLabel != null) { _textLabel.Text = text; _textLabel.style.display = string.IsNullOrEmpty(text) ? DisplayStyle.None : DisplayStyle.Flex; } } /// /// 툴팁을 업데이트합니다. /// /// 툴팁 텍스트 protected void UpdateTooltip(string? tooltipText) { if (_rootButton == null) return; if (string.IsNullOrEmpty(tooltipText)) { UTKTooltipManager.Instance.DetachTooltip(_rootButton); } else { UTKTooltipManager.Instance.UpdateTooltip(_rootButton, tooltipText); } } /// /// 활성화 상태를 업데이트합니다. /// /// 활성화 여부 protected void UpdateEnabled(bool isEnabled) { if (_rootButton != null) { if (isEnabled) { _rootButton.RemoveFromClassList("utk-toolbar-btn--disabled"); } else { _rootButton.AddToClassList("utk-toolbar-btn--disabled"); } } } /// /// 모델 상태 변경 핸들러. 바인딩된 데이터의 시각 상태를 UI에 반영합니다. /// 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 /// /// 클릭 이벤트 핸들러. /// /// 클릭 이벤트 protected virtual void OnClick(ClickEvent evt) { if (_data == null || !_data.IsEnabled) return; _data.ExecuteClick(); RaiseOnButtonClicked(_data); } /// /// OnButtonClicked 이벤트를 발생시킵니다. 파생 클래스에서 사용합니다. /// /// 버튼 데이터 protected void RaiseOnButtonClicked(UTKToolBarButtonData data) { OnButtonClicked?.Invoke(data); } #endregion #region Theme /// /// 테마 변경 이벤트를 구독합니다. /// private void SubscribeToThemeChanges() { UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged; RegisterCallback(OnAttachToPanelForTheme); RegisterCallback(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 /// /// 리소스를 정리합니다. /// public virtual void Dispose() { if (_disposed) return; _disposed = true; // 데이터 바인딩 해제 UnbindData(); // 툴팁 해제 if (_rootButton != null) { UTKTooltipManager.Instance.DetachTooltip(_rootButton); } // 테마 구독 해제 UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; UnregisterCallback(OnAttachToPanelForTheme); UnregisterCallback(OnDetachFromPanelForTheme); // 클릭 이벤트 해제 if (_rootButton != null && _onClickCallback != null) { _rootButton.UnregisterCallback(_onClickCallback); } // 참조 정리 OnButtonClicked = null; _rootButton = null; _iconLabel = null; _textLabel = null; _onClickCallback = null; } #endregion } }