using System; using UVC.UI.Commands; namespace UVC.UI.Toolbar.Model { /// /// 툴바에 사용되는 모든 버튼(표준 버튼, 토글 버튼, 확장 버튼 등)의 기본 추상 클래스입니다. /// IToolbarItem 인터페이스를 구현하여 툴바 시스템의 일부가 됩니다. /// 버튼이 가져야 할 공통적인 속성(예: 텍스트, 아이콘 경로, 활성화 상태, 툴팁 키)과 /// 기본적인 동작(예: 클릭 시 커맨드 실행, 상태 변경 알림)을 정의합니다. /// /// 이 클래스를 상속받아 특정 유형의 버튼(예: ToolbarStandardButton, ToolbarToggleButton)을 구현할 수 있습니다. /// /// /// /// // ToolbarButtonBase를 상속받는 사용자 정의 버튼 예시 /// public class MyCustomButton : ToolbarButtonBase /// { /// public string CustomProperty { get; set; } /// /// public MyCustomButton(string text, string iconPath, ICommand command, string tooltip = null) /// { /// Text = text; // 부모 클래스의 Text 속성 사용 /// IconSpritePath = iconPath; // 부모 클래스의 IconSpritePath 속성 사용 /// ClickCommand = command; // 부모 클래스의 ClickCommand 속성 사용 /// Tooltip = tooltip; // 부모 클래스의 Tooltip 속성 사용 /// IsEnabled = true; // 기본적으로 활성화 상태로 설정 /// } /// /// public override void ExecuteClick(object parameter = null) /// { /// if (!IsEnabled) return; // 비활성화 상태면 아무것도 하지 않음 /// /// Debug.Log($"MyCustomButton '{Text}' clicked!"); /// // 기본 클릭 로직 실행 (예: ClickCommand 실행) /// base.ExecuteClick(parameter); /// /// // 이 버튼만의 추가적인 로직 수행 /// // PerformCustomAction(); /// } /// } /// /// // 사용 예 /// // ICommand saveCommand = new ActionCommand(() => { Debug.Log("Save HandleClick triggered!"); }); /// // MyCustomButton saveButton = new MyCustomButton("Save", "icons/save_icon", saveCommand, "Save the current file"); /// // toolbarModel.AddItem(saveButton); /// /// public abstract class ToolbarButtonBase : IToolbarItem { /// /// 버튼의 상태(예: Text, IconSpritePath, IsEnabled)가 변경되었을 때 발생하는 이벤트입니다. /// View 레이어(예: ToolbarView)는 이 이벤트를 구독하여 버튼의 시각적 표현을 업데이트합니다. /// /// /// 속성(Text, IconSpritePath, IsEnabled)의 setter 내부에서 자동으로 호출됩니다. /// 여러 상태를 한 번에 변경한 후 수동으로 알리고 싶다면 NotifyStateChanged() 메서드를 사용할 수 있습니다. /// public event Action OnStateChanged; /// /// 버튼이 클릭되었을 때 발생하는 이벤트입니다. /// ExecuteClick 메서드가 호출될 때 발생합니다. /// public event Action OnClicked; protected string _text; /// /// 버튼에 표시될 텍스트 또는 텍스트의 다국어 키입니다. /// 값이 변경되면 OnStateChanged 이벤트가 발생합니다. /// /// /// /// button.Text = "새 작업"; // 직접 텍스트 설정 /// button.Text = "toolbar_button_new_task_key"; // 다국어 키 설정 (LocalizationManager가 이 키를 실제 텍스트로 변환) /// /// public string Text { get => _text; set { if (_text != value) { _text = value; OnStateChanged?.Invoke(); } } } protected string _iconSpritePath; /// /// 버튼에 표시될 아이콘의 Resources 폴더 내 경로입니다 (확장자 제외). /// 값이 변경되면 OnStateChanged 이벤트가 발생합니다. /// 아이콘이 없는 경우 null 또는 빈 문자열로 설정할 수 있습니다. /// /// /// /// button.IconSpritePath = "ToolbarIcons/OpenIcon"; // Resources/ToolbarIcons/OpenIcon.png (또는 다른 지원 형식) /// /// public string IconSpritePath { get => _iconSpritePath; set { if (_iconSpritePath != value) { _iconSpritePath = value; OnStateChanged?.Invoke(); } } } protected bool _isEnabled = true; /// /// 버튼의 활성화 상태를 나타냅니다. true이면 사용자와 상호작용할 수 있고, false이면 비활성화되어 상호작용할 수 없습니다. /// 값이 변경되면 OnStateChanged 이벤트가 발생합니다. /// /// /// /// button.IsEnabled = false; // 버튼 비활성화 /// if (button.IsEnabled) { /* 버튼 사용 가능 로직 */ } /// /// public bool IsEnabled { get => _isEnabled; set { if (_isEnabled != value) { _isEnabled = value; OnStateChanged?.Invoke(); } } } protected string _tooltip; /// /// 버튼에 마우스를 올렸을 때 표시될 툴팁의 텍스트 또는 다국어 키입니다. /// Tooltip 변경 시에는 기본적으로 OnStateChanged 이벤트가 발생하지 않지만, /// 필요에 따라 View에서 이 값을 직접 참조하여 툴팁을 업데이트할 수 있습니다. /// /// /// /// button.Tooltip = "tooltip_save_button"; // 다국어 키 사용 /// button.Tooltip = "클릭하여 문서를 저장합니다."; // 직접 텍스트 사용 /// /// public string Tooltip { get => _tooltip; set { if (_tooltip != value) { _tooltip = value; // Tooltip 변경 시 OnStateChanged를 호출할 필요는 일반적으로 없으나, // 만약 UI가 Tooltip 자체를 표시하는 등의 로직이 있다면 필요할 수 있습니다. // 여기서는 툴팁 내용이 동적으로 변경되는 경우가 적다고 가정하고 생략합니다. // 필요하다면 OnStateChanged?.Invoke(); 추가 } } } /// /// 버튼이 클릭되었을 때 실행될 동작을 정의하는 커맨드 객체입니다. /// ICommand 인터페이스를 구현한 객체를 할당합니다 (예: ActionCommand). /// /// /// /// // ActionCommand를 사용하여 간단한 동작 정의 /// button.ClickCommand = new ActionCommand(() => /// { /// Debug.Log($"'{button.Text}' 버튼이 클릭되었습니다."); /// // 특정 기능 수행 /// }); /// /// // 파라미터가 있는 커맨드 /// button.ClickCommand = new ActionCommand((fileName) => /// { /// Debug.Log($"파일 열기: {fileName}"); /// }); /// // ExecuteClick 메서드 호출 시 파라미터 전달 필요 /// // button.ExecuteClick("MyDocument.txt"); /// /// public ICommand ClickCommand { get; set; } /// /// 버튼 클릭 로직을 실행합니다. /// 이 메서드는 일반적으로 UI 시스템(예: Unity UI의 Button.onClick 이벤트)에 의해 호출되도록 설계됩니다. /// 버튼이 활성화(IsEnabled == true)되어 있고 ClickCommand가 할당되어 있다면, 해당 커맨드를 실행합니다. /// IUndoableCommand인 경우 UndoRedoManager를 통해 실행하여 Undo/Redo 히스토리에 기록됩니다. /// 파생 클래스에서 이 메서드를 재정의하여 특정 버튼 타입에 맞는 추가적인 클릭 동작을 구현할 수 있습니다. /// /// ClickCommand에 전달할 선택적 파라미터입니다. public virtual void ExecuteClick(object parameter = null) { if (!IsEnabled) return; if (ClickCommand != null) { // IUndoableCommand인 경우 UndoRedoManager를 통해 실행 if (ClickCommand is IUndoableCommand undoableCommand) { // UndoRedoManager가 존재하는지 확인 (Studio 씬에서만 사용 가능) var undoRedoManager = UnityEngine.Object.FindAnyObjectByType(); if (undoRedoManager != null) { undoRedoManager.ExecuteCommand(undoableCommand, parameter); } else { // UndoRedoManager가 없으면 직접 실행 ClickCommand.Execute(parameter); } } else { // 일반 ICommand는 직접 실행 ClickCommand.Execute(parameter); } } OnClicked?.Invoke(); // 클릭 이벤트 발생 } /// /// OnStateChanged 이벤트를 외부에서 강제로 발생시킵니다. /// 여러 속성을 변경한 후 한 번에 UI 업데이트를 트리거하거나, /// 내부 상태 변경이 속성 변경을 통하지 않고 발생했을 때 유용합니다. /// /// /// /// public void UpdateButtonAppearance(string newText, string newIcon) /// { /// _text = newText; // 직접 필드 값 변경 (setter의 OnStateChanged 호출 안됨) /// _iconSpritePath = newIcon; // 직접 필드 값 변경 /// NotifyStateChanged(); // 수동으로 상태 변경 알림 /// } /// /// public void NotifyStateChanged() { OnStateChanged?.Invoke(); } /// /// 이 버튼 모델에 연결된 모든 이벤트 핸들러를 정리(구독 해제)합니다. /// 주로 View가 파괴되거나 UI가 재생성될 때 호출되어 메모리 누수를 방지합니다. /// 파생 클래스에서는 이 메서드를 재정의하여 해당 클래스에 특화된 이벤트를 추가로 정리해야 합니다. /// public virtual void ClearEventHandlers() { OnStateChanged = null; OnClicked = null; } } }