472 lines
22 KiB
C#
472 lines
22 KiB
C#
#nullable enable
|
|
using UnityEngine;
|
|
using UVC.Locale;
|
|
using UVC.UI.Commands;
|
|
using UVC.UI.Toolbar;
|
|
using UVC.UI.Toolbar.Model;
|
|
using UVC.UI.Toolbar.View;
|
|
|
|
namespace UVC.UI.ToolBar
|
|
{
|
|
/// <summary>
|
|
/// 툴바의 생성, 설정 및 관리를 담당하는 MonoBehaviour 컨트롤러 클래스입니다.
|
|
/// ToolbarModel(데이터)과 ToolbarView(UI 표현) 사이의 중재자 역할을 합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 주요 역할:
|
|
/// - 초기화(Awake, Start): 필요한 컴포넌트(특히 ToolbarView)를 찾거나 확인합니다.
|
|
/// - 모델 생성 및 설정(Start): ToolbarModel 인스턴스를 만들고,
|
|
/// AddStandardButton, AddRadioButton 등의 메서드를 사용해 툴바에 표시될 항목들을 정의하고 추가합니다.
|
|
/// - 뷰 초기화(Start): 설정된 ToolbarModel을 ToolbarView에 전달하여 UI를 렌더링하도록 합니다.
|
|
///
|
|
/// 이 클래스의 인스턴스는 Unity 씬 내의 GameObject에 컴포넌트로 추가되어야 합니다.
|
|
/// ToolbarView 컴포넌트도 동일한 GameObject 또는 그 자식에 존재해야 합니다.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <code>
|
|
/// // Unity 에디터에서 GameObject를 생성하고 이 Toolbox 스크립트를 추가합니다.
|
|
/// // 또한, ToolbarView 스크립트도 해당 GameObject 또는 자식 GameObject에 추가하고,
|
|
/// // ToolbarView의 프리팹 필드들(standardButtonPrefab 등)을 Inspector에서 할당해야 합니다.
|
|
///
|
|
/// // 이 스크립트의 Start 메서드 내에서 툴바 항목들이 정의됩니다.
|
|
/// // 예:
|
|
/// // mainToolbar.AddStandardButton("파일 열기", "icons/open", new ActionCommand(OpenFile), "파일을 엽니다.");
|
|
/// // private void OpenFile() { Debug.Log("파일 열기 기능 실행"); }
|
|
/// </code>
|
|
/// </example>
|
|
public class Toolbox : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// 툴바의 데이터 모델입니다. 툴바 항목들의 정보와 구조를 담고 있습니다.
|
|
/// protected로 선언되어 파생 클래스에서 접근 가능합니다.
|
|
/// </summary>
|
|
protected ToolbarModel model;
|
|
|
|
/// <summary>
|
|
/// 툴바의 UI 표현을 담당하는 뷰 컴포넌트입니다.
|
|
/// protected로 선언되어 파생 클래스에서 접근 가능합니다.
|
|
/// </summary>
|
|
protected ToolbarView view;
|
|
|
|
/// <summary>
|
|
/// 툴박스의 버튼이 클릭되거나 상태가 변경될 때 발생하는 이벤트입니다.
|
|
/// raiseEvent가 true일 때만 호출됩니다.
|
|
/// </summary>
|
|
public event System.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
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// MonoBehaviour의 Awake 메서드입니다.
|
|
/// 주로 현재 GameObject 또는 자식 GameObject에서 ToolbarView 컴포넌트를 찾아 mainToolbarView 필드에 할당합니다.
|
|
/// </summary>
|
|
protected void Awake()
|
|
{
|
|
// 1. 이 GameObject에 연결된 ToolbarView 컴포넌트를 찾습니다.
|
|
view = GetComponent<ToolbarView>();
|
|
|
|
// 2. 만약 현재 GameObject에 없다면, 자식 GameObject들 중에서 ToolbarView 컴포넌트를 찾습니다.
|
|
if (view == null)
|
|
{
|
|
view = GetComponentInChildren<ToolbarView>();
|
|
}
|
|
|
|
if (view == null)
|
|
{
|
|
Debug.LogError("Toolbox: ToolbarView 컴포넌트를 찾을 수 없습니다. GameObject에 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// MonoBehaviour의 Start 메서드입니다. 첫 번째 프레임 업데이트 전에 호출됩니다.
|
|
/// ToolbarModel을 생성하고, 다양한 툴바 항목들을 모델에 추가한 후,
|
|
/// 설정된 모델을 사용하여 ToolbarView를 초기화(UI 렌더링)합니다.
|
|
/// </summary>
|
|
private ToolbarModel generateModel()
|
|
{
|
|
// ToolbarModel 인스턴스 생성
|
|
var toolbarModel = new ToolbarModel();
|
|
|
|
// --- 툴바 모델 구성 시작 ---
|
|
|
|
// 컴포넌트 목록
|
|
toolbarModel.AddStandardButton("컴포넌트 목록",
|
|
"Prefabs/UI/Toolbar/images/ic_menu_elements",
|
|
new ActionCommand(() => Debug.Log("컴포넌트 목록 버튼 클릭됨")),
|
|
"컴포넌트 목록 창을 엽니다.");
|
|
|
|
// 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();
|
|
|
|
// 예시 : 확장 버튼 (브러시 크기 선택)
|
|
// AddExpandableButton으로 주 버튼을 만들고, 반환된 객체의 SubButtons 리스트에 하위 버튼들을 추가합니다.
|
|
var expandableBtnModel = toolbarModel.AddExpandableButton("button_brush_size", // 주 버튼 텍스트/키
|
|
"Prefabs/UI/Toolbar/images/ic_brush_default_white", // 주 버튼 기본 아이콘
|
|
new ActionCommand(() => Debug.Log("브러시 크기 주 버튼 클릭됨 (Command)")), // 주 버튼 자체의 커맨드
|
|
"붓 사이즈 선택 합니다."); // 주 버튼 툴팁
|
|
|
|
// 하위 버튼1: 작은 브러시 (ToolbarStandardButton 사용)
|
|
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); // 확장 버튼 모델에 하위 버튼 추가
|
|
|
|
// 하위 버튼2: 중간 브러시
|
|
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) =>
|
|
{
|
|
// LocalizationManager를 사용하여 텍스트를 현재 언어에 맞게 가져올 수 있습니다.
|
|
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("Toolbox: 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("Toolbox: 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("Toolbox: 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("Toolbox: 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("Toolbox: 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 토글 버튼의 현재 선택 상태를 가져옵니다.
|
|
/// </summary>
|
|
/// <param name="buttonText">상태를 확인할 토글 버튼의 텍스트입니다.</param>
|
|
/// <returns>토글 버튼의 선택 상태입니다. 버튼을 찾지 못하면 false를 반환합니다.</returns>
|
|
public bool GetToggleButtonState(string buttonText)
|
|
{
|
|
if (model == null)
|
|
{
|
|
Debug.LogError("Toolbox: ToolbarModel이 설정되지 않았습니다.");
|
|
return false;
|
|
}
|
|
var button = model.GetToggleButtonByText(buttonText);
|
|
return button?.IsSelected ?? false;
|
|
}
|
|
|
|
protected void OnDestroy()
|
|
{
|
|
model = null;
|
|
view = null;
|
|
}
|
|
|
|
}
|
|
}
|