356 lines
11 KiB
C#
356 lines
11 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UVC.UI.List.Tree
|
|
{
|
|
/// <summary>
|
|
/// 트리 리스트 아이템의 순수 데이터 모델.
|
|
///
|
|
/// 목적:
|
|
/// - 이름/옵션/선택/펼침 상태 및 계층(부모-자식) 보유
|
|
/// - 자식 추가/삽입/복제/재배치/초기화 등 계층 편집 기능
|
|
///
|
|
/// 동등성/해시:
|
|
/// - Id(Guid) 기반 비교/해시 코드.
|
|
///
|
|
/// 주의사항:
|
|
/// - ClearChildren은 각 자식에 Dispose를 호출하여 내부 상태를 정리한 뒤
|
|
/// 리스트를 비웁니다(연결만 끊는 수준을 넘어 자식도 정리됨).
|
|
/// - AddClone계열은 전달된 원본 child를 Dispose한 뒤 복제본을 추가합니다(파괴적).
|
|
///
|
|
/// 이벤트 처리:
|
|
/// - 이벤트는 TreeList에서 중앙 관리됩니다.
|
|
/// - 데이터 변경 알림은 TreeList.NotifyDataChanged()를 통해 처리합니다.
|
|
/// </summary>
|
|
public class TreeListItemData : IDisposable
|
|
{
|
|
#region 내부 필드 (protected Fields)
|
|
|
|
/// <summary>고유 식별자(Id).</summary>
|
|
protected Guid _id = Guid.NewGuid();
|
|
|
|
/// <summary>아이템 이름.</summary>
|
|
protected string _name = string.Empty;
|
|
|
|
/// <summary>추가 옵션 문자열.</summary>
|
|
protected string _option = string.Empty;
|
|
|
|
/// <summary>자식 펼침 여부.</summary>
|
|
protected bool _isExpanded = false;
|
|
|
|
/// <summary>선택 여부.</summary>
|
|
protected bool _isSelected = false;
|
|
|
|
/// <summary>부모</summary>
|
|
protected TreeListItemData? _parent;
|
|
|
|
/// <summary>자식 리스트.</summary>
|
|
protected List<TreeListItemData> _children = new List<TreeListItemData>();
|
|
|
|
#endregion
|
|
|
|
#region 공개 프로퍼티 (Public Properties)
|
|
|
|
/// <summary>
|
|
/// 아이템 이름.
|
|
/// UI 갱신은 TreeList.SetItemName()을 통해 처리합니다.
|
|
/// </summary>
|
|
public string Name
|
|
{
|
|
get => _name;
|
|
set => _name = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 옵션 문자열.
|
|
/// UI 갱신은 TreeList.SetItemOption()을 통해 처리합니다.
|
|
/// </summary>
|
|
public string Option
|
|
{
|
|
get => _option;
|
|
set => _option = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 펼침 상태.
|
|
/// UI 갱신은 TreeList.SetItemExpanded()를 통해 처리합니다.
|
|
/// </summary>
|
|
public bool IsExpanded
|
|
{
|
|
get => _isExpanded;
|
|
set => _isExpanded = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 선택 상태.
|
|
/// UI 갱신은 TreeList.SelectItem()/DeselectItem()을 통해 처리합니다.
|
|
/// </summary>
|
|
public bool IsSelected
|
|
{
|
|
get => _isSelected;
|
|
set => _isSelected = value;
|
|
}
|
|
|
|
/// <summary>부모 데이터(내부 전용).</summary>
|
|
internal TreeListItemData? Parent
|
|
{
|
|
get => _parent;
|
|
set => _parent = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 자식 컬렉션(내부 전용).
|
|
/// </summary>
|
|
internal List<TreeListItemData> Children
|
|
{
|
|
get => _children;
|
|
set => _children = value ?? new List<TreeListItemData>();
|
|
}
|
|
|
|
/// <summary>고유 식별자(Id).</summary>
|
|
public Guid Id => _id;
|
|
|
|
/// <summary>
|
|
/// 리스트/모델 가시성 상태
|
|
/// </summary>
|
|
public bool IsVisible { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// 외부와의 안정 매핑용 키
|
|
/// </summary>
|
|
public string ExternalKey { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// 외부에서 사용하기 위한 사용자 정의 데이터 태그
|
|
/// Transform, GameObject 등 임의 객체 참조에 사용
|
|
/// </summary>
|
|
public object? Tag { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region 생성자 (Constructors)
|
|
|
|
/// <summary>
|
|
/// 기본 생성자.
|
|
/// 초기값: Name="", Option="", IsExpanded=false, IsSelected=false, Children=[]
|
|
/// </summary>
|
|
public TreeListItemData()
|
|
{
|
|
_name = string.Empty;
|
|
_option = string.Empty;
|
|
_isExpanded = false;
|
|
_isSelected = false;
|
|
_children = new List<TreeListItemData>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이름과 초기 자식으로 생성합니다.
|
|
/// </summary>
|
|
/// <param name="generalName">아이템 이름(필수).</param>
|
|
/// <param name="childrenItemData">초기 자식 목록(null 허용).</param>
|
|
public TreeListItemData(string generalName, List<TreeListItemData>? childrenItemData = null)
|
|
{
|
|
_name = generalName;
|
|
_option = string.Empty;
|
|
_isExpanded = false;
|
|
_children = childrenItemData ?? new List<TreeListItemData>();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 자식 관리 메서드 (Child Management Methods)
|
|
|
|
/// <summary>
|
|
/// 자식을 끝에 추가합니다.
|
|
/// UI 갱신은 TreeList를 통해 처리해야 합니다.
|
|
/// </summary>
|
|
/// <param name="child">추가할 자식.</param>
|
|
public void AddChild(TreeListItemData child)
|
|
{
|
|
child.Parent = this;
|
|
_children.Add(child);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 자식을 지정 인덱스에 삽입합니다.
|
|
/// UI 갱신은 TreeList를 통해 처리해야 합니다.
|
|
/// </summary>
|
|
/// <param name="child">삽입할 자식.</param>
|
|
/// <param name="index">삽입 인덱스(0 기반).</param>
|
|
public void AddChildAt(TreeListItemData child, int index)
|
|
{
|
|
child._parent = this;
|
|
_children.Insert(index, child);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정 자식을 지정 인덱스 위치에 삽입합니다.
|
|
/// 주의: 기존 위치에서 제거하지 않으므로 "이동"이 아닌 "삽입" 동작입니다.
|
|
/// </summary>
|
|
/// <param name="child">삽입할 기존 자식 참조.</param>
|
|
/// <param name="index">삽입 인덱스(0 기반).</param>
|
|
public void SwapChild(TreeListItemData child, int index)
|
|
{
|
|
_children.Insert(index, child);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 자식을 제거합니다.
|
|
/// UI 갱신은 TreeList를 통해 처리해야 합니다.
|
|
/// </summary>
|
|
public bool RemoveChild(TreeListItemData child)
|
|
{
|
|
if (_children.Remove(child))
|
|
{
|
|
child._parent = null;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 자식을 제거하고 정리합니다.
|
|
/// 각 자식에 대해 Parent=null 및 Dispose()를 호출한 뒤 리스트를 비웁니다.
|
|
/// </summary>
|
|
public void ClearChildren()
|
|
{
|
|
foreach (var child in _children)
|
|
{
|
|
child._parent = null;
|
|
child.Dispose();
|
|
}
|
|
_children.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 비교 연산자 (Comparison Operators)
|
|
|
|
/// <summary>
|
|
/// 이름(대소문자 구분, Ordinal) 기반 동등 비교.
|
|
/// </summary>
|
|
public static bool operator ==(TreeListItemData? left, TreeListItemData? right)
|
|
{
|
|
// 같은 객체인지 확인 (메모리 주소 비교)
|
|
if (ReferenceEquals(left, right))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// 하나 이상이 null이면 false (ReferenceEquals에서 둘 다 null인 경우는 true 반환)
|
|
if (left is null || right is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// 고유 식별자(Id)로 비교
|
|
return left.Id == right.Id;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이름 기반 비동등 비교.
|
|
/// </summary>
|
|
public static bool operator !=(TreeListItemData? left, TreeListItemData? right)
|
|
{
|
|
return !(left == right);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 객체 메서드 (Object Methods)
|
|
|
|
/// <summary>
|
|
/// 이름 기반 해시 코드 반환.
|
|
/// </summary>
|
|
public override int GetHashCode()
|
|
{
|
|
return Id.GetHashCode();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이름 기반 동등성 비교(== 사용).
|
|
/// </summary>
|
|
/// <param name="obj">비교 대상.</param>
|
|
public override bool Equals(object? obj)
|
|
{
|
|
// obj가 TreeListItemData 타입인지 확인
|
|
if (obj is TreeListItemData other)
|
|
{
|
|
// TreeListItemData면 == 연산자로 비교
|
|
return this == other;
|
|
}
|
|
|
|
// 다른 타입이면 false
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 참조/자식 컬렉션을 정리합니다.
|
|
/// - Parent=null, Children.Clear()
|
|
/// - 하위 항목을 재귀적으로 Dispose하지는 않습니다
|
|
/// (재귀 정리는 ClearChildrenInternal에서 수행).
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
if (_parent != null && _parent.Children.Contains(this)) _parent.Children.Remove(this);
|
|
_parent = null;
|
|
Children?.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 깊은 복제본을 생성합니다(자식까지 재귀 복제).
|
|
/// </summary>
|
|
/// <returns>복제된 새 인스턴스.</returns>
|
|
public TreeListItemData CloneWithChild()
|
|
{
|
|
TreeListItemData clone = new TreeListItemData();
|
|
clone._id = this.Id;
|
|
clone.Name = this.Name;
|
|
clone.Option = this.Option;
|
|
clone.IsExpanded = this.IsExpanded;
|
|
clone.IsSelected = this.IsSelected;
|
|
foreach (var child in this.Children)
|
|
{
|
|
clone.AddChild(child.CloneWithChild());
|
|
}
|
|
return clone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 인스턴스의 복사본인 <see cref="TreeListItemData"/>의 새 인스턴스를 생성합니다.
|
|
/// </summary>
|
|
/// <returns>현재 인스턴스와 동일한 속성 값을 가진 새 <see cref="TreeListItemData"/> 객체를 생성합니다.</returns>
|
|
public virtual TreeListItemData Clone()
|
|
{
|
|
TreeListItemData clone = new TreeListItemData();
|
|
clone._id = this.Id;
|
|
clone.Name = this.Name;
|
|
clone.Option = this.Option;
|
|
clone.IsExpanded = this.IsExpanded;
|
|
clone.IsSelected = this.IsSelected;
|
|
return clone;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// 데이터 변경 종류 열거형.
|
|
/// </summary>
|
|
public enum ChangedType
|
|
{
|
|
Name,
|
|
Option,
|
|
Expanded,
|
|
ResetChildren,
|
|
AddChild,
|
|
RemoveChild,
|
|
AddAtChild,
|
|
AddCloneChild,
|
|
AddCloneAtChild,
|
|
SwapChild,
|
|
TailButtons,// 향후 버튼 관련 변경 추가 가능
|
|
Delete, // 아이템 삭제 (Delete 키로 삭제 시)
|
|
}
|
|
} |