#nullable enable
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UVC.Extension;
using UVC.Locale;
using UVC.UI.Toolbar.Model;
using UVC.UI.Tooltip;
using UVC.UI.Util;
using UVC.Util;
namespace UVC.UI.Toolbar.View
{
///
/// ToolbarModel에 정의된 데이터를 기반으로 실제 툴바 UI를 생성하고 관리하는 MonoBehaviour 클래스입니다.
/// 사용자의 UI 상호작용을 감지하여 모델의 상태를 변경하거나 커맨드를 실행하고,
/// 모델의 상태 변경에 따라 UI를 업데이트합니다.
///
///
/// 주요 역할:
/// - UI 렌더링: ToolbarModel의 Items 리스트를 순회하며 각 항목에 맞는 UI GameObject(버튼, 구분선 등)를 프리팹으로부터 생성하여 화면에 표시합니다.
/// - 이벤트 바인딩: 생성된 UI 요소(예: Unity Button의 onClick)와 ToolbarButtonBase 모델의 ExecuteClick 메서드를 연결합니다.
/// 또한, 모델의 OnStateChanged, OnToggleStateChanged 등의 이벤트를 구독하여 모델 상태 변경 시 UI를 업데이트합니다.
/// - UI 업데이트: 모델의 상태(텍스트, 아이콘, 활성화 상태, 선택 상태 등)가 변경되면 해당 UI 요소의 시각적 표현을 업데이트합니다.
/// - 리소스 관리: 생성된 UI GameObject들과 이벤트 구독을 관리하며, 툴바가 파괴되거나 다시 렌더링될 때 정리합니다(ClearToolbar).
/// - 하위 로직 위임: 라디오 버튼 그룹 관리는 `ToggleGroupManager`에, 확장 버튼의 하위 메뉴 관리는 `SubMenuHandler`에 위임하여 클래스의 복잡도를 낮춥니다.
///
/// 이 클래스의 인스턴스는 Unity 씬 내의 GameObject에 컴포넌트로 추가되어야 하며,
/// Inspector를 통해 필요한 프리팹들(standardButtonPrefab, toggleButtonPrefab 등)과
/// UI 요소들이 배치될 부모 Transform(toolbarContainer), LayoutGroup 등을 할당받아야 합니다.
///
///
///
/// // 1. Unity 에디터에서 빈 GameObject를 만들고 ToolbarView 스크립트를 추가합니다.
/// // 2. Inspector에서 ToolbarView 컴포넌트의 다음 필드들을 설정합니다:
/// // - Standard Button Prefab: 표준 버튼 UI 프리팹
/// // - Toggle Button Prefab: 토글 버튼 UI 프리팹
/// // - Radio Button Prefab: 라디오 버튼 UI 프리팹
/// // - Expandable Button Prefab: 확장 버튼 UI 프리팹
/// // - Separator Prefab: 구분선 UI 프리팹
/// // - Sub Menu Panel Prefab: 확장 버튼의 하위 메뉴 패널 UI 프리팹
/// // - Toolbar Container: 생성된 툴바 항목 UI들이 자식으로 추가될 Transform
/// // - Layout Group: Toolbar Container에 연결된 LayoutGroup (예: VerticalLayoutGroup)
///
/// // 3. 툴바를 제어하는 다른 스크립트(예: UIManager, ToolbarController)에서 아래와 같이 사용합니다.
/// public class MyToolbarController : MonoBehaviour
/// {
/// public ToolbarView toolbarView; // Inspector에서 ToolbarView 할당
///
/// void Start()
/// {
/// // 3-1. 툴바에 표시할 데이터 모델(ToolbarModel)을 생성하고 설정합니다.
/// ToolbarModel myModel = new ToolbarModel();
///
/// // 표준 버튼 추가
/// myModel.AddStandardButton("새 파일", "icons/new_file", null, "새 파일을 생성합니다.");
///
/// // 구분선 추가
/// myModel.AddSeparator();
///
/// // 토글 버튼 추가
/// myModel.AddToggleButton("그리드 보기", false, "icons/grid_on", "icons/grid_off",
/// (IsSelected) => Debug.Log($"그리드 표시: {IsSelected}"));
///
/// // 라디오 버튼 그룹 추가
/// string viewModeGroup = "ViewMode";
/// myModel.AddRadioButton(viewModeGroup, "2D 보기", true, "icons/view_2d");
/// myModel.AddRadioButton(viewModeGroup, "3D 보기", false, "icons/view_3d");
///
/// // 3-2. 설정된 모델을 ToolbarView에 전달하여 UI를 생성하도록 합니다.
/// toolbarView.Initialize(myModel);
/// }
/// }
///
///
public class ToolbarView : MonoBehaviour
{
///
/// 현재 툴바 UI의 기반이 되는 데이터 모델입니다.
/// Initialize 메서드를 통해 외부에서 주입받습니다.
///
protected ToolbarModel ToolbarModel { get; private set; }
// --- Inspector에서 할당할 프리팹 및 UI 요소들 ---
[Header("UI Prefabs")]
[Tooltip("표준 버튼 UI에 사용될 프리팹입니다.")]
public GameObject standardButtonPrefab;
[Tooltip("토글 버튼 UI에 사용될 프리팹입니다. IsOn=false로 설정해 놔야 합니다.")]
public GameObject toggleButtonPrefab;
[Tooltip("라디오 버튼 UI에 사용될 프리팹입니다. IsOn=false로 설정해 놔야 합니다.")]
public GameObject radioButtonPrefab;
[Tooltip("확장 가능한 버튼 UI에 사용될 프리팹입니다.")]
public GameObject expandableButtonPrefab;
[Tooltip("구분선 UI에 사용될 프리팹입니다.")]
public GameObject separatorPrefab;
[Tooltip("확장 버튼의 하위 메뉴 패널 UI에 사용될 프리팹입니다.")]
public GameObject subMenuPanelPrefab;
[Header("UI Layout")]
[Tooltip("생성된 툴바 항목 UI들이 자식으로 추가될 부모 Transform입니다.")]
public Transform toolbarContainer;
[Tooltip("툴바 항목들의 자동 배치를 담당하는 LayoutGroup 컴포넌트입니다 (예: VerticalLayoutGroup).")]
public LayoutGroup layoutGroup;
///
/// 툴바 버튼 모델(ToolbarButtonBase)과 해당 모델을 나타내는 실제 UI GameObject를 매핑하는 딕셔너리입니다.
/// 모델 상태 변경 시 해당 GameObject를 찾아 UI를 업데이트하거나, UI 정리 시 사용됩니다.
///
protected Dictionary _modelToGameObjectMap = new Dictionary();
///
/// 버튼 타입별 UI 처리기를 저장하는 딕셔너리입니다.
/// 각 버튼 타입(표준, 토글 등)에 맞는 IButtonViewProcessor 구현체를 등록하여 사용합니다.
///
private Dictionary _buttonViewProcessors = new Dictionary();
// --- 헬퍼 클래스 ---
private ToggleGroupManager _toggleGroupManager;
private SubMenuHandler _subMenuHandler;
private Canvas _canvas;
///
/// 렌더링 작업에 사용되는 정적 캔버스 인스턴스를 가져옵니다.
///
public Canvas Canvas
{
get
{
if (_canvas == null) _canvas = CanvasUtil.GetOrCreate("StaticCanvas");
return _canvas;
}
}
///
/// MonoBehaviour의 Awake 메서드입니다.
/// 필수 참조를 확인 및 초기화하고, 버튼 뷰 프로세서와 헬퍼 클래스들을 준비합니다.
///
protected virtual void Awake()
{
// 필수 프리팹들이 Inspector에서 할당되었는지 확인합니다.
if (standardButtonPrefab == null || toggleButtonPrefab == null || radioButtonPrefab == null ||
expandableButtonPrefab == null || separatorPrefab == null || subMenuPanelPrefab == null)
{
Debug.LogError("ToolbarView: 필수 프리팹이 할당되지 않았습니다. Inspector에서 모든 프리팹을 설정해주세요.", this);
}
// UI 컨테이너와 레이아웃 그룹을 자동으로 찾거나 설정합니다.
if (toolbarContainer == null) toolbarContainer = GetComponent();
if (toolbarContainer == null) toolbarContainer = GetComponentInChildren(true);
if (toolbarContainer == null) Debug.LogError("ToolbarView: toolbarContainer가 할당되지 않았습니다.", this);
if (layoutGroup == null && toolbarContainer != null)
{
layoutGroup = toolbarContainer.gameObject.GetComponent();
if (layoutGroup == null)
{
layoutGroup = toolbarContainer.gameObject.AddComponent();
Debug.LogWarning("ToolbarView: LayoutGroup이 toolbarContainer에 없어 새로 추가합니다. (기본 VerticalLayoutGroup)", this);
}
}
if (layoutGroup == null) Debug.LogError("ToolbarView: layoutGroup이 할당되지 않았습니다.", this);
// 각 버튼 타입에 대한 뷰 프로세서를 등록합니다.
_buttonViewProcessors[typeof(ToolbarStandardButton)] = new ToolbarStandardButtonViewProcessor();
_buttonViewProcessors[typeof(ToolbarToggleButton)] = new ToolbarToggleButtonViewProcessor();
_buttonViewProcessors[typeof(ToolbarRadioButton)] = new ToolbarRadioButtonViewProcessor();
_buttonViewProcessors[typeof(ToolbarExpandableButton)] = new ToolbarExpandableButtonViewProcessor();
// 헬퍼 클래스들을 초기화합니다.
_toggleGroupManager = new ToggleGroupManager(toolbarContainer);
_subMenuHandler = new SubMenuHandler(this);
}
///
/// 지정된 ToolbarModel을 사용하여 툴바 UI를 초기화하고 렌더링합니다.
/// 기존 UI가 있다면 정리(ClearToolbar)한 후, 새 모델에 따라 UI를 다시 생성(RenderToolbar)합니다.
///
/// 화면에 표시할 툴바의 데이터 모델입니다.
public virtual void Initialize(ToolbarModel toolbarModel)
{
this.ToolbarModel = toolbarModel;
// 필수 조건들을 다시 한 번 확인합니다.
if (standardButtonPrefab == null || toggleButtonPrefab == null || radioButtonPrefab == null ||
expandableButtonPrefab == null || separatorPrefab == null || subMenuPanelPrefab == null)
{
Debug.LogError("ToolbarView: 필수 프리팹이 할당되지 않았습니다.", this);
return;
}
if (toolbarContainer == null)
{
Debug.LogError("ToolbarView: Initialize 실패. toolbarContainer가 할당되지 않았습니다.", this);
return;
}
if (layoutGroup == null)
{
Debug.LogError("ToolbarView: Initialize 실패. layoutGroup이 할당되지 않았습니다.", this);
return;
}
if (this.ToolbarModel == null)
{
Debug.LogError("ToolbarView: Initialize 실패. 제공된 toolbarModel이 null입니다.", this);
ClearToolbar(); // 모델이 없으면 기존 UI라도 정리합니다.
return;
}
// 모든 조건이 충족되면 툴바 렌더링을 시작합니다.
RenderToolbar();
}
///
/// 현재 툴바에 표시된 모든 UI 요소들을 제거하고, 관련된 이벤트 구독을 해제합니다.
/// 툴바를 새로 그리거나 뷰가 파괴될 때 호출됩니다.
///
protected virtual void ClearToolbar()
{
if (_modelToGameObjectMap != null)
{
foreach (var pair in _modelToGameObjectMap)
{
if (pair.Key != null)
{
// 모델에 연결된 모든 이벤트 구독을 명시적으로 해제합니다.
pair.Key.ClearEventHandlers();
}
if (pair.Value != null)
{
// UI 컴포넌트의 이벤트 리스너를 명시적으로 해제합니다.
Toggle toggleComponent = pair.Value.GetComponent();
if (toggleComponent != null) toggleComponent.onValueChanged.RemoveAllListeners();
Button buttonComponent = pair.Value.GetComponent