diff --git a/Perspex.Layout/ILayoutRoot.cs b/Perspex.Layout/ILayoutRoot.cs
index 4962760a21..6a7a0fee8d 100644
--- a/Perspex.Layout/ILayoutRoot.cs
+++ b/Perspex.Layout/ILayoutRoot.cs
@@ -6,10 +6,19 @@
namespace Perspex.Layout
{
+ ///
+ /// Defines the root of a layoutable tree.
+ ///
public interface ILayoutRoot : ILayoutable
{
+ ///
+ /// The size available to layout the controls.
+ ///
Size ClientSize { get; }
+ ///
+ /// The layout manager to use for laying out the tree.
+ ///
ILayoutManager LayoutManager { get; }
}
}
diff --git a/Perspex.Layout/ILayoutable.cs b/Perspex.Layout/ILayoutable.cs
index 2af9ec7572..50add8b5f2 100644
--- a/Perspex.Layout/ILayoutable.cs
+++ b/Perspex.Layout/ILayoutable.cs
@@ -1,48 +1,119 @@
// -----------------------------------------------------------------------
//
-// Copyright 2013 MIT Licence. See licence.md for more information.
+// Copyright 2015 MIT Licence. See licence.md for more information.
//
// -----------------------------------------------------------------------
namespace Perspex.Layout
{
- // TODO: Probably want to move width/height/etc properties to different interface.
+ ///
+ /// Defines layout-related functionality for a control.
+ ///
public interface ILayoutable : IVisual
{
+ ///
+ /// Gets the size that this element computed during the measure pass of the layout process.
+ ///
Size DesiredSize { get; }
- double Width { get; }
+ ///
+ /// Gets the width of the element.
+ ///
+ double Width { get; }
+ ///
+ /// Gets the height of the element.
+ ///
double Height { get; }
+ ///
+ /// Gets the minimum width of the element.
+ ///
double MinWidth { get; }
+ ///
+ /// Gets the maximum width of the element.
+ ///
double MaxWidth { get; }
+ ///
+ /// Gets the minimum height of the element.
+ ///
double MinHeight { get; }
+ ///
+ /// Gets the maximum height of the element.
+ ///
double MaxHeight { get; }
+ ///
+ /// Gets the margin around the element.
+ ///
+ Thickness Margin { get; }
+
+ ///
+ /// Gets the element's preferred horizontal alignment in its parent.
+ ///
HorizontalAlignment HorizontalAlignment { get; }
+ ///
+ /// Gets the element's preferred vertical alignment in its parent.
+ ///
VerticalAlignment VerticalAlignment { get; }
+ ///
+ /// Gets a value indicating whether the control's layout measure is valid.
+ ///
bool IsMeasureValid { get; }
+ ///
+ /// Gets a value indicating whether the control's layouts arrange is valid.
+ ///
bool IsArrangeValid { get; }
+ ///
+ /// Gets the available size passed in the previous layout pass, if any.
+ ///
Size? PreviousMeasure { get; }
+ ///
+ /// Gets the layout rect passed in the previous layout pass, if any.
+ ///
Rect? PreviousArrange { get; }
+ ///
+ /// Creates the visual children of the control, if necessary
+ ///
void ApplyTemplate();
+ ///
+ /// Carries out a measure of the control.
+ ///
+ /// The available size for the control.
+ ///
+ /// If true, the control will be measured even if has not
+ /// changed from the last measure.
+ ///
void Measure(Size availableSize, bool force = false);
+ ///
+ /// Arranges the control and its children.
+ ///
+ /// The control's new bounds.
+ ///
+ /// If true, the control will be arranged even if has not changed
+ /// from the last arrange.
+ ///
void Arrange(Rect rect, bool force = false);
+ ///
+ /// Invalidates the measurement of the control and queues a new layout pass.
+ ///
void InvalidateMeasure();
+ ///
+ /// Invalidates the arrangement of the control and queues a new layout pass.
+ ///
void InvalidateArrange();
}
}
diff --git a/Perspex.Layout/Layoutable.cs b/Perspex.Layout/Layoutable.cs
index ffcbeaed02..1f0087d9f8 100644
--- a/Perspex.Layout/Layoutable.cs
+++ b/Perspex.Layout/Layoutable.cs
@@ -12,53 +12,122 @@ namespace Perspex.Layout
using Serilog;
using Serilog.Core.Enrichers;
+ ///
+ /// Defines how a control aligns itself horizontally in its parent control.
+ ///
public enum HorizontalAlignment
{
+ ///
+ /// The control stretches to fill the width of the parent control.
+ ///
Stretch,
+
+ ///
+ /// The control aligns itself to the left of the parent control.
+ ///
Left,
+
+ ///
+ /// The control centers itself in the parent control.
+ ///
Center,
+
+ ///
+ /// The control aligns itself to the right of the parent control.
+ ///
Right,
}
+ ///
+ /// Defines how a control aligns itself vertically in its parent control.
+ ///
public enum VerticalAlignment
{
+ ///
+ /// The control stretches to fill the height of the parent control.
+ ///
Stretch,
+
+ ///
+ /// The control aligns itself to the top of the parent control.
+ ///
Top,
+
+ ///
+ /// The control centers itself within the parent control.
+ ///
Center,
+
+ ///
+ /// The control aligns itself to the bottom of the parent control.
+ ///
Bottom,
}
+ ///
+ /// Implements layout-related functionality for a control.
+ ///
public class Layoutable : Visual, ILayoutable
{
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty WidthProperty =
- PerspexProperty.Register("Width", double.NaN);
+ PerspexProperty.Register(nameof(Width), double.NaN);
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty HeightProperty =
- PerspexProperty.Register("Height", double.NaN);
+ PerspexProperty.Register(nameof(Height), double.NaN);
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty MinWidthProperty =
- PerspexProperty.Register("MinWidth");
+ PerspexProperty.Register(nameof(MinWidth));
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty MaxWidthProperty =
- PerspexProperty.Register("MaxWidth", double.PositiveInfinity);
+ PerspexProperty.Register(nameof(MaxWidth), double.PositiveInfinity);
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty MinHeightProperty =
- PerspexProperty.Register("MinHeight");
+ PerspexProperty.Register(nameof(MinHeight));
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty MaxHeightProperty =
- PerspexProperty.Register("MaxHeight", double.PositiveInfinity);
+ PerspexProperty.Register(nameof(MaxHeight), double.PositiveInfinity);
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty MarginProperty =
- PerspexProperty.Register("Margin");
+ PerspexProperty.Register(nameof(Margin));
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty HorizontalAlignmentProperty =
- PerspexProperty.Register("HorizontalAlignment");
+ PerspexProperty.Register(nameof(HorizontalAlignment));
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty VerticalAlignmentProperty =
- PerspexProperty.Register("VerticalAlignment");
+ PerspexProperty.Register(nameof(VerticalAlignment));
+ ///
+ /// Defines the property.
+ ///
public static readonly PerspexProperty UseLayoutRoundingProperty =
- PerspexProperty.Register("UseLayoutRounding", defaultValue: true, inherits: true);
+ PerspexProperty.Register(nameof(UseLayoutRounding), defaultValue: true, inherits: true);
private Size? previousMeasure;
@@ -66,6 +135,9 @@ namespace Perspex.Layout
private ILogger layoutLog;
+ ///
+ /// Initializes static members of the class.
+ ///
static Layoutable()
{
Layoutable.AffectsMeasure(Visual.IsVisibleProperty);
@@ -80,6 +152,9 @@ namespace Perspex.Layout
Layoutable.AffectsMeasure(Layoutable.VerticalAlignmentProperty);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
public Layoutable()
{
this.layoutLog = Log.ForContext(new[]
@@ -90,98 +165,155 @@ namespace Perspex.Layout
});
}
+ ///
+ /// Gets or sets the width of the element.
+ ///
public double Width
{
get { return this.GetValue(WidthProperty); }
set { this.SetValue(WidthProperty, value); }
}
+ ///
+ /// Gets or sets the height of the element.
+ ///
public double Height
{
get { return this.GetValue(HeightProperty); }
set { this.SetValue(HeightProperty, value); }
}
+ ///
+ /// Gets or sets the minimum width of the element.
+ ///
public double MinWidth
{
get { return this.GetValue(MinWidthProperty); }
set { this.SetValue(MinWidthProperty, value); }
}
+ ///
+ /// Gets or sets the maximum width of the element.
+ ///
public double MaxWidth
{
get { return this.GetValue(MaxWidthProperty); }
set { this.SetValue(MaxWidthProperty, value); }
}
+ ///
+ /// Gets or sets the minimum height of the element.
+ ///
public double MinHeight
{
get { return this.GetValue(MinHeightProperty); }
set { this.SetValue(MinHeightProperty, value); }
}
+ ///
+ /// Gets or sets the maximum height of the element.
+ ///
public double MaxHeight
{
get { return this.GetValue(MaxHeightProperty); }
set { this.SetValue(MaxHeightProperty, value); }
}
+ ///
+ /// Gets or sets the margin around the element.
+ ///
public Thickness Margin
{
get { return this.GetValue(MarginProperty); }
set { this.SetValue(MarginProperty, value); }
}
+ ///
+ /// Gets or sets the element's preferred horizontal alignment in its parent.
+ ///
public HorizontalAlignment HorizontalAlignment
{
get { return this.GetValue(HorizontalAlignmentProperty); }
set { this.SetValue(HorizontalAlignmentProperty, value); }
}
+ ///
+ /// Gets or sets the element's preferred vertical alignment in its parent.
+ ///
public VerticalAlignment VerticalAlignment
{
get { return this.GetValue(VerticalAlignmentProperty); }
set { this.SetValue(VerticalAlignmentProperty, value); }
}
+ ///
+ /// Gets the size that this element computed during the measure pass of the layout process.
+ ///
public Size DesiredSize
{
get;
set;
}
+ ///
+ /// Gets a value indicating whether the control's layout measure is valid.
+ ///
public bool IsMeasureValid
{
get;
private set;
}
+ ///
+ /// Gets a value indicating whether the control's layouts arrange is valid.
+ ///
public bool IsArrangeValid
{
get;
private set;
}
+ ///
+ /// Gets or sets a value that determines whether the element should be snapped to pixel
+ /// boundaries at layout time.
+ ///
public bool UseLayoutRounding
{
get { return this.GetValue(UseLayoutRoundingProperty); }
set { this.SetValue(UseLayoutRoundingProperty, value); }
}
+ ///
+ /// Gets the available size passed in the previous layout pass, if any.
+ ///
Size? ILayoutable.PreviousMeasure
{
get { return this.previousMeasure; }
}
+ ///
+ /// Gets the layout rect passed in the previous layout pass, if any.
+ ///
Rect? ILayoutable.PreviousArrange
{
get { return this.previousArrange; }
}
+ ///
+ /// Creates the visual children of the control, if necessary
+ ///
public virtual void ApplyTemplate()
{
}
+ ///
+ /// Carries out a measure of the control.
+ ///
+ /// The available size for the control.
+ ///
+ /// If true, the control will be measured even if has not
+ /// changed from the last measure.
+ ///
public void Measure(Size availableSize, bool force = false)
{
if (double.IsNaN(availableSize.Width) || double.IsNaN(availableSize.Height))
@@ -207,6 +339,14 @@ namespace Perspex.Layout
}
}
+ ///
+ /// Arranges the control and its children.
+ ///
+ /// The control's new bounds.
+ ///
+ /// If true, the control will be arranged even if has not changed
+ /// from the last arrange.
+ ///
public void Arrange(Rect rect, bool force = false)
{
if (IsInvalidRect(rect))
@@ -231,6 +371,9 @@ namespace Perspex.Layout
}
}
+ ///
+ /// Invalidates the measurement of the control and queues a new layout pass.
+ ///
public void InvalidateMeasure()
{
var parent = this.GetVisualParent();
@@ -260,6 +403,9 @@ namespace Perspex.Layout
}
}
+ ///
+ /// Invalidates the arrangement of the control and queues a new layout pass.
+ ///
public void InvalidateArrange()
{
var root = this.GetLayoutRoot();
@@ -278,16 +424,107 @@ namespace Perspex.Layout
}
}
+ ///
+ /// Marks a property as affecting the control's measurement.
+ ///
+ /// The property.
+ ///
+ /// After a call to this method in a control's static constructor, any change to the
+ /// property will cause to be called on the element.
+ ///
+ protected static void AffectsMeasure(PerspexProperty property)
+ {
+ property.Changed.Subscribe(AffectsMeasureInvalidate);
+ }
+
+ ///
+ /// Marks a property as affecting the control's arrangement.
+ ///
+ /// The property.
+ ///
+ /// After a call to this method in a control's static constructor, any change to the
+ /// property will cause to be called on the element.
+ ///
protected static void AffectsArrange(PerspexProperty property)
{
property.Changed.Subscribe(AffectsArrangeInvalidate);
}
- protected static void AffectsMeasure(PerspexProperty property)
+ ///
+ /// The default implementation of the control's measure pass.
+ ///
+ /// The size available to the control.
+ /// The desired size for the control.
+ ///
+ /// This method calls which is probably the method you
+ /// want to override in order to modify a control's arrangement.
+ ///
+ protected virtual Size MeasureCore(Size availableSize)
{
- property.Changed.Subscribe(AffectsMeasureInvalidate);
+ if (this.IsVisible)
+ {
+ this.ApplyTemplate();
+
+ var constrained = LayoutHelper
+ .ApplyLayoutConstraints(this, availableSize)
+ .Deflate(this.Margin);
+
+ var measured = this.MeasureOverride(constrained);
+ var width = measured.Width;
+ var height = measured.Height;
+
+ if (!double.IsNaN(this.Width))
+ {
+ width = this.Width;
+ }
+
+ width = Math.Min(width, this.MaxWidth);
+ width = Math.Max(width, this.MinWidth);
+
+ if (!double.IsNaN(this.Height))
+ {
+ height = this.Height;
+ }
+
+ height = Math.Min(height, this.MaxHeight);
+ height = Math.Max(height, this.MinHeight);
+
+ return new Size(width, height).Inflate(this.Margin);
+ }
+ else
+ {
+ return new Size();
+ }
+ }
+
+ ///
+ /// Measures the control and its child elements as part of a layout pass.
+ ///
+ /// The size available to the control.
+ /// The desired size for the control.
+ protected virtual Size MeasureOverride(Size availableSize)
+ {
+ double width = 0;
+ double height = 0;
+
+ foreach (ILayoutable child in this.GetVisualChildren().OfType())
+ {
+ child.Measure(availableSize);
+ width = Math.Max(width, child.DesiredSize.Width);
+ height = Math.Max(height, child.DesiredSize.Height);
+ }
+
+ return new Size(width, height);
}
+ ///
+ /// The default implementation of the control's arrange pass.
+ ///
+ /// The control's new bounds.
+ ///
+ /// This method calls which is probably the method you
+ /// want to override in order to modify a control's arrangement.
+ ///
protected virtual void ArrangeCore(Rect finalRect)
{
if (this.IsVisible)
@@ -349,6 +586,11 @@ namespace Perspex.Layout
}
}
+ ///
+ /// Positions child elements as part of a layout pass.
+ ///
+ /// The size available to the control.
+ /// The actual size used.
protected virtual Size ArrangeOverride(Size finalSize)
{
foreach (ILayoutable child in this.GetVisualChildren().OfType())
@@ -359,84 +601,50 @@ namespace Perspex.Layout
return finalSize;
}
- protected virtual Size MeasureCore(Size availableSize)
- {
- if (this.IsVisible)
- {
- this.ApplyTemplate();
-
- var constrained = LayoutHelper
- .ApplyLayoutConstraints(this, availableSize)
- .Deflate(this.Margin);
-
- var measured = this.MeasureOverride(constrained);
- var width = measured.Width;
- var height = measured.Height;
-
- if (!double.IsNaN(this.Width))
- {
- width = this.Width;
- }
-
- width = Math.Min(width, this.MaxWidth);
- width = Math.Max(width, this.MinWidth);
-
- if (!double.IsNaN(this.Height))
- {
- height = this.Height;
- }
-
- height = Math.Min(height, this.MaxHeight);
- height = Math.Max(height, this.MinHeight);
-
- return new Size(width, height).Inflate(this.Margin);
- }
- else
- {
- return new Size();
- }
- }
-
- protected virtual Size MeasureOverride(Size availableSize)
- {
- double width = 0;
- double height = 0;
-
- foreach (ILayoutable child in this.GetVisualChildren().OfType())
- {
- child.Measure(availableSize);
- width = Math.Max(width, child.DesiredSize.Width);
- height = Math.Max(height, child.DesiredSize.Height);
- }
-
- return new Size(width, height);
- }
-
- private static void AffectsArrangeInvalidate(PerspexPropertyChangedEventArgs e)
+ ///
+ /// Calls on the control on which a property changed.
+ ///
+ /// The event args.
+ private static void AffectsMeasureInvalidate(PerspexPropertyChangedEventArgs e)
{
ILayoutable control = e.Sender as ILayoutable;
if (control != null)
{
- control.InvalidateArrange();
+ control.InvalidateMeasure();
}
}
- private static void AffectsMeasureInvalidate(PerspexPropertyChangedEventArgs e)
+ ///
+ /// Calls on the control on which a property changed.
+ ///
+ /// The event args.
+ private static void AffectsArrangeInvalidate(PerspexPropertyChangedEventArgs e)
{
ILayoutable control = e.Sender as ILayoutable;
if (control != null)
{
- control.InvalidateMeasure();
+ control.InvalidateArrange();
}
}
+ ///
+ /// Tests whether a control's size can be changed by a layout pass.
+ ///
+ /// The control.
+ /// True if the control's size can change; otherwise false.
private static bool IsResizable(ILayoutable control)
{
return double.IsNaN(control.Width) || double.IsNaN(control.Height);
}
+ ///
+ /// Tests whether any of a 's properties incude nagative values,
+ /// a NaN or Infinity.
+ ///
+ /// The rect.
+ /// True if the rect is invalid; otherwise false.
private static bool IsInvalidRect(Rect rect)
{
return rect.Width < 0 || rect.Height < 0 ||
@@ -446,6 +654,12 @@ namespace Perspex.Layout
double.IsNaN(rect.Width) || double.IsNaN(rect.Height);
}
+ ///
+ /// Tests whether any of a 's properties incude nagative values,
+ /// a NaN or Infinity.
+ ///
+ /// The size.
+ /// True if the size is invalid; otherwise false.
private static bool IsInvalidSize(Size size)
{
return size.Width < 0 || size.Height < 0 ||
@@ -453,6 +667,12 @@ namespace Perspex.Layout
double.IsNaN(size.Width) || double.IsNaN(size.Height);
}
+ ///
+ /// Gets the layout root, together with its distance.
+ ///
+ ///
+ /// A tuple containing the layout root and the root's distance from this control.
+ ///
private Tuple GetLayoutRoot()
{
var control = (IVisual)this;
diff --git a/Perspex.Layout/Perspex.Layout.csproj b/Perspex.Layout/Perspex.Layout.csproj
index 3bc42d9e3b..74bbec7aa6 100644
--- a/Perspex.Layout/Perspex.Layout.csproj
+++ b/Perspex.Layout/Perspex.Layout.csproj
@@ -56,6 +56,7 @@
+