Files

376 lines
22 KiB
C#

using UVC.UI.Commands;
namespace UVC.UI.Toolbar.Model
{
/// <summary>
/// 툴바의 전체적인 데이터 컨테이너 및 관리 클래스입니다.
/// 툴바에 표시될 모든 항목(IToolbarItem)들의 목록을 관리하며,
/// 각 항목의 생성 및 초기 설정을 위한 헬퍼 메서드들을 제공합니다.
/// 이 모델은 UI 표현(View)과 로직(Controller)으로부터 분리되어 툴바의 순수한 데이터 구조를 나타냅니다.
/// </summary>
/// <remarks>
/// 역할:
/// - 툴바 항목 저장: 툴바에 포함될 버튼, 구분선 등의 IToolbarItem 객체들을 리스트 형태로 보관합니다.
/// - 툴바 항목 생성: AddStandardButton, AddToggleButton 등의 팩토리 메서드를 통해 특정 타입의 툴바 항목을 쉽게 생성하고 모델에 추가할 수 있도록 합니다.
/// - 라디오 버튼 그룹 관리: 동일한 그룹 이름을 가진 ToolbarRadioButton들을 내부적으로 ToolbarRadioButtonGroup으로 묶어 관리합니다.
///
/// 사용 흐름:
/// 1. Toolbar (또는 유사한 관리 클래스)에서 ToolbarModel의 인스턴스를 생성합니다.
/// 2. 다양한 AddChild[ButtonType] 메서드 또는 AddItem 메서드를 호출하여 툴바 항목 모델들을 생성하고 ToolbarModel에 추가합니다.
/// 3. 설정이 완료된 ToolbarModel 객체를 ToolbarView의 Initialize 메서드에 전달하여 UI를 렌더링하도록 합니다.
/// </remarks>
/// <example>
/// <code>
/// // Toolbar 등에서 ToolbarModel 사용 예시
///
/// // 1. ToolbarModel 인스턴스 생성
/// ToolbarModel myToolbar = new ToolbarModel();
///
/// // 2. 표준 버튼 추가
/// ICommand openCommand = new ActionCommand(() => Debug.Log("파일 열기 실행"));
/// myToolbar.AddStandardButton("button_open_file", "icons/open", openCommand, "tooltip_open_file_description");
///
/// // 3. 구분선 추가
/// myToolbar.AddSeparator();
///
/// // 4. 토글 버튼 추가
/// myToolbar.AddToggleButton(
/// "button_toggle_grid",
/// false, // 초기 상태 (꺼짐)
/// "icons/grid_on", // 켜짐 상태 아이콘
/// "icons/grid_off", // 꺼짐 상태 아이콘
/// (IsSelected) => Debug.Log($"그리드 표시: {IsSelected}"), // OnToggle 콜백
/// null, // ClickCommand (필요시 설정)
/// "tooltip_toggle_grid_visibility"
/// );
///
/// // 5. 라디오 버튼 그룹 추가
/// string viewModeGroup = "ViewModeSelection";
/// myToolbar.AddRadioButton(viewModeGroup, "button_2d_view", true, "icons/2d_view", null,
/// (IsSelected) => { if(IsSelected) Debug.Log("2D 뷰 선택됨"); },
/// null, "tooltip_2d_view");
/// myToolbar.AddRadioButton(viewModeGroup, "button_3d_view", false, "icons/3d_view", null,
/// (IsSelected) => { if(IsSelected) Debug.Log("3D 뷰 선택됨"); },
/// null, "tooltip_3d_view");
///
/// // 6. 설정된 모델을 ToolbarView에 전달
/// // toolbarView.Initialize(myToolbar);
/// </code>
/// </example>
public class ToolbarModel
{
/// <summary>
/// 툴바에 추가된 모든 항목(IToolbarItem)들의 리스트입니다.
/// 이 리스트의 순서대로 툴바에 항목들이 표시됩니다.
/// 외부에서는 읽기만 가능하도록 private set으로 설정되어 있으며, 항목 추가는 AddItem 또는 AddChild[ButtonType] 메서드를 통해 이루어집니다.
/// </summary>
public System.Collections.Generic.List<IToolbarItem> Items { get; private set; }
/// <summary>
/// 라디오 버튼 그룹들을 관리하는 딕셔너리입니다.
/// 키는 라디오 버튼의 GroupName이며, 값은 해당 그룹을 관리하는 ToolbarRadioButtonGroup 객체입니다.
/// ToolbarRadioButton이 AddItem을 통해 추가될 때 내부적으로 사용됩니다.
/// </summary>
private System.Collections.Generic.Dictionary<string, ToolbarRadioButtonGroup> _radioGroups;
/// <summary>
/// ToolbarModel의 새 인스턴스를 초기화합니다.
/// Items 리스트와 _radioGroups 딕셔너리를 빈 상태로 생성합니다.
/// </summary>
public ToolbarModel()
{
Items = new System.Collections.Generic.List<IToolbarItem>();
_radioGroups = new System.Collections.Generic.Dictionary<string, ToolbarRadioButtonGroup>();
}
/// <summary>
/// 지정된 툴바 항목(IToolbarItem)을 모델에 추가합니다.
/// 추가된 항목은 Items 리스트의 끝에 추가됩니다.
/// 만약 추가되는 항목이 ToolbarRadioButton이라면, 해당 버튼을 적절한 ToolbarRadioButtonGroup에 등록하거나 새로 생성하여 관리합니다.
/// </summary>
/// <param name="item">툴바에 추가할 IToolbarItem 객체입니다 (예: ToolbarStandardButton, ToolbarSeparator 등).</param>
public void AddItem(IToolbarItem item)
{
Items.Add(item);
if (item is ToolbarRadioButton radioButton)
{
if (!_radioGroups.TryGetValue(radioButton.GroupName, out var group))
{
group = new ToolbarRadioButtonGroup();
_radioGroups.Add(radioButton.GroupName, group);
}
group.RegisterButton(radioButton);
radioButton.RadioGroup = group; // 버튼 모델에 그룹 참조 설정
// 그룹 내 초기 선택 상태 결정 (만약 이 버튼이 initialState=true이고, 그룹에 아직 선택된 버튼이 없다면)
// 또는 모든 버튼이 추가된 후 그룹별로 InitializeSelection()을 호출할 수도 있습니다.
// 현재는 AddRadioButton 메서드에서 initialState에 따른 SetSelected 호출로 처리하고 있습니다.
}
// UI 갱신 로직은 ToolbarModel 자체에서는 직접 호출하지 않습니다.
// View가 Model의 변경 사항을 구독하거나, Controller가 View의 업데이트를 트리거합니다.
// 혹은, Model에 아이템이 추가/제거될 때 이벤트를 발생시켜 View가 감지하도록 할 수 있습니다.
}
/// <summary>
/// 새로운 ToolbarStandardButton을 생성하여 툴바 모델에 추가합니다.
/// </summary>
/// <param name="text">버튼에 표시될 텍스트 또는 다국어 키입니다.</param>
/// <param name="iconSpritePath">버튼 아이콘의 Resources 경로입니다 (선택 사항).</param>
/// <param name="command">버튼 클릭 시 실행될 ICommand 객체입니다 (선택 사항).</param>
/// <param name="tooltip">버튼 툴팁의 텍스트 또는 다국어 키입니다 (선택 사항).</param>
/// <returns>생성되고 추가된 ToolbarStandardButton 객체입니다.</returns>
public ToolbarStandardButton AddStandardButton(string text, string iconSpritePath = null, ICommand command = null, string tooltip = null)
{
var button = new ToolbarStandardButton { Text = text, IconSpritePath = iconSpritePath, ClickCommand = command, Tooltip = tooltip };
AddItem(button);
return button;
}
/// <summary>
/// 새로운 ToolbarToggleButton을 생성하여 툴바 모델에 추가합니다.
/// </summary>
/// <param name="text">버튼 텍스트 또는 다국어 키입니다.</param>
/// <param name="initialState">버튼의 초기 선택 상태입니다 (기본값: false, 해제됨).</param>
/// <param name="onIconSpritePath">버튼이 선택(On) 상태일 때의 아이콘 경로입니다 (선택 사항).</param>
/// <param name="offIconSpritePath">버튼이 해제(Off) 상태일 때의 아이콘 경로입니다 (선택 사항).</param>
/// <param name="onToggle">버튼의 선택 상태가 변경될 때 호출될 콜백 함수입니다 (선택 사항).</param>
/// <param name="command">버튼 클릭 시 실행될 ICommand 객체입니다 (선택 사항).</param>
/// <param name="tooltip">버튼 툴팁의 텍스트 또는 다국어 키입니다 (선택 사항).</param>
/// <returns>생성되고 추가된 ToolbarToggleButton 객체입니다.</returns>
public ToolbarToggleButton AddToggleButton(string text, bool initialState = false, string onIconSpritePath = null, string offIconSpritePath = null, System.Action<bool> onToggle = null, ICommand command = null, string tooltip = null)
{
// ToolbarToggleButton의 IconSpritePath는 '선택된 상태'의 아이콘을 의미합니다.
var button = new ToolbarToggleButton
{
Text = text,
IsSelected = initialState,
IconSpritePath = onIconSpritePath, // 선택 시 아이콘
OffIconSpritePath = offIconSpritePath, // 해제 시 아이콘
OnToggle = onToggle,
ClickCommand = command,
Tooltip = tooltip
};
AddItem(button);
return button;
}
/// <summary>
/// 새로운 ToolbarRadioButton을 생성하여 툴바 모델에 추가합니다.
/// 동일한 groupName을 가진 라디오 버튼들은 하나의 그룹으로 동작합니다.
/// </summary>
/// <param name="groupName">이 라디오 버튼이 속할 그룹의 이름입니다.</param>
/// <param name="text">버튼 텍스트 또는 다국어 키입니다.</param>
/// <param name="initialState">버튼의 초기 선택 상태입니다. 같은 그룹 내에서 하나의 버튼만 true로 설정해야 합니다.</param>
/// <param name="iconSpritePath">버튼이 선택(On) 상태일 때의 아이콘 경로입니다 (선택 사항).</param>
/// <param name="offIconSpritePath">버튼이 해제(Off) 상태일 때의 아이콘 경로입니다 (선택 사항).</param>
/// <param name="onToggle">버튼의 선택 상태가 변경될 때 호출될 콜백 함수입니다 (선택 사항).</param>
/// <param name="command">버튼 클릭(선택) 시 실행될 ICommand 객체입니다 (선택 사항).</param>
/// <param name="tooltip">버튼 툴팁의 텍스트 또는 다국어 키입니다 (선택 사항).</param>
/// <returns>생성되고 추가된 ToolbarRadioButton 객체입니다.</returns>
public ToolbarRadioButton AddRadioButton(string groupName, string text, bool initialState = false, string iconSpritePath = null, string offIconSpritePath = null, System.Action<bool> onToggle = null, ICommand command = null, string tooltip = null)
{
var button = new ToolbarRadioButton(groupName)
{
Text = text,
IsSelected = initialState,// 초기 IsSelected 상태 설정
IconSpritePath = iconSpritePath,// 선택 시 아이콘
OffIconSpritePath = offIconSpritePath,// 해제 시 아이콘
OnToggle = onToggle,
ClickCommand = command,
Tooltip = tooltip
};
// AddItem 내에서 그룹 등록 및 버튼에 그룹 참조 설정이 이루어집니다.
AddItem(button);
// initialState가 true인 경우, 해당 그룹 내에서 이 버튼을 명시적으로 선택된 상태로 만듭니다.
// 이렇게 하면 여러 라디오 버튼 중 initialState가 true인 마지막 버튼이 최종 선택됩니다.
// 또는, 그룹별로 모든 버튼이 추가된 후 한 번만 선택 로직을 실행하는 것도 방법입니다.
if (initialState && _radioGroups.TryGetValue(groupName, out var group))
{
group.SetSelected(button);
}
return button;
}
/// <summary>
/// 새로운 ToolbarExpandableButton(확장 가능한 버튼)을 생성하여 툴바 모델에 추가합니다.
/// </summary>
/// <param name="text">주 버튼에 표시될 텍스트 또는 다국어 키입니다.</param>
/// <param name="iconSpritePath">주 버튼의 기본 아이콘 경로입니다 (선택 사항).</param>
/// <param name="command">주 버튼 클릭 시 실행될 ICommand 객체입니다 (선택 사항, 하위 메뉴 토글과는 별개일 수 있음).</param>
/// <param name="tooltip">주 버튼 툴팁의 텍스트 또는 다국어 키입니다 (선택 사항).</param>
/// <param name="updateIconOnSelection">서브 버튼 선택 시 주 버튼의 아이콘을 변경할지 여부입니다 (기본값: true).</param>
/// <returns>생성되고 추가된 ToolbarExpandableButton 객체입니다.</returns>
public ToolbarExpandableButton AddExpandableButton(string text, string iconSpritePath = null, ICommand command = null, string tooltip = null, bool updateIconOnSelection = true)
{
var button = new ToolbarExpandableButton { Text = text, IconSpritePath = iconSpritePath, ClickCommand = command, Tooltip = tooltip, UpdateIconOnSelection = updateIconOnSelection };
button.SetOriginalText(text); // 원본 텍스트 저장 (OnSubButtonSelectionChanged 이벤트에서 사용)
AddItem(button);
return button;
}
// <summary>
/// 새로운 ToolbarSeparator(구분선)를 생성하여 툴바 모델에 추가합니다.
/// </summary>
public void AddSeparator()
{
AddItem(new ToolbarSeparator());
}
/// <summary>
/// 지정된 이름의 라디오 버튼 그룹을 가져옵니다.
/// </summary>
/// <param name="groupName">라디오 버튼 그룹의 이름입니다.</param>
/// <returns>해당 이름의 ToolbarRadioButtonGroup 객체입니다. 존재하지 않으면 null을 반환합니다.</returns>
public ToolbarRadioButtonGroup GetRadioButtonGroup(string groupName)
{
if (_radioGroups.TryGetValue(groupName, out var group))
{
return group;
}
return null;
}
/// <summary>
/// 지정된 그룹 내에서 특정 라디오 버튼을 선택하거나, 모든 선택을 해제합니다.
/// </summary>
/// <param name="groupName">라디오 버튼 그룹의 이름입니다.</param>
/// <param name="buttonToSelect">선택할 라디오 버튼입니다. null을 전달하면 그룹 내 모든 버튼의 선택이 해제됩니다.</param>
/// <param name="raiseEvent">true이면 상태 변경 이벤트(OnToggle, OnStateChanged 등)를 발생시키고, false이면 이벤트 없이 상태만 변경합니다. 기본값은 true입니다.</param>
/// <returns>작업 성공 여부입니다. 그룹이 존재하지 않으면 false를 반환합니다.</returns>
public bool SetRadioButtonSelection(string groupName, ToolbarRadioButton buttonToSelect, bool raiseEvent = true)
{
if (!_radioGroups.TryGetValue(groupName, out var group))
{
UnityEngine.Debug.LogWarning($"SetRadioButtonSelection: 그룹 '{groupName}'을 찾을 수 없습니다.");
return false;
}
if (buttonToSelect == null)
{
group.ClearSelection(raiseEvent);
}
else
{
group.SetSelected(buttonToSelect, raiseEvent);
}
return true;
}
/// <summary>
/// 그룹 이름으로 라디오 버튼 그룹 내 모든 선택을 해제합니다.
/// </summary>
/// <param name="groupName">라디오 버튼 그룹의 이름입니다.</param>
/// <param name="raiseEvent">true이면 상태 변경 이벤트(OnToggle, OnStateChanged 등)를 발생시키고, false이면 이벤트 없이 상태만 변경합니다. 기본값은 true입니다.</param>
/// <returns>작업 성공 여부입니다. 그룹이 존재하지 않으면 false를 반환합니다.</returns>
public bool ClearRadioButtonSelection(string groupName, bool raiseEvent = true)
{
return SetRadioButtonSelection(groupName, null, raiseEvent);
}
/// <summary>
/// 지정된 토글 버튼의 선택 상태를 설정합니다.
/// </summary>
/// <param name="toggleButton">상태를 변경할 토글 버튼입니다.</param>
/// <param name="isSelected">설정할 선택 상태입니다.</param>
/// <param name="raiseEvent">true이면 상태 변경 이벤트(OnToggle, OnStateChanged 등)를 발생시키고, false이면 이벤트 없이 상태만 변경합니다. 기본값은 true입니다.</param>
public void SetToggleButtonState(ToolbarToggleButton toggleButton, bool isSelected, bool raiseEvent = true)
{
if (toggleButton == null)
{
UnityEngine.Debug.LogWarning("SetToggleButtonState: toggleButton이 null입니다.");
return;
}
toggleButton.SetSelected(isSelected, raiseEvent);
}
/// <summary>
/// 텍스트로 라디오 버튼을 찾아 선택 상태를 설정합니다.
/// </summary>
/// <param name="groupName">라디오 버튼 그룹의 이름입니다.</param>
/// <param name="buttonText">선택할 라디오 버튼의 텍스트입니다. null 또는 빈 문자열을 전달하면 그룹 내 모든 버튼의 선택이 해제됩니다.</param>
/// <param name="raiseEvent">true이면 상태 변경 이벤트를 발생시키고, false이면 이벤트 없이 상태만 변경합니다. 기본값은 true입니다.</param>
/// <returns>찾아서 선택한 라디오 버튼입니다. 그룹이 없거나 버튼을 찾지 못하면 null을 반환합니다.</returns>
public ToolbarRadioButton SetRadioButtonSelectionByText(string groupName, string buttonText, bool raiseEvent = true)
{
if (!_radioGroups.TryGetValue(groupName, out var group))
{
UnityEngine.Debug.LogWarning($"SetRadioButtonSelectionByText: 그룹 '{groupName}'을 찾을 수 없습니다.");
return null;
}
if (string.IsNullOrEmpty(buttonText))
{
group.ClearSelection(raiseEvent);
return null;
}
var button = group.FindButtonByText(buttonText);
if (button == null)
{
UnityEngine.Debug.LogWarning($"SetRadioButtonSelectionByText: 그룹 '{groupName}'에서 텍스트 '{buttonText}'인 버튼을 찾을 수 없습니다.");
return null;
}
group.SetSelected(button, raiseEvent);
return button;
}
/// <summary>
/// 텍스트로 토글 버튼을 찾아 선택 상태를 설정합니다.
/// </summary>
/// <param name="buttonText">상태를 변경할 토글 버튼의 텍스트입니다.</param>
/// <param name="isSelected">설정할 선택 상태입니다.</param>
/// <param name="raiseEvent">true이면 상태 변경 이벤트를 발생시키고, false이면 이벤트 없이 상태만 변경합니다. 기본값은 true입니다.</param>
/// <returns>찾아서 상태를 변경한 토글 버튼입니다. 찾지 못하면 null을 반환합니다.</returns>
public ToolbarToggleButton SetToggleButtonStateByText(string buttonText, bool isSelected, bool raiseEvent = true)
{
foreach (var item in Items)
{
if (item is ToolbarToggleButton toggleButton && toggleButton.Text == buttonText)
{
toggleButton.SetSelected(isSelected, raiseEvent);
return toggleButton;
}
}
UnityEngine.Debug.LogWarning($"SetToggleButtonStateByText: 텍스트 '{buttonText}'인 토글 버튼을 찾을 수 없습니다.");
return null;
}
/// <summary>
/// 텍스트로 토글 버튼을 찾아 반환합니다.
/// </summary>
/// <param name="buttonText">찾을 토글 버튼의 텍스트입니다.</param>
/// <returns>찾은 토글 버튼입니다. 찾지 못하면 null을 반환합니다.</returns>
public ToolbarToggleButton? GetToggleButtonByText(string buttonText)
{
foreach (var item in Items)
{
if (item is ToolbarToggleButton toggleButton && toggleButton.Text == buttonText)
{
return toggleButton;
}
}
return null;
}
// 만약 모든 라디오 버튼 그룹의 초기 선택 상태를 한 번에 설정하고 싶다면 다음과 같은 메서드를 추가할 수 있습니다.
// /// <summary>
// /// 모든 라디오 버튼 그룹에 대해 초기 선택 상태를 설정합니다.
// /// 각 그룹에서 IsSelected가 true로 설정된 버튼이 없다면 첫 번째 버튼을 선택합니다.
// /// </summary>
// public void FinalizeRadioGroupSelections()
// {
// foreach (var group in _radioGroups.Values)
// {
// group.InitializeSelection();
// }
// }
// 실제 UI 렌더링 및 상호작용 로직은 이 클래스 또는 별도의 UI View 클래스에서 처리됩니다.
// (예: Unity UI GameObject 생성, 이벤트 연결 등)
}
}