508 lines
25 KiB
C#
508 lines
25 KiB
C#
#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
|
|
{
|
|
/// <summary>
|
|
/// ToolbarModel에 정의된 데이터를 기반으로 실제 툴바 UI를 생성하고 관리하는 MonoBehaviour 클래스입니다.
|
|
/// 사용자의 UI 상호작용을 감지하여 모델의 상태를 변경하거나 커맨드를 실행하고,
|
|
/// 모델의 상태 변경에 따라 UI를 업데이트합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 주요 역할:
|
|
/// - 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 등을 할당받아야 합니다.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 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);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public class ToolbarView : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// 현재 툴바 UI의 기반이 되는 데이터 모델입니다.
|
|
/// Initialize 메서드를 통해 외부에서 주입받습니다.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// 툴바 버튼 모델(ToolbarButtonBase)과 해당 모델을 나타내는 실제 UI GameObject를 매핑하는 딕셔너리입니다.
|
|
/// 모델 상태 변경 시 해당 GameObject를 찾아 UI를 업데이트하거나, UI 정리 시 사용됩니다.
|
|
/// </summary>
|
|
protected Dictionary<ToolbarButtonBase, GameObject> _modelToGameObjectMap = new Dictionary<ToolbarButtonBase, GameObject>();
|
|
|
|
/// <summary>
|
|
/// 버튼 타입별 UI 처리기를 저장하는 딕셔너리입니다.
|
|
/// 각 버튼 타입(표준, 토글 등)에 맞는 IButtonViewProcessor 구현체를 등록하여 사용합니다.
|
|
/// </summary>
|
|
private Dictionary<System.Type, IButtonViewProcessor> _buttonViewProcessors = new Dictionary<System.Type, IButtonViewProcessor>();
|
|
|
|
|
|
// --- 헬퍼 클래스 ---
|
|
private ToggleGroupManager _toggleGroupManager;
|
|
private SubMenuHandler _subMenuHandler;
|
|
|
|
private Canvas _canvas;
|
|
/// <summary>
|
|
/// 렌더링 작업에 사용되는 정적 캔버스 인스턴스를 가져옵니다.
|
|
/// </summary>
|
|
public Canvas Canvas
|
|
{
|
|
get
|
|
{
|
|
if (_canvas == null) _canvas = CanvasUtil.GetOrCreate("StaticCanvas");
|
|
return _canvas;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// MonoBehaviour의 Awake 메서드입니다.
|
|
/// 필수 참조를 확인 및 초기화하고, 버튼 뷰 프로세서와 헬퍼 클래스들을 준비합니다.
|
|
/// </summary>
|
|
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<Transform>();
|
|
if (toolbarContainer == null) toolbarContainer = GetComponentInChildren<Transform>(true);
|
|
if (toolbarContainer == null) Debug.LogError("ToolbarView: toolbarContainer가 할당되지 않았습니다.", this);
|
|
|
|
if (layoutGroup == null && toolbarContainer != null)
|
|
{
|
|
layoutGroup = toolbarContainer.gameObject.GetComponent<LayoutGroup>();
|
|
if (layoutGroup == null)
|
|
{
|
|
layoutGroup = toolbarContainer.gameObject.AddComponent<VerticalLayoutGroup>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정된 ToolbarModel을 사용하여 툴바 UI를 초기화하고 렌더링합니다.
|
|
/// 기존 UI가 있다면 정리(ClearToolbar)한 후, 새 모델에 따라 UI를 다시 생성(RenderToolbar)합니다.
|
|
/// </summary>
|
|
/// <param name="toolbarModel">화면에 표시할 툴바의 데이터 모델입니다.</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 툴바에 표시된 모든 UI 요소들을 제거하고, 관련된 이벤트 구독을 해제합니다.
|
|
/// 툴바를 새로 그리거나 뷰가 파괴될 때 호출됩니다.
|
|
/// </summary>
|
|
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<Toggle>();
|
|
if (toggleComponent != null) toggleComponent.onValueChanged.RemoveAllListeners();
|
|
|
|
Button buttonComponent = pair.Value.GetComponent<Button>();
|
|
if (buttonComponent != null) buttonComponent.onClick.RemoveAllListeners();
|
|
|
|
TooltipHandler handler = pair.Value.GetComponent<TooltipHandler>();
|
|
if (handler != null)
|
|
{
|
|
handler.OnPointerEnterAction = null;
|
|
handler.OnPointerExitAction = null;
|
|
}
|
|
// UI GameObject를 파괴합니다.
|
|
Destroy(pair.Value);
|
|
}
|
|
}
|
|
_modelToGameObjectMap.Clear();
|
|
}
|
|
|
|
// 헬퍼 클래스들이 관리하던 리소스도 정리합니다.
|
|
_toggleGroupManager?.Clear();
|
|
_subMenuHandler?.CloseSubMenu();
|
|
|
|
// 툴팁이 화면에 남아있을 경우를 대비해 숨깁니다.
|
|
if (TooltipManager.Instance != null && TooltipManager.Instance.IsInitialized)
|
|
{
|
|
TooltipManager.Instance.HideTooltip();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정된 버튼 모델 타입에 맞는 IButtonViewProcessor를 반환합니다.
|
|
/// </summary>
|
|
/// <param name="buttonModelType">버튼 모델의 System.Type 객체입니다.</param>
|
|
/// <returns>해당 타입의 처리기. 없으면 null을 반환합니다.</returns>
|
|
internal IButtonViewProcessor GetButtonViewProcessor(System.Type buttonModelType)
|
|
{
|
|
if (_buttonViewProcessors.TryGetValue(buttonModelType, out var processor))
|
|
{
|
|
return processor;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// ToolbarModel에 정의된 항목들을 기반으로 실제 UI 요소들을 생성하여 툴바를 구성합니다.
|
|
/// </summary>
|
|
protected void RenderToolbar()
|
|
{
|
|
ClearToolbar(); // 기존 UI 및 이벤트 구독 정리
|
|
|
|
if (ToolbarModel == null || ToolbarModel.Items == null) return;
|
|
if (toolbarContainer == null)
|
|
{
|
|
Debug.LogError("ToolbarView: RenderToolbar 중단. toolbarContainer가 null입니다.", this);
|
|
return;
|
|
}
|
|
|
|
// 모델의 각 아이템을 순회하며 UI를 생성합니다.
|
|
foreach (var itemModel in ToolbarModel.Items)
|
|
{
|
|
GameObject? itemUIObject = null;
|
|
|
|
if (itemModel is ToolbarSeparator)
|
|
{
|
|
// 구분선 모델인 경우
|
|
if (separatorPrefab != null) itemUIObject = Instantiate(separatorPrefab, toolbarContainer);
|
|
else Debug.LogError("ToolbarView: separatorPrefab이 할당되지 않았습니다.", this);
|
|
}
|
|
else if (itemModel is ToolbarButtonBase buttonModel)
|
|
{
|
|
// 버튼 모델인 경우
|
|
IButtonViewProcessor processor = GetButtonViewProcessor(buttonModel.GetType());
|
|
if (processor != null)
|
|
{
|
|
// 해당 버튼 타입의 프로세서를 사용하여 UI를 생성하고 설정합니다.
|
|
itemUIObject = processor.CreateButtonUI(buttonModel, toolbarContainer, this);
|
|
if (itemUIObject != null)
|
|
{
|
|
_modelToGameObjectMap[buttonModel] = itemUIObject;
|
|
processor.SetupButtonInteractions(buttonModel, itemUIObject, this);
|
|
|
|
// 모델의 상태 변경 이벤트를 구독하여 UI를 자동으로 업데이트하도록 합니다.
|
|
buttonModel.OnStateChanged += () => processor.UpdateCommonButtonVisuals(buttonModel, itemUIObject, this);
|
|
|
|
if (buttonModel is ToolbarToggleButton toggleModel)
|
|
{
|
|
toggleModel.OnToggleStateChanged += (isSelected) => processor.UpdateToggleStateVisuals(toggleModel, itemUIObject, isSelected, this);
|
|
}
|
|
|
|
// 툴팁 설정
|
|
if (!string.IsNullOrEmpty(buttonModel.Tooltip))
|
|
{
|
|
TooltipHandler tooltipHandler = itemUIObject.GetComponent<TooltipHandler>() ?? itemUIObject.AddComponent<TooltipHandler>();
|
|
tooltipHandler.Tooltip = buttonModel.Tooltip;
|
|
if (TooltipManager.Instance != null && TooltipManager.Instance.IsInitialized)
|
|
{
|
|
tooltipHandler.OnPointerEnterAction = TooltipManager.Instance.HandlePointerEnter;
|
|
tooltipHandler.OnPointerExitAction = TooltipManager.Instance.HandlePointerExit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"ToolbarView: {buttonModel.GetType().Name}의 UI를 생성하지 못했습니다 (Processor 반환 null).", this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"ToolbarView: {buttonModel.GetType().Name}에 대한 프리팹이 할당되지 않았거나 알 수 없는 버튼 타입입니다.", this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"ToolbarView: 알 수 없는 IToolbarItem 타입 ({itemModel.GetType().Name})입니다.", this);
|
|
}
|
|
|
|
if (itemUIObject != null)
|
|
{
|
|
itemUIObject.name = $"ToolbarItem_{itemModel.GetType().Name}_{(itemModel is ToolbarButtonBase b ? b.Text : "")}";
|
|
}
|
|
}
|
|
|
|
// 모든 UI 항목이 추가된 후 레이아웃을 강제로 업데이트합니다.
|
|
if (layoutGroup != null)
|
|
{
|
|
layoutGroup.FitToChildren(width: layoutGroup is VerticalLayoutGroup, height: true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 버튼 모델과 UI GameObject를 기반으로 공통적인 시각적 요소(텍스트, 아이콘, 활성화 상태)를 업데이트합니다.
|
|
/// 이 메서드는 각 IButtonViewProcessor 구현체에서 호출됩니다.
|
|
/// </summary>
|
|
/// <param name="model">업데이트할 버튼의 데이터 모델입니다.</param>
|
|
/// <param name="itemObj">업데이트할 UI GameObject입니다.</param>
|
|
internal void InternalUpdateCommonButtonVisuals(ToolbarButtonBase model, GameObject itemObj)
|
|
{
|
|
if (model == null || itemObj == null) return;
|
|
|
|
// 1. 텍스트 업데이트 (다국어 지원 포함)
|
|
TextMeshProUGUI buttonTextComponent = itemObj.GetComponentInChildren<TextMeshProUGUI>(true);
|
|
if (buttonTextComponent != null)
|
|
{
|
|
string displayText = model.Text;
|
|
if (LocalizationManager.Instance != null && !string.IsNullOrEmpty(model.Text))
|
|
{
|
|
displayText = LocalizationManager.Instance.GetString(model.Text, model.Text);
|
|
}
|
|
buttonTextComponent.text = displayText;
|
|
}
|
|
|
|
// 2. 아이콘 업데이트
|
|
Image iconImageComponent = itemObj.transform.Find("Icon")?.GetComponent<Image>() ?? itemObj.GetComponentInChildren<Image>(true);
|
|
if (iconImageComponent != null)
|
|
{
|
|
string iconPathToLoad = (model is ToolbarToggleButton toggleButton && !toggleButton.IsSelected)
|
|
? toggleButton.OffIconSpritePath
|
|
: model.IconSpritePath;
|
|
|
|
if (!string.IsNullOrEmpty(iconPathToLoad))
|
|
{
|
|
Sprite iconSprite = LoadSpriteFromResources(iconPathToLoad);
|
|
iconImageComponent.sprite = iconSprite;
|
|
iconImageComponent.enabled = (iconSprite != null);
|
|
}
|
|
else
|
|
{
|
|
iconImageComponent.sprite = null;
|
|
iconImageComponent.enabled = false;
|
|
}
|
|
}
|
|
|
|
// 3. 활성화 상태(interactable) 업데이트
|
|
Selectable selectableComponent = itemObj.GetComponent<Selectable>();
|
|
if (selectableComponent != null)
|
|
{
|
|
selectableComponent.interactable = model.IsEnabled;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 라디오 버튼 그룹 이름에 해당하는 ToggleGroup을 가져오거나 생성합니다. (ToggleGroupManager에 위임)
|
|
/// </summary>
|
|
internal ToggleGroup GetOrCreateToggleGroup(string groupName)
|
|
{
|
|
return _toggleGroupManager.GetOrCreateToggleGroup(groupName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 확장 버튼의 하위 메뉴를 토글합니다. (SubMenuHandler에 위임)
|
|
/// </summary>
|
|
internal void ToggleSubMenu(ToolbarExpandableButton expandableButtonModel, GameObject expandableButtonObj)
|
|
{
|
|
_subMenuHandler.ToggleSubMenu(expandableButtonModel, expandableButtonObj);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정된 경로에서 Sprite를 로드합니다. Sprite 파일은 Resources 폴더 또는 그 하위 폴더에 있어야 합니다.
|
|
/// </summary>
|
|
/// <param name="spritePath">Resources 폴더 기준의 Sprite 경로입니다 (확장자 제외).</param>
|
|
/// <returns>로드된 Sprite 객체. 실패 시 null을 반환합니다.</returns>
|
|
protected Sprite LoadSpriteFromResources(string spritePath)
|
|
{
|
|
if (string.IsNullOrEmpty(spritePath)) return null;
|
|
|
|
Sprite loadedSprite = Resources.Load<Sprite>(spritePath);
|
|
if (loadedSprite == null)
|
|
{
|
|
Debug.LogError($"ToolbarView: Resources 폴더에서 '{spritePath}' 경로의 Sprite를 로드할 수 없습니다.", this);
|
|
}
|
|
return loadedSprite;
|
|
}
|
|
|
|
/// <summary>
|
|
/// MonoBehaviour의 Update 메서드입니다. 매 프레임 호출됩니다.
|
|
/// SubMenuHandler의 Update를 호출하여 하위 메뉴 외부 클릭 감지 등을 처리합니다.
|
|
/// </summary>
|
|
protected virtual void Update()
|
|
{
|
|
_subMenuHandler?.Update();
|
|
}
|
|
|
|
/// <summary>
|
|
/// MonoBehaviour의 OnDestroy 메서드입니다. 이 컴포넌트 또는 GameObject가 파괴될 때 호출됩니다.
|
|
/// 툴바 UI를 정리(ClearToolbar)하여 메모리 누수를 방지합니다.
|
|
/// </summary>
|
|
protected virtual void OnDestroy()
|
|
{
|
|
ClearToolbar();
|
|
|
|
if (TooltipManager.Instance != null && TooltipManager.Instance.IsInitialized)
|
|
{
|
|
TooltipManager.Instance.HideTooltip();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 버튼 모델에 해당하는 UI GameObject의 Icon 자식에 있는 ImageColorChangeBehaviour의 선택 상태를 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="buttonModel">상태를 업데이트할 버튼 모델입니다.</param>
|
|
/// <param name="isSelected">설정할 선택 상태입니다.</param>
|
|
public void UpdateIconColorState(ToolbarButtonBase buttonModel, bool isSelected)
|
|
{
|
|
if (buttonModel == null) return;
|
|
|
|
if (_modelToGameObjectMap.TryGetValue(buttonModel, out GameObject buttonObj) && buttonObj != null)
|
|
{
|
|
ImageColorChangeBehaviour colorBehaviour = buttonObj.GetComponentInChildren<ImageColorChangeBehaviour>();
|
|
if (colorBehaviour != null)
|
|
{
|
|
colorBehaviour.SetSelected(isSelected);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 라디오 버튼 그룹 내 모든 버튼의 Icon 색상 상태를 업데이트합니다.
|
|
/// </summary>
|
|
/// <param name="groupName">라디오 버튼 그룹 이름입니다.</param>
|
|
/// <param name="selectedButton">선택된 버튼입니다. null이면 모든 버튼이 비선택 상태가 됩니다.</param>
|
|
public void UpdateRadioGroupIconColors(string groupName, ToolbarRadioButton? selectedButton)
|
|
{
|
|
if (ToolbarModel == null) return;
|
|
|
|
var group = ToolbarModel.GetRadioButtonGroup(groupName);
|
|
if (group == null) return;
|
|
|
|
foreach (var button in group.GetButtons())
|
|
{
|
|
bool isSelected = (selectedButton != null && button == selectedButton);
|
|
UpdateIconColorState(button, isSelected);
|
|
}
|
|
}
|
|
}
|
|
}
|