File: LegacyLayouts\GridCalc.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.Maui.Graphics;
 
namespace Microsoft.Maui.Controls.Compatibility
{
	public partial class Grid
	{
 
#pragma warning disable CS0672 // Member overrides obsolete member
		protected override void LayoutChildren(double x, double y, double width, double height)
#pragma warning restore CS0672 // Member overrides obsolete member
		{
			if (!InternalChildren.Any())
				return;
 
			// grab a snapshot of this grid, fully-measured, so we can do the layout
			var structure = new GridStructure(this, width, height);
 
			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 += structure.Columns[i].ActualWidth;
				double posy = y + r * RowSpacing;
				for (var i = 0; i < r; i++)
					posy += structure.Rows[i].ActualHeight;
 
				double w = structure.Columns[c].ActualWidth;
				for (var i = 1; i < cs; i++)
					w += ColumnSpacing + structure.Columns[c + i].ActualWidth;
				double h = structure.Rows[r].ActualHeight;
				for (var i = 1; i < rs; i++)
					h += RowSpacing + structure.Rows[r + i].ActualHeight;
 
				// in the future we can might maybe optimize by passing the already calculated size request
				LayoutChildIntoBoundingRegion(child, new Rect(posx, posy, w, h));
			}
		}
 
#pragma warning disable CS0672 // Member overrides obsolete member
		protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
#pragma warning restore CS0672 // Member overrides obsolete member
		{
			if (!InternalChildren.Any())
				return new SizeRequest(new Size(0, 0));
 
			// grab a snapshot of this grid, fully-measured, so we can do the layout
			var structure = new GridStructure(this, widthConstraint, heightConstraint, true);
 
			double columnWidthSum = 0;
			double nonStarColumnWidthSum = 0;
			for (var index = 0; index < structure.Columns.Count; index++)
			{
				ColumnDefinition c = structure.Columns[index];
				columnWidthSum += c.ActualWidth;
				if (!c.Width.IsStar)
					nonStarColumnWidthSum += c.ActualWidth;
			}
			double rowHeightSum = 0;
			double nonStarRowHeightSum = 0;
			for (var index = 0; index < structure.Rows.Count; index++)
			{
				RowDefinition r = structure.Rows[index];
				rowHeightSum += r.ActualHeight;
				if (!r.Height.IsStar)
					nonStarRowHeightSum += r.ActualHeight;
			}
 
			var request = new Size(columnWidthSum + (structure.Columns.Count - 1) * ColumnSpacing, rowHeightSum + (structure.Rows.Count - 1) * RowSpacing);
			var minimum = new Size(nonStarColumnWidthSum + (structure.Columns.Count - 1) * ColumnSpacing, nonStarRowHeightSum + (structure.Rows.Count - 1) * RowSpacing);
 
			var result = new SizeRequest(request, minimum);
			return result;
		}
 
		/// <summary>
		/// Creates a snapshot of the Grid row/column structure, optionally with measurement
		/// </summary>
		class GridStructure
		{
			// These aren't strictly necessary; we could just use the auto-properties. But when we drop .NET Standard 1.0
			// support, the public accessors should be made into ReadOnlyList and we'll need these private lists
			List<ColumnDefinition> _columns = new List<ColumnDefinition>();
			List<RowDefinition> _rows = new List<RowDefinition>();
 
			// Technically speaking, these should really be ReadOnlyList, but AsReadOnly isn't available in .NET Standard 1.0
			// So for now we're just going to trust our internal code not to rely on modifying these
			public List<ColumnDefinition> Columns => _columns;
			public List<RowDefinition> Rows => _rows;
 
			public GridStructure(Grid grid)
			{
				EnsureRowsColumnsInitialized(grid);
			}
 
