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