diff --git a/Perspex.Base/Threading/NGenerics/Constants.cs b/NGenerics/Constants.cs similarity index 100% rename from Perspex.Base/Threading/NGenerics/Constants.cs rename to NGenerics/Constants.cs diff --git a/Perspex.Base/Threading/NGenerics/ComparisonComparer.cs b/NGenerics/DataStructures/Comparers/ComparisonComparer.cs similarity index 96% rename from Perspex.Base/Threading/NGenerics/ComparisonComparer.cs rename to NGenerics/DataStructures/Comparers/ComparisonComparer.cs index b1c5a2a860..8ba462c107 100644 --- a/Perspex.Base/Threading/NGenerics/ComparisonComparer.cs +++ b/NGenerics/DataStructures/Comparers/ComparisonComparer.cs @@ -19,7 +19,7 @@ namespace NGenerics.Comparers /// /// The type of the objects to compare. //[Serializable] - internal sealed class ComparisonComparer : IComparer + public sealed class ComparisonComparer : IComparer { #region Globals diff --git a/Perspex.Base/Threading/NGenerics/KeyValuePairComparer.cs b/NGenerics/DataStructures/Comparers/KeyValuePairComparer.cs similarity index 96% rename from Perspex.Base/Threading/NGenerics/KeyValuePairComparer.cs rename to NGenerics/DataStructures/Comparers/KeyValuePairComparer.cs index 05f14d1342..3cf39a1766 100644 --- a/Perspex.Base/Threading/NGenerics/KeyValuePairComparer.cs +++ b/NGenerics/DataStructures/Comparers/KeyValuePairComparer.cs @@ -18,7 +18,7 @@ namespace NGenerics.Comparers { /// The key type. /// The value type. //[Serializable] - internal class KeyValuePairComparer : IComparer> { + public class KeyValuePairComparer : IComparer> { #region Globals diff --git a/NGenerics/DataStructures/Comparers/ReverseComparer.cs b/NGenerics/DataStructures/Comparers/ReverseComparer.cs new file mode 100644 index 0000000000..0b7f91b62d --- /dev/null +++ b/NGenerics/DataStructures/Comparers/ReverseComparer.cs @@ -0,0 +1,83 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; +using System.Collections.Generic; +using NGenerics.Util; + +namespace NGenerics.Comparers +{ + /// + /// A comparer that wraps the IComparable interface to reproduce the opposite comparison result. + /// + /// The type of the objects to compare. + //[Serializable] + public sealed class ReverseComparer : IComparer + { + #region Globals + + private IComparer comparerToUse; + + #endregion + + #region Construction + /// + public ReverseComparer() + { + comparerToUse = Comparer.Default; + } + + + /// The comparer to reverse. + /// is a null reference (Nothing in Visual Basic). + public ReverseComparer(IComparer comparer) + { + + Guard.ArgumentNotNull(comparer, "comparer"); + comparerToUse = comparer; + } + + #endregion + + #region IComparer Members + + /// + public int Compare(T x, T y) + { + return (comparerToUse.Compare(y, x)); + } + + #endregion + + #region Public Members + + /// + /// Gets or sets the comparer used in this instance. + /// + /// The comparer. + /// is a null reference (Nothing in Visual Basic). + public IComparer Comparer + { + get + { + return comparerToUse; + } + set + { + + Guard.ArgumentNotNull(value, "value"); + + comparerToUse = value; + } + } + + #endregion + } +} diff --git a/NGenerics/DataStructures/General/Heap.cs b/NGenerics/DataStructures/General/Heap.cs new file mode 100644 index 0000000000..0e00b3d1b8 --- /dev/null +++ b/NGenerics/DataStructures/General/Heap.cs @@ -0,0 +1,429 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; +using System.Collections; +using System.Collections.Generic; +using NGenerics.Comparers; +using System.Diagnostics.CodeAnalysis; +using NGenerics.Util; + +namespace NGenerics.DataStructures.General +{ + /// + /// An implementation of a Heap data structure. + /// + /// The type of item stored in the . + //[Serializable] + [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + public class Heap : ICollection, IHeap + { + #region Globals + + const string heapIsEmpty = "The heap is empty."; + private readonly List data; + private readonly IComparer comparerToUse; + private readonly HeapType thisType; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the class. + /// + /// The type of Heap to create. + /// is not either or . + /// + /// + /// + /// + public Heap(HeapType type) : this(type, Comparer.Default) { } + + + /// + /// Initializes a new instance of the class. + /// + /// The type of heap. + /// The capacity. + /// is not either or . + /// is less than 0. + /// . + /// + /// + /// + /// + public Heap(HeapType type, int capacity) : this(type, capacity, Comparer.Default) { } + + + /// + /// Initializes a new instance of the class. + /// + /// The type of heap. + /// The comparer. + /// is not either or . + public Heap(HeapType type, Comparison comparer) : this(type, new ComparisonComparer(comparer)){} + + /// + /// Initializes a new instance of the class. + /// + /// The type of heap. + /// The capacity of the heap to start with. + /// The comparer. + /// is not either or . + /// is less than 0. + public Heap(HeapType type, int capacity, Comparison comparer) : this(type, capacity, new ComparisonComparer(comparer)) { } + + /// + /// Initializes a new instance of the class. + /// + /// The type of Heap to create. + /// The comparer to use. + /// is a null reference (Nothing in Visual Basic). + /// is not either or . + /// + /// + /// + /// + public Heap(HeapType type, IComparer comparer) + { + Guard.ArgumentNotNull(comparer, "comparer"); + + if ((type != HeapType.Minimum) && (type != HeapType.Maximum)) + { + throw new ArgumentOutOfRangeException("type"); + } + + thisType = type; + + data = new List {default(T)}; + + comparerToUse = type == HeapType.Minimum ? comparer : new ReverseComparer(comparer); + } + + + /// The type of heap. + /// The initial capacity of the Heap. + /// The comparer to use. + /// is less than 0.. + /// is a null reference (Nothing in Visual Basic). + /// is not either or . + public Heap(HeapType type, int capacity, IComparer comparer) + { + Guard.ArgumentNotNull(comparer, "comparer"); + + if ((type != HeapType.Minimum) && (type != HeapType.Maximum)) + { + throw new ArgumentOutOfRangeException("type"); + } + thisType = type; + + data = new List(capacity) {default(T)}; + + comparerToUse = type == HeapType.Minimum ? comparer : new ReverseComparer(comparer); + } + + #endregion + + #region Public Members + + + /// + /// + /// + /// + /// + public T Root + { + get + { + #region Validation + + if (Count == 0) + { + throw new InvalidOperationException(heapIsEmpty); + } + + #endregion + + return data[1]; + } + } + + + /// + /// + /// + /// + /// + public T RemoveRoot() + { + #region Validation + + if (Count == 0) + { + throw new InvalidOperationException(heapIsEmpty); + } + + #endregion + + // The minimum item to return. + var minimum = data[1]; + + RemoveRootItem(minimum); + return minimum; + } + + + /// + /// Removes the root item. + /// + /// The item. + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + protected virtual void RemoveRootItem(T item) + { + + // The last item in the heap + var last = data[Count]; + data.RemoveAt(Count); + + // If there's still items left in this heap, re-heapify it. + if (Count > 0) + { + // Re-heapify the binary tree to conform to the heap property + var counter = 1; + + while ((counter * 2) < (data.Count)) + { + var child = counter * 2; + + if (((child + 1) < (data.Count)) && + (comparerToUse.Compare(data[child + 1], data[child]) < 0)) + { + child++; + } + + if (comparerToUse.Compare(last, data[child]) <= 0) + { + break; + } + + data[counter] = data[child]; + counter = child; + } + + data[counter] = last; + } + + } + + + /// + /// Gets the type of heap represented by this instance. + /// + /// The type of heap. + /// + /// + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + public HeapType Type + { + get + { + return thisType; + } + } + + #endregion + + #region ICollection Members + + + + /// + /// + /// + /// + /// + public bool IsEmpty + { + get + { + return Count == 0; + } + } + + + + /// + /// + /// + /// + /// + public bool Contains(T item) + { + return data.Contains(item); + } + + /// + /// + /// + /// + /// + public void CopyTo(T[] array, int arrayIndex) + { + #region Validation + + Guard.ArgumentNotNull(array, "array"); + + if ((array.Length - arrayIndex) < Count) + { + throw new ArgumentException(Constants.NotEnoughSpaceInTheTargetArray, "array"); + } + + #endregion + + for (var i = 1; i < data.Count; i++) + { + array[arrayIndex++] = data[i]; + } + } + + /// + /// + /// + /// + /// + public int Count + { + get + { + return data.Count - 1; + } + } + + /// + /// + /// + /// + /// + public void Add(T item) + { + AddItem(item); + } + + /// + /// Adds the item. + /// + /// The item to add. + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + protected virtual void AddItem(T item) + { + // Add a dummy to the end of the list (it will be replaced) + data.Add(default(T)); + + var counter = data.Count - 1; + + while ((counter > 1) && (comparerToUse.Compare(data[counter / 2], item) > 0)) + { + data[counter] = data[counter / 2]; + counter = counter / 2; + } + + data[counter] = item; + } + + + + /// + /// Always. + [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")] + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// + /// + /// + /// + public IEnumerator GetEnumerator() + { + for (var i = 1; i < data.Count; i++) + { + yield return data[i]; + } + } + + /// + /// + /// + /// + /// + public void Clear() + { + ClearItems(); + } + /// + /// Clears all the objects in this instance. + /// + /// + /// Notes to Inheritors: + /// Derived classes can override this method to change the behavior of the method. + /// + protected virtual void ClearItems() + { + data.RemoveRange(1, data.Count - 1); // Clears all objects in this instance except the first dummy one. + } + + #endregion + + #region ICollection Members + + /// + /// + /// + /// + /// + public bool IsReadOnly + { + get + { + return false; + } + } + + #endregion + + #region IEnumerable Members + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } +} diff --git a/NGenerics/DataStructures/General/HeapType.cs b/NGenerics/DataStructures/General/HeapType.cs new file mode 100644 index 0000000000..ba961f1d2b --- /dev/null +++ b/NGenerics/DataStructures/General/HeapType.cs @@ -0,0 +1,29 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + + +namespace NGenerics.DataStructures.General +{ + /// + /// The type of to implemented. + /// + public enum HeapType + { + /// + /// Makes the heap a Minimum-Heap - the smallest item is kept in the root of the . + /// + Minimum, + + /// + /// Makes the heap a Maximum-Heap - the largest item is kept in the root of the . + /// + Maximum + } +} \ No newline at end of file diff --git a/NGenerics/DataStructures/General/IHeap.cs b/NGenerics/DataStructures/General/IHeap.cs new file mode 100644 index 0000000000..2758af6391 --- /dev/null +++ b/NGenerics/DataStructures/General/IHeap.cs @@ -0,0 +1,41 @@ +/* + Copyright 2007-2013 The NGenerics Team + (https://github.com/ngenerics/ngenerics/wiki/Team) + + This program is licensed under the GNU Lesser General Public License (LGPL). You should + have received a copy of the license along with the source code. If not, an online copy + of the license can be found at http://www.gnu.org/copyleft/lesser.html. +*/ + + +using System; + +namespace NGenerics.DataStructures.General +{ + /// + /// An interface for the data structure. + /// + /// The type of elements in the heap. + public interface IHeap + { + /// + /// Adds the specified item. + /// + /// The item. + void Add(T item); + + /// + /// Removes the root and returns it. + /// + /// The root of the . + /// The is empty. + T RemoveRoot(); + + /// + /// Gets the root. + /// + /// The root. + /// The is empty. + T Root { get; } + } +} diff --git a/Perspex.Base/Threading/NGenerics/IQueue.cs b/NGenerics/DataStructures/Queues/IQueue.cs similarity index 97% rename from Perspex.Base/Threading/NGenerics/IQueue.cs rename to NGenerics/DataStructures/Queues/IQueue.cs index 67490b4892..a113d42f13 100644 --- a/Perspex.Base/Threading/NGenerics/IQueue.cs +++ b/NGenerics/DataStructures/Queues/IQueue.cs @@ -18,7 +18,7 @@ namespace NGenerics.DataStructures.Queues /// /// The type of the elements in the queue. [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] - internal interface IQueue + public interface IQueue { /// /// Enqueues the item at the back of the queue. diff --git a/Perspex.Base/Threading/NGenerics/PriorityQueue.cs b/NGenerics/DataStructures/Queues/PriorityQueue.cs similarity index 99% rename from Perspex.Base/Threading/NGenerics/PriorityQueue.cs rename to NGenerics/DataStructures/Queues/PriorityQueue.cs index f24cc0c41c..662ae39bae 100644 --- a/Perspex.Base/Threading/NGenerics/PriorityQueue.cs +++ b/NGenerics/DataStructures/Queues/PriorityQueue.cs @@ -29,7 +29,7 @@ namespace NGenerics.DataStructures.Queues [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] //[Serializable] - internal class PriorityQueue : ICollection, IQueue + public class PriorityQueue : ICollection, IQueue { #region Globals diff --git a/Perspex.Base/Threading/NGenerics/PriorityQueueType.cs b/NGenerics/DataStructures/Queues/PriorityQueueType.cs similarity index 95% rename from Perspex.Base/Threading/NGenerics/PriorityQueueType.cs rename to NGenerics/DataStructures/Queues/PriorityQueueType.cs index 4b23aa4696..8c87d87811 100644 --- a/Perspex.Base/Threading/NGenerics/PriorityQueueType.cs +++ b/NGenerics/DataStructures/Queues/PriorityQueueType.cs @@ -12,7 +12,7 @@ namespace NGenerics.DataStructures.Queues /// /// Specifies the Priority Queue type (min or max). /// - internal enum PriorityQueueType + public enum PriorityQueueType { /// /// Specify a Minimum . diff --git a/Perspex.Base/Threading/NGenerics/BinarySearchTreeBase.cs b/NGenerics/DataStructures/Trees/BinarySearchTreeBase.cs similarity index 99% rename from Perspex.Base/Threading/NGenerics/BinarySearchTreeBase.cs rename to NGenerics/DataStructures/Trees/BinarySearchTreeBase.cs index c4d77aa9be..ea2c44ff34 100644 --- a/Perspex.Base/Threading/NGenerics/BinarySearchTreeBase.cs +++ b/NGenerics/DataStructures/Trees/BinarySearchTreeBase.cs @@ -24,7 +24,7 @@ namespace NGenerics.DataStructures.Trees /// /// //[Serializable] - internal abstract class BinarySearchTreeBase : ISearchTree + public abstract class BinarySearchTreeBase : ISearchTree { #region Globals diff --git a/Perspex.Base/Threading/NGenerics/BinaryTree.cs b/NGenerics/DataStructures/Trees/BinaryTree.cs similarity index 99% rename from Perspex.Base/Threading/NGenerics/BinaryTree.cs rename to NGenerics/DataStructures/Trees/BinaryTree.cs index 02f1b85096..f1bda540c1 100644 --- a/Perspex.Base/Threading/NGenerics/BinaryTree.cs +++ b/NGenerics/DataStructures/Trees/BinaryTree.cs @@ -23,7 +23,7 @@ namespace NGenerics.DataStructures.Trees /// The type of elements in the . //[Serializable] [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] - internal class BinaryTree : ICollection, ITree + public class BinaryTree : ICollection, ITree { #region Globals diff --git a/Perspex.Base/Threading/NGenerics/ISearchTree.cs b/NGenerics/DataStructures/Trees/ISearchTree.cs similarity index 98% rename from Perspex.Base/Threading/NGenerics/ISearchTree.cs rename to NGenerics/DataStructures/Trees/ISearchTree.cs index 6547a80faa..c9f5fb0534 100644 --- a/Perspex.Base/Threading/NGenerics/ISearchTree.cs +++ b/NGenerics/DataStructures/Trees/ISearchTree.cs @@ -21,7 +21,7 @@ namespace NGenerics.DataStructures.Trees /// /// The type of element to hold in the tree. [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] - internal interface ISearchTree : ICollection { + public interface ISearchTree : ICollection { /// /// Gets the largest item in the tree. /// diff --git a/Perspex.Base/Threading/NGenerics/ITree.cs b/NGenerics/DataStructures/Trees/ITree.cs similarity index 98% rename from Perspex.Base/Threading/NGenerics/ITree.cs rename to NGenerics/DataStructures/Trees/ITree.cs index b7f49400d5..c80d8e8604 100644 --- a/Perspex.Base/Threading/NGenerics/ITree.cs +++ b/NGenerics/DataStructures/Trees/ITree.cs @@ -14,7 +14,7 @@ namespace NGenerics.DataStructures.Trees { /// An interface for the tree data structure /// /// The type of elements in the tree. - internal interface ITree { + public interface ITree { /// /// Adds the specified child to the tree. /// diff --git a/Perspex.Base/Threading/NGenerics/NodeColor.cs b/NGenerics/DataStructures/Trees/NodeColor.cs similarity index 100% rename from Perspex.Base/Threading/NGenerics/NodeColor.cs rename to NGenerics/DataStructures/Trees/NodeColor.cs diff --git a/Perspex.Base/Threading/NGenerics/RedBlackTree.cs b/NGenerics/DataStructures/Trees/RedBlackTree.cs similarity index 99% rename from Perspex.Base/Threading/NGenerics/RedBlackTree.cs rename to NGenerics/DataStructures/Trees/RedBlackTree.cs index d7e2bfb699..422155aa4a 100644 --- a/Perspex.Base/Threading/NGenerics/RedBlackTree.cs +++ b/NGenerics/DataStructures/Trees/RedBlackTree.cs @@ -24,7 +24,7 @@ namespace NGenerics.DataStructures.Trees { /// The type of element to keep in the tree. [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] //[Serializable] - internal class RedBlackTree : BinarySearchTreeBase { + public class RedBlackTree : BinarySearchTreeBase { #region Construction diff --git a/Perspex.Base/Threading/NGenerics/RedBlackTreeDictionary.cs b/NGenerics/DataStructures/Trees/RedBlackTreeDictionary.cs similarity index 98% rename from Perspex.Base/Threading/NGenerics/RedBlackTreeDictionary.cs rename to NGenerics/DataStructures/Trees/RedBlackTreeDictionary.cs index 5333915862..de0d677d07 100644 --- a/Perspex.Base/Threading/NGenerics/RedBlackTreeDictionary.cs +++ b/NGenerics/DataStructures/Trees/RedBlackTreeDictionary.cs @@ -31,7 +31,7 @@ namespace NGenerics.DataStructures.Trees #if (!SILVERLIGHT && !WINDOWSPHONE) //[Serializable] #endif - internal class RedBlackTree : RedBlackTree>, IDictionary // BinarySearchTreeBase + public class RedBlackTree : RedBlackTree>, IDictionary // BinarySearchTreeBase { #region Construction /// diff --git a/Perspex.Base/Threading/NGenerics/RedBlackTreeList.cs b/NGenerics/DataStructures/Trees/RedBlackTreeList.cs similarity index 100% rename from Perspex.Base/Threading/NGenerics/RedBlackTreeList.cs rename to NGenerics/DataStructures/Trees/RedBlackTreeList.cs diff --git a/Perspex.Base/Threading/NGenerics/RedBlackTreeNode.cs b/NGenerics/DataStructures/Trees/RedBlackTreeNode.cs similarity index 100% rename from Perspex.Base/Threading/NGenerics/RedBlackTreeNode.cs rename to NGenerics/DataStructures/Trees/RedBlackTreeNode.cs diff --git a/NGenerics/NGenerics.csproj b/NGenerics/NGenerics.csproj new file mode 100644 index 0000000000..bb4044554e --- /dev/null +++ b/NGenerics/NGenerics.csproj @@ -0,0 +1,76 @@ + + + + + 11.0 + Debug + AnyCPU + {415E048E-4611-4815-9CF2-D774E29079AC} + Library + Properties + NGenerics + NGenerics + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Perspex.Base/Threading/NGenerics/IVisitor.cs b/NGenerics/Patterns/Visitor/IVisitor.cs similarity index 96% rename from Perspex.Base/Threading/NGenerics/IVisitor.cs rename to NGenerics/Patterns/Visitor/IVisitor.cs index 6ec7cb8852..d9460e51a9 100644 --- a/Perspex.Base/Threading/NGenerics/IVisitor.cs +++ b/NGenerics/Patterns/Visitor/IVisitor.cs @@ -14,7 +14,7 @@ namespace NGenerics.Patterns.Visitor /// Provides an interface for visitors. /// /// The type of objects to be visited. - internal interface IVisitor + public interface IVisitor { /// /// Gets a value indicating whether this instance is done performing it's work.. diff --git a/Perspex.Base/Threading/NGenerics/InOrderVisitor.cs b/NGenerics/Patterns/Visitor/InOrderVisitor.cs similarity index 95% rename from Perspex.Base/Threading/NGenerics/InOrderVisitor.cs rename to NGenerics/Patterns/Visitor/InOrderVisitor.cs index dbe64f07dc..12ba98e55e 100644 --- a/Perspex.Base/Threading/NGenerics/InOrderVisitor.cs +++ b/NGenerics/Patterns/Visitor/InOrderVisitor.cs @@ -14,7 +14,7 @@ namespace NGenerics.Patterns.Visitor /// An in order implementation of the class. /// /// The type of objects to be visited. - internal sealed class InOrderVisitor : OrderedVisitor + public sealed class InOrderVisitor : OrderedVisitor { #region Construction diff --git a/Perspex.Base/Threading/NGenerics/KeyTrackingVisitor.cs b/NGenerics/Patterns/Visitor/KeyTrackingVisitor.cs similarity index 94% rename from Perspex.Base/Threading/NGenerics/KeyTrackingVisitor.cs rename to NGenerics/Patterns/Visitor/KeyTrackingVisitor.cs index 4265115586..d03f935fe3 100644 --- a/Perspex.Base/Threading/NGenerics/KeyTrackingVisitor.cs +++ b/NGenerics/Patterns/Visitor/KeyTrackingVisitor.cs @@ -17,7 +17,7 @@ namespace NGenerics.Patterns.Visitor /// /// The type of the keys for the items to be visited. /// The type of the values for the items to be visited. - internal sealed class KeyTrackingVisitor : IVisitor> + public sealed class KeyTrackingVisitor : IVisitor> { #region Globals diff --git a/Perspex.Base/Threading/NGenerics/OrderedVisitor.cs b/NGenerics/Patterns/Visitor/OrderedVisitor.cs similarity index 98% rename from Perspex.Base/Threading/NGenerics/OrderedVisitor.cs rename to NGenerics/Patterns/Visitor/OrderedVisitor.cs index c639825d09..881c4bfda1 100644 --- a/Perspex.Base/Threading/NGenerics/OrderedVisitor.cs +++ b/NGenerics/Patterns/Visitor/OrderedVisitor.cs @@ -19,7 +19,7 @@ namespace NGenerics.Patterns.Visitor /// Used primarily as a base class for Visitors specializing in a specific order type. /// /// The type of objects to be visited. - internal class OrderedVisitor : IVisitor + public class OrderedVisitor : IVisitor { #region Globals diff --git a/Perspex.Base/Threading/NGenerics/TrackingVisitor.cs b/NGenerics/Patterns/Visitor/TrackingVisitor.cs similarity index 96% rename from Perspex.Base/Threading/NGenerics/TrackingVisitor.cs rename to NGenerics/Patterns/Visitor/TrackingVisitor.cs index 14f5e6f2b6..17d4c67fa4 100644 --- a/Perspex.Base/Threading/NGenerics/TrackingVisitor.cs +++ b/NGenerics/Patterns/Visitor/TrackingVisitor.cs @@ -18,7 +18,7 @@ namespace NGenerics.Patterns.Visitor /// data structures. /// /// The type of objects to be visited. - internal sealed class TrackingVisitor : IVisitor + public sealed class TrackingVisitor : IVisitor { #region Globals diff --git a/Perspex.Base/Threading/NGenerics/ValueTrackingVisitor.cs b/NGenerics/Patterns/Visitor/ValueTrackingVisitor.cs similarity index 94% rename from Perspex.Base/Threading/NGenerics/ValueTrackingVisitor.cs rename to NGenerics/Patterns/Visitor/ValueTrackingVisitor.cs index d0f8f0eff9..01d79e6617 100644 --- a/Perspex.Base/Threading/NGenerics/ValueTrackingVisitor.cs +++ b/NGenerics/Patterns/Visitor/ValueTrackingVisitor.cs @@ -17,7 +17,7 @@ namespace NGenerics.Patterns.Visitor /// /// The type of key of the KeyValuePair. /// The type of value of the KeyValuePair. - internal sealed class ValueTrackingVisitor : IVisitor> + public sealed class ValueTrackingVisitor : IVisitor> { #region Globals diff --git a/NGenerics/Properties/AssemblyInfo.cs b/NGenerics/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f36d08c227 --- /dev/null +++ b/NGenerics/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NGenerics")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NGenerics")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Perspex.Base/Threading/NGenerics/Guard.cs b/NGenerics/Util/Guard.cs similarity index 98% rename from Perspex.Base/Threading/NGenerics/Guard.cs rename to NGenerics/Util/Guard.cs index bd3de2a291..89f70a7cba 100644 --- a/Perspex.Base/Threading/NGenerics/Guard.cs +++ b/NGenerics/Util/Guard.cs @@ -15,7 +15,7 @@ namespace NGenerics.Util /// /// Performs common argument validation. /// - internal static class Guard + public static class Guard { #region Methods diff --git a/Perspex.Application/Application.cs b/Perspex.Application/Application.cs index f6dafd8211..c78d67f1fb 100644 --- a/Perspex.Application/Application.cs +++ b/Perspex.Application/Application.cs @@ -10,6 +10,8 @@ namespace Perspex using System.Threading; using Perspex.Controls; using Perspex.Input; + using Perspex.Layout; + using Perspex.Rendering; using Perspex.Styling; using Perspex.Threading; using Splat; @@ -90,6 +92,9 @@ namespace Perspex Locator.CurrentMutable.Register(() => this.FocusManager, typeof(IFocusManager)); Locator.CurrentMutable.Register(() => this.InputManager, typeof(IInputManager)); Locator.CurrentMutable.Register(() => this.styler, typeof(IStyler)); + + Locator.CurrentMutable.Register(() => new LayoutManager(), typeof(ILayoutManager)); + Locator.CurrentMutable.Register(() => new RenderManager(), typeof(IRenderManager)); } } } diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index e560962bbd..18f0441456 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -54,28 +54,6 @@ - - - - - - - - - - - - - - - - - - - - - - @@ -98,7 +76,12 @@ - + + + {415e048e-4611-4815-9cf2-d774e29079ac} + NGenerics + + diff --git a/Perspex.Controls/Presenters/ContentPresenter.cs b/Perspex.Controls/Presenters/ContentPresenter.cs index 3d9958cf6f..fb233d4aa2 100644 --- a/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/Perspex.Controls/Presenters/ContentPresenter.cs @@ -15,7 +15,7 @@ namespace Perspex.Controls.Presenters public class ContentPresenter : Control, IVisual { public static readonly PerspexProperty ContentProperty = - ContentControl.ContentProperty.AddOwner(); + ContentControl.ContentProperty.AddOwner(); private bool createdChild; diff --git a/Perspex.Controls/Primitives/ScrollBar.cs b/Perspex.Controls/Primitives/ScrollBar.cs index 6c075a8057..5a8c0f683f 100644 --- a/Perspex.Controls/Primitives/ScrollBar.cs +++ b/Perspex.Controls/Primitives/ScrollBar.cs @@ -61,5 +61,10 @@ namespace Perspex.Controls.Primitives get { return this.GetValue(OrientationProperty); } set { this.SetValue(OrientationProperty, value); } } + + protected override Size MeasureOverride(Size availableSize) + { + return base.MeasureOverride(availableSize); + } } } diff --git a/Perspex.Controls/Primitives/TemplatedControl.cs b/Perspex.Controls/Primitives/TemplatedControl.cs index 207e231745..90d305f4f8 100644 --- a/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/Perspex.Controls/Primitives/TemplatedControl.cs @@ -26,11 +26,11 @@ namespace Perspex.Controls.Primitives set { this.SetValue(TemplateProperty, value); } } - public sealed override void Render(IDrawingContext context) + public override void Render(IDrawingContext context) { } - protected sealed override void ApplyTemplate() + protected override void ApplyTemplate() { if (!this.templateApplied) { diff --git a/Perspex.Controls/ScrollViewer.cs b/Perspex.Controls/ScrollViewer.cs index e26ca99831..f37954659e 100644 --- a/Perspex.Controls/ScrollViewer.cs +++ b/Perspex.Controls/ScrollViewer.cs @@ -46,6 +46,11 @@ namespace Perspex.Controls private set { this.SetValue(ViewportProperty, value); } } + protected override Size MeasureOverride(Size availableSize) + { + return base.MeasureOverride(availableSize); + } + protected override void OnTemplateApplied() { this.presenter = this.GetTemplateChild("presenter"); diff --git a/Perspex.Controls/TextBox.cs b/Perspex.Controls/TextBox.cs index 94d826b50e..710617a708 100644 --- a/Perspex.Controls/TextBox.cs +++ b/Perspex.Controls/TextBox.cs @@ -45,7 +45,9 @@ namespace Perspex.Controls set { - value = Math.Min(Math.Max(value, 0), this.Text.Length); + var text = this.Text ?? string.Empty; + + value = Math.Min(Math.Max(value, 0), text.Length); if (this.caretIndex != value) { @@ -80,7 +82,7 @@ namespace Perspex.Controls private void OnKeyDown(object sender, KeyEventArgs e) { - string text = this.Text; + string text = this.Text ?? string.Empty; switch (e.Key) { diff --git a/Perspex.Controls/Window.cs b/Perspex.Controls/Window.cs index f4f5843bb4..19aac2f48c 100644 --- a/Perspex.Controls/Window.cs +++ b/Perspex.Controls/Window.cs @@ -45,6 +45,8 @@ namespace Perspex.Controls this.impl = Locator.Current.GetService(); this.inputManager = Locator.Current.GetService(); + this.LayoutManager = Locator.Current.GetService(); + this.RenderManager = Locator.Current.GetService(); if (this.impl == null) { @@ -58,6 +60,18 @@ namespace Perspex.Controls "Could not create input manager: maybe Application.RegisterServices() wasn't called?"); } + if (this.LayoutManager == null) + { + throw new InvalidOperationException( + "Could not create layout manager: maybe Application.RegisterServices() wasn't called?"); + } + + if (this.RenderManager == null) + { + throw new InvalidOperationException( + "Could not create render manager: maybe Application.RegisterServices() wasn't called?"); + } + this.impl.SetOwner(this); this.impl.Activated = this.HandleActivated; this.impl.Closed = this.HandleClosed; @@ -69,10 +83,9 @@ namespace Perspex.Controls this.dispatcher = Dispatcher.UIThread; this.renderer = renderInterface.CreateRenderer(this.impl.Handle, clientSize.Width, clientSize.Height); - this.LayoutManager = new LayoutManager(this); + this.LayoutManager.Root = this; this.LayoutManager.LayoutNeeded.Subscribe(_ => this.HandleLayoutNeeded()); - this.RenderManager = new RenderManager(); this.RenderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded()); this.GetObservable(TitleProperty).Subscribe(s => this.impl.SetTitle(s)); diff --git a/Perspex.Diagnostics/DevTools.cs b/Perspex.Diagnostics/DevTools.cs index d5c7d4d66a..cafc19450b 100644 --- a/Perspex.Diagnostics/DevTools.cs +++ b/Perspex.Diagnostics/DevTools.cs @@ -25,7 +25,7 @@ namespace Perspex.Diagnostics { DataTemplates = new DataTemplates { - new TreeDataTemplate(GetHeader, x => x.Children), + new TreeDataTemplate(GetHeader, x => x.Children, x => true), }, [!TreeView.ItemsProperty] = this[!DevTools.RootProperty].Select(x => { diff --git a/Perspex.Diagnostics/ViewModels/PropertyDetails.cs b/Perspex.Diagnostics/ViewModels/PropertyDetails.cs index 212069dca1..3fd5b355dd 100644 --- a/Perspex.Diagnostics/ViewModels/PropertyDetails.cs +++ b/Perspex.Diagnostics/ViewModels/PropertyDetails.cs @@ -14,7 +14,7 @@ namespace Perspex.Diagnostics.ViewModels public PropertyDetails(PerspexPropertyValue value) { this.Name = value.Property.Name; - this.Value = value.CurrentValue; + this.Value = value.CurrentValue ?? "(null)"; this.Priority = (value.PriorityValue != null) ? Enum.GetName(typeof(BindingPriority), value.PriorityValue.ValuePriority) : value.Property.Inherits ? "Inherited" : "Unset"; diff --git a/Perspex.Layout.UnitTests/LayoutableTests.cs b/Perspex.Layout.UnitTests/LayoutableTests.cs new file mode 100644 index 0000000000..705662762a --- /dev/null +++ b/Perspex.Layout.UnitTests/LayoutableTests.cs @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Layout.UnitTests +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Moq.AutoMock; + using Perspex.Controls; + using Perspex.Input; + using Perspex.Platform; + using Perspex.Rendering; + using Perspex.Styling; + using Splat; + + [TestClass] + public class LayoutableTests + { + private Mock layoutManager; + + [TestMethod] + public void Calling_InvalidateMeasure_On_Window_Should_Call_LayoutManager_InvalidateMeasure() + { + using (var d = Locator.Current.WithResolver()) + { + this.RegisterServices(); + + Window target = new Window(); + } + } + + private void RegisterServices() + { + var l = Locator.CurrentMutable; + var m = new AutoMocker(); + + var lm = m.CreateInstance(); + //this.layoutManager = + + l.RegisterConstant(new Mock().Object, typeof(IInputManager)); + l.RegisterConstant(this.layoutManager.Object, typeof(ILayoutManager)); + l.RegisterConstant(new Mock().Object, typeof(IPlatformRenderInterface)); + l.RegisterConstant(new Mock().Object, typeof(IRenderManager)); + l.RegisterConstant(new Mock().Object, typeof(IStyler)); + l.RegisterConstant(new Mock().Object, typeof(IWindowImpl)); + } + } +} diff --git a/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj b/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj new file mode 100644 index 0000000000..1aefe5f42b --- /dev/null +++ b/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj @@ -0,0 +1,122 @@ + + + + Debug + AnyCPU + {DB070A10-BF39-4752-8456-86E9D5928478} + Library + Properties + Perspex.Layout.UnitTests + Perspex.Layout.UnitTests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll + + + ..\packages\Moq.AutoMock.0.3.2.1\lib\net40\Moq.AutoMock.dll + + + ..\packages\Splat.1.5.1\lib\Net45\Splat.dll + + + + + + + + + + + + + + + + + + + + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/Perspex.Layout.UnitTests/Properties/AssemblyInfo.cs b/Perspex.Layout.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f5e6e51355 --- /dev/null +++ b/Perspex.Layout.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Layout.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Layout.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("dbe66cf2-493d-42f4-bd18-d56b15d5dffd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Perspex.Layout.UnitTests/app.config b/Perspex.Layout.UnitTests/app.config new file mode 100644 index 0000000000..3bdf7fb823 --- /dev/null +++ b/Perspex.Layout.UnitTests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Perspex.Layout.UnitTests/packages.config b/Perspex.Layout.UnitTests/packages.config new file mode 100644 index 0000000000..105b5d5ca8 --- /dev/null +++ b/Perspex.Layout.UnitTests/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Perspex.Layout/ILayoutManager.cs b/Perspex.Layout/ILayoutManager.cs index 0e8a84e92b..ac42ff713a 100644 --- a/Perspex.Layout/ILayoutManager.cs +++ b/Perspex.Layout/ILayoutManager.cs @@ -9,16 +9,56 @@ namespace Perspex.Layout using System; using System.Reactive; + /// + /// Manages measuring and arranging of controls. + /// + /// + /// Each layout root element such as a window has its own LayoutManager that is responsible + /// for laying out its child controls. When a layout is required the + /// observable will fire and the root element should respond by calling + /// at the earliest opportunity to carry out the layout. + /// public interface ILayoutManager { + /// + /// Gets or sets the root element that the manager is attached to. + /// + /// + /// This must be set before the layout manager can be used. + /// + ILayoutRoot Root { get; set; } + + /// + /// Gets an observable that is fired when a layout pass is needed. + /// IObservable LayoutNeeded { get; } + /// + /// Gets a value indicating whether a layout is queued. + /// + /// + /// Returns true when has been fired, but + /// has not yet been called. + /// bool LayoutQueued { get; } + /// + /// Executes a layout pass. + /// void ExecuteLayoutPass(); - void InvalidateMeasure(ILayoutable item); + /// + /// Notifies the layout manager that a control requires a measure. + /// + /// The control. + /// The control's distance from the layout root. + void InvalidateMeasure(ILayoutable control, int distance); - void InvalidateArrange(ILayoutable item); + /// + /// Notifies the layout manager that a control requires an arrange. + /// + /// The control. + /// The control's distance from the layout root. + void InvalidateArrange(ILayoutable control, int distance); } } diff --git a/Perspex.Layout/ILayoutable.cs b/Perspex.Layout/ILayoutable.cs index 04f95652e8..2918d7bc8f 100644 --- a/Perspex.Layout/ILayoutable.cs +++ b/Perspex.Layout/ILayoutable.cs @@ -6,6 +6,7 @@ namespace Perspex.Layout { + // TODO: Probably want to move width/height/etc properties to different interface. public interface ILayoutable : IVisual { Size? DesiredSize { get; } @@ -26,12 +27,20 @@ namespace Perspex.Layout VerticalAlignment VerticalAlignment { get; } - void Arrange(Rect rect); + bool IsMeasureValid { get; } + + bool IsArrangeValid { get; } + + Size? PreviousMeasure { get; } + + Rect? PreviousArrange { get; } void Measure(Size availableSize); - void InvalidateArrange(); + void Arrange(Rect rect); void InvalidateMeasure(); + + void InvalidateArrange(); } } diff --git a/Perspex.Layout/LayoutManager.cs b/Perspex.Layout/LayoutManager.cs index 1443759146..c54a865ecd 100644 --- a/Perspex.Layout/LayoutManager.cs +++ b/Perspex.Layout/LayoutManager.cs @@ -7,64 +7,191 @@ namespace Perspex.Layout { using System; + using System.Linq; using System.Reactive; using System.Reactive.Subjects; + using NGenerics.DataStructures.General; + /// + /// Manages measuring and arranging of controls. + /// + /// + /// Each layout root element such as a window has its own LayoutManager that is responsible + /// for laying out its child controls. When a layout is required the + /// observable will fire and the root element should respond by calling + /// at the earliest opportunity to carry out the layout. + /// public class LayoutManager : ILayoutManager { - private ILayoutRoot root; + /// + /// The maximum number of times a measure/arrange loop can be retried. + /// + private const int MaxTries = 3; + /// + /// Called when a layout is needed. + /// private Subject layoutNeeded; - public LayoutManager(ILayoutRoot root) - { - Contract.Requires(root != null); + /// + /// Whether a measure is needed on the next layout pass. + /// + private bool measureNeeded = true; + + /// + /// The controls that need to be measured, sorted by distance to layout root. + /// + private Heap toMeasure = new Heap(HeapType.Minimum); - this.root = root; + /// + /// Initializes a new instance of the class. + /// + public LayoutManager() + { this.layoutNeeded = new Subject(); } + /// + /// Gets or sets the root element that the manager is attached to. + /// + /// + /// This must be set before the layout manager can be used. + /// + public ILayoutRoot Root + { + get; + set; + } + + /// + /// Gets an observable that is fired when a layout pass is needed. + /// public IObservable LayoutNeeded { get { return this.layoutNeeded; } } + /// + /// Gets a value indicating whether a layout is queued. + /// + /// + /// Returns true when has been fired, but + /// has not yet been called. + /// public bool LayoutQueued { get; private set; } + /// + /// Executes a layout pass. + /// public void ExecuteLayoutPass() { this.LayoutQueued = false; - this.root.Measure(this.root.ClientSize); - this.root.Arrange(new Rect(this.root.ClientSize)); + + Layoutable.DebugMeasureCount = Layoutable.DebugArrangeCount = 0; + + if (this.measureNeeded) + { + this.ExecuteMeasure(); + this.measureNeeded = false; + } + + this.Root.Arrange(new Rect(this.Root.ClientSize)); + + System.Diagnostics.Debug.WriteLine(Environment.TickCount + " " + Layoutable.DebugMeasureCount + " " + Layoutable.DebugArrangeCount); } - public void InvalidateMeasure(ILayoutable item) + /// + /// Notifies the layout manager that a control requires a measure. + /// + /// The control. + /// The control's distance from the layout root. + public void InvalidateMeasure(ILayoutable control, int distance) { + this.measureNeeded = true; + this.toMeasure.Add(new Item(control, distance)); + if (!this.LayoutQueued) { - IVisual visual = item as IVisual; + IVisual visual = control as IVisual; this.layoutNeeded.OnNext(Unit.Default); this.LayoutQueued = true; } } - public void InvalidateArrange(ILayoutable item) + /// + /// Notifies the layout manager that a control requires an arrange. + /// + /// The control. + /// The control's distance from the layout root. + public void InvalidateArrange(ILayoutable control, int distance) { + //this.toArrange.Add(item); + if (!this.LayoutQueued) { - IVisual visual = item as IVisual; + IVisual visual = control as IVisual; this.layoutNeeded.OnNext(Unit.Default); this.LayoutQueued = true; } } - public void LayoutFinished() + private void ExecuteMeasure() { - this.LayoutQueued = false; + for (int i = 0; i < MaxTries; ++i) + { + var measure = this.toMeasure; + + this.toMeasure = new Heap(HeapType.Minimum); + + if (!this.Root.IsMeasureValid) + { + this.Root.Measure(this.Root.ClientSize); + } + else + { + foreach (var item in measure) + { + if (!item.Control.IsMeasureValid) + { + var control = item.Control; + + while (!control.PreviousMeasure.HasValue) + { + control = (ILayoutable)control.VisualParent; + } + + control.Measure(control.PreviousMeasure.Value); + } + } + } + + if (this.toMeasure.Count == 0) + { + break; + } + } + } + + private class Item : IComparable + { + public Item(ILayoutable control, int distance) + { + this.Control = control; + this.Distance = distance; + } + + public ILayoutable Control { get; private set; } + + public int Distance { get; private set; } + + public int CompareTo(Item other) + { + return this.Distance - other.Distance; + } } } } diff --git a/Perspex.Layout/Layoutable.cs b/Perspex.Layout/Layoutable.cs index 30e9e43797..036bf94937 100644 --- a/Perspex.Layout/Layoutable.cs +++ b/Perspex.Layout/Layoutable.cs @@ -58,6 +58,24 @@ namespace Perspex.Layout public static readonly PerspexProperty VerticalAlignmentProperty = PerspexProperty.Register("VerticalAlignment"); + private Size? previousMeasure; + + private Rect? previousArrange; + + static Layoutable() + { + Layoutable.AffectsMeasure(IsVisibleProperty); + Layoutable.AffectsMeasure(WidthProperty); + Layoutable.AffectsMeasure(HeightProperty); + Layoutable.AffectsMeasure(MinWidthProperty); + Layoutable.AffectsMeasure(MaxWidthProperty); + Layoutable.AffectsMeasure(MinHeightProperty); + Layoutable.AffectsMeasure(MaxHeightProperty); + Layoutable.AffectsMeasure(MarginProperty); + Layoutable.AffectsMeasure(HorizontalAlignmentProperty); + Layoutable.AffectsMeasure(VerticalAlignmentProperty); + } + public double Width { get { return this.GetValue(WidthProperty); } @@ -123,6 +141,31 @@ namespace Perspex.Layout set; } + public bool IsMeasureValid + { + get; + private set; + } + + public bool IsArrangeValid + { + get; + private set; + } + + Size? ILayoutable.PreviousMeasure + { + get { return this.previousMeasure; } + } + + Rect? ILayoutable.PreviousArrange + { + get { return this.previousArrange; } + } + + public static int DebugMeasureCount { get; set; } + public static int DebugArrangeCount { get; set; } + public void Measure(Size availableSize) { if (double.IsNaN(availableSize.Width) || double.IsNaN(availableSize.Height)) @@ -130,8 +173,11 @@ namespace Perspex.Layout throw new InvalidOperationException("Cannot call Measure using a size with NaN values."); } - availableSize = availableSize.Deflate(this.Margin); + ++DebugMeasureCount; + this.DesiredSize = this.MeasureCore(availableSize).Constrain(availableSize); + this.IsMeasureValid = true; + this.previousMeasure = availableSize; this.Log().Debug( "Measure of {0} (#{1:x8}) requested {2} ", @@ -154,35 +200,40 @@ namespace Perspex.Layout throw new InvalidOperationException("Arrange called before Measure."); } + ++DebugArrangeCount; + this.Log().Debug( "Arrange of {0} (#{1:x8}) gave {2} ", this.GetType().Name, this.GetHashCode(), rect); - if (this.DesiredSize.HasValue) - { - this.ArrangeCore(rect); - } + this.ArrangeCore(rect); + this.previousArrange = rect; } public void InvalidateMeasure() { - ILayoutRoot root = this.GetLayoutRoot(); + var root = this.GetLayoutRoot(); + + this.IsMeasureValid = false; - if (root != null && root.LayoutManager != null) + if (root != null && root.Item1.LayoutManager != null) { - root.LayoutManager.InvalidateMeasure(this); + root.Item1.LayoutManager.InvalidateMeasure(this, root.Item2); } } public void InvalidateArrange() { - ILayoutRoot root = this.GetLayoutRoot(); + var root = this.GetLayoutRoot(); - if (root != null) + this.IsMeasureValid = false; + this.IsArrangeValid = false; + + if (root != null && root.Item1.LayoutManager != null) { - root.LayoutManager.InvalidateArrange(this); + root.Item1.LayoutManager.InvalidateArrange(this, root.Item2); } } @@ -265,7 +316,8 @@ namespace Perspex.Layout { this.ApplyTemplate(); - var constrained = LayoutHelper.ApplyLayoutConstraints(this, availableSize) + var constrained = LayoutHelper + .ApplyLayoutConstraints(this, availableSize) .Deflate(this.Margin); var measured = this.MeasureOverride(constrained); @@ -331,9 +383,18 @@ namespace Perspex.Layout } } - private ILayoutRoot GetLayoutRoot() + private Tuple GetLayoutRoot() { - return this.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + var control = (IVisual)this; + var distance = 0; + + while (control != null && !(control is ILayoutRoot)) + { + control = control.GetVisualParent(); + ++distance; + } + + return control != null ? Tuple.Create((ILayoutRoot)control, distance) : null; } } } diff --git a/Perspex.Layout/Perspex.Layout.csproj b/Perspex.Layout/Perspex.Layout.csproj index af8d421bd3..d771d3da3a 100644 --- a/Perspex.Layout/Perspex.Layout.csproj +++ b/Perspex.Layout/Perspex.Layout.csproj @@ -35,6 +35,10 @@ + + {415e048e-4611-4815-9cf2-d774e29079ac} + NGenerics + {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base diff --git a/Perspex.sln b/Perspex.sln index 7f3e24d7d9..8ceac37052 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -51,6 +51,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication-Mono", "Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Base.UnitTests", "Perspex.Base.UnitTests\Perspex.Base.UnitTests.csproj", "{2905FF23-53FB-45E6-AA49-6AF47A172056}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{2BAFBE53-7FA4-4BB9-976F-9AFCC4F9847D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NGenerics", "NGenerics\NGenerics.csproj", "{415E048E-4611-4815-9CF2-D774E29079AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +144,10 @@ Global {2905FF23-53FB-45E6-AA49-6AF47A172056}.Debug|Any CPU.Build.0 = Debug|Any CPU {2905FF23-53FB-45E6-AA49-6AF47A172056}.Release|Any CPU.ActiveCfg = Release|Any CPU {2905FF23-53FB-45E6-AA49-6AF47A172056}.Release|Any CPU.Build.0 = Release|Any CPU + {415E048E-4611-4815-9CF2-D774E29079AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {415E048E-4611-4815-9CF2-D774E29079AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {415E048E-4611-4815-9CF2-D774E29079AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {415E048E-4611-4815-9CF2-D774E29079AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -150,5 +158,6 @@ Global {DABFD304-D6A4-4752-8123-C2CCF7AC7831} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {FB05AC90-89BA-4F2F-A924-F37875FB547C} = {1D577B69-F23B-4C4F-8605-97704DAC75A9} {54F237D5-A70A-4752-9656-0C70B1A7B047} = {554AF661-FE23-4647-930E-48B617E68F9D} + {415E048E-4611-4815-9CF2-D774E29079AC} = {2BAFBE53-7FA4-4BB9-976F-9AFCC4F9847D} EndGlobalSection EndGlobal diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 47f71077ce..df26527906 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -113,36 +113,49 @@ namespace TestApplication Window window = new Window { Title = "Perspex Test Application", - Content = new Grid + Content = new ScrollViewer { - RowDefinitions = new RowDefinitions + Width = 200, + Height = 200, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Content = new Image { - new RowDefinition(1, GridUnitType.Star), - new RowDefinition(GridLength.Auto), + Source = new Bitmap("github_icon.png"), + Width = 400, + Height = 400, }, - Children = new Controls - { - new TabControl - { - Items = new[] - { - ButtonsTab(), - TextTab(), - ImagesTab(), - ListsTab(), - SlidersTab(), - LayoutTab(), - } - }, - new TextBlock - { - Text = "Press F12 for Dev Tools", - HorizontalAlignment = HorizontalAlignment.Right, - Margin = new Thickness(2), - [Grid.RowProperty] = 1, - } - } }, + //Content = new Grid + //{ + // RowDefinitions = new RowDefinitions + // { + // new RowDefinition(1, GridUnitType.Star), + // new RowDefinition(GridLength.Auto), + // }, + // Children = new Controls + // { + // new TabControl + // { + // Items = new[] + // { + // ButtonsTab(), + // TextTab(), + // ImagesTab(), + // ListsTab(), + // SlidersTab(), + // LayoutTab(), + // } + // }, + // new TextBlock + // { + // Text = "Press F12 for Dev Tools", + // HorizontalAlignment = HorizontalAlignment.Right, + // Margin = new Thickness(2), + // [Grid.RowProperty] = 1, + // } + // } + //}, }; DevTools.Attach(window); diff --git a/Windows/Perspex.Direct2D1/Renderer.cs b/Windows/Perspex.Direct2D1/Renderer.cs index 41b26588a7..505ebdc541 100644 --- a/Windows/Perspex.Direct2D1/Renderer.cs +++ b/Windows/Perspex.Direct2D1/Renderer.cs @@ -133,11 +133,21 @@ namespace Perspex.Direct2D1 transform *= Matrix.Translation(visual.Bounds.Position); - using (context.PushClip(visual.Bounds)) + //using (context.PushClip(visual.Bounds)) using (context.PushTransform(transform)) { visual.Render(context); + context.DrawRectange(new Pen(Brushes.Red, 0.5), visual.Bounds); + + context.DrawText(Brushes.Red, visual.Bounds, new FormattedText + { + Text = visual.GetType().Name, + FontFamilyName = "Seguo UI", + FontSize = 16, + FontStyle = FontStyle.Normal, + }); + foreach (var child in visual.VisualChildren) { this.Render(child, context); diff --git a/Windows/Perspex.Direct2D1/TextService.cs b/Windows/Perspex.Direct2D1/TextService.cs index b4673d6427..290959ada8 100644 --- a/Windows/Perspex.Direct2D1/TextService.cs +++ b/Windows/Perspex.Direct2D1/TextService.cs @@ -35,7 +35,7 @@ namespace Perspex.Direct2D1 { return new TextLayout( factory, - text.Text, + text.Text ?? string.Empty, GetTextFormat(factory, text), (float)constraint.Width, (float)constraint.Height);