diff --git a/Perspex/Controls/ColumnDefinition.cs b/Perspex/Controls/ColumnDefinition.cs
new file mode 100644
index 0000000000..a07d2d45e2
--- /dev/null
+++ b/Perspex/Controls/ColumnDefinition.cs
@@ -0,0 +1,53 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ public class ColumnDefinition : DefinitionBase
+ {
+ public static readonly PerspexProperty MaxWidthProperty =
+ PerspexProperty.Register("MaxWidth", double.PositiveInfinity);
+
+ public static readonly PerspexProperty MinWidthProperty =
+ PerspexProperty.Register("MinWidth");
+
+ public static readonly PerspexProperty WidthProperty =
+ PerspexProperty.Register("Width", new GridLength(1, GridUnitType.Star));
+
+ public ColumnDefinition()
+ {
+ }
+
+ public ColumnDefinition(GridLength width)
+ {
+ this.Width = width;
+ }
+
+ public double ActualWidth
+ {
+ get;
+ internal set;
+ }
+
+ public double MaxWidth
+ {
+ get { return this.GetValue(MaxWidthProperty); }
+ set { this.SetValue(MaxWidthProperty, value); }
+ }
+
+ public double MinWidth
+ {
+ get { return this.GetValue(MinWidthProperty); }
+ set { this.SetValue(MinWidthProperty, value); }
+ }
+
+ public GridLength Width
+ {
+ get { return this.GetValue(WidthProperty); }
+ set { this.SetValue(WidthProperty, value); }
+ }
+ }
+}
diff --git a/Perspex/Controls/ColumnDefinitions.cs b/Perspex/Controls/ColumnDefinitions.cs
new file mode 100644
index 0000000000..73656b8854
--- /dev/null
+++ b/Perspex/Controls/ColumnDefinitions.cs
@@ -0,0 +1,12 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ public class ColumnDefinitions : PerspexList
+ {
+ }
+}
diff --git a/Perspex/Controls/DefinitionBase.cs b/Perspex/Controls/DefinitionBase.cs
new file mode 100644
index 0000000000..f36cab0052
--- /dev/null
+++ b/Perspex/Controls/DefinitionBase.cs
@@ -0,0 +1,20 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ public class DefinitionBase : PerspexObject
+ {
+ public static readonly PerspexProperty SharedSizeGroupProperty =
+ PerspexProperty.Register("SharedSizeGroup", inherits: true);
+
+ public string SharedSizeGroup
+ {
+ get { return this.GetValue(SharedSizeGroupProperty); }
+ set { this.SetValue(SharedSizeGroupProperty, value); }
+ }
+ }
+}
diff --git a/Perspex/Controls/Grid.cs b/Perspex/Controls/Grid.cs
new file mode 100644
index 0000000000..57d5c8bfb5
--- /dev/null
+++ b/Perspex/Controls/Grid.cs
@@ -0,0 +1,805 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ public class Grid : Panel
+ {
+ public static readonly PerspexProperty ColumnProperty =
+ PerspexProperty.RegisterAttached("Column");
+
+ public static readonly PerspexProperty ColumnSpanProperty =
+ PerspexProperty.RegisterAttached("ColumnSpan", 1);
+
+ public static readonly PerspexProperty IsSharedSizeScopeProperty =
+ PerspexProperty.RegisterAttached("IsSharedSizeScopeProperty");
+
+ public static readonly PerspexProperty RowProperty =
+ PerspexProperty.RegisterAttached("Row");
+
+ public static readonly PerspexProperty RowSpanProperty =
+ PerspexProperty.RegisterAttached("RowSpan", 1);
+
+ private Segment[,] rowMatrix;
+ private Segment[,] colMatrix;
+
+ public Grid()
+ {
+ this.ColumnDefinitions = new ColumnDefinitions();
+ this.RowDefinitions = new RowDefinitions();
+ }
+
+ public ColumnDefinitions ColumnDefinitions
+ {
+ get;
+ set;
+ }
+
+ public RowDefinitions RowDefinitions
+ {
+ get;
+ set;
+ }
+
+ public static int GetColumn(PerspexObject element)
+ {
+ return element.GetValue(ColumnProperty);
+ }
+
+ public static int GetColumnSpan(PerspexObject element)
+ {
+ return element.GetValue(ColumnSpanProperty);
+ }
+
+ public static int GetRow(PerspexObject element)
+ {
+ return element.GetValue(RowProperty);
+ }
+
+ public static int GetRowSpan(PerspexObject element)
+ {
+ return element.GetValue(RowSpanProperty);
+ }
+
+ public static void SetColumn(PerspexObject element, int value)
+ {
+ element.SetValue(ColumnProperty, value);
+ }
+
+ public static void SetColumnSpan(PerspexObject element, int value)
+ {
+ element.SetValue(ColumnSpanProperty, value);
+ }
+
+ public static void SetRow(PerspexObject element, int value)
+ {
+ element.SetValue(RowProperty, value);
+ }
+
+ public static void SetRowSpan(PerspexObject element, int value)
+ {
+ element.SetValue(RowSpanProperty, value);
+ }
+
+ protected override Size MeasureContent(Size constraint)
+ {
+ Size totalSize = constraint;
+ int colCount = this.ColumnDefinitions.Count;
+ int rowCount = this.RowDefinitions.Count;
+ double totalStarsX = 0;
+ double totalStarsY = 0;
+ bool emptyRows = rowCount == 0;
+ bool emptyCols = colCount == 0;
+ bool hasChildren = this.Children.Count > 0;
+
+ if (emptyRows)
+ {
+ rowCount = 1;
+ }
+
+ if (emptyCols)
+ {
+ colCount = 1;
+ }
+
+ this.CreateMatrices(rowCount, colCount);
+
+ if (emptyRows)
+ {
+ this.rowMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star);
+ this.rowMatrix[0, 0].Stars = 1.0;
+ totalStarsY += 1.0;
+ }
+ else
+ {
+ for (int i = 0; i < rowCount; i++)
+ {
+ RowDefinition rowdef = this.RowDefinitions[i];
+ GridLength height = rowdef.Height;
+
+ rowdef.ActualHeight = double.PositiveInfinity;
+ this.rowMatrix[i, i] = new Segment(0, rowdef.MinHeight, rowdef.MaxHeight, height.GridUnitType);
+
+ if (height.GridUnitType == GridUnitType.Pixel)
+ {
+ this.rowMatrix[i, i].OfferedSize = Clamp(height.Value, this.rowMatrix[i, i].Min, this.rowMatrix[i, i].Max);
+ this.rowMatrix[i, i].DesiredSize = this.rowMatrix[i, i].OfferedSize;
+ rowdef.ActualHeight = this.rowMatrix[i, i].OfferedSize;
+ }
+ else if (height.GridUnitType == GridUnitType.Star)
+ {
+ this.rowMatrix[i, i].Stars = height.Value;
+ totalStarsY += height.Value;
+ }
+ else if (height.GridUnitType == GridUnitType.Auto)
+ {
+ this.rowMatrix[i, i].OfferedSize = Clamp(0, this.rowMatrix[i, i].Min, this.rowMatrix[i, i].Max);
+ this.rowMatrix[i, i].DesiredSize = this.rowMatrix[i, i].OfferedSize;
+ }
+ }
+ }
+
+ if (emptyCols)
+ {
+ this.colMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star);
+ this.colMatrix[0, 0].Stars = 1.0;
+ totalStarsX += 1.0;
+ }
+ else
+ {
+ for (int i = 0; i < colCount; i++)
+ {
+ ColumnDefinition coldef = this.ColumnDefinitions[i];
+ GridLength width = coldef.Width;
+
+ coldef.ActualWidth = double.PositiveInfinity;
+ this.colMatrix[i, i] = new Segment(0, coldef.MinWidth, coldef.MaxWidth, width.GridUnitType);
+
+ if (width.GridUnitType == GridUnitType.Pixel)
+ {
+ this.colMatrix[i, i].OfferedSize = Clamp(width.Value, this.colMatrix[i, i].Min, this.colMatrix[i, i].Max);
+ this.colMatrix[i, i].DesiredSize = this.colMatrix[i, i].OfferedSize;
+ coldef.ActualWidth = this.colMatrix[i, i].OfferedSize;
+ }
+ else if (width.GridUnitType == GridUnitType.Star)
+ {
+ this.colMatrix[i, i].Stars = width.Value;
+ totalStarsX += width.Value;
+ }
+ else if (width.GridUnitType == GridUnitType.Auto)
+ {
+ this.colMatrix[i, i].OfferedSize = Clamp(0, this.colMatrix[i, i].Min, this.colMatrix[i, i].Max);
+ this.colMatrix[i, i].DesiredSize = this.colMatrix[i, i].OfferedSize;
+ }
+ }
+ }
+
+ List sizes = new List();
+ GridNode node;
+ GridNode separator = new GridNode(null, 0, 0, 0);
+ int separatorIndex;
+
+ sizes.Add(separator);
+
+ // Pre-process the grid children so that we know what types of elements we have so
+ // we can apply our special measuring rules.
+ GridWalker gridWalker = new GridWalker(this, this.rowMatrix, this.colMatrix);
+
+ for (int i = 0; i < 6; i++)
+ {
+ // These bools tell us which grid element type we should be measuring. i.e.
+ // 'star/auto' means we should measure elements with a star row and auto col
+ bool autoAuto = i == 0;
+ bool starAuto = i == 1;
+ bool autoStar = i == 2;
+ bool starAutoAgain = i == 3;
+ bool nonStar = i == 4;
+ bool remainingStar = i == 5;
+
+ if (hasChildren)
+ {
+ this.ExpandStarCols(totalSize);
+ this.ExpandStarRows(totalSize);
+ }
+
+ foreach (Control child in this.Children)
+ {
+ int col, row;
+ int colspan, rowspan;
+ double childSizeX = 0;
+ double childSizeY = 0;
+ bool starCol = false;
+ bool starRow = false;
+ bool autoCol = false;
+ bool autoRow = false;
+
+ col = Math.Min(GetColumn(child), colCount - 1);
+ row = Math.Min(GetRow(child), rowCount - 1);
+ colspan = Math.Min(GetColumnSpan(child), colCount - col);
+ rowspan = Math.Min(GetRowSpan(child), rowCount - row);
+
+ for (int r = row; r < row + rowspan; r++)
+ {
+ starRow |= this.rowMatrix[r, r].Type == GridUnitType.Star;
+ autoRow |= this.rowMatrix[r, r].Type == GridUnitType.Auto;
+ }
+
+ for (int c = col; c < col + colspan; c++)
+ {
+ starCol |= this.colMatrix[c, c].Type == GridUnitType.Star;
+ autoCol |= this.colMatrix[c, c].Type == GridUnitType.Auto;
+ }
+
+ // This series of if statements checks whether or not we should measure
+ // the current element and also if we need to override the sizes
+ // passed to the Measure call.
+
+ // If the element has Auto rows and Auto columns and does not span Star
+ // rows/cols it should only be measured in the auto_auto phase.
+ // There are similar rules governing auto/star and star/auto elements.
+ // NOTE: star/auto elements are measured twice. The first time with
+ // an override for height, the second time without it.
+ if (autoRow && autoCol && !starRow && !starCol)
+ {
+ if (!autoAuto)
+ {
+ continue;
+ }
+
+ childSizeX = double.PositiveInfinity;
+ childSizeY = double.PositiveInfinity;
+ }
+ else if (starRow && autoCol && !starCol)
+ {
+ if (!(starAuto || starAutoAgain))
+ {
+ continue;
+ }
+
+ if (starAuto && gridWalker.HasAutoStar)
+ {
+ childSizeY = double.PositiveInfinity;
+ }
+
+ childSizeX = double.PositiveInfinity;
+ }
+ else if (autoRow && starCol && !starRow)
+ {
+ if (!autoStar)
+ {
+ continue;
+ }
+
+ childSizeY = double.PositiveInfinity;
+ }
+ else if ((autoRow || autoCol) && !(starRow || starCol))
+ {
+ if (!nonStar)
+ {
+ continue;
+ }
+
+ if (autoRow)
+ {
+ childSizeY = double.PositiveInfinity;
+ }
+
+ if (autoCol)
+ {
+ childSizeX = double.PositiveInfinity;
+ }
+ }
+ else if (!(starRow || starCol))
+ {
+ if (!nonStar)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ if (!remainingStar)
+ {
+ continue;
+ }
+ }
+
+ for (int r = row; r < row + rowspan; r++)
+ {
+ childSizeY += this.rowMatrix[r, r].OfferedSize;
+ }
+
+ for (int c = col; c < col + colspan; c++)
+ {
+ childSizeX += this.colMatrix[c, c].OfferedSize;
+ }
+
+ child.Measure(new Size(childSizeX, childSizeY));
+ Size desired = child.DesiredSize.Value;
+
+ // Elements distribute their height based on two rules:
+ // 1) Elements with rowspan/colspan == 1 distribute their height first
+ // 2) Everything else distributes in a LIFO manner.
+ // As such, add all UIElements with rowspan/colspan == 1 after the separator in
+ // the list and everything else before it. Then to process, just keep popping
+ // elements off the end of the list.
+ if (!starAuto)
+ {
+ node = new GridNode(this.rowMatrix, row + rowspan - 1, row, desired.Height);
+ separatorIndex = sizes.IndexOf(separator);
+ sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node);
+ }
+
+ node = new GridNode(this.colMatrix, col + colspan - 1, col, desired.Width);
+
+ separatorIndex = sizes.IndexOf(separator);
+ sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node);
+ }
+
+ sizes.Remove(separator);
+
+ while (sizes.Count > 0)
+ {
+ node = sizes.Last();
+ node.Matrix[node.Row, node.Column].DesiredSize = Math.Max(node.Matrix[node.Row, node.Column].DesiredSize, node.Size);
+ this.AllocateDesiredSize(rowCount, colCount);
+ sizes.Remove(node);
+ }
+
+ sizes.Add(separator);
+ }
+
+ // Once we have measured and distributed all sizes, we have to store
+ // the results. Every time we want to expand the rows/cols, this will
+ // be used as the baseline.
+ this.SaveMeasureResults();
+
+ sizes.Remove(separator);
+
+ double gridSizeX = 0;
+ double gridSizeY = 0;
+
+ for (int c = 0; c < colCount; c++)
+ {
+ gridSizeX += this.colMatrix[c, c].DesiredSize;
+ }
+
+ for (int r = 0; r < rowCount; r++)
+ {
+ gridSizeY += this.rowMatrix[r, r].DesiredSize;
+ }
+
+ return new Size(gridSizeX, gridSizeY);
+ }
+
+ protected override Size ArrangeContent(Size finalSize)
+ {
+ int colCount = this.ColumnDefinitions.Count;
+ int rowCount = this.RowDefinitions.Count;
+ int colMatrixDim = this.colMatrix.GetUpperBound(0) + 1;
+ int rowMatrixDim = this.rowMatrix.GetUpperBound(0) + 1;
+
+ this.RestoreMeasureResults();
+
+ double totalConsumedX = 0;
+ double totalConsumedY = 0;
+
+ for (int c = 0; c < colMatrixDim; c++)
+ {
+ this.colMatrix[c, c].OfferedSize = this.colMatrix[c, c].DesiredSize;
+ totalConsumedX += this.colMatrix[c, c].OfferedSize;
+ }
+
+ for (int r = 0; r < rowMatrixDim; r++)
+ {
+ this.rowMatrix[r, r].OfferedSize = this.rowMatrix[r, r].DesiredSize;
+ totalConsumedY += this.rowMatrix[r, r].OfferedSize;
+ }
+
+ if (totalConsumedX != finalSize.Width)
+ {
+ this.ExpandStarCols(finalSize);
+ }
+
+ if (totalConsumedY != finalSize.Height)
+ {
+ this.ExpandStarRows(finalSize);
+ }
+
+ for (int c = 0; c < colCount; c++)
+ {
+ this.ColumnDefinitions[c].ActualWidth = this.colMatrix[c, c].OfferedSize;
+ }
+
+ for (int r = 0; r < rowCount; r++)
+ {
+ this.RowDefinitions[r].ActualHeight = this.rowMatrix[r, r].OfferedSize;
+ }
+
+ foreach (Control child in this.Children)
+ {
+ int col = Math.Min(GetColumn(child), colMatrixDim - 1);
+ int row = Math.Min(GetRow(child), rowMatrixDim - 1);
+ int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - col);
+ int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - row);
+
+ double childFinalX = 0;
+ double childFinalY = 0;
+ double childFinalW = 0;
+ double childFinalH = 0;
+
+ for (int c = 0; c < col; c++)
+ {
+ childFinalX += this.colMatrix[c, c].OfferedSize;
+ }
+
+ for (int c = col; c < col + colspan; c++)
+ {
+ childFinalW += this.colMatrix[c, c].OfferedSize;
+ }
+
+ for (int r = 0; r < row; r++)
+ {
+ childFinalY += this.rowMatrix[r, r].OfferedSize;
+ }
+
+ for (int r = row; r < row + rowspan; r++)
+ {
+ childFinalH += this.rowMatrix[r, r].OfferedSize;
+ }
+
+ child.Arrange(new Rect(childFinalX, childFinalY, childFinalW, childFinalH));
+ }
+
+ return finalSize;
+ }
+
+ private static double Clamp(double val, double min, double max)
+ {
+ if (val < min)
+ {
+ return min;
+ }
+ else if (val > max)
+ {
+ return max;
+ }
+ else
+ {
+ return val;
+ }
+ }
+
+ private void CreateMatrices(int rowCount, int colCount)
+ {
+ if (this.rowMatrix == null || this.colMatrix == null ||
+ this.rowMatrix.GetUpperBound(0) != rowCount - 1 ||
+ this.colMatrix.GetUpperBound(0) != colCount - 1)
+ {
+ this.rowMatrix = new Segment[rowCount, rowCount];
+ this.colMatrix = new Segment[colCount, colCount];
+ }
+ }
+
+ private void ExpandStarCols(Size availableSize)
+ {
+ int columnsCount = this.ColumnDefinitions.Count;
+ double width = availableSize.Width;
+
+ for (int i = 0; i < this.colMatrix.GetUpperBound(0) + 1; i++)
+ {
+ if (this.colMatrix[i, i].Type == GridUnitType.Star)
+ {
+ this.colMatrix[i, i].OfferedSize = 0;
+ }
+ else
+ {
+ width = Math.Max(availableSize.Width - this.colMatrix[i, i].OfferedSize, 0);
+ }
+ }
+
+ this.AssignSize(this.colMatrix, 0, this.colMatrix.GetUpperBound(0), ref width, GridUnitType.Star, false);
+ width = Math.Max(0, width);
+
+ if (columnsCount > 0)
+ {
+ for (int i = 0; i < this.colMatrix.GetUpperBound(0) + 1; i++)
+ {
+ if (this.colMatrix[i, i].Type == GridUnitType.Star)
+ {
+ this.ColumnDefinitions[i].ActualWidth = this.colMatrix[i, i].OfferedSize;
+ }
+ }
+ }
+ }
+
+ private void ExpandStarRows(Size availableSize)
+ {
+ int rowCount = this.RowDefinitions.Count;
+ double height = availableSize.Height;
+
+ // When expanding star rows, we need to zero out their height before
+ // calling AssignSize. AssignSize takes care of distributing the
+ // available size when there are Mins and Maxs applied.
+ for (int i = 0; i < this.rowMatrix.GetUpperBound(0) + 1; i++)
+ {
+ if (this.rowMatrix[i, i].Type == GridUnitType.Star)
+ {
+ this.rowMatrix[i, i].OfferedSize = 0.0;
+ }
+ else
+ {
+ height = Math.Max(availableSize.Height - this.rowMatrix[i, i].OfferedSize, 0);
+ }
+ }
+
+ this.AssignSize(this.rowMatrix, 0, this.rowMatrix.GetUpperBound(0), ref height, GridUnitType.Star, false);
+
+ if (rowCount > 0)
+ {
+ for (int i = 0; i < this.rowMatrix.GetUpperBound(0) + 1; i++)
+ {
+ if (this.rowMatrix[i, i].Type == GridUnitType.Star)
+ {
+ this.RowDefinitions[i].ActualHeight = this.rowMatrix[i, i].OfferedSize;
+ }
+ }
+ }
+ }
+
+ private void AssignSize(
+ Segment[,] matrix,
+ int start,
+ int end,
+ ref double size,
+ GridUnitType type,
+ bool desiredSize)
+ {
+ double count = 0;
+ bool assigned;
+
+ // Count how many segments are of the correct type. If we're measuring Star rows/cols
+ // we need to count the number of stars instead.
+ for (int i = start; i <= end; i++)
+ {
+ double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
+ if (segmentSize < matrix[i, i].Max)
+ {
+ count += type == GridUnitType.Star ? matrix[i, i].Stars : 1;
+ }
+ }
+
+ do
+ {
+ double contribution = size / count;
+
+ assigned = false;
+
+ for (int i = start; i <= end; i++)
+ {
+ double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
+
+ if (!(matrix[i, i].Type == type && segmentSize < matrix[i, i].Max))
+ {
+ continue;
+ }
+
+ double newsize = segmentSize;
+ newsize += contribution * (type == GridUnitType.Star ? matrix[i, i].Stars : 1);
+ newsize = Math.Min(newsize, matrix[i, i].Max);
+ assigned |= newsize > segmentSize;
+ size -= newsize - segmentSize;
+
+ if (desiredSize)
+ {
+ matrix[i, i].DesiredSize = newsize;
+ }
+ else
+ {
+ matrix[i, i].OfferedSize = newsize;
+ }
+ }
+ }
+ while (assigned);
+ }
+
+ private void AllocateDesiredSize(int rowCount, int colCount)
+ {
+ // First allocate the heights of the RowDefinitions, then allocate
+ // the widths of the ColumnDefinitions.
+ for (int i = 0; i < 2; i++)
+ {
+ Segment[,] matrix = i == 0 ? this.rowMatrix : this.colMatrix;
+ int count = i == 0 ? rowCount : colCount;
+
+ for (int row = count - 1; row >= 0; row--)
+ {
+ for (int col = row; col >= 0; col--)
+ {
+ bool spansStar = false;
+ for (int j = row; j >= col; j--)
+ {
+ spansStar |= matrix[j, j].Type == GridUnitType.Star;
+ }
+
+ // This is the amount of pixels which must be available between the grid rows
+ // at index 'col' and 'row'. i.e. if 'row' == 0 and 'col' == 2, there must
+ // be at least 'matrix [row][col].size' pixels of height allocated between
+ // all the rows in the range col -> row.
+ double current = matrix[row, col].DesiredSize;
+
+ // Count how many pixels have already been allocated between the grid rows
+ // in the range col -> row. The amount of pixels allocated to each grid row/column
+ // is found on the diagonal of the matrix.
+ double totalAllocated = 0;
+
+ for (int k = row; k >= col; k--)
+ {
+ totalAllocated += matrix[k, k].DesiredSize;
+ }
+
+ // If the size requirement has not been met, allocate the additional required
+ // size between 'pixel' rows, then 'star' rows, finally 'auto' rows, until all
+ // height has been assigned.
+ if (totalAllocated < current)
+ {
+ double additional = current - totalAllocated;
+
+ if (spansStar)
+ {
+ this.AssignSize(matrix, col, row, ref additional, GridUnitType.Star, true);
+ }
+ else
+ {
+ this.AssignSize(matrix, col, row, ref additional, GridUnitType.Pixel, true);
+ this.AssignSize(matrix, col, row, ref additional, GridUnitType.Auto, true);
+ }
+ }
+ }
+ }
+ }
+
+ for (int r = 0; r < this.rowMatrix.GetUpperBound(0) + 1; r++)
+ {
+ this.rowMatrix[r, r].OfferedSize = this.rowMatrix[r, r].DesiredSize;
+ }
+
+ for (int c = 0; c < this.colMatrix.GetUpperBound(0) + 1; c++)
+ {
+ this.colMatrix[c, c].OfferedSize = this.colMatrix[c, c].DesiredSize;
+ }
+ }
+
+ private void SaveMeasureResults()
+ {
+ for (int i = 0; i < this.rowMatrix.GetUpperBound(0) + 1; i++)
+ {
+ for (int j = 0; j < this.rowMatrix.GetUpperBound(0) + 1; j++)
+ {
+ this.rowMatrix[i, j].OriginalSize = this.rowMatrix[i, j].OfferedSize;
+ }
+ }
+
+ for (int i = 0; i < this.colMatrix.GetUpperBound(0); i++)
+ {
+ for (int j = 0; j < this.colMatrix.GetUpperBound(0); j++)
+ {
+ this.colMatrix[i, j].OriginalSize = this.colMatrix[i, j].OfferedSize;
+ }
+ }
+ }
+
+ private void RestoreMeasureResults()
+ {
+ for (int i = 0; i < this.rowMatrix.GetUpperBound(0) + 1; i++)
+ {
+ for (int j = 0; j < this.rowMatrix.GetUpperBound(0) + 1; j++)
+ {
+ this.rowMatrix[i, j].OfferedSize = this.rowMatrix[i, j].OriginalSize;
+ }
+ }
+
+ for (int i = 0; i < this.colMatrix.GetUpperBound(0) + 1; i++)
+ {
+ for (int j = 0; j < this.colMatrix.GetUpperBound(0) + 1; j++)
+ {
+ this.colMatrix[i, j].OfferedSize = this.colMatrix[i, j].OriginalSize;
+ }
+ }
+ }
+
+ private struct Segment
+ {
+ public double OriginalSize;
+ public double Max;
+ public double Min;
+ public double DesiredSize;
+ public double OfferedSize;
+ public double Stars;
+ public GridUnitType Type;
+
+ public Segment(double offeredSize, double min, double max, GridUnitType type)
+ {
+ this.OriginalSize = 0;
+ this.Min = min;
+ this.Max = max;
+ this.DesiredSize = 0;
+ this.OfferedSize = offeredSize;
+ this.Stars = 0;
+ this.Type = type;
+ }
+
+ public void Init(double offeredSize, double min, double max, GridUnitType type)
+ {
+ this.OfferedSize = offeredSize;
+ this.Min = min;
+ this.Max = max;
+ this.Type = type;
+ }
+ }
+
+ private struct GridNode
+ {
+ public int Row;
+ public int Column;
+ public double Size;
+ public Segment[,] Matrix;
+
+ public GridNode(Segment[,] matrix, int row, int col, double size)
+ {
+ this.Matrix = matrix;
+ this.Row = row;
+ this.Column = col;
+ this.Size = size;
+ }
+ }
+
+ private class GridWalker
+ {
+ public GridWalker(Grid grid, Segment[,] rowMatrix, Segment[,] colMatrix)
+ {
+ foreach (Control child in grid.Children)
+ {
+ bool starCol = false;
+ bool starRow = false;
+ bool autoCol = false;
+ bool autoRow = false;
+
+ int col = Math.Min(Grid.GetColumn(child), colMatrix.GetUpperBound(0));
+ int row = Math.Min(Grid.GetRow(child), rowMatrix.GetUpperBound(0));
+ int colspan = Math.Min(Grid.GetColumnSpan(child), colMatrix.GetUpperBound(0));
+ int rowspan = Math.Min(Grid.GetRowSpan(child), rowMatrix.GetUpperBound(0));
+
+ for (int r = row; r < row + rowspan; r++)
+ {
+ starRow |= rowMatrix[r, r].Type == GridUnitType.Star;
+ autoRow |= rowMatrix[r, r].Type == GridUnitType.Auto;
+ }
+
+ for (int c = col; c < col + colspan; c++)
+ {
+ starCol |= colMatrix[c, c].Type == GridUnitType.Star;
+ autoCol |= colMatrix[c, c].Type == GridUnitType.Auto;
+ }
+
+ this.HasAutoAuto |= autoRow && autoCol && !starRow && !starCol;
+ this.HasStarAuto |= starRow && autoCol;
+ this.HasAutoStar |= autoRow && starCol;
+ }
+ }
+
+ public bool HasAutoAuto { get; private set; }
+
+ public bool HasStarAuto { get; private set; }
+
+ public bool HasAutoStar { get; private set; }
+ }
+ }
+}
diff --git a/Perspex/Controls/GridLength.cs b/Perspex/Controls/GridLength.cs
new file mode 100644
index 0000000000..5958b58898
--- /dev/null
+++ b/Perspex/Controls/GridLength.cs
@@ -0,0 +1,121 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ using System;
+
+ public enum GridUnitType
+ {
+ Auto = 0,
+ Pixel = 1,
+ Star = 2,
+ }
+
+ public struct GridLength : IEquatable
+ {
+ private GridUnitType type;
+
+ private double value;
+
+ public GridLength(double value)
+ : this(value, GridUnitType.Pixel)
+ {
+ }
+
+ public GridLength(double value, GridUnitType type)
+ {
+ if (value < 0 || double.IsNaN(value) || double.IsInfinity(value))
+ {
+ throw new ArgumentException("Invalid value", "value");
+ }
+
+ if (type < GridUnitType.Auto || type > GridUnitType.Star)
+ {
+ throw new ArgumentException("Invalid value", "type");
+ }
+
+ this.type = type;
+ this.value = value;
+ }
+
+ public static GridLength Auto
+ {
+ get { return new GridLength(0, GridUnitType.Auto); }
+ }
+
+ public GridUnitType GridUnitType
+ {
+ get { return this.type; }
+ }
+
+ public bool IsAbsolute
+ {
+ get { return this.type == GridUnitType.Pixel; }
+ }
+
+ public bool IsAuto
+ {
+ get { return this.type == GridUnitType.Auto; }
+ }
+
+ public bool IsStar
+ {
+ get { return this.type == GridUnitType.Star; }
+ }
+
+ public double Value
+ {
+ get { return this.value; }
+ }
+
+ public static bool operator ==(GridLength a, GridLength b)
+ {
+ return (a.IsAuto && b.IsAuto) || (a.value == b.value && a.type == b.type);
+ }
+
+ public static bool operator !=(GridLength gl1, GridLength gl2)
+ {
+ return !(gl1 == gl2);
+ }
+
+ public override bool Equals(object o)
+ {
+ if (o == null)
+ {
+ return false;
+ }
+
+ if (!(o is GridLength))
+ {
+ return false;
+ }
+
+ return this == (GridLength)o;
+ }
+
+ public bool Equals(GridLength gridLength)
+ {
+ return this == gridLength;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.value.GetHashCode() ^ this.type.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ if (this.IsAuto)
+ {
+ return "Auto";
+ }
+
+ string s = this.value.ToString();
+ return this.IsStar ? s + "*" : s;
+ }
+ }
+}
diff --git a/Perspex/Controls/RowDefinition.cs b/Perspex/Controls/RowDefinition.cs
new file mode 100644
index 0000000000..230fb7a14a
--- /dev/null
+++ b/Perspex/Controls/RowDefinition.cs
@@ -0,0 +1,53 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ public class RowDefinition : DefinitionBase
+ {
+ public static readonly PerspexProperty MaxHeightProperty =
+ PerspexProperty.Register("MaxHeight", double.PositiveInfinity);
+
+ public static readonly PerspexProperty MinHeightProperty =
+ PerspexProperty.Register("MinHeight");
+
+ public static readonly PerspexProperty HeightProperty =
+ PerspexProperty.Register("Height", new GridLength(1, GridUnitType.Star));
+
+ public RowDefinition()
+ {
+ }
+
+ public RowDefinition(GridLength height)
+ {
+ this.Height = height;
+ }
+
+ public double ActualHeight
+ {
+ get;
+ internal set;
+ }
+
+ public double MaxHeight
+ {
+ get { return this.GetValue(MaxHeightProperty); }
+ set { this.SetValue(MaxHeightProperty, value); }
+ }
+
+ public double MinHeight
+ {
+ get { return this.GetValue(MinHeightProperty); }
+ set { this.SetValue(MinHeightProperty, value); }
+ }
+
+ public GridLength Height
+ {
+ get { return this.GetValue(HeightProperty); }
+ set { this.SetValue(HeightProperty, value); }
+ }
+ }
+}
diff --git a/Perspex/Controls/RowDefinitions.cs b/Perspex/Controls/RowDefinitions.cs
new file mode 100644
index 0000000000..60ea0da556
--- /dev/null
+++ b/Perspex/Controls/RowDefinitions.cs
@@ -0,0 +1,12 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ public class RowDefinitions : PerspexList
+ {
+ }
+}
diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj
index 5c7cd43d9c..7608927c88 100644
--- a/Perspex/Perspex.csproj
+++ b/Perspex/Perspex.csproj
@@ -71,6 +71,13 @@
+
+
+
+
+
+
+
diff --git a/Perspex/PerspexProperty.cs b/Perspex/PerspexProperty.cs
index 03d8ea8c32..f02925f724 100644
--- a/Perspex/PerspexProperty.cs
+++ b/Perspex/PerspexProperty.cs
@@ -105,6 +105,35 @@ namespace Perspex
return result;
}
+ ///
+ /// Registers an attached .
+ ///
+ /// The type of the class that is registering the property.
+ /// The type of the class that the property is to be registered on.
+ /// The type of the property's value.
+ /// The name of the property.
+ /// The default value of the property.
+ /// Whether the property inherits its value.
+ ///
+ public static PerspexProperty RegisterAttached(
+ string name,
+ TValue defaultValue = default(TValue),
+ bool inherits = false)
+ where TOwner : PerspexObject
+ {
+ Contract.Requires(name != null);
+
+ PerspexProperty result = new PerspexProperty(
+ name,
+ typeof(TOwner),
+ defaultValue,
+ inherits);
+
+ PerspexObject.Register(typeof(THost), result);
+
+ return result;
+ }
+
///
/// Provides access to a property's binding via the
/// indexer.
diff --git a/Perspex/Themes/Default/CheckBoxStyle.cs b/Perspex/Themes/Default/CheckBoxStyle.cs
index 1706bedee5..60b5049d4c 100644
--- a/Perspex/Themes/Default/CheckBoxStyle.cs
+++ b/Perspex/Themes/Default/CheckBoxStyle.cs
@@ -48,10 +48,13 @@ namespace Perspex.Themes.Default
Border result = new Border
{
[~Border.BackgroundProperty] = control[~CheckBox.BackgroundProperty],
- Content = new StackPanel
+ Content = new Grid
{
- Orientation = Orientation.Horizontal,
- Gap = 8,
+ ColumnDefinitions = new ColumnDefinitions
+ {
+ new ColumnDefinition(GridLength.Auto),
+ new ColumnDefinition(new GridLength(1, GridUnitType.Star)),
+ },
Children = new PerspexList
{
new Border
@@ -67,10 +70,12 @@ namespace Perspex.Themes.Default
StrokeThickness = 2,
VerticalAlignment = VerticalAlignment.Center,
},
+ [Grid.ColumnProperty] = 0,
},
new ContentPresenter
{
[~ContentPresenter.ContentProperty] = control[~CheckBox.ContentProperty],
+ [Grid.ColumnProperty] = 1,
},
},
},