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 {