Files
EnglewoodLAB/Assets/Scripts/UVC/UIToolkit/Menu/UTKMenuItemData.cs

261 lines
9.0 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using UVC.UI.Commands;
namespace UVC.UIToolkit
{
/// <summary>
/// UIToolkit 메뉴 시스템에서 개별 메뉴 아이템을 나타내는 데이터 클래스입니다.
/// IDisposable을 구현하여 Command 등의 리소스를 안전하게 정리합니다.
/// </summary>
/// <example>
/// <code>
/// // 일반 메뉴 아이템 생성
/// var menuItem = new UTKMenuItemData(
/// "file_open",
/// "menu_file_open",
/// new OpenFileCommand(),
/// shortcut: "Ctrl+O"
/// );
///
/// // 하위 메뉴가 있는 아이템 생성
/// var fileMenu = new UTKMenuItemData("file", "menu_file");
/// fileMenu.AddSubMenuItem(menuItem);
///
/// // 구분선 생성
/// var separator = UTKMenuItemData.CreateSeparator();
///
/// // 사용 후 정리
/// menuItem.Dispose();
/// fileMenu.Dispose();
/// </code>
/// </example>
public class UTKMenuItemData : IDisposable
{
#region Properties
/// <summary>메뉴 아이템의 고유 식별자</summary>
public string ItemId { get; private set; }
/// <summary>UI에 표시될 이름 (다국어 키)</summary>
public string DisplayName { get; private set; }
/// <summary>실행될 명령</summary>
public ICommand? Command { get; private set; }
/// <summary>Command 실행 시 전달될 파라미터</summary>
public object? CommandParameter { get; set; }
/// <summary>하위 메뉴 아이템 리스트</summary>
public List<UTKMenuItemData> SubMenuItems { get; private set; }
/// <summary>구분선 여부</summary>
public bool IsSeparator { get; private set; }
/// <summary>활성화 상태</summary>
public bool IsEnabled { get; set; }
/// <summary>단축키 문자열</summary>
public string? Shortcut { get; set; }
/// <summary>메뉴 깊이 (0: 최상위)</summary>
public int Depth { get; internal set; }
/// <summary>부모 메뉴 아이템</summary>
public UTKMenuItemData? Parent { get; internal set; }
#endregion
#region Constructor
/// <summary>
/// UTKMenuItemData의 새 인스턴스를 초기화합니다.
/// </summary>
/// <param name="itemId">메뉴 아이템의 고유 ID</param>
/// <param name="displayName">표시 이름 (다국어 키)</param>
/// <param name="command">실행할 명령 (선택 사항)</param>
/// <param name="commandParameter">Command 파라미터 (선택 사항)</param>
/// <param name="subMenuItems">하위 메뉴 아이템 목록 (선택 사항)</param>
/// <param name="isSeparator">구분선 여부 (기본값: false)</param>
/// <param name="isEnabled">활성화 상태 (기본값: true)</param>
/// <param name="shortcut">단축키 문자열 (선택 사항)</param>
/// <exception cref="ArgumentNullException">itemId 또는 displayName이 null인 경우</exception>
public UTKMenuItemData(
string itemId,
string displayName,
ICommand? command = null,
object? commandParameter = null,
List<UTKMenuItemData>? subMenuItems = null,
bool isSeparator = false,
bool isEnabled = true,
string? shortcut = null)
{
if (string.IsNullOrEmpty(itemId))
throw new ArgumentNullException(nameof(itemId), "ItemId는 null이거나 빈 문자열일 수 없습니다.");
ItemId = itemId;
DisplayName = displayName ?? string.Empty;
Command = command;
CommandParameter = commandParameter;
SubMenuItems = subMenuItems ?? new List<UTKMenuItemData>();
IsSeparator = isSeparator;
IsEnabled = isEnabled;
Depth = 0;
Shortcut = shortcut;
// 하위 메뉴 아이템의 깊이와 부모 관계 설정
SetupDepthAndParent();
}
#endregion
#region Methods
/// <summary>
/// 하위 메뉴 아이템을 추가합니다.
/// </summary>
/// <param name="subItem">추가할 하위 메뉴 아이템</param>
/// <exception cref="ArgumentNullException">subItem이 null인 경우</exception>
/// <exception cref="InvalidOperationException">구분선에 하위 메뉴를 추가하려는 경우</exception>
public void AddSubMenuItem(UTKMenuItemData subItem)
{
if (_disposed)
throw new ObjectDisposedException(nameof(UTKMenuItemData), "이미 정리된 객체에 하위 메뉴를 추가할 수 없습니다.");
if (subItem == null)
throw new ArgumentNullException(nameof(subItem), "추가할 하위 메뉴 아이템이 null입니다.");
if (IsSeparator)
throw new InvalidOperationException("구분선에는 하위 메뉴를 추가할 수 없습니다.");
// 깊이와 부모 관계 설정
subItem.Depth = this.Depth + 1;
subItem.Parent = this;
SubMenuItems.Add(subItem);
}
/// <summary>
/// 구분선을 생성하는 팩토리 메서드입니다.
/// </summary>
/// <param name="itemId">구분선의 고유 ID (null일 경우 GUID로 자동 생성)</param>
/// <returns>구분선 역할을 하는 새로운 UTKMenuItemData 객체</returns>
public static UTKMenuItemData CreateSeparator(string? itemId = null)
{
return new UTKMenuItemData(
itemId ?? $"separator_{Guid.NewGuid()}",
string.Empty,
null,
null,
null,
true
);
}
/// <summary>
/// 특정 ID의 하위 메뉴 아이템이 존재하는지 확인합니다.
/// </summary>
/// <param name="itemId">확인할 메뉴 아이템 ID</param>
/// <returns>하위 메뉴에 해당 ID가 존재하면 true, 그렇지 않으면 false</returns>
public bool HasSubMenuItem(string itemId)
{
if (string.IsNullOrEmpty(itemId))
return false;
// 성능 최적화: StringComparison.Ordinal 사용
foreach (var item in SubMenuItems)
{
if (string.Equals(item.ItemId, itemId, StringComparison.Ordinal))
return true;
}
return false;
}
/// <summary>
/// 모든 하위 메뉴 항목의 깊이와 부모 관계를 구성합니다.
/// </summary>
/// <remarks>
/// 이 메서드는 하위 메뉴 항목 컬렉션을 반복하며 깊이와 부모 속성을 설정합니다.
/// 깊이는 현재 항목의 깊이에 따라 증가하고, 부모는 현재 항목으로 설정됩니다.
/// 하위 메뉴 항목에 자체 하위 메뉴가 포함된 경우, 재귀적으로 호출됩니다.
/// </remarks>
private void SetupDepthAndParent()
{
for (int i = 0; i < SubMenuItems.Count; i++)
{
SubMenuItems[i].Depth = this.Depth + 1;
SubMenuItems[i].Parent = this;
if (SubMenuItems[i].SubMenuItems.Count > 0)
{
SubMenuItems[i].SetupDepthAndParent();
}
}
}
#endregion
#region IDisposable
private bool _disposed;
/// <summary>
/// 리소스를 정리합니다. Command가 IDisposable인 경우 함께 정리합니다.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// 리소스를 정리합니다.
/// </summary>
/// <param name="disposing">관리되는 리소스를 정리할지 여부</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// 하위 메뉴 아이템 재귀적으로 정리
if (SubMenuItems != null)
{
foreach (var subItem in SubMenuItems)
{
subItem?.Dispose();
}
SubMenuItems.Clear();
}
// Command가 IDisposable이면 정리
if (Command is IDisposable disposableCommand)
{
disposableCommand.Dispose();
}
// 참조 정리
Command = null;
CommandParameter = null;
Parent = null;
}
_disposed = true;
}
/// <summary>
/// 소멸자
/// </summary>
~UTKMenuItemData()
{
Dispose(false);
}
#endregion
}
}