diff --git a/docs/build.md b/docs/build.md
index b3be7581c4..935341c0d5 100644
--- a/docs/build.md
+++ b/docs/build.md
@@ -7,8 +7,11 @@ Perspex requires Visual Studio 2015 to build on Windows.
### Install GTK Sharp
To compile the full project under windows, you must have [gtk-sharp](http://www.mono-project.com/download/#download-win) installed. However, if you're
-not interested in building the cross-platform bits you can simply unload the Perspex.Cairo and
-Perspex.Gtk project in Visual Studio.
+not interested in building the cross-platform bits you can simply unload these projects from Visual Studio:
+
+ - Perspex.Cairo
+ - Perspex.Cairo.RenderTests
+ - Perspex.Gtk
### Clone the Perspex repository
@@ -19,7 +22,7 @@ is linked as a submodule in the git repository, so run:
git submodule update --init
-The next step is to download the Skia native libraries. Run ```getnatives.ps1``` PowerShell script which can be found under the folder ```Perspex\src\Skia\```.
+The next step is to download the Skia native libraries. Run ```getnatives.ps1``` PowerShell script which can be found under the folder ```src\Skia\```.
## Linux
@@ -39,14 +42,14 @@ Then install the needed packages:
### Clone the Perspex repository
- git clone https://github.com/grokys/Perspex.git
+ git clone https://github.com/Perspex/Perspex.git
We currently need to build our own private version of ReactiveUI as it doesn't work on mono. This
is linked as a submodule in the git repository, so run:
git submodule update --init
-The next step is to download the Skia native libraries. Run ```getnatives.sh``` script which can be found under the folder ```Perspex\src\Skia\```.
+The next step is to download the Skia native libraries. Run ```getnatives.sh``` script which can be found under the folder ```src\Skia\```.
### Load the Project in MonoDevelop
@@ -57,4 +60,4 @@ Set the TestApplication project as the startup project and click Run.
There will be some compile errors in the tests, but ignore them for now.
-You can track the Linux version's progress in the [Linux issue](https://github.com/grokys/Perspex/issues/78).
+You can track the Linux version's progress in the [Linux issue](https://github.com/Perspex/Perspex/issues/78).
diff --git a/samples/ControlCatalog/App.paml.cs b/samples/ControlCatalog/App.paml.cs
index b1aa671215..8a49ae20f1 100644
--- a/samples/ControlCatalog/App.paml.cs
+++ b/samples/ControlCatalog/App.paml.cs
@@ -5,6 +5,7 @@ using Perspex.Controls;
using Perspex.Diagnostics;
using Perspex.Markup.Xaml;
using Perspex.Themes.Default;
+using Serilog;
namespace ControlCatalog
{
@@ -14,6 +15,7 @@ namespace ControlCatalog
{
RegisterServices();
InitializeSubsystems(GetPlatformId());
+ InitializeLogging();
Styles = new DefaultTheme();
InitializeComponent();
}
@@ -38,6 +40,16 @@ namespace ControlCatalog
PerspexXamlLoader.Load(this);
}
+ private void InitializeLogging()
+ {
+#if DEBUG
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Error()
+ .WriteTo.Trace(outputTemplate: "{Message}")
+ .CreateLogger();
+#endif
+ }
+
private int GetPlatformId()
{
var args = Environment.GetCommandLineArgs();
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 1a26286147..d3bf036619 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -36,6 +36,14 @@
+
+ ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.dll
+ True
+
+
+ ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.FullNetFx.dll
+ True
+
@@ -93,6 +101,7 @@
Designer
+
diff --git a/samples/ControlCatalog/packages.config b/samples/ControlCatalog/packages.config
new file mode 100644
index 0000000000..76c9b4d2a5
--- /dev/null
+++ b/samples/ControlCatalog/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Perspex.Base/PriorityValue.cs b/src/Perspex.Base/PriorityValue.cs
index e45eba172f..b68fae1869 100644
--- a/src/Perspex.Base/PriorityValue.cs
+++ b/src/Perspex.Base/PriorityValue.cs
@@ -224,26 +224,29 @@ namespace Perspex
/// The priority level that the value came from.
private void UpdateValue(object value, int priority)
{
- if (TypeUtilities.TryCast(_valueType, value, out value))
+ object castValue;
+
+ if (TypeUtilities.TryCast(_valueType, value, out castValue))
{
var old = _value;
- if (_validate != null && value != PerspexProperty.UnsetValue)
+ if (_validate != null && castValue != PerspexProperty.UnsetValue)
{
- value = _validate(value);
+ castValue = _validate(castValue);
}
ValuePriority = priority;
- _value = value;
+ _value = castValue;
_changed.OnNext(Tuple.Create(old, _value));
}
else if (_logger != null)
{
_logger.Error(
- "Binding produced invalid value for {$Type} {$Property}: {$Value}",
- _valueType,
+ "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
_name,
- value);
+ _valueType,
+ value,
+ value.GetType());
}
}
diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs
index 376ca5c0c9..1ece331c13 100644
--- a/src/Perspex.Base/Threading/DispatcherTimer.cs
+++ b/src/Perspex.Base/Threading/DispatcherTimer.cs
@@ -146,6 +146,33 @@ namespace Perspex.Threading
return Disposable.Create(() => timer.Stop());
}
+ ///
+ /// Runs a method once, after the specified interval.
+ ///
+ ///
+ /// The method to call after the interval has elapsed.
+ ///
+ /// The interval after which to call the method.
+ /// The priority to use.
+ /// An used to cancel the timer.
+ public static IDisposable RunOnce(
+ Action action,
+ TimeSpan interval,
+ DispatcherPriority priority = DispatcherPriority.Normal)
+ {
+ var timer = new DispatcherTimer(priority) { Interval = interval };
+
+ timer.Tick += (s, e) =>
+ {
+ action();
+ timer.Stop();
+ };
+
+ timer.Start();
+
+ return Disposable.Create(() => timer.Stop());
+ }
+
///
/// Starts the timer.
///
diff --git a/src/Perspex.Controls/DockPanel.cs b/src/Perspex.Controls/DockPanel.cs
index a1224a5272..8108a8c3cb 100644
--- a/src/Perspex.Controls/DockPanel.cs
+++ b/src/Perspex.Controls/DockPanel.cs
@@ -1,445 +1,176 @@
namespace Perspex.Controls
{
using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using Layout;
- public class DockPanel : Panel
+ ///
+ /// Defines the available docking modes for a control in a .
+ ///
+ public enum Dock
{
- public static readonly PerspexProperty DockProperty = PerspexProperty.RegisterAttached("Dock");
+ Left = 0,
+ Bottom,
+ Right,
+ Top
+ }
+ ///
+ /// A panel which arranges its children at the top, bottom, left, right or center.
+ ///
+ public class DockPanel : Panel
+ {
+ ///
+ /// Defines the Dock attached property.
+ ///
+ public static readonly PerspexProperty DockProperty =
+ PerspexProperty.RegisterAttached("Dock");
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly PerspexProperty LastChildFillProperty =
+ PerspexProperty.Register(
+ nameof(LastChildFillProperty),
+ defaultValue: true);
+
+ ///
+ /// Initializes static members of the class.
+ ///
static DockPanel()
{
AffectsArrange(DockProperty);
}
- // ReSharper disable once UnusedMember.Global
- public static Dock GetDock(PerspexObject perspexObject)
+ ///
+ /// Gets the value of the Dock attached property on the specified control.
+ ///
+ /// The control.
+ /// The Dock attached property.
+ public static Dock GetDock(Control control)
{
- return perspexObject.GetValue(DockProperty);
+ return control.GetValue(DockProperty);
}
- // ReSharper disable once UnusedMember.Global
- public static void SetDock(PerspexObject element, Dock dock)
+ ///
+ /// Sets the value of the Dock attached property on the specified control.
+ ///
+ /// The control.
+ /// The value of the Dock property.
+ public static void SetDock(Control control, Dock value)
{
- element.SetValue(DockProperty, dock);
+ control.SetValue(DockProperty, value);
}
- public static readonly PerspexProperty LastChildFillProperty = PerspexProperty.Register(nameof(LastChildFillProperty), defaultValue: true);
-
+ ///
+ /// Gets or sets a value which indicates whether the last child of the
+ /// fills the remaining space in the panel.
+ ///
public bool LastChildFill
{
get { return GetValue(LastChildFillProperty); }
set { SetValue(LastChildFillProperty, value); }
}
- protected override Size MeasureOverride(Size availableSize)
- {
- if (!LastChildFill)
- {
- return MeasureItemsThatWillBeDocked(availableSize, Children);
- }
-
- var sizeRequiredByDockingItems = MeasureItemsThatWillBeDocked(availableSize, Children.WithoutLast());
- var elementThatWillFill = Children.Last();
- elementThatWillFill.Measure(availableSize - sizeRequiredByDockingItems);
- var finalSize = sizeRequiredByDockingItems.Inflate(new Thickness(elementThatWillFill.DesiredSize.Width, elementThatWillFill.DesiredSize.Height));
- return finalSize;
- }
-
- private static Size MeasureItemsThatWillBeDocked(Size availableSize, IEnumerable children)
+ ///
+ protected override Size MeasureOverride(Size constraint)
{
- var requiredHorizontalLength = 0D;
- var requiredVerticalLength = 0D;
+ double usedWidth = 0.0;
+ double usedHeight = 0.0;
+ double maximumWidth = 0.0;
+ double maximumHeight = 0.0;
- foreach (var control in children)
+ // Measure each of the Children
+ foreach (Control element in Children)
{
- control.Measure(availableSize);
-
- var dock = control.GetValue(DockProperty);
- if (IsHorizontal(dock))
- {
- requiredHorizontalLength += control.DesiredSize.Width;
- }
- else
+ // Get the child's desired size
+ Size remainingSize = new Size(
+ Math.Max(0.0, constraint.Width - usedWidth),
+ Math.Max(0.0, constraint.Height - usedHeight));
+ element.Measure(remainingSize);
+ Size desiredSize = element.DesiredSize;
+
+ // Decrease the remaining space for the rest of the children
+ switch (GetDock(element))
{
- requiredVerticalLength += control.DesiredSize.Height;
+ case Dock.Left:
+ case Dock.Right:
+ maximumHeight = Math.Max(maximumHeight, usedHeight + desiredSize.Height);
+ usedWidth += desiredSize.Width;
+ break;
+ case Dock.Top:
+ case Dock.Bottom:
+ maximumWidth = Math.Max(maximumWidth, usedWidth + desiredSize.Width);
+ usedHeight += desiredSize.Height;
+ break;
}
}
- return new Size(requiredHorizontalLength, requiredVerticalLength);
- }
-
- private static bool IsHorizontal(Dock dock)
- {
- return dock == Dock.Left || dock == Dock.Right;
- }
-
- protected override Size ArrangeOverride(Size finalSize)
- {
- if (!LastChildFill)
- {
- return ArrangeAllChildren(finalSize);
- }
- else
- {
- return ArrangeChildrenAndFillLastChild(finalSize);
- }
- }
-
- private Size ArrangeChildrenAndFillLastChild(Size finalSize)
- {
- var docker = new DockingArranger();
- var requiredSize = docker.ArrangeAndGetUsedSize(finalSize, Children.WithoutLast());
- ArrangeToFill(Children.Last(), finalSize, docker.UsedMargin);
- return requiredSize;
- }
-
- private Size ArrangeAllChildren(Size finalSize)
- {
- return new DockingArranger().ArrangeAndGetUsedSize(finalSize, Children);
+ maximumWidth = Math.Max(maximumWidth, usedWidth);
+ maximumHeight = Math.Max(maximumHeight, usedHeight);
+ return new Size(maximumWidth, maximumHeight);
}
- private static void ArrangeToFill(ILayoutable layoutable, Size containerSize, Margin margin)
+ ///
+ protected override Size ArrangeOverride(Size arrangeSize)
{
- var containerRect = new Rect(new Point(0, 0), containerSize);
- var marginsCutout = margin.AsThickness();
- var withoutMargins = containerRect.Deflate(marginsCutout);
-
- layoutable.Arrange(withoutMargins);
- }
+ double left = 0.0;
+ double top = 0.0;
+ double right = 0.0;
+ double bottom = 0.0;
- private class DockingArranger
- {
- public Margin UsedMargin { get; private set; }
+ // Arrange each of the Children
+ var children = Children;
+ int dockedCount = children.Count - (LastChildFill ? 1 : 0);
+ int index = 0;
- public Size ArrangeAndGetUsedSize(Size availableSize, IEnumerable children)
+ foreach (Control element in children)
{
- var leftArranger = new LeftDocker(availableSize);
- var rightArranger = new RightDocker(availableSize);
- var topArranger = new LeftDocker(availableSize.Swap());
- var bottomArranger = new RightDocker(availableSize.Swap());
-
- UsedMargin = new Margin();
-
- foreach (var control in children)
+ // Determine the remaining space left to arrange the element
+ Rect remainingRect = new Rect(
+ left,
+ top,
+ Math.Max(0.0, arrangeSize.Width - left - right),
+ Math.Max(0.0, arrangeSize.Height - top - bottom));
+
+ // Trim the remaining Rect to the docked size of the element
+ // (unless the element should fill the remaining space because
+ // of LastChildFill)
+ if (index < dockedCount)
{
- Rect dockedRect;
- var dock = control.GetValue(DockProperty);
- switch (dock)
+ Size desiredSize = element.DesiredSize;
+ switch (GetDock(element))
{
case Dock.Left:
- dockedRect = leftArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments());
+ left += desiredSize.Width;
+ remainingRect = remainingRect.WithWidth(desiredSize.Width);
break;
-
case Dock.Top:
- UsedMargin.Swap();
- dockedRect = topArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap();
- UsedMargin.Swap();
+ top += desiredSize.Height;
+ remainingRect = remainingRect.WithHeight(desiredSize.Height);
break;
-
case Dock.Right:
- dockedRect = rightArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments());
+ right += desiredSize.Width;
+ remainingRect = new Rect(
+ Math.Max(0.0, arrangeSize.Width - right),
+ remainingRect.Y,
+ desiredSize.Width,
+ remainingRect.Height);
break;
-
case Dock.Bottom:
- UsedMargin.Swap();
- dockedRect = bottomArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap();
- UsedMargin.Swap();
+ bottom += desiredSize.Height;
+ remainingRect = new Rect(
+ remainingRect.X,
+ Math.Max(0.0, arrangeSize.Height - bottom),
+ remainingRect.Width,
+ desiredSize.Height);
break;
-
- default:
- throw new InvalidOperationException($"Invalid dock value {dock}");
}
-
- control.Arrange(dockedRect);
}
- return availableSize;
- }
- }
-
- private class LeftDocker : Docker
- {
- public LeftDocker(Size availableSize) : base(availableSize)
- {
- }
-
- public override Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments)
- {
- var marginsCutout = margin.AsThickness();
- var availableRect = OriginalRect.Deflate(marginsCutout);
- var alignedRect = AlignToLeft(availableRect, childSize, alignments.Vertical);
-
- AccumulatedOffset += childSize.Width;
- margin.Horizontal = margin.Horizontal.Offset(childSize.Width, 0);
-
- return alignedRect;
- }
-
- private static Rect AlignToLeft(Rect availableRect, Size childSize, Alignment verticalAlignment)
- {
- return availableRect.AlignChild(childSize, Alignment.Start, verticalAlignment);
- }
- }
-
- private class RightDocker : Docker
- {
- public RightDocker(Size availableSize) : base(availableSize)
- {
- }
-
- public override Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments)
- {
- var marginsCutout = margin.AsThickness();
- var withoutMargins = OriginalRect.Deflate(marginsCutout);
- var finalRect = withoutMargins.AlignChild(childSize, Alignment.End, alignments.Vertical);
-
- AccumulatedOffset += childSize.Width;
- margin.Horizontal = margin.Horizontal.Offset(0, childSize.Width);
-
- return finalRect;
- }
- }
-
- private abstract class Docker
- {
- protected Docker(Size availableSize)
- {
- OriginalRect = new Rect(new Point(0, 0), availableSize);
+ element.Arrange(remainingRect);
+ index++;
}
- protected double AccumulatedOffset { get; set; }
-
- protected Rect OriginalRect { get; }
-
- public abstract Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments);
- }
- }
-
- public class Margin
- {
- public Segment Horizontal { get; set; }
- public Segment Vertical { get; set; }
- }
-
- public enum Alignment
- {
- Stretch, Start, Middle, End,
- }
-
- public static class SegmentMixin
- {
- public static Segment AlignToStart(this Segment container, double length)
- {
- return new Segment(container.Start, container.Start + length);
- }
-
- public static Segment AlignToEnd(this Segment container, double length)
- {
- return new Segment(container.End - length, container.End);
- }
-
- public static Segment AlignToMiddle(this Segment container, double length)
- {
- var start = container.Start + (container.Length - length) / 2;
- return new Segment(start, start + length);
- }
- }
-
- public struct Alignments
- {
- public Alignments(Alignment horizontal, Alignment vertical)
- {
- Horizontal = horizontal;
- Vertical = vertical;
- }
-
- public Alignment Horizontal { get; }
-
- public Alignment Vertical { get; }
- }
-
- public static class CoordinateMixin
- {
- private static Point Swap(this Point p)
- {
- return new Point(p.Y, p.X);
- }
-
- public static Size Swap(this Size s)
- {
- return new Size(s.Height, s.Width);
- }
-
- public static Rect Swap(this Rect r)
- {
- return new Rect(r.Position.Swap(), r.Size.Swap());
- }
-
- public static Segment Offset(this Segment l, double startOffset, double endOffset)
- {
- return new Segment(l.Start + startOffset, l.End + endOffset);
- }
-
- public static void Swap(this Margin m)
- {
- var v = m.Vertical;
- m.Vertical = m.Horizontal;
- m.Horizontal = v;
- }
-
- public static Thickness AsThickness(this Margin margin)
- {
- return new Thickness(margin.Horizontal.Start, margin.Vertical.Start, margin.Horizontal.End, margin.Vertical.End);
- }
-
- private static Alignment AsAlignment(this HorizontalAlignment horz)
- {
- switch (horz)
- {
- case HorizontalAlignment.Stretch:
- return Alignment.Stretch;
- case HorizontalAlignment.Left:
- return Alignment.Start;
- case HorizontalAlignment.Center:
- return Alignment.Middle;
- case HorizontalAlignment.Right:
- return Alignment.End;
- default:
- throw new ArgumentOutOfRangeException(nameof(horz), horz, null);
- }
- }
-
- private static Alignment AsAlignment(this VerticalAlignment vert)
- {
- switch (vert)
- {
- case VerticalAlignment.Stretch:
- return Alignment.Stretch;
- case VerticalAlignment.Top:
- return Alignment.Start;
- case VerticalAlignment.Center:
- return Alignment.Middle;
- case VerticalAlignment.Bottom:
- return Alignment.End;
- default:
- throw new ArgumentOutOfRangeException(nameof(vert), vert, null);
- }
- }
-
- public static Alignments GetAlignments(this ILayoutable layoutable)
- {
- return new Alignments(layoutable.HorizontalAlignment.AsAlignment(), layoutable.VerticalAlignment.AsAlignment());
- }
-
- public static Alignments Swap(this Alignments alignments)
- {
- return new Alignments(alignments.Vertical, alignments.Horizontal);
- }
- }
-
- public enum Dock
- {
- Left = 0,
- Bottom,
- Right,
- Top
- }
-
- public static class RectMixin
- {
- public static Rect AlignChild(this Rect container, Size childSize, Alignment horizontalAlignment, Alignment verticalAlignment)
- {
- var horzSegment = container.GetHorizontalCoordinates();
- var vertSegment = container.GetVerticalCoordinates();
-
- var horzResult = GetAlignedSegment(childSize.Width, horizontalAlignment, horzSegment);
- var vertResult = GetAlignedSegment(childSize.Height, verticalAlignment, vertSegment);
-
- return FromSegments(horzResult, vertResult);
- }
-
- private static Rect FromSegments(Segment horzSegment, Segment vertSegment)
- {
- return new Rect(horzSegment.Start, vertSegment.Start, horzSegment.Length, vertSegment.Length);
- }
-
- private static Segment GetAlignedSegment(double width, Alignment alignment, Segment horzSegment)
- {
- switch (alignment)
- {
- case Alignment.Start:
- return horzSegment.AlignToStart(width);
-
- case Alignment.Middle:
- return horzSegment.AlignToMiddle(width);
-
- case Alignment.End:
- return horzSegment.AlignToEnd(width);
-
- default:
- return new Segment(horzSegment.Start, horzSegment.End);
- }
- }
-
- private static Segment GetHorizontalCoordinates(this Rect rect)
- {
- return new Segment(rect.X, rect.Right);
- }
-
- private static Segment GetVerticalCoordinates(this Rect rect)
- {
- return new Segment(rect.Y, rect.Bottom);
- }
- }
-
- public struct Segment
- {
- public Segment(double start, double end)
- {
- Start = start;
- End = end;
- }
-
- public double Start { get; }
- public double End { get; }
-
- public double Length => End - Start;
-
- public override string ToString()
- {
- return $"Start: {Start}, End: {End}";
- }
- }
-
- public static class EnumerableMixin
- {
- private static IEnumerable Shrink(this IEnumerable source, int left, int right)
- {
- int i = 0;
- var buffer = new Queue(right + 1);
-
- foreach (T x in source)
- {
- if (i >= left) // Read past left many elements at the start
- {
- buffer.Enqueue(x);
- if (buffer.Count > right) // Build a buffer to drop right many elements at the end
- yield return buffer.Dequeue();
- }
- else i++;
- }
- }
- public static IEnumerable WithoutLast(this IEnumerable source, int n = 1)
- {
- return source.Shrink(0, n);
- }
- public static IEnumerable WithoutFirst(this IEnumerable source, int n = 1)
- {
- return source.Shrink(n, 0);
+ return arrangeSize;
}
}
}
\ No newline at end of file
diff --git a/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs b/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs
index 86e2b890d1..0a9c3544d7 100644
--- a/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs
+++ b/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs
@@ -12,6 +12,19 @@ namespace Perspex.Controls.Generators
///
public class ItemContainerEventArgs : EventArgs
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The index of the first container in the source items.
+ /// The container.
+ public ItemContainerEventArgs(
+ int startingIndex,
+ ItemContainer container)
+ {
+ StartingIndex = startingIndex;
+ Containers = new[] { container };
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/Perspex.Controls/Generators/TreeContainerIndex.cs b/src/Perspex.Controls/Generators/TreeContainerIndex.cs
index 9de4ca1050..ab07cb454d 100644
--- a/src/Perspex.Controls/Generators/TreeContainerIndex.cs
+++ b/src/Perspex.Controls/Generators/TreeContainerIndex.cs
@@ -1,6 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using System.Collections.Generic;
namespace Perspex.Controls.Generators
@@ -19,6 +20,16 @@ namespace Perspex.Controls.Generators
private readonly Dictionary