diff options
Diffstat (limited to 'Xamarin.Forms.Core/GridCalc.cs')
-rw-r--r-- | Xamarin.Forms.Core/GridCalc.cs | 698 |
1 files changed, 698 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core/GridCalc.cs b/Xamarin.Forms.Core/GridCalc.cs new file mode 100644 index 00000000..778e188e --- /dev/null +++ b/Xamarin.Forms.Core/GridCalc.cs @@ -0,0 +1,698 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms +{ + public partial class Grid + { + List<ColumnDefinition> _columns; + List<RowDefinition> _rows; + + protected override void LayoutChildren(double x, double y, double width, double height) + { + if (!InternalChildren.Any()) + return; + + MeasureGrid(width, height); + + // Make copies so if InvalidateMeasure is called during layout we dont crash when these get nulled + List<ColumnDefinition> columnsCopy = _columns; + List<RowDefinition> rowsCopy = _rows; + + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible) + continue; + int r = GetRow(child); + int c = GetColumn(child); + int rs = GetRowSpan(child); + int cs = GetColumnSpan(child); + + double posx = x + c * ColumnSpacing; + for (var i = 0; i < c; i++) + posx += columnsCopy[i].ActualWidth; + double posy = y + r * RowSpacing; + for (var i = 0; i < r; i++) + posy += rowsCopy[i].ActualHeight; + + double w = columnsCopy[c].ActualWidth; + for (var i = 1; i < cs; i++) + w += ColumnSpacing + columnsCopy[c + i].ActualWidth; + double h = rowsCopy[r].ActualHeight; + for (var i = 1; i < rs; i++) + h += RowSpacing + rowsCopy[r + i].ActualHeight; + + // in the future we can might maybe optimize by passing the already calculated size request + LayoutChildIntoBoundingRegion(child, new Rectangle(posx, posy, w, h)); + } + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + if (!InternalChildren.Any()) + return new SizeRequest(new Size(0, 0)); + + MeasureGrid(widthConstraint, heightConstraint, true); + + double columnWidthSum = 0; + double nonStarColumnWidthSum = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + columnWidthSum += c.ActualWidth; + if (!c.Width.IsStar) + nonStarColumnWidthSum += c.ActualWidth; + } + double rowHeightSum = 0; + double nonStarRowHeightSum = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + rowHeightSum += r.ActualHeight; + if (!r.Height.IsStar) + nonStarRowHeightSum += r.ActualHeight; + } + + var request = new Size(columnWidthSum + (_columns.Count - 1) * ColumnSpacing, rowHeightSum + (_rows.Count - 1) * RowSpacing); + var minimum = new Size(nonStarColumnWidthSum + (_columns.Count - 1) * ColumnSpacing, nonStarRowHeightSum + (_rows.Count - 1) * RowSpacing); + + var result = new SizeRequest(request, minimum); + return result; + } + + void AssignAbsoluteCells() + { + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsAbsolute) + row.ActualHeight = row.Height.Value; + } + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsAbsolute) + col.ActualWidth = col.Width.Value; + } + } + + void CalculateAutoCells(double width, double height) + { + // this require multiple passes. First process the 1-span, then 2, 3, ... + // And this needs to be run twice, just in case a lower-span column can be determined by a larger span + for (var iteration = 0; iteration < 2; iteration++) + { + for (var rowspan = 1; rowspan <= _rows.Count; rowspan++) + { + for (var i = 0; i < _rows.Count; i++) + { + RowDefinition row = _rows[i]; + if (!row.Height.IsAuto) + continue; + if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3 + continue; + + double actualHeight = row.ActualHeight; + double minimumHeight = row.MinimumHeight; + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + double assignedHeight = GetAssignedRowHeight(child); + double widthRequest = assignedWidth + GetUnassignedWidth(width); + double heightRequest = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height); + + SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins); + actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + } + if (actualHeight >= 0) + row.ActualHeight = actualHeight; + if (minimumHeight >= 0) + row.MinimumHeight = minimumHeight; + } + } + + for (var colspan = 1; colspan <= _columns.Count; colspan++) + { + for (var i = 0; i < _columns.Count; i++) + { + ColumnDefinition col = _columns[i]; + if (!col.Width.IsAuto) + continue; + if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip + continue; + + double actualWidth = col.ActualWidth; + double minimumWidth = col.MinimumWidth; + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + double assignedHeight = GetAssignedRowHeight(child); + double widthRequest = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width); + double heightRequest = assignedHeight + GetUnassignedHeight(height); + + SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins); + actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + } + if (actualWidth >= 0) + col.ActualWidth = actualWidth; + if (minimumWidth >= 0) + col.MinimumWidth = actualWidth; + } + } + } + } + + void CalculateStarCells(double width, double height, double totalStarsWidth, double totalStarsHeight) + { + double starColWidth = GetUnassignedWidth(width) / totalStarsWidth; + double starRowHeight = GetUnassignedHeight(height) / totalStarsHeight; + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + col.ActualWidth = col.Width.Value * starColWidth; + } + + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + row.ActualHeight = row.Height.Value * starRowHeight; + } + } + + void ContractColumnsIfNeeded(double width, Func<ColumnDefinition, bool> predicate) + { + double columnWidthSum = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + columnWidthSum += c.ActualWidth; + } + + double rowHeightSum = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + rowHeightSum += r.ActualHeight; + } + + var request = new Size(columnWidthSum + (_columns.Count - 1) * ColumnSpacing, rowHeightSum + (_rows.Count - 1) * RowSpacing); + if (request.Width > width) + { + double contractionSpace = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + if (predicate(c)) + contractionSpace += c.ActualWidth - c.MinimumWidth; + } + if (contractionSpace > 0) + { + // contract as much as we can but no more + double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Width - width, 0)); + double contractFactor = contractionNeeded / contractionSpace; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (!predicate(col)) + continue; + double availableSpace = col.ActualWidth - col.MinimumWidth; + double contraction = availableSpace * contractFactor; + col.ActualWidth -= contraction; + contractionNeeded -= contraction; + } + } + } + } + + void ContractRowsIfNeeded(double height, Func<RowDefinition, bool> predicate) + { + double columnSum = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition c = _columns[index]; + columnSum += Math.Max(0, c.ActualWidth); + } + double rowSum = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + rowSum += Math.Max(0, r.ActualHeight); + } + + var request = new Size(columnSum + (_columns.Count - 1) * ColumnSpacing, rowSum + (_rows.Count - 1) * RowSpacing); + if (request.Height <= height) + return; + double contractionSpace = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition r = _rows[index]; + if (predicate(r)) + contractionSpace += r.ActualHeight - r.MinimumHeight; + } + if (!(contractionSpace > 0)) + return; + // contract as much as we can but no more + double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Height - height, 0)); + double contractFactor = contractionNeeded / contractionSpace; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (!predicate(row)) + continue; + double availableSpace = row.ActualHeight - row.MinimumHeight; + double contraction = availableSpace * contractFactor; + row.ActualHeight -= contraction; + contractionNeeded -= contraction; + } + } + + void EnsureRowsColumnsInitialized() + { + _columns = ColumnDefinitions == null ? new List<ColumnDefinition>() : ColumnDefinitions.ToList(); + _rows = RowDefinitions == null ? new List<RowDefinition>() : RowDefinitions.ToList(); + + int lastRow = -1; + for (var index = 0; index < InternalChildren.Count; index++) + { + Element w = InternalChildren[index]; + lastRow = Math.Max(lastRow, GetRow(w) + GetRowSpan(w) - 1); + } + lastRow = Math.Max(lastRow, RowDefinitions.Count - 1); + + int lastCol = -1; + for (var index = 0; index < InternalChildren.Count; index++) + { + Element w = InternalChildren[index]; + lastCol = Math.Max(lastCol, GetColumn(w) + GetColumnSpan(w) - 1); + } + lastCol = Math.Max(lastCol, ColumnDefinitions.Count - 1); + + while (_columns.Count <= lastCol) + _columns.Add(new ColumnDefinition()); + while (_rows.Count <= lastRow) + _rows.Add(new RowDefinition()); + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + col.ActualWidth = -1; + } + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + row.ActualHeight = -1; + } + } + + void ExpandLastAutoColumnIfNeeded(double width, bool expandToRequest) + { + for (var index = 0; index < InternalChildren.Count; index++) + { + Element element = InternalChildren[index]; + var child = (View)element; + if (!child.IsVisible) + continue; + + ColumnDefinition col = GetLastAutoColumn(child); + if (col == null) + continue; + + double assignedWidth = GetAssignedColumnWidth(child); + double w = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width); + SizeRequest sizeRequest = child.Measure(w, GetAssignedRowHeight(child), MeasureFlags.IncludeMargins); + double requiredWidth = expandToRequest ? sizeRequest.Request.Width : sizeRequest.Minimum.Width; + double deltaWidth = requiredWidth - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing; + if (deltaWidth > 0) + { + col.ActualWidth += deltaWidth; + } + } + } + + void ExpandLastAutoRowIfNeeded(double height, bool expandToRequest) + { + for (var index = 0; index < InternalChildren.Count; index++) + { + Element element = InternalChildren[index]; + var child = (View)element; + if (!child.IsVisible) + continue; + + RowDefinition row = GetLastAutoRow(child); + if (row == null) + continue; + + double assignedHeight = GetAssignedRowHeight(child); + double h = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height); + SizeRequest sizeRequest = child.Measure(GetAssignedColumnWidth(child), h, MeasureFlags.IncludeMargins); + double requiredHeight = expandToRequest ? sizeRequest.Request.Height : sizeRequest.Minimum.Height; + double deltaHeight = requiredHeight - assignedHeight - (GetRowSpan(child) - 1) * RowSpacing; + if (deltaHeight > 0) + { + row.ActualHeight += deltaHeight; + } + } + } + + void MeasureAndContractStarredColumns(double width, double height, double totalStarsWidth) + { + double starColWidth; + starColWidth = MeasuredStarredColumns(); + + if (!double.IsPositiveInfinity(width) && double.IsPositiveInfinity(height)) + { + // re-zero columns so GetUnassignedWidth returns correctly + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + col.ActualWidth = 0; + } + + starColWidth = Math.Max(starColWidth, GetUnassignedWidth(width) / totalStarsWidth); + } + + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + col.ActualWidth = col.Width.Value * starColWidth; + } + + ContractColumnsIfNeeded(width, c => c.Width.IsStar); + } + + void MeasureAndContractStarredRows(double width, double height, double totalStarsHeight) + { + double starRowHeight; + starRowHeight = MeasureStarredRows(); + + if (!double.IsPositiveInfinity(height) && double.IsPositiveInfinity(width)) + { + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + row.ActualHeight = 0; + } + + starRowHeight = Math.Max(starRowHeight, GetUnassignedHeight(height) / totalStarsHeight); + } + + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + row.ActualHeight = row.Height.Value * starRowHeight; + } + + ContractRowsIfNeeded(height, r => r.Height.IsStar); + } + + double MeasuredStarredColumns() + { + double starColWidth; + for (var iteration = 0; iteration < 2; iteration++) + { + for (var colspan = 1; colspan <= _columns.Count; colspan++) + { + for (var i = 0; i < _columns.Count; i++) + { + ColumnDefinition col = _columns[i]; + if (!col.Width.IsStar) + continue; + if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip + continue; + + double actualWidth = col.ActualWidth; + double minimumWidth = col.MinimumWidth; + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1) + continue; + double assignedWidth = GetAssignedColumnWidth(child); + + SizeRequest sizeRequest = child.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins); + actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing); + } + if (actualWidth >= 0) + col.ActualWidth = actualWidth; + + if (minimumWidth >= 0) + col.MinimumWidth = minimumWidth; + } + } + } + + //Measure the stars + starColWidth = 1; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (!col.Width.IsStar) + continue; + starColWidth = Math.Max(starColWidth, col.ActualWidth / col.Width.Value); + } + + return starColWidth; + } + + void MeasureGrid(double width, double height, bool requestSize = false) + { + EnsureRowsColumnsInitialized(); + + AssignAbsoluteCells(); + + CalculateAutoCells(width, height); + + if (!requestSize) + { + ContractColumnsIfNeeded(width, c => c.Width.IsAuto); + ContractRowsIfNeeded(height, r => r.Height.IsAuto); + } + + double totalStarsHeight = 0; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.Height.IsStar) + totalStarsHeight += row.Height.Value; + } + + double totalStarsWidth = 0; + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.Width.IsStar) + totalStarsWidth += col.Width.Value; + } + + if (requestSize) + { + MeasureAndContractStarredColumns(width, height, totalStarsWidth); + MeasureAndContractStarredRows(width, height, totalStarsHeight); + } + else + { + CalculateStarCells(width, height, totalStarsWidth, totalStarsHeight); + } + + ZeroUnassignedCells(); + + ExpandLastAutoRowIfNeeded(height, requestSize); + ExpandLastAutoColumnIfNeeded(width, requestSize); + } + + double MeasureStarredRows() + { + double starRowHeight; + for (var iteration = 0; iteration < 2; iteration++) + { + for (var rowspan = 1; rowspan <= _rows.Count; rowspan++) + { + for (var i = 0; i < _rows.Count; i++) + { + RowDefinition row = _rows[i]; + if (!row.Height.IsStar) + continue; + if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3 + continue; + + double actualHeight = row.ActualHeight; + double minimumHeight = row.MinimumHeight; + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (View)InternalChildren[index]; + if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1) + continue; + double assignedHeight = GetAssignedRowHeight(child); + double assignedWidth = GetAssignedColumnWidth(child); + + SizeRequest sizeRequest = child.Measure(assignedWidth, double.PositiveInfinity, MeasureFlags.IncludeMargins); + actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1)); + } + if (actualHeight >= 0) + row.ActualHeight = actualHeight; + + if (minimumHeight >= 0) + row.MinimumHeight = minimumHeight; + } + } + } + + // 3. Star columns: + + //Measure the stars + starRowHeight = 1; + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (!row.Height.IsStar) + continue; + starRowHeight = Math.Max(starRowHeight, row.ActualHeight / row.Height.Value); + } + + return starRowHeight; + } + + void ZeroUnassignedCells() + { + for (var index = 0; index < _columns.Count; index++) + { + ColumnDefinition col = _columns[index]; + if (col.ActualWidth < 0) + col.ActualWidth = 0; + } + for (var index = 0; index < _rows.Count; index++) + { + RowDefinition row = _rows[index]; + if (row.ActualHeight < 0) + row.ActualHeight = 0; + } + } + + #region Helpers + + static bool IsInColumn(BindableObject child, int column) + { + int childColumn = GetColumn(child); + int span = GetColumnSpan(child); + return childColumn <= column && column < childColumn + span; + } + + static bool IsInRow(BindableObject child, int row) + { + int childRow = GetRow(child); + int span = GetRowSpan(child); + return childRow <= row && row < childRow + span; + } + + int NumberOfUnsetColumnWidth(BindableObject child) + { + var n = 0; + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index; i < index + span; i++) + if (_columns[i].ActualWidth <= 0) + n++; + return n; + } + + int NumberOfUnsetRowHeight(BindableObject child) + { + var n = 0; + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index; i < index + span; i++) + if (_rows[i].ActualHeight <= 0) + n++; + return n; + } + + double GetAssignedColumnWidth(BindableObject child) + { + var actual = 0d; + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index; i < index + span; i++) + if (_columns[i].ActualWidth >= 0) + actual += _columns[i].ActualWidth; + return actual; + } + + double GetAssignedRowHeight(BindableObject child) + { + var actual = 0d; + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index; i < index + span; i++) + if (_rows[i].ActualHeight >= 0) + actual += _rows[i].ActualHeight; + return actual; + } + + ColumnDefinition GetLastAutoColumn(BindableObject child) + { + int index = GetColumn(child); + int span = GetColumnSpan(child); + for (int i = index + span - 1; i >= index; i--) + if (_columns[i].Width.IsAuto) + return _columns[i]; + return null; + } + + RowDefinition GetLastAutoRow(BindableObject child) + { + int index = GetRow(child); + int span = GetRowSpan(child); + for (int i = index + span - 1; i >= index; i--) + if (_rows[i].Height.IsAuto) + return _rows[i]; + return null; + } + + double GetUnassignedHeight(double heightRequest) + { + double assigned = (_rows.Count - 1) * RowSpacing; + for (var i = 0; i < _rows.Count; i++) + { + double actual = _rows[i].ActualHeight; + if (actual >= 0) + assigned += actual; + } + return heightRequest - assigned; + } + + double GetUnassignedWidth(double widthRequest) + { + double assigned = (_columns.Count - 1) * ColumnSpacing; + for (var i = 0; i < _columns.Count; i++) + { + double actual = _columns[i].ActualWidth; + if (actual >= 0) + assigned += actual; + } + return widthRequest - assigned; + } + + #endregion + } +}
\ No newline at end of file |