From ee3743e9429145594e743de3f64cd949479a4b8c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 13 Jun 2014 01:36:44 +0200 Subject: [PATCH] Added Grid. --- Perspex/Controls/ColumnDefinition.cs | 53 ++ Perspex/Controls/ColumnDefinitions.cs | 12 + Perspex/Controls/DefinitionBase.cs | 20 + Perspex/Controls/Grid.cs | 805 ++++++++++++++++++++++++ Perspex/Controls/GridLength.cs | 121 ++++ Perspex/Controls/RowDefinition.cs | 53 ++ Perspex/Controls/RowDefinitions.cs | 12 + Perspex/Perspex.csproj | 7 + Perspex/PerspexProperty.cs | 29 + Perspex/Themes/Default/CheckBoxStyle.cs | 11 +- 10 files changed, 1120 insertions(+), 3 deletions(-) create mode 100644 Perspex/Controls/ColumnDefinition.cs create mode 100644 Perspex/Controls/ColumnDefinitions.cs create mode 100644 Perspex/Controls/DefinitionBase.cs create mode 100644 Perspex/Controls/Grid.cs create mode 100644 Perspex/Controls/GridLength.cs create mode 100644 Perspex/Controls/RowDefinition.cs create mode 100644 Perspex/Controls/RowDefinitions.cs 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, }, }, },