diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs new file mode 100644 index 00000000..9e20da44 --- /dev/null +++ b/Runtime/external/RTree.cs @@ -0,0 +1,524 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.UIWidgets.ui; + +namespace Unity.UIWidgets.Runtime.external +{ + public interface ISpatialData + { + uiRect bounds { get; } + } + + public class IndexedRect : ISpatialData + { + private uiRect _bounds; + + public uiRect bounds + { + get { return _bounds; } + } + + public readonly int index; + + public IndexedRect(uiRect bounds, int index) + { + this._bounds = bounds; + this.index = index; + } + } + + /// + /// Non-generic class to produce instances of the generic class, + /// optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// The ignored parameter is solely present to aid type inference. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Value is ignored - type may be used by type inference + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create + (TSource ignored, + Func projection) + { + return new ProjectionComparer(projection); + } + } + + /// + /// Class generic in the source only to produce instances of the + /// doubly generic class, optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + } + + /// + /// Comparer which projects each element of the comparison to a key, and then compares + /// those keys using the specified (or default) comparer for the key type. + /// + /// Type of elements which this comparer will be asked to compare + /// Type of the key projected from the element + public class ProjectionComparer : IComparer + { + private readonly IComparer comparer; + private readonly Func projection; + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// The default comparer for the projected type is used. + /// + /// Projection to use during comparisons + public ProjectionComparer(Func projection) + : this(projection, null) + { + } + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// + /// Projection to use during comparisons + /// + /// The comparer to use on the keys. May be null, in + /// which case the default comparer will be used. + /// + public ProjectionComparer(Func projection, IComparer comparer) + { + this.comparer = comparer ?? Comparer.Default; + this.projection = projection; + } + + /// + /// Compares x and y by projecting them to keys and then comparing the keys. + /// Null values are not projected; they obey the + /// standard comparer contract such that two null values are equal; any null value is + /// less than any non-null value. + /// + public int Compare(TSource x, TSource y) + { + // Don't want to project from nullity + if (x == null && y == null) return 0; + if (x == null) return -1; + if (y == null) return 1; + return comparer.Compare(projection(x), projection(y)); + } + } + + public interface BBoxHierarchy where T : ISpatialData + { + IReadOnlyList Search(in uiRect boundingBox); + void BulkLoad(IEnumerable items); + + void Insert(T data); + + void Clear(); + } + + public class RTree : BBoxHierarchy where T : ISpatialData + { + public class RTreeNode : ISpatialData + { + internal readonly List children; + private uiRect _Rect; + + internal RTreeNode(List items, int height) + { + Height = height; + children = items; + ResetRect(); + } + + public IReadOnlyList Children => children; + public int Height { get; } + public bool IsLeaf => Height == 1; + public uiRect bounds => _Rect; + + internal void Add(ISpatialData node) + { + children.Add(node); + _Rect = bounds.expandToInclude(node.bounds); + } + + internal void Remove(ISpatialData node) + { + children.Remove(node); + ResetRect(); + } + + internal void RemoveRange(int index, int count) + { + children.RemoveRange(index, count); + ResetRect(); + } + + internal void ResetRect() + { + _Rect = GetEnclosingRect(children); + } + } + #region Search + + private List DoSearch(in uiRect boundingBox) + { + if (!uiRectHelper.overlaps(Root.bounds, boundingBox)) + return new List(); + + var intersections = new List(); + var queue = new Queue(); + queue.Enqueue(Root); + + while (queue.Count != 0) + { + var item = queue.Dequeue(); + if (item.IsLeaf) + { + foreach (var leafChildItem in item.children.Cast()) + if (uiRectHelper.overlaps(leafChildItem.bounds, boundingBox)) + intersections.Add(leafChildItem); + } + else + { + foreach (var child in item.children.Cast()) + if (uiRectHelper.overlaps(child.bounds, boundingBox)) + queue.Enqueue(child); + } + } + + return intersections; + } + + #endregion + + private static uiRect GetEnclosingRect(IEnumerable items) + { + var uiRect = uiRectHelper.zero; + foreach (var data in items) uiRect = uiRect.expandToInclude(data.bounds); + return uiRect; + } + + private List GetAllChildren(List list, RTreeNode n) + { + if (n.IsLeaf) + list.AddRange( + n.children.Cast()); + else + foreach (var node in n.children.Cast()) + GetAllChildren(list, node); + + return list; + } + + #region Sort Functions + + private static readonly IComparer CompareMinX = + ProjectionComparer.Create(d => d.bounds.left); + + private static readonly IComparer CompareMinY = + ProjectionComparer.Create(d => d.bounds.top); + + #endregion + + #region Insert + + private List FindCoveringArea(in uiRect area, int depth) + { + var path = new List(); + var node = Root; + var _area = area; //FIX CS1628 + + while (true) + { + path.Add(node); + if (node.IsLeaf || path.Count == depth) return path; + + node = node.children + .Select(c => new + {EnlargedArea = c.bounds.expandToInclude(_area).area, c.bounds.area, Node = c as RTreeNode}) + .OrderBy(x => x.EnlargedArea) + .ThenBy(x => x.area) + .Select(x => x.Node) + .First(); + } + } + + private void Insert(ISpatialData data, int depth) + { + var path = FindCoveringArea(data.bounds, depth); + + var insertNode = path.Last(); + insertNode.Add(data); + + while (--depth >= 0) + if (path[depth].children.Count > maxEntries) + { + var newNode = SplitNode(path[depth]); + if (depth == 0) + SplitRoot(newNode); + else + path[depth - 1].Add(newNode); + } + else + { + path[depth].ResetRect(); + } + } + + #region SplitNode + + private void SplitRoot(RTreeNode newRTreeNode) + { + Root = new RTreeNode(new List {Root, newRTreeNode}, Root.Height + 1); + } + + private RTreeNode SplitNode(RTreeNode rTreeNode) + { + SortChildren(rTreeNode); + + var splitPoint = GetBestSplitIndex(rTreeNode.children); + var newChildren = rTreeNode.children.Skip(splitPoint).ToList(); + rTreeNode.RemoveRange(splitPoint, rTreeNode.children.Count - splitPoint); + return new RTreeNode(newChildren, rTreeNode.Height); + } + + #region SortChildren + + private void SortChildren(RTreeNode rTreeNode) + { + rTreeNode.children.Sort(CompareMinX); + var splitsByX = GetPotentialSplitMargins(rTreeNode.children); + rTreeNode.children.Sort(CompareMinY); + var splitsByY = GetPotentialSplitMargins(rTreeNode.children); + + if (splitsByX < splitsByY) + rTreeNode.children.Sort(CompareMinX); + } + + private float GetPotentialSplitMargins(List children) + { + return GetPotentialEnclosingMargins(children) + + GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); + } + + private float GetPotentialEnclosingMargins(List children) + { + var uiRect = uiRectHelper.zero; + var i = 0; + for (; i < minEntries; i++) uiRect = uiRect.expandToInclude(children[i].bounds); + + var totalMargin = uiRect.margin; + for (; i < children.Count - minEntries; i++) + { + uiRect = uiRect.expandToInclude(children[i].bounds); + totalMargin += uiRect.margin; + } + + return totalMargin; + } + + #endregion + + private int GetBestSplitIndex(List children) + { + return Enumerable.Range(minEntries, children.Count - minEntries) + .Select(i => + { + var leftRect = GetEnclosingRect(children.Take(i)); + var rightRect = GetEnclosingRect(children.Skip(i)); + + var overlap = leftRect.intersect(rightRect).area; + var totalArea = leftRect.area + rightRect.area; + return new {i, overlap, totalArea}; + }) + .OrderBy(x => x.overlap) + .ThenBy(x => x.totalArea) + .Select(x => x.i) + .First(); + } + + #endregion + + #endregion + + #region BuildTree + + private RTreeNode BuildTree(List data) + { + var treeHeight = GetDepth(data.Count); + var rootMaxEntries = (int) Math.Ceiling(data.Count / Math.Pow(maxEntries, treeHeight - 1)); + return BuildNodes(data, 0, data.Count - 1, treeHeight, rootMaxEntries); + } + + private int GetDepth(int numNodes) + { + return (int) Math.Ceiling(Math.Log(numNodes) / Math.Log(maxEntries)); + } + + private RTreeNode BuildNodes(List data, int left, int right, int height, int maxEntries) + { + var num = right - left + 1; + if (num <= maxEntries) + return height == 1 + ? new RTreeNode(data.GetRange(left, num), height) + : new RTreeNode( + new List + { + BuildNodes(data, left, right, height - 1, this.maxEntries) + }, + height); + + data.Sort(left, num, CompareMinX); + + var nodeSize = (num + (maxEntries - 1)) / maxEntries; + var subSortLength = nodeSize * (int) Math.Ceiling(Math.Sqrt(maxEntries)); + + var children = new List(maxEntries); + for (var subCounter = left; subCounter <= right; subCounter += subSortLength) + { + var subRight = Math.Min(subCounter + subSortLength - 1, right); + data.Sort(subCounter, subRight - subCounter + 1, CompareMinY); + + for (var nodeCounter = subCounter; nodeCounter <= subRight; nodeCounter += nodeSize) + children.Add( + BuildNodes( + data, + nodeCounter, + Math.Min(nodeCounter + nodeSize - 1, subRight), + height - 1, + this.maxEntries)); + } + + return new RTreeNode(children, height); + } + + #endregion + private const int DefaultMaxEntries = 9; + private const int MinimumMaxEntries = 4; + private const int MinimumMinEntries = 2; + private const float DefaultFillFactor = 0.4f; + + private readonly EqualityComparer comparer; + private readonly int maxEntries; + private readonly int minEntries; + + public RTree() : this(DefaultMaxEntries) + { + } + + public RTree(int maxEntries) + : this(maxEntries, EqualityComparer.Default) + { + } + + public RTree(int maxEntries, EqualityComparer comparer) + { + this.comparer = comparer; + this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); + minEntries = Math.Max(MinimumMinEntries, (int) Math.Ceiling(this.maxEntries * DefaultFillFactor)); + + Clear(); + } + + public RTreeNode Root { get; private set; } + public uiRect uiRect => Root.bounds; + + public int Count { get; private set; } + + public void Clear() + { + Root = new RTreeNode(new List(), 1); + Count = 0; + } + + public IReadOnlyList Search() + { + return GetAllChildren(new List(), Root); + } + + public IReadOnlyList Search(in uiRect boundingBox) + { + return DoSearch(boundingBox); + } + + public void Insert(T item) + { + Insert(item, Root.Height); + Count++; + } + + public void BulkLoad(IEnumerable items) + { + var data = items.Cast().ToList(); + if (data.Count == 0) return; + + if (Root.IsLeaf && + Root.children.Count + data.Count < maxEntries) + { + foreach (var i in data) + Insert((T) i); + return; + } + + if (data.Count < minEntries) + { + foreach (var i in data) + Insert((T) i); + return; + } + + var dataRoot = BuildTree(data); + Count += data.Count; + + if (Root.children.Count == 0) + { + Root = dataRoot; + } + else if (Root.Height == dataRoot.Height) + { + if (Root.children.Count + dataRoot.children.Count <= maxEntries) + foreach (var isd in dataRoot.children) + Root.Add(isd); + else + SplitRoot(dataRoot); + } + else + { + if (Root.Height < dataRoot.Height) + { + var tmp = Root; + Root = dataRoot; + dataRoot = tmp; + } + + Insert(dataRoot, Root.Height - dataRoot.Height); + } + } + } +} \ No newline at end of file diff --git a/Runtime/external/RTree.cs.meta b/Runtime/external/RTree.cs.meta new file mode 100644 index 00000000..30f3da65 --- /dev/null +++ b/Runtime/external/RTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a93ee633aad3dc4395851f56b650cc2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/external/SplayTree.cs b/Runtime/external/SplayTree.cs new file mode 100644 index 00000000..f35515ac --- /dev/null +++ b/Runtime/external/SplayTree.cs @@ -0,0 +1,493 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Unity.UIWidgets.Runtime.external +{ class SplayTree : IDictionary where TKey : IComparable { + SplayTreeNode root; + int count; + int version = 0; + + public void Add(TKey key, TValue value) { + this.Set(key, value, throwOnExisting: true); + } + + public void Add(KeyValuePair item) { + this.Set(item.Key, item.Value, throwOnExisting: true); + } + + public void AddAll(IEnumerable list) { + foreach (var key in list) { + this.Add(new KeyValuePair(key, default)); + } + } + + void Set(TKey key, TValue value, bool throwOnExisting) { + if (this.count == 0) { + this.version++; + this.root = new SplayTreeNode(key, value); + this.count = 1; + return; + } + + this.Splay(key); + + var c = key.CompareTo(this.root.Key); + if (c == 0) { + if (throwOnExisting) { + throw new ArgumentException("An item with the same key already exists in the tree."); + } + + this.version++; + this.root.Value = value; + return; + } + + var n = new SplayTreeNode(key, value); + if (c < 0) { + n.LeftChild = this.root.LeftChild; + n.RightChild = this.root; + this.root.LeftChild = null; + } + else { + n.RightChild = this.root.RightChild; + n.LeftChild = this.root; + this.root.RightChild = null; + } + + this.root = n; + this.count++; + this.Splay(key); + this.version++; + } + + public void Clear() { + this.root = null; + this.count = 0; + this.version++; + } + + public bool ContainsKey(TKey key) { + if (this.count == 0) { + return false; + } + + this.Splay(key); + + return key.CompareTo(this.root.Key) == 0; + } + + public bool Contains(KeyValuePair item) { + if (this.count == 0) { + return false; + } + + this.Splay(item.Key); + + return item.Key.CompareTo(this.root.Key) == 0 && + (ReferenceEquals(this.root.Value, item.Value) || + (!ReferenceEquals(item.Value, null) && item.Value.Equals(this.root.Value))); + } + + public KeyValuePair? First() { + SplayTreeNode t = this.root; + if (t == null) { + return null; + } + + while (t.LeftChild != null) { + t = t.LeftChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair FirstOrDefault() { + SplayTreeNode t = this.root; + if (t == null) { + return new KeyValuePair(default(TKey), default(TValue)); + } + + while (t.LeftChild != null) { + t = t.LeftChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair? Last() { + SplayTreeNode t = this.root; + if (t == null) { + return null; + } + + while (t.RightChild != null) { + t = t.RightChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair LastOrDefault() { + SplayTreeNode t = this.root; + if (t == null) { + return new KeyValuePair(default(TKey), default(TValue)); + } + + while (t.RightChild != null) { + t = t.RightChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + void Splay(TKey key) { + SplayTreeNode l, r, t, y, header; + l = r = header = new SplayTreeNode(default(TKey), default(TValue)); + t = this.root; + while (true) { + var c = key.CompareTo(t.Key); + if (c < 0) { + if (t.LeftChild == null) { + break; + } + + if (key.CompareTo(t.LeftChild.Key) < 0) { + y = t.LeftChild; + t.LeftChild = y.RightChild; + y.RightChild = t; + t = y; + if (t.LeftChild == null) { + break; + } + } + + r.LeftChild = t; + r = t; + t = t.LeftChild; + } + else if (c > 0) { + if (t.RightChild == null) { + break; + } + + if (key.CompareTo(t.RightChild.Key) > 0) { + y = t.RightChild; + t.RightChild = y.LeftChild; + y.LeftChild = t; + t = y; + if (t.RightChild == null) { + break; + } + } + + l.RightChild = t; + l = t; + t = t.RightChild; + } + else { + break; + } + } + + l.RightChild = t.LeftChild; + r.LeftChild = t.RightChild; + t.LeftChild = header.RightChild; + t.RightChild = header.LeftChild; + this.root = t; + } + + public bool Remove(TKey key) { + if (this.count == 0) { + return false; + } + + this.Splay(key); + + if (key.CompareTo(this.root.Key) != 0) { + return false; + } + + if (this.root.LeftChild == null) { + this.root = this.root.RightChild; + } + else { + var swap = this.root.RightChild; + this.root = this.root.LeftChild; + this.Splay(key); + this.root.RightChild = swap; + } + + this.version++; + this.count--; + return true; + } + + public bool TryGetValue(TKey key, out TValue value) { + if (this.count == 0) { + value = default(TValue); + return false; + } + + this.Splay(key); + if (key.CompareTo(this.root.Key) != 0) { + value = default(TValue); + return false; + } + + value = this.root.Value; + return true; + } + + public TValue this[TKey key] { + get { + if (this.count == 0) { + throw new KeyNotFoundException("The key was not found in the tree."); + } + + this.Splay(key); + if (key.CompareTo(this.root.Key) != 0) { + throw new KeyNotFoundException("The key was not found in the tree."); + } + + return this.root.Value; + } + + set { this.Set(key, value, throwOnExisting: false); } + } + + public int Count { + get { return this.count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public bool Remove(KeyValuePair item) { + if (this.count == 0) { + return false; + } + + this.Splay(item.Key); + + if (item.Key.CompareTo(this.root.Key) == 0 && (ReferenceEquals(this.root.Value, item.Value) || + (!ReferenceEquals(item.Value, null) && + item.Value.Equals(this.root.Value)))) { + return false; + } + + if (this.root.LeftChild == null) { + this.root = this.root.RightChild; + } + else { + var swap = this.root.RightChild; + this.root = this.root.LeftChild; + this.Splay(item.Key); + this.root.RightChild = swap; + } + + this.version++; + this.count--; + return true; + } + + public void Trim(int depth) { + if (depth < 0) { + throw new ArgumentOutOfRangeException("depth", "The trim depth must not be negative."); + } + + if (this.count == 0) { + return; + } + + if (depth == 0) { + this.Clear(); + } + else { + var prevCount = this.count; + this.count = this.Trim(this.root, depth - 1); + if (prevCount != this.count) { + this.version++; + } + } + } + + int Trim(SplayTreeNode node, int depth) { + if (depth == 0) { + node.LeftChild = null; + node.RightChild = null; + return 1; + } + else { + int count = 1; + + if (node.LeftChild != null) { + count += this.Trim(node.LeftChild, depth - 1); + } + + if (node.RightChild != null) { + count += this.Trim(node.RightChild, depth - 1); + } + + return count; + } + } + + public ICollection Keys { + get { return new TiedList(this, this.version, this.AsList(node => node.Key)); } + } + + public ICollection Values { + get { return new TiedList(this, this.version, this.AsList(node => node.Value)); } + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) { + this.AsList(node => new KeyValuePair(node.Key, node.Value)).CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() { + return new TiedList>(this, this.version, + this.AsList(node => new KeyValuePair(node.Key, node.Value))).GetEnumerator(); + } + + IList AsList(Func selector) { + if (this.root == null) { + return new TEnumerator[0]; + } + + var result = new List(this.count); + this.PopulateList(this.root, result, selector); + return result; + } + + void PopulateList(SplayTreeNode node, List list, + Func selector) { + if (node.LeftChild != null) { + this.PopulateList(node.LeftChild, list, selector); + } + + list.Add(selector(node)); + if (node.RightChild != null) { + this.PopulateList(node.RightChild, list, selector); + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + + sealed class SplayTreeNode { + public readonly TKey Key; + + public TValue Value; + public SplayTreeNode LeftChild; + public SplayTreeNode RightChild; + + public SplayTreeNode(TKey key, TValue value) { + this.Key = key; + this.Value = value; + } + } + + sealed class TiedList : IList { + readonly SplayTree tree; + readonly int version; + readonly IList backingList; + + public TiedList(SplayTree tree, int version, IList backingList) { + if (tree == null) { + throw new ArgumentNullException("tree"); + } + + if (backingList == null) { + throw new ArgumentNullException("backingList"); + } + + this.tree = tree; + this.version = version; + this.backingList = backingList; + } + + public int IndexOf(T item) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList.IndexOf(item); + } + + public void Insert(int index, T item) { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) { + throw new NotSupportedException(); + } + + public T this[int index] { + get { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList[index]; + } + set { throw new NotSupportedException(); } + } + + public void Add(T item) { + throw new NotSupportedException(); + } + + public void Clear() { + throw new NotSupportedException(); + } + + public bool Contains(T item) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + this.backingList.CopyTo(array, arrayIndex); + } + + public int Count { + get { return this.tree.count; } + } + + public bool IsReadOnly { + get { return true; } + } + + public bool Remove(T item) { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + foreach (var item in this.backingList) { + yield return item; + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + } + } +} \ No newline at end of file diff --git a/Runtime/external/SplayTree.cs.meta b/Runtime/external/SplayTree.cs.meta new file mode 100644 index 00000000..21a69192 --- /dev/null +++ b/Runtime/external/SplayTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 600f58b77585e6845aa9f0924a5af554 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/painting/gradient.cs b/Runtime/painting/gradient.cs index 289ec8b7..a4eafd51 100644 --- a/Runtime/painting/gradient.cs +++ b/Runtime/painting/gradient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.ui; using UnityEngine; using Color = Unity.UIWidgets.ui.Color; diff --git a/Runtime/painting/text_painter.cs b/Runtime/painting/text_painter.cs index e93f54d1..7449ca25 100644 --- a/Runtime/painting/text_painter.cs +++ b/Runtime/painting/text_painter.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Unity.UIWidgets.foundation; using Unity.UIWidgets.service; using Unity.UIWidgets.ui; diff --git a/Runtime/ui/geometry.cs b/Runtime/ui/geometry.cs index 9bea0fbb..a6230747 100644 --- a/Runtime/ui/geometry.cs +++ b/Runtime/ui/geometry.cs @@ -541,6 +541,14 @@ public Size size { get { return new Size(this.width, this.height); } } + public float area { + get { return this.width * this.height; } + } + + public float margin { + get { return this.width + this.height; } + } + public static readonly Rect zero = new Rect(0, 0, 0, 0); public static readonly Rect one = new Rect(0, 0, 1, 1); diff --git a/Runtime/ui/painting/draw_cmd.cs b/Runtime/ui/painting/draw_cmd.cs index 622dfd84..9aa7089e 100644 --- a/Runtime/ui/painting/draw_cmd.cs +++ b/Runtime/ui/painting/draw_cmd.cs @@ -1,4 +1,4 @@ -namespace Unity.UIWidgets.ui { +namespace Unity.UIWidgets.ui { public abstract class DrawCmd { } diff --git a/Runtime/ui/painting/path.cs b/Runtime/ui/painting/path.cs index 1c157ab1..b30c96da 100644 --- a/Runtime/ui/painting/path.cs +++ b/Runtime/ui/painting/path.cs @@ -226,6 +226,14 @@ public Rect getBounds() { return Rect.fromLTRB(this._minX, this._minY, this._maxX, this._maxY); } + public Rect getBoundsWithMargin(float margin) { + if (this._minX - margin >= this._maxX + margin || this._minY - margin >= this._maxY + margin) { + return Rect.zero; + } + + return Rect.fromLTRB(this._minX - margin, this._minY - margin, this._maxX + margin, this._maxY + margin); + } + public static Path combine(PathOperation operation, Path path1, Path path2) { D.assert(path1 != null); D.assert(path2 != null); diff --git a/Runtime/ui/painting/picture.cs b/Runtime/ui/painting/picture.cs index e2fd22ca..d8e03c83 100644 --- a/Runtime/ui/painting/picture.cs +++ b/Runtime/ui/painting/picture.cs @@ -1,17 +1,26 @@ using System; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; namespace Unity.UIWidgets.ui { public class Picture { - public Picture(List drawCmds, Rect paintBounds, bool isDynamic = false) { + public Picture(List drawCmds, + Rect paintBounds, + bool isDynamic = false, + BBoxHierarchy bbh = null, + List stateUpdatesIndices = null) { this.drawCmds = drawCmds; this.paintBounds = paintBounds; this._isDynamic = isDynamic; + this.bbh = bbh; + this.stateUpdatesIndices = stateUpdatesIndices; } public readonly List drawCmds; public readonly Rect paintBounds; + public readonly BBoxHierarchy bbh; + public readonly List stateUpdatesIndices; public bool isDynamic { get { return this._isDynamic; } @@ -24,6 +33,10 @@ public class PictureRecorder { readonly List _drawCmds = new List(); readonly List _states = new List(); + + readonly BBoxHierarchy _bbh = new RTree(); + + readonly List _stateUpdateIndices = new List(); bool _isDynamic; @@ -51,6 +64,8 @@ public void reset() { layerOffset = null, paintBounds = Rect.zero, }); + this._bbh.Clear(); + this._stateUpdateIndices.Clear(); } void restoreToCount(int count) { @@ -84,7 +99,12 @@ public Picture endRecording() { } var state = this._getState(); - return new Picture(new List(this._drawCmds), state.paintBounds, this._isDynamic); + return new Picture( + new List(this._drawCmds), + state.paintBounds, + this._isDynamic, + this._bbh, + this._stateUpdateIndices); } public void addDrawCmd(DrawCmd drawCmd) { @@ -93,6 +113,7 @@ public void addDrawCmd(DrawCmd drawCmd) { switch (drawCmd) { case DrawSave _: this._states.Add(this._getState().copy()); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; case DrawSaveLayer cmd: { this._states.Add(new CanvasState { @@ -102,6 +123,7 @@ public void addDrawCmd(DrawCmd drawCmd) { layerOffset = cmd.rect.topLeft, paintBounds = Rect.zero, }); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -110,6 +132,7 @@ public void addDrawCmd(DrawCmd drawCmd) { if (this._states.Count > 1) { this.restore(); } + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -117,6 +140,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preTranslate(cmd.dx, cmd.dy); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -124,6 +148,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preScale(cmd.sx, (cmd.sy ?? cmd.sx)); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -138,6 +163,7 @@ public void addDrawCmd(DrawCmd drawCmd) { cmd.offset.dx, cmd.offset.dy); } + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -146,6 +172,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preSkew(cmd.sx, cmd.sy); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -153,18 +180,21 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preConcat(cmd.matrix); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case DrawResetMatrix _: { var state = this._getState(); state.xform = Matrix3.I(); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case DrawSetMatrix cmd: { var state = this._getState(); state.xform = new Matrix3(cmd.matrix); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -173,6 +203,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rect); state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -181,6 +212,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rrect.outerRect); state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -194,6 +226,7 @@ public void addDrawCmd(DrawCmd drawCmd) { cache.computeFillMesh(0.0f, out _); var rect = cache.fillMesh.transform(state.xform).bounds; state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -232,9 +265,13 @@ public void addDrawCmd(DrawCmd drawCmd) { float sigma = scale * paint.maskFilter.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(mesh.bounds.inflate(sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(mesh.bounds.inflate(sigma3 + 5)).Value, + this._drawCmds.Count - 1)); } else { this._addPaintBounds(mesh.bounds); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(mesh.bounds.inflate(5)).Value, + this._drawCmds.Count - 1)); } break; @@ -246,6 +283,8 @@ public void addDrawCmd(DrawCmd drawCmd) { cmd.image.width, cmd.image.height); rect = state.xform.mapRect(rect); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -257,6 +296,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -268,6 +309,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -279,6 +322,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.picture.paintBounds); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.picture.isDynamic) { this._isDynamic = true; } @@ -297,9 +342,13 @@ public void addDrawCmd(DrawCmd drawCmd) { float sigma = scale * paint.maskFilter.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(rect.inflate(sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(sigma3 + 5)).Value, + this._drawCmds.Count - 1)); } else { this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); } break; diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index 9235d296..1441b1c9 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using UnityEngine; using UnityEngine.Rendering; @@ -766,6 +768,21 @@ void _drawPicture(Picture picture, bool needsSave = true) { int saveCount = 0; var drawCmds = picture.drawCmds; + var queryBound = this._currentLayer.currentState.matrix?.invert().Value + .mapRect(this._currentLayer.layerBounds) ?? + this._currentLayer.layerBounds; + + if (!uiRectHelper.contains(queryBound, uiRectHelper.fromRect(picture.paintBounds).Value)) { + var indices = picture.bbh.Search(queryBound).Select(bound => bound.index); + List cmdIndices = indices.ToList(); + cmdIndices.AddRange(picture.stateUpdatesIndices); + cmdIndices.Sort(); + drawCmds = new List(); + for (int i = 0; i < cmdIndices.Count; i++) { + drawCmds.Add(picture.drawCmds[cmdIndices[i]]); + } + } + foreach (var drawCmd in drawCmds) { switch (drawCmd) { case DrawSave _: @@ -899,6 +916,24 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { int saveCount = 0; var drawCmds = picture.drawCmds; + var queryBound = this._currentLayer.currentState.matrix?.invert().Value + .mapRect(this._currentLayer.layerBounds) ?? + this._currentLayer.layerBounds; + + if (!uiRectHelper.contains(queryBound, picture.paintBounds)) { + var indices = picture.bbh.Search(queryBound).Select(bound => bound.index); + List cmdIndices = indices.ToList(); + cmdIndices.Capacity += picture.stateUpdatesIndices.Count; + for (int i = 0; i < picture.stateUpdatesIndices.Count; i++) { + cmdIndices.Add(picture.stateUpdatesIndices[i]); + } + cmdIndices.Sort(); + drawCmds = new List(); + for (int i = 0; i < cmdIndices.Count; i++) { + drawCmds.Add(picture.drawCmds[cmdIndices[i]]); + } + } + foreach (var drawCmd in drawCmds) { switch (drawCmd) { case uiDrawSave _: diff --git a/Runtime/ui/renderer/common/geometry/path/path.cs b/Runtime/ui/renderer/common/geometry/path/path.cs index adcac101..82041cdf 100644 --- a/Runtime/ui/renderer/common/geometry/path/path.cs +++ b/Runtime/ui/renderer/common/geometry/path/path.cs @@ -170,6 +170,14 @@ public uiRect getBounds() { return uiRectHelper.fromLTRB(this._minX, this._minY, this._maxX, this._maxY); } + public uiRect getBoundsWithMargin(float margin) { + if (this._minX - margin >= this._maxX + margin || this._minY - margin >= this._maxY + margin) { + return uiRectHelper.zero; + } + + return uiRectHelper.fromLTRB(this._minX - margin, this._minY - margin, this._maxX + margin, this._maxY + margin); + } + void _appendMoveTo(float x, float y) { this._commands.Add((float) uiPathCommand.moveTo); this._commands.Add(x); diff --git a/Runtime/ui/renderer/common/geometry/rect.cs b/Runtime/ui/renderer/common/geometry/rect.cs index 07a68b51..aa7ea7cb 100644 --- a/Runtime/ui/renderer/common/geometry/rect.cs +++ b/Runtime/ui/renderer/common/geometry/rect.cs @@ -26,6 +26,14 @@ public float height { get { return this.bottom - this.top; } } + public float area { + get { return this.width * this.height; } + } + + public float margin { + get { return this.width + this.height; } + } + public uiOffset topLeft { get { return new uiOffset(this.left, this.top); } } diff --git a/Runtime/ui/renderer/common/picture.cs b/Runtime/ui/renderer/common/picture.cs index 8581deab..15496bdd 100644 --- a/Runtime/ui/renderer/common/picture.cs +++ b/Runtime/ui/renderer/common/picture.cs @@ -1,25 +1,34 @@ using System; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; namespace Unity.UIWidgets.ui { public class uiPicture : PoolObject { public uiPicture() { } - public static uiPicture create(List drawCmds, uiRect paintBounds) { + public static uiPicture create(List drawCmds, + uiRect paintBounds, + BBoxHierarchy bbh = null, + uiList stateUpdateIndices = null) { var picture = ObjectPool.alloc(); picture.drawCmds = drawCmds; picture.paintBounds = paintBounds; + picture.bbh = bbh; + picture.stateUpdatesIndices = stateUpdateIndices; return picture; } public List drawCmds; public uiRect paintBounds; + public BBoxHierarchy bbh; + public uiList stateUpdatesIndices; public override void clear() { //the recorder will dispose the draw commands this.drawCmds = null; + ObjectPool>.release(this.stateUpdatesIndices); } } @@ -27,6 +36,10 @@ public class uiPictureRecorder { readonly List _drawCmds = new List(128); readonly List _states = new List(32); + + readonly BBoxHierarchy _bbh = new RTree(); + + readonly List _stateUpdateIndices = new List(); public uiPictureRecorder() { this.reset(); @@ -59,15 +72,23 @@ public void reset() { layerOffset = null, paintBounds = uiRectHelper.zero }); + this._bbh.Clear(); + this._stateUpdateIndices.Clear(); } public uiPicture endRecording() { if (this._states.Count > 1) { throw new Exception("unmatched save/restore commands"); } - + + uiList stateUpdateIndices = ObjectPool>.alloc(); + stateUpdateIndices.AddRange(this._stateUpdateIndices); var state = this._getState(); - return uiPicture.create(this._drawCmds, state.paintBounds); + return uiPicture.create( + this._drawCmds, + state.paintBounds, + bbh: this._bbh, + stateUpdateIndices: stateUpdateIndices); } public void addDrawCmd(uiDrawCmd drawCmd) { @@ -76,6 +97,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { switch (drawCmd) { case uiDrawSave _: this._states.Add(this._getState().copy()); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; case uiDrawSaveLayer cmd: { this._states.Add(new uiCanvasState { @@ -85,6 +107,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { layerOffset = cmd.rect.Value.topLeft, paintBounds = uiRectHelper.zero }); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawRestore _: { @@ -102,6 +125,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { } this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawTranslate cmd: { @@ -109,6 +133,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preTranslate(cmd.dx, cmd.dy); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawScale cmd: { @@ -116,6 +141,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preScale(cmd.sx, (cmd.sy ?? cmd.sx)); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawRotate cmd: { @@ -131,6 +157,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { } this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawSkew cmd: { @@ -138,6 +165,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preSkew(cmd.sx, cmd.sy); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawConcat cmd: { @@ -145,18 +173,21 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preConcat(cmd.matrix.Value); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawResetMatrix _: { var state = this._getState(); state.xform = uiMatrix3.I(); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawSetMatrix cmd: { var state = this._getState(); state.xform = new uiMatrix3(cmd.matrix.Value); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipRect cmd: { @@ -165,6 +196,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rect.Value); state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipRRect cmd: { @@ -173,6 +205,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = state.xform.mapRect(uiRectHelper.fromRect(cmd.rrect.outerRect).Value); state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipPath cmd: { @@ -188,6 +221,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); ObjectPool.release(transformedMesh); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawPath cmd: { @@ -228,9 +262,13 @@ public void addDrawCmd(uiDrawCmd drawCmd) { float sigma = scale * paint.maskFilter.Value.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(uiRectHelper.inflate(mesh.bounds, sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(mesh.bounds, sigma3 + 5), + this._drawCmds.Count - 1)); } else { this._addPaintBounds(mesh.bounds); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(mesh.bounds, 5), + this._drawCmds.Count - 1)); } ObjectPool.release(mesh); @@ -242,24 +280,32 @@ public void addDrawCmd(uiDrawCmd drawCmd) { cmd.image.width, cmd.image.height); rect = state.xform.mapRect(rect); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawImageRect cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst.Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawImageNine cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst.Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawPicture cmd: { var state = this._getState(); var rect = state.xform.mapRect(uiRectHelper.fromRect(cmd.picture.paintBounds).Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawTextBlob cmd: { @@ -268,15 +314,21 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = uiRectHelper.fromRect( cmd.textBlob.Value.shiftedBoundsInText(cmd.offset.Value.dx, cmd.offset.Value.dy)).Value; rect = state.xform.mapRect(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); var paint = cmd.paint; if (paint.maskFilter != null && paint.maskFilter.Value.sigma != 0) { float sigma = scale * paint.maskFilter.Value.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(uiRectHelper.inflate(rect, sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, sigma3 + 5), + this._drawCmds.Count - 1)); } else { this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); } break; diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 77ae2045..8304485f 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -1,7 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using UnityEngine; namespace Unity.UIWidgets.ui { @@ -472,7 +472,8 @@ public void layout(ParagraphConstraints constraints) { // If all the positions have not changed, use the cached ellipsized text // else update the cache - if (!(this._ellipsizedLength == textStart + textCount - truncateCount && + if (!(this._ellipsizedText != null && + this._ellipsizedLength == textStart + textCount - truncateCount && this._ellipsizedText.Length == this._ellipsizedLength + ellipsis.Length && this._ellipsizedText.EndsWith(ellipsis))) { this._ellipsizedText = @@ -1120,491 +1121,5 @@ float getLineXOffset(float lineTotalAdvance) { } } - class SplayTree : IDictionary where TKey : IComparable { - SplayTreeNode root; - int count; - int version = 0; - public void Add(TKey key, TValue value) { - this.Set(key, value, throwOnExisting: true); - } - - public void Add(KeyValuePair item) { - this.Set(item.Key, item.Value, throwOnExisting: true); - } - - public void AddAll(IEnumerable list) { - foreach (var key in list) { - this.Add(new KeyValuePair(key, default)); - } - } - - void Set(TKey key, TValue value, bool throwOnExisting) { - if (this.count == 0) { - this.version++; - this.root = new SplayTreeNode(key, value); - this.count = 1; - return; - } - - this.Splay(key); - - var c = key.CompareTo(this.root.Key); - if (c == 0) { - if (throwOnExisting) { - throw new ArgumentException("An item with the same key already exists in the tree."); - } - - this.version++; - this.root.Value = value; - return; - } - - var n = new SplayTreeNode(key, value); - if (c < 0) { - n.LeftChild = this.root.LeftChild; - n.RightChild = this.root; - this.root.LeftChild = null; - } - else { - n.RightChild = this.root.RightChild; - n.LeftChild = this.root; - this.root.RightChild = null; - } - - this.root = n; - this.count++; - this.Splay(key); - this.version++; - } - - public void Clear() { - this.root = null; - this.count = 0; - this.version++; - } - - public bool ContainsKey(TKey key) { - if (this.count == 0) { - return false; - } - - this.Splay(key); - - return key.CompareTo(this.root.Key) == 0; - } - - public bool Contains(KeyValuePair item) { - if (this.count == 0) { - return false; - } - - this.Splay(item.Key); - - return item.Key.CompareTo(this.root.Key) == 0 && - (ReferenceEquals(this.root.Value, item.Value) || - (!ReferenceEquals(item.Value, null) && item.Value.Equals(this.root.Value))); - } - - public KeyValuePair? First() { - SplayTreeNode t = this.root; - if (t == null) { - return null; - } - - while (t.LeftChild != null) { - t = t.LeftChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair FirstOrDefault() { - SplayTreeNode t = this.root; - if (t == null) { - return new KeyValuePair(default(TKey), default(TValue)); - } - - while (t.LeftChild != null) { - t = t.LeftChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair? Last() { - SplayTreeNode t = this.root; - if (t == null) { - return null; - } - - while (t.RightChild != null) { - t = t.RightChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair LastOrDefault() { - SplayTreeNode t = this.root; - if (t == null) { - return new KeyValuePair(default(TKey), default(TValue)); - } - - while (t.RightChild != null) { - t = t.RightChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - void Splay(TKey key) { - SplayTreeNode l, r, t, y, header; - l = r = header = new SplayTreeNode(default(TKey), default(TValue)); - t = this.root; - while (true) { - var c = key.CompareTo(t.Key); - if (c < 0) { - if (t.LeftChild == null) { - break; - } - - if (key.CompareTo(t.LeftChild.Key) < 0) { - y = t.LeftChild; - t.LeftChild = y.RightChild; - y.RightChild = t; - t = y; - if (t.LeftChild == null) { - break; - } - } - - r.LeftChild = t; - r = t; - t = t.LeftChild; - } - else if (c > 0) { - if (t.RightChild == null) { - break; - } - - if (key.CompareTo(t.RightChild.Key) > 0) { - y = t.RightChild; - t.RightChild = y.LeftChild; - y.LeftChild = t; - t = y; - if (t.RightChild == null) { - break; - } - } - - l.RightChild = t; - l = t; - t = t.RightChild; - } - else { - break; - } - } - - l.RightChild = t.LeftChild; - r.LeftChild = t.RightChild; - t.LeftChild = header.RightChild; - t.RightChild = header.LeftChild; - this.root = t; - } - - public bool Remove(TKey key) { - if (this.count == 0) { - return false; - } - - this.Splay(key); - - if (key.CompareTo(this.root.Key) != 0) { - return false; - } - - if (this.root.LeftChild == null) { - this.root = this.root.RightChild; - } - else { - var swap = this.root.RightChild; - this.root = this.root.LeftChild; - this.Splay(key); - this.root.RightChild = swap; - } - - this.version++; - this.count--; - return true; - } - - public bool TryGetValue(TKey key, out TValue value) { - if (this.count == 0) { - value = default(TValue); - return false; - } - - this.Splay(key); - if (key.CompareTo(this.root.Key) != 0) { - value = default(TValue); - return false; - } - - value = this.root.Value; - return true; - } - - public TValue this[TKey key] { - get { - if (this.count == 0) { - throw new KeyNotFoundException("The key was not found in the tree."); - } - - this.Splay(key); - if (key.CompareTo(this.root.Key) != 0) { - throw new KeyNotFoundException("The key was not found in the tree."); - } - - return this.root.Value; - } - - set { this.Set(key, value, throwOnExisting: false); } - } - - public int Count { - get { return this.count; } - } - - public bool IsReadOnly { - get { return false; } - } - - public bool Remove(KeyValuePair item) { - if (this.count == 0) { - return false; - } - - this.Splay(item.Key); - - if (item.Key.CompareTo(this.root.Key) == 0 && (ReferenceEquals(this.root.Value, item.Value) || - (!ReferenceEquals(item.Value, null) && - item.Value.Equals(this.root.Value)))) { - return false; - } - - if (this.root.LeftChild == null) { - this.root = this.root.RightChild; - } - else { - var swap = this.root.RightChild; - this.root = this.root.LeftChild; - this.Splay(item.Key); - this.root.RightChild = swap; - } - - this.version++; - this.count--; - return true; - } - - public void Trim(int depth) { - if (depth < 0) { - throw new ArgumentOutOfRangeException("depth", "The trim depth must not be negative."); - } - - if (this.count == 0) { - return; - } - - if (depth == 0) { - this.Clear(); - } - else { - var prevCount = this.count; - this.count = this.Trim(this.root, depth - 1); - if (prevCount != this.count) { - this.version++; - } - } - } - - int Trim(SplayTreeNode node, int depth) { - if (depth == 0) { - node.LeftChild = null; - node.RightChild = null; - return 1; - } - else { - int count = 1; - - if (node.LeftChild != null) { - count += this.Trim(node.LeftChild, depth - 1); - } - - if (node.RightChild != null) { - count += this.Trim(node.RightChild, depth - 1); - } - - return count; - } - } - - public ICollection Keys { - get { return new TiedList(this, this.version, this.AsList(node => node.Key)); } - } - - public ICollection Values { - get { return new TiedList(this, this.version, this.AsList(node => node.Value)); } - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) { - this.AsList(node => new KeyValuePair(node.Key, node.Value)).CopyTo(array, arrayIndex); - } - - public IEnumerator> GetEnumerator() { - return new TiedList>(this, this.version, - this.AsList(node => new KeyValuePair(node.Key, node.Value))).GetEnumerator(); - } - - IList AsList(Func selector) { - if (this.root == null) { - return new TEnumerator[0]; - } - - var result = new List(this.count); - this.PopulateList(this.root, result, selector); - return result; - } - - void PopulateList(SplayTreeNode node, List list, - Func selector) { - if (node.LeftChild != null) { - this.PopulateList(node.LeftChild, list, selector); - } - - list.Add(selector(node)); - if (node.RightChild != null) { - this.PopulateList(node.RightChild, list, selector); - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - - sealed class SplayTreeNode { - public readonly TKey Key; - - public TValue Value; - public SplayTreeNode LeftChild; - public SplayTreeNode RightChild; - - public SplayTreeNode(TKey key, TValue value) { - this.Key = key; - this.Value = value; - } - } - - sealed class TiedList : IList { - readonly SplayTree tree; - readonly int version; - readonly IList backingList; - - public TiedList(SplayTree tree, int version, IList backingList) { - if (tree == null) { - throw new ArgumentNullException("tree"); - } - - if (backingList == null) { - throw new ArgumentNullException("backingList"); - } - - this.tree = tree; - this.version = version; - this.backingList = backingList; - } - - public int IndexOf(T item) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList.IndexOf(item); - } - - public void Insert(int index, T item) { - throw new NotSupportedException(); - } - - public void RemoveAt(int index) { - throw new NotSupportedException(); - } - - public T this[int index] { - get { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList[index]; - } - set { throw new NotSupportedException(); } - } - - public void Add(T item) { - throw new NotSupportedException(); - } - - public void Clear() { - throw new NotSupportedException(); - } - - public bool Contains(T item) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - this.backingList.CopyTo(array, arrayIndex); - } - - public int Count { - get { return this.tree.count; } - } - - public bool IsReadOnly { - get { return true; } - } - - public bool Remove(T item) { - throw new NotSupportedException(); - } - - public IEnumerator GetEnumerator() { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - foreach (var item in this.backingList) { - yield return item; - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } - } } \ No newline at end of file diff --git a/Runtime/widgets/list_wheel_scroll_view.cs b/Runtime/widgets/list_wheel_scroll_view.cs index bb050600..9aae4085 100644 --- a/Runtime/widgets/list_wheel_scroll_view.cs +++ b/Runtime/widgets/list_wheel_scroll_view.cs @@ -6,6 +6,7 @@ using Unity.UIWidgets.painting; using Unity.UIWidgets.physics; using Unity.UIWidgets.rendering; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.scheduler; using Unity.UIWidgets.ui; using UnityEngine; diff --git a/Runtime/widgets/sliver.cs b/Runtime/widgets/sliver.cs index cbd9bad0..8a52d4eb 100644 --- a/Runtime/widgets/sliver.cs +++ b/Runtime/widgets/sliver.cs @@ -5,6 +5,7 @@ using Unity.UIWidgets.foundation; using Unity.UIWidgets.painting; using Unity.UIWidgets.rendering; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.ui; namespace Unity.UIWidgets.widgets {