using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace RosettaUI.UndoSystem { /// /// Undo/Redo用のElement階層上の位置情報 /// /// 保存されたElementがすでに削除済みだが同じ階層構造位置に同じ型のElementがある場合はUndoできるようにする /// 主にListで要素の値を変更し要素削除されたあとUndoを想定しており、 /// 1回目のUndoで要素を追加した際Elementが再生成されてしまい次のUndoで値を元に戻すことができなくなってしまう問題を解決する /// public class ElementHierarchyPath { private readonly struct ElementAndIndex { public readonly Element element; public readonly int childIndex; // 親のChildrenにおけるインデックス。ルートの場合は-1 public ElementAndIndex(Element element, int childIndex) { this.element = element; this.childIndex = childIndex; } public Element GetChild() => GetChild(element); public Element GetChild(Element e) { if (e?.Children == null) return null; if (childIndex < 0 || childIndex >= e.Children.Count) return null; return e.Children[childIndex]; } [SuppressMessage("ReSharper", "ParameterHidesMember")] public void Deconstruct(out Element element, out int childIndex) { element = this.element; childIndex = this.childIndex; } } // ルートからターゲットまでのElementとその子インデックスのペア private readonly List _elementAndIndices = new(); public Element TargetElement => _elementAndIndices.Last().element; public void Clear() { _elementAndIndices.Clear(); } public void Initialize(Element element) { Clear(); if (element == null) return; _elementAndIndices.Add(new ElementAndIndex(element, -1)); for(var e = element; e.Parent != null; e = e.Parent) { var parent = e.Parent; var childIndex = ((List)parent.Children).IndexOf(e); _elementAndIndices.Add(new ElementAndIndex(parent, childIndex)); } _elementAndIndices.Reverse(); } /// /// 記録した位置に現在存在するElementを取得する /// 存在しない、または型が異なる場合はfalseを返す /// public bool TryGetExistingElement(out Element targetElement) { targetElement = null; if (_elementAndIndices.Count == 0) return false; // 保存されているElementが有効ならそのまま返す var lastElement = _elementAndIndices.Last().element; if (lastElement != null && lastElement.EnableInHierarchy()) { targetElement = lastElement; return true; } // パスの最初が有効なルートじゃなかったら失敗 var firstElement = _elementAndIndices.First().element; if (!firstElement.Enable || !RosettaUIRoot.IsRootElement(firstElement)) { return false; } // デタッチされているかEnableではないElementのIndexを調べる var removedIndex = -1; for (var i = 1; i < _elementAndIndices.Count; i++) { var (element, _) = _elementAndIndices[i]; if (!element.Enable || element.Parent == null) { removedIndex = i; break; } } if (removedIndex <= 0) { return false; } // 存在するエレメントから辿って現在のElementと記録されたElementの型が一致するか確認しながら辿る var existingElement = _elementAndIndices[removedIndex - 1].GetChild(); for (var index = removedIndex; index < _elementAndIndices.Count; index++) { if (existingElement is not { Enable: true }) { return false; } var elementAndIndex = _elementAndIndices[index]; if (existingElement.GetType() != elementAndIndex.element.GetType()) { return false; } if (index < _elementAndIndices.Count - 1) { existingElement = elementAndIndex.GetChild(existingElement); } } targetElement = existingElement; return targetElement != null; } } }