Files
EnglewoodLAB/Assets/Scripts/UVC/UI/ToolBar/Model/ToolbarExpandableButton.cs

224 lines
11 KiB
C#

using System;
using System.Collections.Generic;
// using UnityEngine.UI; // Image 사용을 위해 필요할 수 있으나, 모델 클래스에서는 직접적인 UI 참조를 최소화하는 것이 좋음
namespace UVC.UI.Toolbar.Model
{
/// <summary>
/// 클릭 시 하위 버튼 목록(서브 메뉴)을 표시하거나 숨길 수 있는 확장형 버튼입니다.
/// ToolbarButtonBase를 상속받아 기본적인 버튼 속성(텍스트, 아이콘, 커맨드 등)을 가집니다.
/// 주 버튼을 클릭하면 연결된 서브 메뉴가 표시되며, 서브 메뉴의 버튼을 선택하면
/// 주 버튼의 텍스트나 아이콘이 선택된 하위 버튼의 것으로 변경될 수 있습니다.
/// </summary>
/// <remarks>
/// 이 모델 클래스는 데이터와 상태를 관리합니다. 실제 서브 메뉴의 표시/숨김 및 UI 렌더링은
/// ToolbarView와 같은 View 클래스에서 처리됩니다.
/// </remarks>
/// <example>
/// <code>
/// // Toolbar 등에서 확장 버튼 생성 및 설정 예시
/// var brushToolButton = mainToolbar.AddExpandableButton(
/// "brush_tool_main", // 주 버튼 텍스트 (또는 다국어 키)
/// "icons/brush_default", // 주 버튼 기본 아이콘 경로
/// new ActionCommand(() => Debug.Log("브러시 주 버튼 클릭됨 (하위 메뉴 토글은 View에서 처리)")),
/// "tooltip_brush_tool" // 주 버튼 툴팁 키
/// );
///
/// // 하위 버튼1: 작은 브러시
/// var smallBrush = new ToolbarStandardButton
/// {
/// Text = "brush_small",
/// IconSpritePath = "icons/brush_small_icon",
/// Tooltip = "tooltip_brush_small",
/// ClickCommand = new ActionCommand(() => Debug.Log("작은 브러시 선택됨"))
/// };
/// brushToolButton.SubButtons.AddChild(smallBrush);
///
/// // 하위 버튼2: 중간 브러시
/// var mediumBrush = new ToolbarStandardButton
/// {
/// Text = "brush_medium",
/// IconSpritePath = "icons/brush_medium_icon",
/// Tooltip = "tooltip_brush_medium",
/// ClickCommand = new ActionCommand(() => Debug.Log("중간 브러시 선택됨"))
/// };
/// brushToolButton.SubButtons.AddChild(mediumBrush);
///
/// // 하위 버튼 선택 시 콜백 설정 (선택 사항)
/// brushToolButton.OnSubButtonSelected = (selectedSub) =>
/// {
/// Debug.Log($"하위 버튼 '{selectedSub.Text}' 선택됨. 주 버튼 아이콘/텍스트 업데이트됨.");
/// // 여기서 추가적인 로직 수행 가능 (예: 실제 브러시 크기 변경)
/// };
/// </code>
/// </example>
public class ToolbarExpandableButton : ToolbarButtonBase
{
/// <summary>
/// 이 확장 버튼에 속한 하위 버튼들의 목록입니다.
/// 이 목록에 있는 버튼들은 주 버튼 클릭 시 View에 의해 표시될 수 있습니다.
/// 각 하위 버튼은 ToolbarButtonBase 또는 그 파생 클래스의 인스턴스여야 합니다.
/// </summary>
public List<ToolbarButtonBase> SubButtons { get; private set; }
/// <summary>
/// 하위 버튼 중 하나가 선택되었을 때 호출되는 액션(콜백)입니다.
/// 선택된 하위 버튼 객체가 파라미터로 전달됩니다.
/// 주 버튼의 모양이 변경된 후, 추가적인 로직을 수행하고자 할 때 사용합니다.
/// </summary>
/// <remarks>
/// 이 콜백은 SelectSubButton 메서드 내에서, 주 버튼의 Text/IconSpritePath가 업데이트된 후 호출됩니다.
/// </remarks>
public Action<ToolbarButtonBase> OnSubButtonSelected { get; set; }
/// <summary>
/// 하위 버튼 선택 변경 시 발생하는 이벤트입니다.
/// 확장 버튼의 텍스트와 선택된 하위 버튼의 텍스트가 파라미터로 전달됩니다.
/// Toolbar/Toolbox에서 OnAction 이벤트를 발생시키기 위해 구독합니다.
/// </summary>
public event Action<string, string> OnSubButtonSelectionChanged;
/// <summary>
/// 현재 선택된 하위 버튼을 저장합니다.
/// 동일한 버튼이 다시 선택될 때 이벤트 중복 발생을 방지합니다.
/// </summary>
private ToolbarButtonBase _selectedSubButton;
/// <summary>
/// 현재 선택된 하위 버튼을 가져옵니다.
/// </summary>
public ToolbarButtonBase SelectedSubButton => _selectedSubButton;
/// <summary>
/// AddExpandableButton에서 설정한 원본 텍스트를 저장합니다.
/// OnSubButtonSelectionChanged 이벤트 발생 시 이 값이 Text로 전달됩니다.
/// </summary>
private string _originalText;
/// <summary>
/// AddExpandableButton에서 설정한 원본 텍스트를 가져옵니다.
/// </summary>
public string OriginalText => _originalText;
/// <summary>
/// 서브 버튼 선택 시 주 버튼의 아이콘을 선택된 서브 버튼의 아이콘으로 변경할지 여부입니다.
/// true이면 아이콘이 변경되고, false이면 원래 아이콘을 유지합니다.
/// 기본값은 true입니다.
/// </summary>
public bool UpdateIconOnSelection { get; set; } = false;
/// <summary>
/// ToolbarExpandableButton의 새 인스턴스를 초기화합니다.
/// SubButtons 리스트를 빈 리스트로 생성합니다.
/// </summary>
public ToolbarExpandableButton()
{
SubButtons = new List<ToolbarButtonBase>();
}
/// <summary>
/// 원본 텍스트를 설정합니다. AddExpandableButton에서 호출됩니다.
/// </summary>
/// <param name="text">원본 텍스트</param>
public void SetOriginalText(string text)
{
_originalText = text;
}
/// <summary>
/// 주 확장 버튼이 클릭되었을 때의 로직을 실행합니다.
/// 기본적으로 부모 클래스(ToolbarButtonBase)의 ExecuteClick을 호출하여
/// 할당된 ClickCommand를 실행합니다.
/// </summary>
/// <remarks>
/// 하위 메뉴를 실제로 열고 닫는 동작은 View 레이어(예: ToolbarView)에서 이 버튼의 클릭 이벤트를 감지하여
/// 별도로 처리하는 것이 일반적입니다. 이 메서드는 주 버튼 자체의 커맨드 실행에 중점을 둡니다.
/// </remarks>
/// <param name="parameter">ClickCommand에 전달할 파라미터입니다.</param>
public override void ExecuteClick(object parameter = null)
{
if (!IsEnabled) return;
// 주 버튼 자체에 할당된 ClickCommand 실행 (예: 특정 모드 진입, 상태 변경 등)
base.ExecuteClick(parameter);
// 하위 메뉴를 여는 시각적 동작은 View(예: ToolbarView)에서 이 버튼의 UI 클릭 이벤트를 받아 처리합니다.
// 예를 들어, ToolbarView에서는 이 ExecuteClick() 호출 후, ToggleSubMenu(this)와 같은 메서드를 호출할 수 있습니다.
}
/// <summary>
/// 지정된 하위 버튼을 "선택된" 상태로 처리하고, 주 버튼의 모양(텍스트, 아이콘)을
/// 선택된 하위 버튼의 것으로 업데이트합니다.
/// OnSubButtonSelected 콜백이 설정되어 있다면 호출합니다.
/// </summary>
/// <param name="selectedSubButton">선택된 하위 버튼 객체입니다. ToolbarButtonBase 또는 그 파생 타입이어야 합니다.</param>
/// <param name="buttonObj">
/// [주의] 이 파라미터는 View 레이어의 GameObject를 참조하며, 모델 클래스 설계 원칙에 따르면
/// 모델이 View 객체를 직접 참조하는 것은 바람직하지 않을 수 있습니다.
/// 현재 코드(ToolbarExpandableButton.cs의 SelectSubButton)에서는 아이콘 즉시 로드를 위해 사용되고 있으나,
/// 이상적으로는 아이콘 업데이트도 OnStateChanged 이벤트를 통해 View에서 처리하는 것이 좋습니다.
/// 이 파라미터는 향후 리팩토링 대상이 될 수 있습니다. 현재는 기존 코드 구조를 유지합니다.
/// </param>
public void SelectSubButton(ToolbarButtonBase selectedSubButton)
{
if (selectedSubButton != null && selectedSubButton.IsEnabled)
{
// 동일한 버튼이 다시 선택된 경우 이벤트를 발생시키지 않음
if (_selectedSubButton == selectedSubButton)
{
return;
}
// 현재 선택된 하위 버튼 업데이트
_selectedSubButton = selectedSubButton;
// UpdateIconOnSelection이 true일 때만 주 버튼의 텍스트와 아이콘을 변경
if (UpdateIconOnSelection)
{
// 주 버튼의 텍스트를 선택된 하위 버튼의 텍스트로 변경
// Text 속성의 setter는 내부적으로 OnStateChanged를 호출하여 View 업데이트를 트리거합니다.
if (Text != selectedSubButton.Text)
{
Text = selectedSubButton.Text;
}
// 주 버튼의 아이콘 경로를 선택된 하위 버튼의 아이콘 경로로 변경
// IconSpritePath 속성의 setter는 내부적으로 OnStateChanged를 호출합니다.
if (IconSpritePath != selectedSubButton.IconSpritePath)
{
IconSpritePath = selectedSubButton.IconSpritePath;
}
}
// 하위 버튼 선택 콜백 호출
OnSubButtonSelected?.Invoke(selectedSubButton);
// 하위 버튼 선택 변경 이벤트 발생 (Toolbar/Toolbox에서 OnAction 이벤트 발생용)
// _originalText는 AddExpandableButton에서 설정한 원본 텍스트
OnSubButtonSelectionChanged?.Invoke(_originalText, selectedSubButton.Text);
// 선택된 하위 버튼 자체의 ClickCommand 실행은 여기서 하지 않습니다.
// View에서 하위 버튼 UI 클릭 시 해당 하위 버튼의 ExecuteClick()이 직접 호출되는 것이 일반적입니다.
// 만약 여기서 실행해야 한다면: selectedSubButton.ExecuteClick();
}
}
/// <summary>
/// 이 버튼 모델 및 모든 하위 버튼 모델에 연결된 이벤트 핸들러를 정리합니다.
/// </summary>
public override void ClearEventHandlers()
{
base.ClearEventHandlers(); // 부모 클래스의 이벤트 정리 (OnStateChanged)
OnSubButtonSelected = null;
OnSubButtonSelectionChanged = null;
if (SubButtons != null)
{
foreach (var subButton in SubButtons)
{
subButton?.ClearEventHandlers(); // 각 하위 버튼의 이벤트 핸들러도 정리
}
}
}
}
}