|
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Primitives;
namespace Microsoft.Maui.Layouts
{
public class GridLayoutManager : LayoutManager
{
GridStructure? _gridStructure;
public GridLayoutManager(IGridLayout layout) : base(layout)
{
Grid = layout;
}
public IGridLayout Grid { get; }
public override Size Measure(double widthConstraint, double heightConstraint)
{
_gridStructure = new GridStructure(Grid, widthConstraint, heightConstraint);
var measuredWidth = _gridStructure.MeasuredGridWidth();
var measuredHeight = _gridStructure.MeasuredGridHeight();
return new Size(measuredWidth, measuredHeight);
}
public override Size ArrangeChildren(Rect bounds)
{
_gridStructure ??= new GridStructure(Grid, bounds.Width, bounds.Height);
_gridStructure.PrepareForArrange(bounds.Size);
foreach (var view in Grid)
{
if (view.Visibility == Visibility.Collapsed)
{
continue;
}
var cell = _gridStructure.GetCellBoundsFor(view, bounds.Left, bounds.Top);
view.Arrange(cell);
}
var actual = new Size(_gridStructure.MeasuredGridWidth(), _gridStructure.MeasuredGridHeight());
return actual.AdjustForFill(bounds, Grid);
}
class GridStructure
{
readonly IGridLayout _grid;
readonly double _gridWidthConstraint;
readonly double _gridHeightConstraint;
readonly double _explicitGridHeight;
readonly double _explicitGridWidth;
readonly double _gridMaxHeight;
readonly double _gridMinHeight;
readonly double _gridMaxWidth;
readonly double _gridMinWidth;
readonly bool _isStarWidthPrecomputable;
readonly bool _isStarHeightPrecomputable;
readonly double _rowStarCount;
readonly double _columnStarCount;
Definition[] _rows { get; }
Definition[] _columns { get; }
readonly IView[] _childrenToLayOut;
Cell[] _cells { get; }
readonly Thickness _padding;
readonly double _rowSpacing;
readonly double _columnSpacing;
readonly Dictionary<SpanKey, Span> _spans = new();
public GridStructure(IGridLayout grid, double widthConstraint, double heightConstraint)
{
_grid = grid;
_explicitGridHeight = _grid.Height;
_explicitGridWidth = _grid.Width;
_gridWidthConstraint = Dimension.IsExplicitSet(_explicitGridWidth) ? _explicitGridWidth : widthConstraint;
_gridHeightConstraint = Dimension.IsExplicitSet(_explicitGridHeight) ? _explicitGridHeight : heightConstraint;
_gridMaxHeight = _grid.MaximumHeight;
_gridMinHeight = _grid.MinimumHeight;
_gridMaxWidth = _grid.MaximumWidth;
_gridMinWidth = _grid.MinimumWidth;
// Cache these GridLayout properties so we don't have to keep looking them up via _grid
// (Property access via _grid may have performance implications for some SDKs.)
_padding = grid.Padding;
_columnSpacing = grid.ColumnSpacing;
_rowSpacing = grid.RowSpacing;
_rows = GridStructure.InitializeRows(grid.RowDefinitions);
_columns = GridStructure.InitializeColumns(grid.ColumnDefinitions);
_rowStarCount = CountStars(_rows);
_columnStarCount = CountStars(_columns);
// Determine whether we can figure out the * values before doing any measurements
// i.e., are there any Auto values in the relevant dimensions, and are we working
// with fixed constraints on the Grid itself
_isStarHeightPrecomputable = IsStarHeightPrecomputable();
_isStarWidthPrecomputable = IsStarWidthPrecomputable();
// We could work out the _childrenToLayOut array (with the Collapsed items filtered out) with a Linq 1-liner
// but doing it the hard way means we don't allocate extra enumerators, especially if we're in the
// happy path where _none_ of the children are Collapsed.
var gridChildCount = _grid.Count;
_childrenToLayOut = new IView[gridChildCount];
int currentChild = 0;
for (int n = 0; n < gridChildCount; n++)
{
if (_grid[n].Visibility != Visibility.Collapsed)
{
_childrenToLayOut[currentChild] = _grid[n];
currentChild += 1;
}
}
if (currentChild < gridChildCount)
{
Array.Resize(ref _childrenToLayOut, currentChild);
}
// We'll ignore any collapsed child views during layout
_cells = new Cell[_childrenToLayOut.Length];
InitializeCells();
MeasureCells();
}
static Definition[] Implied()
{
return
[
new Definition(GridLength.Star)
];
}
static Definition[] InitializeRows(IReadOnlyList<IGridRowDefinition> rowDefinitions)
{
int count = rowDefinitions.Count;
if (count == 0)
{
// Since no rows are specified, we'll create an implied row 0
return Implied();
}
var rows = new Definition[count];
for (int n = 0; n < count; n++)
{
var definition = rowDefinitions[n];
rows[n] = new Definition(definition.Height);
}
return rows;
}
static Definition[] InitializeColumns(IReadOnlyList<IGridColumnDefinition> columnDefinitions)
{
int count = columnDefinitions.Count;
if (count == 0)
{
// Since no columns are specified, we'll create an implied column 0
return Implied();
}
var definitions = new Definition[count];
for (int n = 0; n < count; n++)
{
var definition = columnDefinitions[n];
definitions[n] = new Definition(definition.Width);
}
return definitions;
}
void InitializeCells()
{
for (int n = 0; n < _childrenToLayOut.Length; n++)
{
var view = _childrenToLayOut[n];
if (view.Visibility == Visibility.Collapsed)
{
continue;
}
var column = _grid.GetColumn(view).Clamp(0, _columns.Length - 1);
var columnSpan = _grid.GetColumnSpan(view).Clamp(1, _columns.Length - column);
var columnGridLengthType = GridLengthType.None;
for (int columnIndex = column; columnIndex < column + columnSpan; columnIndex++)
{
columnGridLengthType |= ToGridLengthType(_columns[columnIndex].GridLength.GridUnitType);
}
var row = _grid.GetRow(view).Clamp(0, _rows.Length - 1);
var rowSpan = _grid.GetRowSpan(view).Clamp(1, _rows.Length - row);
var rowGridLengthType = GridLengthType.None;
for (int rowIndex = row; rowIndex < row + rowSpan; rowIndex++)
{
rowGridLengthType |= ToGridLengthType(_rows[rowIndex].GridLength.GridUnitType);
}
var cell = new Cell(n, row, column, rowSpan, columnSpan, columnGridLengthType, rowGridLengthType);
// We may have enough info at this point to determine some of the measurement constraints for cells
DetermineCellMeasureWidth(cell);
DetermineCellMeasureHeight(cell);
_cells[n] = cell;
}
if (_isStarWidthPrecomputable)
{
// We have enough information to go ahead and work out the sizes of the * columns
ResolveStarColumns(_gridWidthConstraint);
}
if (_isStarHeightPrecomputable)
{
// We have enough information to go ahead and work out the sizes of the * rows
ResolveStarRows(_gridHeightConstraint);
}
}
public Rect GetCellBoundsFor(IView view, double xOffset, double yOffset)
{
var firstColumn = _grid.GetColumn(view).Clamp(0, _columns.Length - 1);
var columnSpan = _grid.GetColumnSpan(view).Clamp(1, _columns.Length - firstColumn);
var lastColumn = firstColumn + columnSpan;
var firstRow = _grid.GetRow(view).Clamp(0, _rows.Length - 1);
var rowSpan = _grid.GetRowSpan(view).Clamp(1, _rows.Length - firstRow);
var lastRow = firstRow + rowSpan;
double top = TopEdgeOfRow(firstRow);
double left = LeftEdgeOfColumn(firstColumn);
double width = 0;
double height = 0;
for (int n = firstColumn; n < lastColumn; n++)
{
width += _columns[n].Size;
}
for (int n = firstRow; n < lastRow; n++)
{
height += _rows[n].Size;
}
// Account for any space between spanned rows/columns
width += (columnSpan - 1) * _columnSpacing;
height += (rowSpan - 1) * _rowSpacing;
return new Rect(left + xOffset, top + yOffset, width, height);
}
double GridHeight()
{
return SumDefinitions(_rows, _rowSpacing) + _padding.VerticalThickness;
}
double GridWidth()
{
return SumDefinitions(_columns, _columnSpacing) + _padding.HorizontalThickness;
}
double GridMinimumHeight()
{
return SumDefinitions(_rows, _rowSpacing, minimize: true) + _padding.VerticalThickness;
}
double GridMinimumWidth()
{
return SumDefinitions(_columns, _columnSpacing, minimize: true) + _padding.HorizontalThickness;
}
public double MeasuredGridHeight()
{
var height = Dimension.IsExplicitSet(_explicitGridHeight) ? _explicitGridHeight : GridMinimumHeight();
if (_gridMaxHeight >= 0 && height > _gridMaxHeight)
{
height = _gridMaxHeight;
}
if (_gridMinHeight >= 0 && height < _gridMinHeight)
{
height = _gridMinHeight;
}
return height;
}
public double MeasuredGridWidth()
{
var width = Dimension.IsExplicitSet(_explicitGridWidth) ? _explicitGridWidth : GridMinimumWidth();
if (_gridMaxWidth >= 0 && width > _gridMaxWidth)
{
width = _gridMaxWidth;
}
if (_gridMinWidth >= 0 && width < _gridMinWidth)
{
width = _gridMinWidth;
}
return width;
}
static double SumDefinitions(Definition[] definitions, double spacing, bool minimize = false)
{
double sum = 0;
for (int n = 0; n < definitions.Length; n++)
{
sum += minimize ? definitions[n].MinimumSize : definitions[n].Size;
if (n > 0)
{
sum += spacing;
}
}
return sum;
}
void MeasureCells()
{
FirstMeasurePass();
if (!_isStarWidthPrecomputable)
{
// We didn't have enough info to work out the * column sizes earlier, but now that
// we've measured the Auto dimensions, we can finalize those values
ResolveStarColumns(_gridWidthConstraint);
}
if (!_isStarHeightPrecomputable)
{
// We didn't have enough info to work out the * row sizes earlier, but now that
// we've measured the Auto dimensions, we can finalize those values
ResolveStarRows(_gridHeightConstraint);
}
SecondMeasurePass();
ResolveSpans();
// Compress the star values to their minimums for measurement
MinimizeStarsForMeasurement();
}
Size MeasureCell(Cell cell, double width, double height)
{
var child = _childrenToLayOut[cell.ViewIndex];
var result = child.Measure(width, height);
return result;
}
void FirstMeasurePass()
{
for (int n = 0; n < _cells.Length; n++)
{
var cell = _cells[n];
bool treatCellHeightAsAuto = TreatCellHeightAsAuto(cell);
bool treatCellWidthAsAuto = TreatCellWidthAsAuto(cell);
if (double.IsNaN(cell.MeasureHeight) || double.IsNaN(cell.MeasureWidth))
{
// We still have some unknown measure constraints (* rows/columns that need to have
// the Auto measurements settled before we can measure them). So mark this cell for the
// second pass, to be done once we know the constraints.
cell.NeedsSecondPass = true;
continue;
}
var measure = MeasureCell(cell, cell.MeasureWidth, cell.MeasureHeight);
if (treatCellWidthAsAuto)
{
if (cell.ColumnSpan == 1)
{
_columns[cell.Column].Update(measure.Width);
}
else
{
TrackSpan(new Span(cell.Column, cell.ColumnSpan, true, measure.Width));
}
}
if (treatCellHeightAsAuto)
{
if (cell.RowSpan == 1)
{
_rows[cell.Row].Update(measure.Height);
}
else
{
TrackSpan(new Span(cell.Row, cell.RowSpan, false, measure.Height));
}
}
}
}
void SecondMeasurePass()
{
foreach (var cell in _cells)
{
if (!cell.NeedsSecondPass)
{
continue;
}
double width = 0;
double height = 0;
if (double.IsInfinity(cell.MeasureHeight))
{
height = double.PositiveInfinity;
}
else
{
for (int n = cell.Row; n < cell.Row + cell.RowSpan; n++)
{
height += _rows[n].Size;
}
}
if (double.IsInfinity(cell.MeasureWidth))
{
width = double.PositiveInfinity;
}
else
{
for (int n = cell.Column; n < cell.Column + cell.ColumnSpan; n++)
{
width += _columns[n].Size;
}
}
if (width == 0 || height == 0)
{
continue;
}
var measure = MeasureCell(cell, width, height);
if (cell.IsColumnSpanStar && cell.ColumnSpan > 1)
{
TrackSpan(new Span(cell.Column, cell.ColumnSpan, true, measure.Width));
}
else if (cell.ColumnSpan == 1)
{
if (TreatCellWidthAsAuto(cell))
{
_columns[cell.Column].Update(measure.Width);
}
}
if (cell.IsRowSpanStar && cell.RowSpan > 1)
{
TrackSpan(new Span(cell.Row, cell.RowSpan, false, measure.Height));
}
else if (cell.RowSpan == 1)
{
if (TreatCellHeightAsAuto(cell))
{
_rows[cell.Row].Update(measure.Height);
}
}
}
}
void TrackSpan(Span span)
{
if (_spans.TryGetValue(span.Key, out Span? otherSpan))
{
// This span may replace an equivalent but smaller span
if (span.Requested > otherSpan.Requested)
{
_spans[span.Key] = span;
}
}
else
{
_spans[span.Key] = span;
}
}
void ResolveSpans()
{
foreach (var span in _spans.Values)
{
if (span.IsColumn)
{
ResolveSpan(_columns, span.Start, span.Length, _columnSpacing, span.Requested);
}
else
{
ResolveSpan(_rows, span.Start, span.Length, _rowSpacing, span.Requested);
}
}
}
static void ResolveSpan(Definition[] definitions, int start, int length, double spacing, double requestedSize)
{
double currentSize = 0;
var end = start + length;
// Determine how large the spanned area currently is
for (int n = start; n < end; n++)
{
currentSize += definitions[n].Size;
if (n > start)
{
currentSize += spacing;
}
}
if (requestedSize <= currentSize)
{
// If our request fits in the current size, we're good
return;
}
// Figure out how much more space we need in this span
double required = requestedSize - currentSize;
// And how many parts of the span to distribute that space over
int autoCount = 0;
for (int n = start; n < end; n++)
{
if (definitions[n].IsAuto)
{
autoCount += 1;
}
else if (definitions[n].IsStar)
{
// Ah, part of this span is a Star; that means it doesn't count
// for sizing the Auto parts of the span at all. We can just cut out now.
return;
}
}
double distribution = required / autoCount;
// And distribute that over the rows/columns in the span
for (int n = start; n < end; n++)
{
if (definitions[n].IsAuto)
{
definitions[n].Size += distribution;
}
}
}
double LeftEdgeOfColumn(int column)
{
double left = _padding.Left;
for (int n = 0; n < column; n++)
{
left += _columns[n].Size;
left += _columnSpacing;
}
return left;
}
double TopEdgeOfRow(int row)
{
double top = _padding.Top;
for (int n = 0; n < row; n++)
{
top += _rows[n].Size;
top += _rowSpacing;
}
return top;
}
void ResolveStars(Definition[] defs, double availableSpace, Func<Cell, bool> cellCheck, Func<Size, double> dimension, double starCount)
{
Debug.Assert(starCount > 0, "The caller of ResolveStars has already checked that there are star values to resolve.");
if (availableSpace <= 0)
{
// This can happen if an Auto-measured part of a span is larger than the Grid's constraint;
// There's a negative amount of space left for the Star values. Just bail, the
// Star values are already zero and they can't get any smaller.
return;
}
double starSize = 0;
if (double.IsInfinity(availableSpace))
{
// If the available space we're measuring is infinite, then the 'star' doesn't really mean anything
// (each one would be infinite). So instead we'll use the size of the actual view in the star row/column.
// This means that an empty star row/column goes to zero if the available space is infinite.
foreach (var cell in _cells)
{
if (cellCheck(cell)) // Check whether this cell should count toward the type of star value we're measuring
{
// Update the star size if the view in this cell is bigger
starSize = Math.Max(starSize, dimension(_childrenToLayOut[cell.ViewIndex].DesiredSize));
}
}
}
else
{
// If we have a finite space, we can divvy it up among the full star weight
starSize = availableSpace / starCount;
}
foreach (var definition in defs)
{
if (definition.IsStar)
{
// Give the star row/column the appropriate portion of the space based on its weight
definition.Size = starSize * definition.GridLength.Value;
}
}
}
void ResolveStarColumns(double widthConstraint)
{
if (_columnStarCount == 0)
{
return;
}
var availableSpace = widthConstraint - GridWidth();
static bool cellCheck(Cell cell) => cell.IsColumnSpanStar;
static double getDimension(Size size) => size.Width;
ResolveStars(_columns, availableSpace, cellCheck, getDimension, _columnStarCount);
foreach (var cell in _cells)
{
if (double.IsNaN(cell.MeasureWidth))
{
UpdateKnownMeasureWidth(cell);
}
}
}
void ResolveStarRows(double heightConstraint)
{
if (_rowStarCount == 0)
{
return;
}
var availableSpace = heightConstraint - GridHeight();
static bool cellCheck(Cell cell) => cell.IsRowSpanStar;
static double getDimension(Size size) => size.Height;
ResolveStars(_rows, availableSpace, cellCheck, getDimension, _rowStarCount);
foreach (var cell in _cells)
{
if (double.IsNaN(cell.MeasureHeight))
{
UpdateKnownMeasureHeight(cell);
}
}
}
void MinimizeStarsForMeasurement()
{
MinimizeStarRows();
MinimizeStarColumns();
}
void MinimizeStarRows()
{
// Iterate over the cells; determine if they span any star rows
// and find the mininum sizes for those star rows to contain the cell contents
for (int n = 0; n < _cells.Length; n++)
{
var cell = _cells[n];
if (!cell.IsRowSpanStar)
{
// This cell doesn't span any star rows, nothing to do
continue;
}
var start = cell.Row;
var end = start + cell.RowSpan;
var cellRequiredHeight = Math.Min(_gridHeightConstraint, _childrenToLayOut[cell.ViewIndex].DesiredSize.Height);
DetermineMinimumStarSizesInSpan(cellRequiredHeight, _rows, start, end);
}
}
void MinimizeStarColumns()
{
// Iterate over the cells; determine if they span any star columns
// and find the mininum sizes for those star columns to contain the cell contents
for (int n = 0; n < _cells.Length; n++)
{
var cell = _cells[n];
if (!cell.IsColumnSpanStar)
{
// This cell doesn't span any star columns, nothing to do
continue;
}
var start = cell.Column;
var end = start + cell.ColumnSpan;
var cellRequiredWidth = Math.Min(_gridWidthConstraint, _childrenToLayOut[cell.ViewIndex].DesiredSize.Width);
DetermineMinimumStarSizesInSpan(cellRequiredWidth, _columns, start, end);
}
}
static void DetermineMinimumStarSizesInSpan(double spaceNeeded, Definition[] definitions, int start, int end)
{
// Remove the parts of spaceNeeded which are already covered by explicit and auto columns in the span
for (int n = start; n < end; n++)
{
if (definitions[n].IsAbsolute || definitions[n].IsAuto)
{
spaceNeeded -= definitions[n].Size;
}
}
// Determine how much space the star sizes in the span are already requesting
// (because of other overlapping cells, parts of this span may have already been processed)
double spaceAvailable = 0;
int starsInSpan = 0;
for (int n = start; n < end; n++)
{
if (definitions[n].IsStar)
{
starsInSpan += 1;
spaceAvailable += definitions[n].MinimumSize;
}
}
// If previous inflations from other cells haven't given us enough room,
// distribute the amount of space we still need evenly across the stars in the span
if (spaceAvailable < spaceNeeded)
{
var toAdd = (spaceNeeded - spaceAvailable) / starsInSpan;
for (int n = start; n < end; n++)
{
if (definitions[n].IsStar)
{
definitions[n].MinimumSize += toAdd;
if (definitions[n].MinimumSize > definitions[n].Size)
{
definitions[n].MinimumSize = definitions[n].Size;
}
}
}
}
}
public void PrepareForArrange(Size targetSize)
{
// Minimize all the star values (if any); then we can expand them to the target size if necessary
MinimizeStars(_rows);
MinimizeStars(_columns);
bool expandStarRows = _rowStarCount > 0
&& (Dimension.IsExplicitSet(_explicitGridHeight)
|| _grid.VerticalLayoutAlignment == LayoutAlignment.Fill);
bool expandStarColumns = _columnStarCount > 0
&& (Dimension.IsExplicitSet(_explicitGridWidth)
|| _grid.HorizontalLayoutAlignment == LayoutAlignment.Fill);
if (expandStarRows)
{
// If the grid is constrained vertically, we will need to limit the upper size of * rows;
// if not, they can be whatever size makes sense for the content
var limitStarRowHeights = !double.IsInfinity(_gridHeightConstraint);
ExpandStarDefinitions(_rows, targetSize.Height - _padding.VerticalThickness, GridMinimumHeight() - _padding.VerticalThickness,
_rowSpacing, _rowStarCount, limitStarRowHeights);
}
if (expandStarColumns)
{
// If the grid is constrained horizontally, we will need to limit the upper size of * columns;
// if not, they can be whatever size makes sense for the content
var limitStarRowWidths = !double.IsInfinity(_gridWidthConstraint);
ExpandStarDefinitions(_columns, targetSize.Width - _padding.HorizontalThickness, GridMinimumWidth() - _padding.HorizontalThickness,
_columnSpacing, _columnStarCount, limitStarRowWidths);
}
}
static void MinimizeStars(Definition[] defs)
{
foreach (var def in defs)
{
if (def.IsStar)
{
def.Size = def.MinimumSize;
}
}
}
static void ExpandStarDefinitions(Definition[] definitions, double targetSize, double currentSize, double spacing, double starCount, bool limitStarSizes)
{
// Figure out what the star value should be at this size
var starSize = ComputeStarSizeForTarget(targetSize, definitions, spacing, starCount);
if (limitStarSizes)
{
// Before we expand the star values, we need to ensure that the size and minimum size of
// each star row/column do not exceed the value for starSize at the arranged size
// (which may be smaller than the measured size)
EnsureSizeLimit(definitions, starSize);
}
// Inflate the stars so that we fill up the space at this size
ExpandStars(targetSize, currentSize, definitions, starSize, starCount);
}
static void EnsureSizeLimit(Definition[] definitions, double starSize)
{
for (int n = 0; n < definitions.Length; n++)
{
var def = definitions[n];
if (!def.IsStar)
{
continue;
}
var maxSize = starSize * def.GridLength.Value;
def.Size = Math.Min(maxSize, def.Size);
def.MinimumSize = Math.Min(maxSize, def.MinimumSize);
}
}
static double ComputeStarSizeForTarget(double targetSize, Definition[] defs, double spacing, double starCount)
{
var sum = SumDefinitions(defs, spacing, true);
// Remove all the star defintions from the current size
foreach (var def in defs)
{
if (def.IsStar)
{
sum -= def.MinimumSize;
}
}
return (targetSize - sum) / starCount;
}
static void ExpandStars(double targetSize, double currentSize, Definition[] defs, double targetStarSize, double starCount)
{
Debug.Assert(starCount > 0, "Assume that the caller has already checked for the existence of star rows/columns before using this.");
var availableSpace = targetSize - currentSize;
if (availableSpace <= 0)
{
return;
}
// Figure out which is the biggest star definition in this dimension (absolute value and star scale)
var maxCurrentSize = 0.0;
var maxCurrentStarSize = 0.0;
foreach (var definition in defs)
{
if (definition.IsStar)
{
double definitionSize = definition.MinimumSize;
maxCurrentSize = Math.Max(maxCurrentSize, definitionSize);
maxCurrentStarSize = Math.Max(maxCurrentStarSize, definitionSize / definition.GridLength.Value);
}
}
// The targetStarSize is the size that star values would have to have in order for all
// the star rows/columns to fit in the targetSize.
if (maxCurrentStarSize <= targetStarSize)
{
// If the biggest current star size we have in the definitions is less than the
// targetStarSize, that means we have enough room to expand all of our star rows/columns
// to their full size.
foreach (var definition in defs)
{
if (definition.IsStar)
{
definition.Size = targetStarSize * definition.GridLength.Value;
}
}
return;
}
// We don't have enough room for all the star rows/columns to expand to their full size.
// But we still need to fill up the rest of the space, so we'll expand them proportionally.
// Work out the total difference in size between the full star size targets and the
// minimum sizes for all definitions where the minimum is less than the full star size
// target; we'll need that later to distribute available space.
double totaldiff = 0;
foreach (var definition in defs)
{
if (definition.IsStar)
{
double fullTargetSize = targetStarSize * definition.GridLength.Value;
if (definition.MinimumSize < fullTargetSize)
totaldiff += fullTargetSize - definition.MinimumSize;
}
}
foreach (var definition in defs)
{
if (definition.IsStar)
{
// Skip the star rows/columns whose minimums are at or higher than the target sizes
double fullTargetSize = targetStarSize * definition.GridLength.Value;
if (definition.MinimumSize < fullTargetSize)
{
// Figure out how small this definition is relative to the total difference,
// and use that to determine how much of the available space this definition gets
// The goal is to have the definitions expand proportionate to their deficit from
// their full target sizes
var scale = (fullTargetSize - definition.MinimumSize) / totaldiff;
var portion = scale * availableSpace;
definition.Size = definition.MinimumSize + portion;
}
}
}
}
static bool AnyAuto(Definition[] definitions)
{
foreach (var definition in definitions)
{
if (definition.IsAuto)
{
return true;
}
}
return false;
}
bool IsStarWidthPrecomputable()
{
if (double.IsInfinity(_gridWidthConstraint))
{
return false;
}
return !AnyAuto(_columns);
}
bool IsStarHeightPrecomputable()
{
if (double.IsInfinity(_gridHeightConstraint))
{
return false;
}
return !AnyAuto(_rows);
}
void UpdateKnownMeasureWidth(Cell cell)
{
double measureWidth = 0;
for (int column = cell.Column; column < cell.Column + cell.ColumnSpan; column++)
{
measureWidth += _columns[column].Size;
if (column > cell.Column)
{
measureWidth += _columnSpacing;
}
}
cell.MeasureWidth = measureWidth;
}
void UpdateKnownMeasureHeight(Cell cell)
{
double measureHeight = 0;
for (int row = cell.Row; row < cell.Row + cell.RowSpan; row++)
{
measureHeight += _rows[row].Size;
if (row > cell.Row)
{
measureHeight += _rowSpacing;
}
}
cell.MeasureHeight = measureHeight;
}
void DetermineCellMeasureWidth(Cell cell)
{
if (cell.ColumnGridLengthType == GridLengthType.Absolute)
{
UpdateKnownMeasureWidth(cell);
}
else if (TreatCellWidthAsAuto(cell))
{
cell.MeasureWidth = double.PositiveInfinity;
}
// For all other situations, we'll have to wait until we've measured the Auto columns
}
void DetermineCellMeasureHeight(Cell cell)
{
if (cell.RowGridLengthType == GridLengthType.Absolute)
{
UpdateKnownMeasureHeight(cell);
}
else if (TreatCellHeightAsAuto(cell))
{
cell.MeasureHeight = double.PositiveInfinity;
}
// For all other situations, we'll have to wait until we've measured the Auto rows
}
bool TreatCellWidthAsAuto(Cell cell)
{
if (cell.IsColumnSpanStar)
{
// Because the Grid isn't horizontally constrained, we treat * columns as Auto
return double.IsInfinity(_gridWidthConstraint);
}
return cell.IsColumnSpanAuto;
}
bool TreatCellHeightAsAuto(Cell cell)
{
if (cell.IsRowSpanStar)
{
// Because the Grid isn't vertically constrained, we treat * rows as Auto
return double.IsInfinity(_gridHeightConstraint);
}
return cell.IsRowSpanAuto;
}
static double CountStars(Definition[] definitions)
{
// Count up the total weight of star values (e.g., "*, 3*, *" == 5)
var starCount = 0.0;
foreach (var definition in definitions)
{
if (definition.IsStar)
{
starCount += definition.GridLength.Value;
}
}
return starCount;
}
}
// Dictionary key for tracking a Span
record SpanKey(int Start, int Length, bool IsColumn);
class Span
{
public int Start { get; }
public int Length { get; }
public bool IsColumn { get; }
public double Requested { get; }
public SpanKey Key { get; }
public Span(int start, int length, bool isColumn, double requestedLength)
{
Start = start;
Length = length;
IsColumn = isColumn;
Requested = requestedLength;
Key = new SpanKey(Start, Length, IsColumn);
}
}
class Cell
{
public int ViewIndex { get; }
public int Row { get; }
public int Column { get; }
public int RowSpan { get; }
public int ColumnSpan { get; }
public double MeasureWidth { get; set; } = double.NaN;
public double MeasureHeight { get; set; } = double.NaN;
public bool NeedsSecondPass { get; set; }
/// <summary>
/// A combination of all the measurement types in the columns this cell spans
/// </summary>
public GridLengthType ColumnGridLengthType { get; }
/// <summary>
/// A combination of all the measurement types in the rows this cell spans
/// </summary>
public GridLengthType RowGridLengthType { get; }
public Cell(int viewIndex, int row, int column, int rowSpan, int columnSpan,
GridLengthType columnGridLengthType, GridLengthType rowGridLengthType)
{
ViewIndex = viewIndex;
Row = row;
Column = column;
RowSpan = rowSpan;
ColumnSpan = columnSpan;
ColumnGridLengthType = columnGridLengthType;
RowGridLengthType = rowGridLengthType;
}
public bool IsColumnSpanAuto => HasFlag(ColumnGridLengthType, GridLengthType.Auto);
public bool IsRowSpanAuto => HasFlag(RowGridLengthType, GridLengthType.Auto);
public bool IsColumnSpanStar => HasFlag(ColumnGridLengthType, GridLengthType.Star);
public bool IsRowSpanStar => HasFlag(RowGridLengthType, GridLengthType.Star);
static bool HasFlag(GridLengthType a, GridLengthType b)
{
// Avoiding Enum.HasFlag here for performance reasons; we don't need the type check
return (a & b) == b;
}
}
[Flags]
enum GridLengthType
{
None = 0,
Absolute = 1,
Auto = 2,
Star = 4
}
static GridLengthType ToGridLengthType(GridUnitType gridUnitType)
{
return gridUnitType switch
{
GridUnitType.Absolute => GridLengthType.Absolute,
GridUnitType.Star => GridLengthType.Star,
GridUnitType.Auto => GridLengthType.Auto,
_ => GridLengthType.None,
};
}
class Definition
{
readonly GridLength _gridLength;
private double _size;
/// <summary>
/// The current size of this definition
/// </summary>
public double Size
{
get => _size;
set
{
_size = value;
if (!IsStar)
{
MinimumSize = value;
}
}
}
/// <summary>
/// The minimum size of this definition
/// For absolute and auto definitions, this is the same as Size
/// For star definitions, this is the minimum size which can contain the contents of the row/column
/// </summary>
public double MinimumSize { get; set; }
public void Update(double size)
{
if (size > Size)
{
Size = size;
}
}
public bool IsAuto => _gridLength.IsAuto;
public bool IsStar => _gridLength.IsStar;
public bool IsAbsolute => _gridLength.IsAbsolute;
public GridLength GridLength => _gridLength;
public Definition(GridLength gridLength)
{
if (gridLength.IsAbsolute)
{
Size = gridLength.Value;
}
_gridLength = gridLength;
}
}
}
}
|