			public GridStructure(Grid grid, double width, double height, bool requestSize = false) : this(grid)
			{
				AssignAbsoluteCells();
 
				CalculateAutoCells(grid, width, height);
 
				var columnSpacing = grid.ColumnSpacing;
				var rowSpacing = grid.RowSpacing;
 
				if (!requestSize)
				{
					ContractAutoColumnsIfNeeded(width, columnSpacing, rowSpacing);
					ContractAutoRowsIfNeeded(height, columnSpacing, rowSpacing);
				}
 
				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(grid, width, height, totalStarsWidth);
					MeasureAndContractStarredRows(grid, width, height, totalStarsHeight);
				}
				else
				{
					CalculateStarCells(width, height, totalStarsWidth, totalStarsHeight, columnSpacing, rowSpacing);
				}
 
				ZeroUnassignedCells();
 
				ExpandLastAutoRowIfNeeded(grid, height, requestSize);
				ExpandLastAutoColumnIfNeeded(grid, width, requestSize);
			}
 
			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(Grid grid, 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 < grid.InternalChildren.Count; index++)
							{
								var child = (View)(grid.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, grid.ColumnSpacing);
								double heightRequest = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height, grid.RowSpacing);
 
								SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins);
								actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - grid.RowSpacing * (GetRowSpan(child) - 1));
								minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - grid.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 < grid.InternalChildren.Count; index++)
							{
								var child = (View)(grid.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, grid.ColumnSpacing);
								double heightRequest = assignedHeight + GetUnassignedHeight(height, grid.RowSpacing);
 
								SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins);
								actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * grid.ColumnSpacing);
								minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * grid.ColumnSpacing);
							}
							if (actualWidth >= 0)
								col.ActualWidth = actualWidth;
							if (minimumWidth >= 0)
								col.MinimumWidth = minimumWidth;
						}
					}
				}
			}
 
			void CalculateStarCells(double width, double height, double totalStarsWidth, double totalStarsHeight, double columnSpacing, double rowSpacing)
			{
				double starColWidth = GetUnassignedWidth(width, columnSpacing) / totalStarsWidth;
				double starRowHeight = GetUnassignedHeight(height, rowSpacing) / 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;
				}
			}
 
			double ComputeColumnWidthSum()
			{
				double columnwWidthSum = 0;
				for (var index = 0; index < _columns.Count; index++)
				{
					ColumnDefinition c = _columns[index];
					columnwWidthSum += Math.Max(0, c.ActualWidth);
				}
 
				return columnwWidthSum;
			}
 
			double ComputeRowHeightSum()
			{
				double rowHeightSum = 0;
				for (var index = 0; index < _rows.Count; index++)
				{
					RowDefinition r = _rows[index];
					rowHeightSum += Math.Max(0, r.ActualHeight);
				}
 
				return rowHeightSum;
			}
 
			Size ComputeCurrentSize(double columnSpacing, double rowSpacing)
			{
				var columnWidthSum = ComputeColumnWidthSum();
				var rowHeightSum = ComputeRowHeightSum();
 
				return new Size(columnWidthSum + (_columns.Count - 1) * columnSpacing, rowHeightSum + (_rows.Count - 1) * rowSpacing);
			}
 
			void ContractAutoColumnsIfNeeded(double targetWidth, double columnSpacing, double rowSpacing)
			{
				var currentSize = ComputeCurrentSize(columnSpacing, rowSpacing);
 
				if (currentSize.Width <= targetWidth)
				{
					return;
				}
 
				double contractionSpace = 0;
				for (var index = 0; index < _columns.Count; index++)
				{
					ColumnDefinition c = _columns[index];
					if (c.Width.IsAuto)
						contractionSpace += c.ActualWidth - c.MinimumWidth;
				}
				if (contractionSpace > 0)
				{
					// contract as much as we can but no more
					double contractionNeeded = Math.Min(contractionSpace, Math.Max(currentSize.Width - targetWidth, 0));
					double contractFactor = contractionNeeded / contractionSpace;
 
					for (var index = 0; index < _columns.Count; index++)
					{
						ColumnDefinition col = _columns[index];
						if (!col.Width.IsAuto)
							continue;
						double availableSpace = col.ActualWidth - Math.Max(col.MinimumWidth, 0);
						double contraction = availableSpace * contractFactor;
						col.ActualWidth -= contraction;
						contractionNeeded -= contraction;
					}
				}
			}
 
			void ContractAutoRowsIfNeeded(double targetHeight, double columnSpacing, double rowSpacing)
			{
				var currentSize = ComputeCurrentSize(columnSpacing, rowSpacing);
 
				if (currentSize.Height <= targetHeight)
				{
					return;
				}
 
				double contractionSpace = 0;
				for (var index = 0; index < _rows.Count; index++)
				{
					RowDefinition r = _rows[index];
					if (r.Height.IsAuto)
						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(currentSize.Height - targetHeight, 0));
				double contractFactor = contractionNeeded / contractionSpace;
				for (var index = 0; index < _rows.Count; index++)
				{
					RowDefinition row = _rows[index];
					if (!row.Height.IsAuto)
						continue;
					double availableSpace = row.ActualHeight - row.MinimumHeight;
					double contraction = availableSpace * contractFactor;
					row.ActualHeight -= contraction;
					contractionNeeded -= contraction;
				}
			}
 
			void ContractStarColumnsIfNeeded(double targetWidth, double columnSpacing, double rowSpacing)
			{
				var request = ComputeCurrentSize(columnSpacing, rowSpacing);
 
				if (request.Width <= targetWidth)
				{
					return;
				}
 
				double starColumnWidth = 0;
				double starColumnMinWidth = 0;
				double contractionSpace = 0;
 
				for (int n = 0; n < _columns.Count; n++)
				{
					var column = _columns[n];
 
					if (!column.Width.IsStar)
					{
						continue;
					}
 
					if (starColumnWidth == 0)
					{
						starColumnWidth = column.ActualWidth;
					}
					else
					{
						starColumnWidth = Math.Min(column.ActualWidth, starColumnWidth);
					}
 
					if (column.MinimumWidth > starColumnMinWidth)
					{
						starColumnMinWidth = column.MinimumWidth;
					}
 
					contractionSpace += column.ActualWidth - column.MinimumWidth;
				}
 
				if (contractionSpace <= 0)
				{
					return;
				}
 
				// contract as much as we can but no more
				double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Width - targetWidth, 0));
				double contractionFactor = contractionNeeded / contractionSpace;
				var delta = contractionFactor >= 1
					? starColumnWidth - starColumnMinWidth
					: contractionFactor * (starColumnWidth - starColumnMinWidth);
 
				for (var index = 0; index < _columns.Count; index++)
				{
					ColumnDefinition column = _columns[index];
					if (!column.Width.IsStar)
					{
						continue;
					}
 
					column.ActualWidth -= delta * column.Width.Value;
				}
			}
 
			void ContractStarRowsIfNeeded(double targetHeight, double columnSpacing, double rowSpacing)
			{
				var request = ComputeCurrentSize(columnSpacing, rowSpacing);
 
				if (request.Height <= targetHeight)
				{
					return;
				}
 
				double starRowHeight = 0;
				double starRowMinHeight = 0;
				double contractionSpace = 0;
 
				for (int n = 0; n < _rows.Count; n++)
				{
					var row = _rows[n];
 
					if (!row.Height.IsStar)
					{
						continue;
					}
 
					if (starRowHeight == 0)
					{
						starRowHeight = row.ActualHeight;
					}
					else
					{
						starRowHeight = Math.Min(row.ActualHeight, starRowHeight);
					}
 
					if (row.MinimumHeight > starRowMinHeight)
					{
						starRowMinHeight = row.MinimumHeight;
					}
 
					contractionSpace += row.ActualHeight - row.MinimumHeight;
				}
 
				if (contractionSpace <= 0)
				{
					return;
				}
 
				double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Height - targetHeight, 0));
				double contractionFactor = contractionNeeded / contractionSpace;
				var delta = contractionFactor >= 1
					? starRowHeight - starRowMinHeight
					: contractionFactor * (starRowHeight - starRowMinHeight);
 
				for (var index = 0; index < _rows.Count; index++)
				{
					RowDefinition row = _rows[index];
					if (!row.Height.IsStar)
					{
						continue;
					}
 
					row.ActualHeight -= delta * row.Height.Value;
				}
			}
 
			void EnsureRowsColumnsInitialized(Grid grid)
			{
				_columns = grid.ColumnDefinitions == null ? new List<ColumnDefinition>() : grid.ColumnDefinitions.ToList();
				_rows = grid.RowDefinitions == null ? new List<RowDefinition>() : grid.RowDefinitions.ToList();
 
				int lastRow = -1;
				for (var index = 0; index < grid.InternalChildren.Count; index++)
				{
					Element w = grid.InternalChildren[index];
					lastRow = Math.Max(lastRow, GetRow(w) + GetRowSpan(w) - 1);
				}
				lastRow = Math.Max(lastRow, grid.RowDefinitions.Count - 1);
 
				int lastCol = -1;
				for (var index = 0; index < grid.InternalChildren.Count; index++)
				{
					Element w = grid.InternalChildren[index];
					lastCol = Math.Max(lastCol, GetColumn(w) + GetColumnSpan(w) - 1);
				}
				lastCol = Math.Max(lastCol, grid.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(Grid grid, double width, bool expandToRequest)
			{
				for (var index = 0; index < grid.InternalChildren.Count; index++)
				{
					Element element = grid.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, grid.ColumnSpacing);
					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) * grid.ColumnSpacing;
					if (deltaWidth > 0)
					{
						col.ActualWidth += deltaWidth;
					}
				}
			}
 
			void ExpandLastAutoRowIfNeeded(Grid grid, double height, bool expandToRequest)
			{
				for (var index = 0; index < grid.InternalChildren.Count; index++)
				{
					Element element = grid.InternalChildren[index];
					var child = (View)element;
					if (!child.IsVisible)
						continue;
 
					RowDefinition row = GetLastAutoRow(child);
					if (row == null)
						continue;
 
					double assignedHeight = GetAssignedRowHeight(child);
					var unassignedHeight = GetUnassignedHeight(height, grid.RowSpacing);
					double h = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + unassignedHeight;
 
					var acw = GetAssignedColumnWidth(child);
 
					SizeRequest sizeRequest = child.Measure(acw, h, MeasureFlags.IncludeMargins);
 
					double requiredHeight = expandToRequest
						? sizeRequest.Request.Height
						: sizeRequest.Request.Height <= h ? sizeRequest.Request.Height : sizeRequest.Minimum.Height;
 
					double deltaHeight = requiredHeight - assignedHeight - (GetRowSpan(child) - 1) * grid.RowSpacing;
					if (deltaHeight > 0)
					{
						row.ActualHeight += deltaHeight;
					}
				}
			}
 
			void MeasureAndContractStarredColumns(Grid grid, double width, double height, double totalStarsWidth)
			{
				double columnSpacing = grid.ColumnSpacing;
				double rowSpacing = grid.RowSpacing;
 
				double starColWidth;
				starColWidth = MeasuredStarredColumns(grid, GetUnassignedWidth(width, columnSpacing), height, totalStarsWidth);
 
				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, columnSpacing) / totalStarsWidth);
				}
 
				for (var index = 0; index < _columns.Count; index++)
				{
					ColumnDefinition col = _columns[index];
					if (col.Width.IsStar)
						col.ActualWidth = col.Width.Value * starColWidth;
				}
 
				ContractStarColumnsIfNeeded(width, columnSpacing, rowSpacing);
			}
 
			void MeasureAndContractStarredRows(Grid grid, double width, double height, double totalStarsHeight)
			{
				double columnSpacing = grid.ColumnSpacing;
				double rowSpacing = grid.RowSpacing;
 
				double starRowHeight;
				starRowHeight = MeasureStarredRows(grid, width, GetUnassignedHeight(height, rowSpacing), totalStarsHeight);
 
				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, rowSpacing) / totalStarsHeight);
				}
 
				for (var index = 0; index < _rows.Count; index++)
				{
					RowDefinition row = _rows[index];
					if (row.Height.IsStar)
						row.ActualHeight = row.Height.Value * starRowHeight;
				}
 
				ContractStarRowsIfNeeded(height, columnSpacing, rowSpacing);
			}
 
			double MeasuredStarredColumns(Grid grid, double widthConstraint, double heightConstraint, double totalStarsWidth)
			{
				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 < grid.InternalChildren.Count; index++)
							{
								var child = (View)(grid.InternalChildren)[index];
								if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1)
									continue;
								double assignedWidth = GetAssignedColumnWidth(child);
 
								// If we already have row height info, use it when measuring
								double assignedHeight = GetAssignedRowHeight(child);
								var hConstraint = assignedHeight > 0 ? assignedHeight : heightConstraint;
 
								SizeRequest sizeRequest = child.Measure(widthConstraint, hConstraint, MeasureFlags.IncludeMargins);
								var columnSpacing = (GetColumnSpan(child) - 1) * grid.ColumnSpacing;
								actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - columnSpacing);
								minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - columnSpacing);
							}
							if (actualWidth >= 0)
								col.ActualWidth = actualWidth;
 
							if (minimumWidth >= 0)
								col.MinimumWidth = minimumWidth;
						}
					}
				}
 
				double starColRequestWidth = 0;
				double starColMinWidth = 0;
				for (var index = 0; index < _columns.Count; index++)
				{
					ColumnDefinition col = _columns[index];
					if (!col.Width.IsStar || col.Width.Value == 0 || col.ActualWidth <= 0)
						continue;
 
					starColRequestWidth = Math.Max(starColRequestWidth, col.ActualWidth / col.Width.Value);
					starColMinWidth = Math.Max(starColMinWidth, col.MinimumWidth / col.Width.Value);
				}
 
				if (starColRequestWidth * totalStarsWidth <= widthConstraint)
				{
					return starColRequestWidth;
				}
 
				return Math.Max(widthConstraint / totalStarsWidth, starColMinWidth);
			}
 
			double MeasureStarredRows(Grid grid, double widthConstraint, double heightConstraint, double totalStarsHeight)
			{
				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 < grid.InternalChildren.Count; index++)
							{
								var child = (View)(grid.InternalChildren)[index];
								if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1)
									continue;
								double assignedHeight = GetAssignedRowHeight(child);
 
								// If we already have column width info, use it when measuring
								double assignedWidth = GetAssignedColumnWidth(child);
								var wConstraint = assignedWidth > 0 ? assignedWidth : widthConstraint;
 
								SizeRequest sizeRequest = child.Measure(wConstraint, heightConstraint, MeasureFlags.IncludeMargins);
								var rowSpacing = (GetRowSpan(child) - 1) * grid.RowSpacing;
								actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - rowSpacing);
								minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - rowSpacing);
							}
							if (actualHeight >= 0)
								row.ActualHeight = actualHeight;
 
							if (minimumHeight >= 0)
								row.MinimumHeight = minimumHeight;
						}
					}
				}
 
				double starRowRequestHeight = 0;
				double starRowMinHeight = 0;
				for (var index = 0; index < _rows.Count; index++)
				{
					RowDefinition row = _rows[index];
					if (!row.Height.IsStar || row.Height.Value == 0 || row.ActualHeight <= 0)
						continue;
 
					starRowRequestHeight = Math.Max(starRowRequestHeight, row.ActualHeight / row.Height.Value);
					starRowMinHeight = Math.Max(starRowMinHeight, row.MinimumHeight / row.Height.Value);
				}
 
				if (starRowRequestHeight * totalStarsHeight <= heightConstraint)
				{
					return starRowRequestHeight;
				}
 
				return Math.Max(heightConstraint / totalStarsHeight, starRowMinHeight);
			}
 
			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 rowSpacing)
			{
				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 columnSpacing)
			{
				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
		}
	}
}