Files
XRLib/Assets/Scripts/UVC/UI/ToolBar/Toolbar.cs
2025-12-24 18:43:51 +09:00

466 lines
20 KiB
C#

#nullable enable
using UnityEngine;
using UVC.Locale;
using UVC.UI.Commands;
using UVC.UI.Toolbar.Model;
using UVC.UI.Toolbar.View;
using System;
namespace UVC.UI.Toolbar
{
/// <summary>
/// 툴바 액션 타입을 나타내는 열거형입니다.
/// </summary>
public enum ToolbarActionType
{
/// <summary>
/// 일반 버튼 클릭
/// </summary>
Standard,
/// <summary>
/// 라디오 버튼 선택 변경
/// </summary>
Radio,
/// <summary>
/// 토글 버튼 상태 변경
/// </summary>
Toggle,
/// <summary>
/// 확장 버튼의 하위 버튼 선택 변경
/// </summary>
Expandable
}
/// <summary>
/// 툴바 액션 정보를 담는 클래스입니다.
/// </summary>
public class ToolbarActionEventArgs
{
/// <summary>
/// 액션을 발생시킨 툴바 아이템의 텍스트입니다.
/// </summary>
public string Text { get; set; }
/// <summary>
/// 액션에 따른 값입니다.
/// Standard: null
/// Radio: 선택된 항목의 텍스트 (전부 해제 시 null)
/// Toggle: true/false
/// Expandable: 선택된 하위 버튼의 텍스트
/// </summary>
public object? Value { get; set; }
/// <summary>
/// 액션 타입입니다.
/// </summary>
public ToolbarActionType ActionType { get; set; }
}
public class Toolbar : MonoBehaviour
{
protected ToolbarModel model;
protected ToolbarView view;
/// <summary>
/// 툴바의 버튼이 클릭되거나 상태가 변경될 때 발생하는 이벤트입니다.
/// raiseEvent가 true일 때만 호출됩니다.
/// </summary>
public event Action<ToolbarActionEventArgs>? OnAction;
/// <summary>
/// OnAction 이벤트를 발생시킵니다.
/// </summary>
/// <param name="text">액션을 발생시킨 툴바 아이템의 텍스트</param>
/// <param name="actionType">액션 타입</param>
/// <param name="value">액션에 따른 값 (Standard: null, Radio: 선택된 항목 텍스트 또는 null, Toggle: bool, Expandable: 선택된 하위 버튼 텍스트)</param>
protected void RaiseOnAction(string text, ToolbarActionType actionType, object? value = null)
{
OnAction?.Invoke(new ToolbarActionEventArgs
{
Text = text,
ActionType = actionType,
Value = value
});
}
protected void Awake()
{
// 1. 이 GameObject에 연결된 ToolbarView 컴포넌트를 찾습니다.
view = GetComponent<ToolbarView>();
// 2. 만약 현재 GameObject에 없다면, 자식 GameObject들 중에서 ToolbarView 컴포넌트를 찾습니다.
if (view == null)
{
view = GetComponentInChildren<ToolbarView>();
}
}
/// <summary>
/// 툴바 모델을 설정합니다.
/// </summary>
/// <param name="toolbarModel"></param>
public void SetData(ToolbarModel toolbarModel)
{
if (view == null)
{
Debug.LogError("ToolbarView가 할당되지 않았습니다.");
return;
}
model = toolbarModel;
}
/// <summary>
/// 툴바를 초기화합니다.
/// </summary>
public void Initialize()
{
if (view == null)
{
Debug.LogError("ToolbarView가 할당되지 않았습니다.");
return;
}
if (model == null)
{
Debug.LogError("ToolbarModel이 할당되지 않았습니다.");
return;
}
// 모든 버튼 타입의 이벤트 구독
SubscribeButtonEvents();
// ToolbarView 초기화 및 렌더링
view.Initialize(model);
}
/// <summary>
/// 모델 내의 모든 버튼에 대해 이벤트를 구독합니다.
/// </summary>
private void SubscribeButtonEvents()
{
if (model?.Items == null) return;
foreach (var item in model.Items)
{
switch (item)
{
case ToolbarRadioButton radioButton:
// 라디오 버튼의 선택 상태 변경 이벤트 구독
radioButton.OnToggleStateChanged += (isSelected) =>
{
if (isSelected)
{
RaiseOnAction(radioButton.GroupName, ToolbarActionType.Radio, radioButton.Text);
}
};
break;
case ToolbarToggleButton toggleButton:
// 토글 버튼의 상태 변경 이벤트 구독
toggleButton.OnToggleStateChanged += (isSelected) =>
{
RaiseOnAction(toggleButton.Text, ToolbarActionType.Toggle, isSelected);
};
break;
case ToolbarExpandableButton expandableButton:
// 확장 버튼의 하위 버튼 선택 변경 이벤트 구독
expandableButton.OnSubButtonSelectionChanged += (expandableText, selectedSubButtonText) =>
{
RaiseOnAction(expandableText, ToolbarActionType.Expandable, selectedSubButtonText);
};
break;
case ToolbarStandardButton standardButton:
// 표준 버튼의 클릭 이벤트 구독
standardButton.OnClicked += () =>
{
RaiseOnAction(standardButton.Text, ToolbarActionType.Standard, null);
};
break;
}
}
}
private ToolbarModel generateModel()
{
var toolbarModel = new ToolbarModel();
// --- 툴바 모델 구성 ---
// 컴포넌트 목록
toolbarModel.AddStandardButton("컴포넌트 목록",
"Prefabs/UI/Toolbar/images/ic_menu_elements",
new ActionCommand(() => {
// if (!ComponentListWindow.Instance.gameObject.activeSelf) ComponentListWindow.Instance.Show();
// var infos = ComponentList.GenerateData();
// ComponentListWindow.Instance.SetupData(infos);
}),
"컴포넌트 목록 창을 엽니다.");
// playback
toolbarModel.AddStandardButton("Playback",
"Prefabs/UI/Toolbar/images/ic_menu_playback",
new ActionCommand(() => Debug.Log("Playback 버튼 클릭됨")),
"Playback을 실행 시킵니다.");
// 화면 캡처
toolbarModel.AddStandardButton("button_capture_screen",
"Prefabs/UI/Toolbar/images/ic_menu_capture",
new ActionCommand(() => Debug.Log("화면 캡처 버튼 클릭됨")),
"tooltip_capture_screen");
// 화면 녹화 시작/중지 (ToggleButton)
toolbarModel.AddToggleButton("button_record_screen", false,
"Prefabs/UI/Toolbar/images/ic_menu_camera_on",
"Prefabs/UI/Toolbar/images/ic_menu_camera_off",
(isSelected) => Debug.Log($"화면 녹화 상태: {(isSelected ? " " : "")} (OnToggle 콜백)"),
new ActionCommand<bool>((isRecording) => Debug.Log($"화면 녹화 Command 실행: {(isRecording ? " " : " ")}")),
"tooltip_record_screen");
// 화면 확대
toolbarModel.AddStandardButton("화면 확대",
"Prefabs/UI/Toolbar/images/ic_menu_zoom_in",
new ActionCommand(() => Debug.Log("화면 확대 버튼 클릭됨")),
"화면을 한 단계 확대 합니다.");
//화면 축소
toolbarModel.AddStandardButton("화면 축소",
"Prefabs/UI/Toolbar/images/ic_menu_zoom_out",
new ActionCommand(() => Debug.Log("화면 축소 버튼 클릭됨")),
"화면을 한 단계 축소 합니다.");
// 구분선
toolbarModel.AddSeparator();
// RadioButtonGroup 샘플
toolbarModel.AddRadioButton("CameraControlGroup", "Top View", true,
"Prefabs/UI/Toolbar/images/ic_camera_top_on",
"Prefabs/UI/Toolbar/images/ic_camera_top_off_white",
(isSelected) => { if (isSelected) Debug.Log("탑뷰 카메라 선택됨"); },
new ActionCommand(() => Debug.Log("탑뷰 카메라 Command 실행")),
"Top View 시점으로 변경합니다.");
toolbarModel.AddRadioButton("CameraControlGroup", "Quarter View", false,
"Prefabs/UI/Toolbar/images/ic_camera_quarter_on",
"Prefabs/UI/Toolbar/images/ic_camera_quarter_off_white",
(isSelected) => { if (isSelected) Debug.Log("쿼터뷰 카메라 선택됨"); },
new ActionCommand(() => Debug.Log("쿼터뷰 카메라 Command 실행")),
"Quarter View 시점으로 변경합니다.");
toolbarModel.AddRadioButton("CameraControlGroup", "Front View", false,
"Prefabs/UI/Toolbar/images/ic_camera_top_on",
"Prefabs/UI/Toolbar/images/ic_camera_top_off_white",
(isSelected) => { if (isSelected) Debug.Log("프런트뷰 카메라 선택됨"); },
new ActionCommand(() => Debug.Log("프런트뷰 카메라 Command 실행")),
"Front View 시점으로 변경합니다.");
// 구분선
toolbarModel.AddSeparator();
// 기존 확장 버튼 (예시로 남겨두거나 필요에 따라 수정/제거)
var expandableBtnModel = toolbarModel.AddExpandableButton("button_brush_size",
"Prefabs/UI/Toolbar/images/ic_brush_default_white",
new ActionCommand(() => Debug.Log("브러시 크기 주 버튼 클릭됨 (Command)")),
"붓 사이즈 선택 합니다.");
var smallBrushCmd = new ActionCommand(() => Debug.Log($"작은 브러시 선택됨"));
var smallBrush = new ToolbarStandardButton
{
Text = "brush_size_small",
IconSpritePath = "Prefabs/UI/Toolbar/images/ic_brush_small_white",
Tooltip = "tooltip_brush_small",
ClickCommand = smallBrushCmd
};
expandableBtnModel.SubButtons.Add(smallBrush);
var mediumBrush = new ToolbarStandardButton
{
Text = "brush_size_medium",
IconSpritePath = "Prefabs/UI/Toolbar/images/ic_brush_medium_white",
Tooltip = "tooltip_brush_medium",
ClickCommand = new ActionCommand(() => Debug.Log("중간 브러시 선택됨 (Sub-Command 실행)"))
};
expandableBtnModel.SubButtons.Add(mediumBrush);
expandableBtnModel.OnSubButtonSelected = (selectedSubButtonModel) =>
{
string localizedSubButtonText = LocalizationManager.Instance != null ? LocalizationManager.Instance.GetString(selectedSubButtonModel.Text) : selectedSubButtonModel.Text;
Debug.Log($"브러시 크기 '{localizedSubButtonText}' 선택됨 (OnSubButtonSelected 콜백). 주 버튼 업데이트 로직 실행 가능.");
};
// --- 툴바 모델 구성 끝 ---
return toolbarModel;
}
/// <summary>
/// 지정된 그룹 내에서 특정 라디오 버튼을 선택하거나, 모든 선택을 해제합니다.
/// </summary>
/// <param name="groupName">라디오 버튼 그룹의 이름입니다.</param>
/// <param name="buttonToSelect">선택할 라디오 버튼입니다. null을 전달하면 그룹 내 모든 버튼의 선택이 해제됩니다.</param>
/// <param name="raiseEvent">true이면 OnAction 이벤트를 발생시키고, false이면 UI만 업데이트합니다. 기본값은 true입니다.</param>
/// <returns>작업 성공 여부입니다. 모델이 없거나 그룹이 존재하지 않으면 false를 반환합니다.</returns>
public bool SetRadioButtonSelection(string groupName, ToolbarRadioButton buttonToSelect, bool raiseEvent = false)
{
if (model == null)
{
Debug.LogError("Toolbar: ToolbarModel이 설정되지 않았습니다.");
return false;
}
bool result = model.SetRadioButtonSelection(groupName, buttonToSelect, raiseEvent);
if (result)
{
// Icon의 ImageColorChangeBehaviour 색상 상태 업데이트
if (view != null)
{
view.UpdateRadioGroupIconColors(groupName, buttonToSelect);
}
if (raiseEvent)
{
RaiseOnAction(groupName, ToolbarActionType.Radio, buttonToSelect?.Text);
}
}
return result;
}
/// <summary>
/// 그룹 이름으로 라디오 버튼 그룹 내 모든 선택을 해제합니다.
/// </summary>
/// <param name="groupName">라디오 버튼 그룹의 이름입니다.</param>
/// <param name="raiseEvent">true이면 OnAction 이벤트를 발생시키고, false이면 UI만 업데이트합니다. 기본값은 true입니다.</param>
/// <returns>작업 성공 여부입니다. 모델이 없거나 그룹이 존재하지 않으면 false를 반환합니다.</returns>
public bool ClearRadioButtonSelection(string groupName, bool raiseEvent = false)
{
if (model == null)
{
Debug.LogError("Toolbar: ToolbarModel이 설정되지 않았습니다.");
return false;
}
bool result = model.ClearRadioButtonSelection(groupName, raiseEvent);
if (result)
{
// Icon의 ImageColorChangeBehaviour 색상 상태 업데이트 (모두 비선택)
if (view != null)
{
view.UpdateRadioGroupIconColors(groupName, null);
}
if (raiseEvent)
{
RaiseOnAction(groupName, ToolbarActionType.Radio, null);
}
}
return result;
}
/// <summary>
/// 지정된 토글 버튼의 선택 상태를 설정합니다.
/// </summary>
/// <param name="toggleButton">상태를 변경할 토글 버튼입니다.</param>
/// <param name="isSelected">설정할 선택 상태입니다.</param>
/// <param name="raiseEvent">true이면 OnAction 이벤트를 발생시키고, false이면 UI만 업데이트합니다. 기본값은 true입니다.</param>
public void SetToggleButtonState(ToolbarToggleButton toggleButton, bool isSelected, bool raiseEvent = false)
{
if (model == null)
{
Debug.LogError("Toolbar: ToolbarModel이 설정되지 않았습니다.");
return;
}
model.SetToggleButtonState(toggleButton, isSelected, raiseEvent);
// Icon의 ImageColorChangeBehaviour 색상 상태 업데이트
if (view != null)
{
view.UpdateIconColorState(toggleButton, isSelected);
}
if (raiseEvent)
{
RaiseOnAction(toggleButton.Text, ToolbarActionType.Toggle, isSelected);
}
}
/// <summary>
/// 텍스트로 라디오 버튼을 찾아 선택 상태를 설정합니다.
/// </summary>
/// <param name="groupName">라디오 버튼 그룹의 이름입니다.</param>
/// <param name="buttonText">선택할 라디오 버튼의 텍스트입니다. null 또는 빈 문자열을 전달하면 그룹 내 모든 버튼의 선택이 해제됩니다.</param>
/// <param name="raiseEvent">true이면 OnAction 이벤트를 발생시키고, false이면 UI만 업데이트합니다. 기본값은 true입니다.</param>
/// <returns>작업 성공 여부입니다. 모델이 없거나 버튼을 찾지 못하면 false를 반환합니다.</returns>
public bool SetRadioButtonSelection(string groupName, string buttonText, bool raiseEvent = false)
{
if (model == null)
{
Debug.LogError("Toolbar: ToolbarModel이 설정되지 않았습니다.");
return false;
}
var button = model.SetRadioButtonSelectionByText(groupName, buttonText, raiseEvent);
bool result = button != null || string.IsNullOrEmpty(buttonText);
if (result)
{
// Icon의 ImageColorChangeBehaviour 색상 상태 업데이트
if (view != null)
{
view.UpdateRadioGroupIconColors(groupName, button);
}
if (raiseEvent)
{
RaiseOnAction(groupName, ToolbarActionType.Radio, button?.Text);
}
}
return result;
}
/// <summary>
/// 텍스트로 토글 버튼을 찾아 선택 상태를 설정합니다.
/// </summary>
/// <param name="buttonText">상태를 변경할 토글 버튼의 텍스트입니다.</param>
/// <param name="isSelected">설정할 선택 상태입니다.</param>
/// <param name="raiseEvent">true이면 OnAction 이벤트를 발생시키고, false이면 UI만 업데이트합니다. 기본값은 true입니다.</param>
/// <returns>작업 성공 여부입니다. 모델이 없거나 버튼을 찾지 못하면 false를 반환합니다.</returns>
public bool SetToggleButtonState(string buttonText, bool isSelected, bool raiseEvent = false)
{
if (model == null)
{
Debug.LogError("Toolbar: ToolbarModel이 설정되지 않았습니다.");
return false;
}
var button = model.SetToggleButtonStateByText(buttonText, isSelected, raiseEvent);
if (button != null)
{
// Icon의 ImageColorChangeBehaviour 색상 상태 업데이트
if (view != null)
{
view.UpdateIconColorState(button, isSelected);
}
if (raiseEvent)
{
RaiseOnAction(buttonText, ToolbarActionType.Toggle, isSelected);
}
}
return button != null;
}
protected void OnDestroy()
{
model = null;
view = null;
}
// System.Collections.IEnumerator TestModelChange(ToolbarStandardButton standard, ToolbarToggleButton toggle)
// {
// yield return new WaitForSeconds(2f);
// Debug.Log("모델 변경 테스트: 저장 버튼 비활성화 및 텍스트 변경");
// standard.Text = "저장됨";
// standard.IsEnabled = false;
//
// yield return new WaitForSeconds(2f);
// Debug.Log("모델 변경 테스트: 음소거 토글 상태 변경");
// toggle.IsSelected = true;
// }
}
}