315 lines
10 KiB
C#
315 lines
10 KiB
C#
#nullable enable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// UIToolkit 메뉴 시스템의 데이터 모델입니다.
|
|
/// 메뉴 아이템 컬렉션을 관리하고 검색 기능을 제공합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 이 클래스는 메뉴 아이템을 효율적으로 관리하기 위해 Dictionary 캐싱을 사용합니다.
|
|
/// 검색 시간 복잡도: O(1) (Dictionary 사용)
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 모델 생성
|
|
/// var model = new UTKTopMenuModel();
|
|
///
|
|
/// // 메뉴 아이템 추가
|
|
/// var fileMenu = new UTKMenuItemData("file", "menu_file");
|
|
/// model.AddMenuItem(fileMenu);
|
|
///
|
|
/// // 메뉴 아이템 검색
|
|
/// var found = model.FindMenuItem("file");
|
|
///
|
|
/// // 메뉴 아이템 제거
|
|
/// model.RemoveMenuItem("file");
|
|
///
|
|
/// // 모든 메뉴 정리
|
|
/// model.ClearMenuItems();
|
|
///
|
|
/// // 사용 후 정리
|
|
/// model.Dispose();
|
|
/// </code>
|
|
/// </example>
|
|
public class UTKTopMenuModel : IDisposable
|
|
{
|
|
#region Fields
|
|
|
|
/// <summary>빠른 검색을 위한 메뉴 아이템 인덱스 (ItemId -> MenuItemData)</summary>
|
|
private readonly Dictionary<string, UTKMenuItemData> _menuItemIndex;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>최상위 메뉴 아이템 리스트</summary>
|
|
public List<UTKMenuItemData> MenuItems { get; private set; }
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// UTKTopMenuModel의 새 인스턴스를 초기화합니다.
|
|
/// </summary>
|
|
public UTKTopMenuModel()
|
|
{
|
|
MenuItems = new List<UTKMenuItemData>();
|
|
_menuItemIndex = new Dictionary<string, UTKMenuItemData>(StringComparer.Ordinal);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// 메뉴 아이템을 추가합니다.
|
|
/// </summary>
|
|
/// <param name="item">추가할 메뉴 아이템</param>
|
|
/// <exception cref="ArgumentNullException">item이 null인 경우</exception>
|
|
/// <exception cref="ObjectDisposedException">이미 정리된 객체에 접근하는 경우</exception>
|
|
public void AddMenuItem(UTKMenuItemData item)
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(UTKTopMenuModel), "이미 정리된 모델에 메뉴 아이템을 추가할 수 없습니다.");
|
|
|
|
if (item == null)
|
|
throw new ArgumentNullException(nameof(item), "추가할 메뉴 아이템이 null입니다.");
|
|
|
|
MenuItems.Add(item);
|
|
|
|
// 인덱스에 추가 (재귀적으로 하위 메뉴도 인덱싱)
|
|
AddToIndex(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 메뉴 아이템을 제거합니다.
|
|
/// </summary>
|
|
/// <param name="itemId">제거할 메뉴 아이템의 ID</param>
|
|
/// <returns>제거 성공 시 true, 그렇지 않으면 false</returns>
|
|
/// <exception cref="ObjectDisposedException">이미 정리된 객체에 접근하는 경우</exception>
|
|
/// <remarks>
|
|
/// 시간 복잡도: O(n) (n = MenuItems.Count)
|
|
/// 하위 메뉴도 함께 제거되며, 제거된 아이템은 Dispose됩니다.
|
|
/// </remarks>
|
|
public bool RemoveMenuItem(string itemId)
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(UTKTopMenuModel), "이미 정리된 모델에서 메뉴 아이템을 제거할 수 없습니다.");
|
|
|
|
if (string.IsNullOrEmpty(itemId))
|
|
return false;
|
|
|
|
// 최상위 메뉴에서 제거 시도
|
|
for (int i = 0; i < MenuItems.Count; i++)
|
|
{
|
|
if (string.Equals(MenuItems[i].ItemId, itemId, StringComparison.Ordinal))
|
|
{
|
|
var item = MenuItems[i];
|
|
MenuItems.RemoveAt(i);
|
|
|
|
// 인덱스에서 제거 (재귀적으로 하위 메뉴도 제거)
|
|
RemoveFromIndex(item);
|
|
|
|
// 메모리 정리
|
|
item.Dispose();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 최상위에서 찾지 못했으면 재귀적으로 하위 메뉴에서 검색
|
|
return RemoveMenuItemRecursive(MenuItems, itemId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 재귀적으로 메뉴 아이템을 검색합니다.
|
|
/// </summary>
|
|
/// <param name="itemId">검색할 메뉴 아이템의 ID</param>
|
|
/// <returns>찾은 메뉴 아이템, 없으면 null</returns>
|
|
/// <exception cref="ObjectDisposedException">이미 정리된 객체에 접근하는 경우</exception>
|
|
/// <remarks>
|
|
/// 시간 복잡도: O(1) (Dictionary 캐싱 사용)
|
|
/// </remarks>
|
|
public UTKMenuItemData? FindMenuItem(string itemId)
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(UTKTopMenuModel), "이미 정리된 모델에서 메뉴 아이템을 검색할 수 없습니다.");
|
|
|
|
if (string.IsNullOrEmpty(itemId))
|
|
return null;
|
|
|
|
// Dictionary 캐싱으로 O(1) 검색
|
|
return _menuItemIndex.TryGetValue(itemId, out var item) ? item : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 메뉴 아이템을 초기화합니다.
|
|
/// </summary>
|
|
/// <exception cref="ObjectDisposedException">이미 정리된 객체에 접근하는 경우</exception>
|
|
/// <remarks>
|
|
/// 모든 메뉴 아이템을 재귀적으로 Dispose합니다.
|
|
/// </remarks>
|
|
public void ClearMenuItems()
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(UTKTopMenuModel), "이미 정리된 모델의 메뉴 아이템을 초기화할 수 없습니다.");
|
|
|
|
// 모든 메뉴 아이템 정리
|
|
foreach (var item in MenuItems)
|
|
{
|
|
item?.Dispose();
|
|
}
|
|
|
|
MenuItems.Clear();
|
|
_menuItemIndex.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// 메뉴 아이템과 그 하위 메뉴들을 인덱스에 추가합니다.
|
|
/// </summary>
|
|
/// <param name="item">인덱싱할 메뉴 아이템</param>
|
|
private void AddToIndex(UTKMenuItemData item)
|
|
{
|
|
if (item == null)
|
|
return;
|
|
|
|
// 중복 체크 (성능 최적화: ContainsKey 대신 TryAdd 사용)
|
|
_menuItemIndex[item.ItemId] = item;
|
|
|
|
// 하위 메뉴 재귀적 인덱싱
|
|
if (item.SubMenuItems != null)
|
|
{
|
|
foreach (var subItem in item.SubMenuItems)
|
|
{
|
|
AddToIndex(subItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 메뉴 아이템과 그 하위 메뉴들을 인덱스에서 제거합니다.
|
|
/// </summary>
|
|
/// <param name="item">제거할 메뉴 아이템</param>
|
|
private void RemoveFromIndex(UTKMenuItemData item)
|
|
{
|
|
if (item == null)
|
|
return;
|
|
|
|
_menuItemIndex.Remove(item.ItemId);
|
|
|
|
// 하위 메뉴 재귀적 제거
|
|
if (item.SubMenuItems != null)
|
|
{
|
|
foreach (var subItem in item.SubMenuItems)
|
|
{
|
|
RemoveFromIndex(subItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 재귀적으로 메뉴 아이템을 제거합니다.
|
|
/// </summary>
|
|
/// <param name="items">검색할 메뉴 아이템 리스트</param>
|
|
/// <param name="itemId">제거할 메뉴 아이템 ID</param>
|
|
/// <returns>제거 성공 시 true, 그렇지 않으면 false</returns>
|
|
private bool RemoveMenuItemRecursive(List<UTKMenuItemData> items, string itemId)
|
|
{
|
|
if (items == null || string.IsNullOrEmpty(itemId))
|
|
return false;
|
|
|
|
foreach (var item in items)
|
|
{
|
|
if (item.SubMenuItems != null && item.SubMenuItems.Count > 0)
|
|
{
|
|
// 하위 메뉴에서 제거 시도
|
|
for (int i = 0; i < item.SubMenuItems.Count; i++)
|
|
{
|
|
if (string.Equals(item.SubMenuItems[i].ItemId, itemId, StringComparison.Ordinal))
|
|
{
|
|
var subItem = item.SubMenuItems[i];
|
|
item.SubMenuItems.RemoveAt(i);
|
|
|
|
// 인덱스에서 제거
|
|
RemoveFromIndex(subItem);
|
|
|
|
// 메모리 정리
|
|
subItem.Dispose();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 더 깊은 하위 메뉴에서 재귀 검색
|
|
if (RemoveMenuItemRecursive(item.SubMenuItems, itemId))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
private bool _disposed;
|
|
|
|
/// <summary>
|
|
/// 모든 메뉴 아이템을 정리합니다.
|
|
/// </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 (MenuItems != null)
|
|
{
|
|
foreach (var item in MenuItems)
|
|
{
|
|
item?.Dispose();
|
|
}
|
|
MenuItems.Clear();
|
|
}
|
|
|
|
// 인덱스 정리
|
|
_menuItemIndex?.Clear();
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 소멸자
|
|
/// </summary>
|
|
~UTKTopMenuModel()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|