using System.Collections.Generic; using System.Linq; using Unity.UIWidgets.foundation; using Unity.UIWidgets.gestures; using Unity.UIWidgets.painting; using Unity.UIWidgets.ui; using UnityEngine; using Canvas = Unity.UIWidgets.ui.Canvas; using Rect = Unity.UIWidgets.ui.Rect; namespace Unity.UIWidgets.rendering { public enum TableCellVerticalAlignment { top, middle, bottom, baseline, fill } public class TableCellParentData : BoxParentData { public TableCellVerticalAlignment? verticalAlignment; public int x; public int y; public override string ToString() { return base.ToString() + "; " + (this.verticalAlignment == null ? "default vertical alignment" : this.verticalAlignment.ToString()); } } public abstract class TableColumnWidth { protected TableColumnWidth() { } public abstract float minIntrinsicWidth(List cells, float containerWidth); public abstract float maxIntrinsicWidth(List cells, float containerWidth); public virtual float? flex(List cells) { return null; } public override string ToString() { return this.GetType().ToString(); } } public class IntrinsicColumnWidth : TableColumnWidth { public IntrinsicColumnWidth( float? flex = null) { this._flex = flex; } public override float minIntrinsicWidth(List cells, float containerWidth) { float result = 0.0f; foreach (RenderBox cell in cells) { result = Mathf.Max(result, cell.getMinIntrinsicWidth(float.PositiveInfinity)); } return result; } public override float maxIntrinsicWidth(List cells, float containerWidth) { float result = 0.0f; foreach (RenderBox cell in cells) { result = Mathf.Max(result, cell.getMaxIntrinsicWidth(float.PositiveInfinity)); } return result; } public override float? flex(List cells) { return this._flex; } readonly float? _flex; public override string ToString() { return $"${this.GetType()}(flex: {this._flex})"; } } public class FixedColumnWidth : TableColumnWidth { public FixedColumnWidth(float? value = null) { D.assert(value != null); this.value = value.Value; } public readonly float value; public override float minIntrinsicWidth(List cells, float containerWidth) { return this.value; } public override float maxIntrinsicWidth(List cells, float containerWidth) { return this.value; } public override string ToString() { return $"{this.GetType()}({this.value})"; } } public class FractionColumnWidth : TableColumnWidth { public FractionColumnWidth(float? value = null) { D.assert(value != null); this.value = value.Value; } public readonly float value; public override float minIntrinsicWidth(List cells, float containerWidth) { if (!containerWidth.isFinite()) { return 0.0f; } return this.value * containerWidth; } public override float maxIntrinsicWidth(List cells, float containerWidth) { if (!containerWidth.isFinite()) { return 0.0f; } return this.value * containerWidth; } public override string ToString() { return $"{this.GetType()}({this.value})"; } } public class FlexColumnWidth : TableColumnWidth { public FlexColumnWidth(float value = 1.0f) { this.value = value; } public readonly float value; public override float minIntrinsicWidth(List cells, float containerWidth) { return 0.0f; } public override float maxIntrinsicWidth(List cells, float containerWidth) { return 0.0f; } public override float? flex(List cells) { return this.value; } public override string ToString() { return $"{this.GetType()}({this.value})"; } } public class MaxColumnWidth : TableColumnWidth { public MaxColumnWidth( TableColumnWidth a, TableColumnWidth b) { this.a = a; this.b = b; } public readonly TableColumnWidth a; public readonly TableColumnWidth b; public override float minIntrinsicWidth(List cells, float containerWidth) { return Mathf.Max( this.a.minIntrinsicWidth(cells, containerWidth), this.b.minIntrinsicWidth(cells, containerWidth) ); } public override float maxIntrinsicWidth(List cells, float containerWidth) { return Mathf.Max( this.a.maxIntrinsicWidth(cells, containerWidth), this.b.maxIntrinsicWidth(cells, containerWidth) ); } public override float? flex(List cells) { float? aFlex = this.a.flex(cells); if (aFlex == null) { return this.b.flex(cells); } float? bFlex = this.b.flex(cells); if (bFlex == null) { return null; } return Mathf.Max(aFlex.Value, bFlex.Value); } public override string ToString() { return $"{this.GetType()}({this.a}, {this.b})"; } } public class MinColumnWidth : TableColumnWidth { public MinColumnWidth( TableColumnWidth a, TableColumnWidth b) { this.a = a; this.b = b; } public readonly TableColumnWidth a; public readonly TableColumnWidth b; public override float minIntrinsicWidth(List cells, float containerWidth) { return Mathf.Min( this.a.minIntrinsicWidth(cells, containerWidth), this.b.minIntrinsicWidth(cells, containerWidth) ); } public override float maxIntrinsicWidth(List cells, float containerWidth) { return Mathf.Min( this.a.maxIntrinsicWidth(cells, containerWidth), this.b.maxIntrinsicWidth(cells, containerWidth) ); } public override float? flex(List cells) { float? aFlex = this.a.flex(cells); if (aFlex == null) { return this.b.flex(cells); } float? bFlex = this.b.flex(cells); if (bFlex == null) { return null; } return Mathf.Min(aFlex.Value, bFlex.Value); } public override string ToString() { return $"{this.GetType()}({this.a}, {this.b})"; } } public class RenderTable : RenderBox { public RenderTable( int? columns = null, int? rows = null, Dictionary columnWidths = null, TableColumnWidth defaultColumnWidth = null, TableBorder border = null, List rowDecorations = null, ImageConfiguration configuration = null, TableCellVerticalAlignment defaultVerticalAlignment = TableCellVerticalAlignment.top, TextBaseline? textBaseline = null, List> children = null ) { defaultColumnWidth = defaultColumnWidth ?? new FlexColumnWidth(1.0f); configuration = configuration ?? ImageConfiguration.empty; D.assert(columns == null || columns >= 0); D.assert(rows == null || rows >= 0); D.assert(rows == null || children == null); this._columns = columns ?? (children != null && children.isNotEmpty() ? children.First().Count : 0); this._rows = rows ?? 0; this._children = new List(); for (int i = 0; i < this._columns * this._rows; i++) { this._children.Add(null); } this._columnWidths = columnWidths ?? new Dictionary(); this._defaultColumnWidth = defaultColumnWidth; this._border = border; this.rowDecorations = rowDecorations; this._configuration = configuration; this._defaultVerticalAlignment = defaultVerticalAlignment; this._textBaseline = textBaseline; if (children != null) { foreach (List row in children) { this.addRow(row); } } } List _children = new List(); public int columns { get { return this._columns; } set { D.assert(value >= 0); if (value == this.columns) { return; } int oldColumns = this.columns; List oldChildren = this._children; this._columns = value; this._children = new List(); for (int i = 0; i < this.columns * this.rows; i++) { this._children.Add(null); } int columnsToCopy = Mathf.Min(this.columns, oldColumns); for (int y = 0; y < this.rows; y++) { for (int x = 0; x < columnsToCopy; x++) { this._children[x + y * this.columns] = oldChildren[x + y * oldColumns]; } } if (oldColumns > this.columns) { for (int y = 0; y < this.rows; y++) { for (int x = this.columns; x < oldColumns; x++) { int xy = x + y * oldColumns; if (oldChildren[xy] != null) { this.dropChild(oldChildren[xy]); } } } } this.markNeedsLayout(); } } int _columns; public int rows { get { return this._rows; } set { D.assert(value >= 0); if (value == this.rows) { return; } if (this._rows > value) { for (int xy = this.columns * value; xy < this._children.Count; xy++) { if (this._children[xy] != null) { this.dropChild(this._children[xy]); } } } this._rows = value; if (this._children.Count > this.columns * this.rows) { this._children.RemoveRange(this.columns * this.rows, this._children.Count - this.columns * this.rows); } else if (this._children.Count < this.columns * this.rows) { while (this._children.Count < this.columns * this.rows) { this._children.Add(null); } } D.assert(this._children.Count == this.columns * this.rows); this.markNeedsLayout(); } } int _rows; public Dictionary columnWidths { get { return this._columnWidths; } set { value = value ?? new Dictionary(); if (this._columnWidths == value) { return; } this._columnWidths = value; this.markNeedsLayout(); } } Dictionary _columnWidths; public void setColumnWidth(int column, TableColumnWidth value) { if (this._columnWidths.getOrDefault(column) == value) { return; } this._columnWidths[column] = value; this.markNeedsLayout(); ; } public TableColumnWidth defaultColumnWidth { get { return this._defaultColumnWidth; } set { D.assert(value != null); if (this.defaultColumnWidth == value) { return; } this._defaultColumnWidth = value; this.markNeedsLayout(); } } TableColumnWidth _defaultColumnWidth; public TableBorder border { get { return this._border; } set { if (this.border == value) { return; } this._border = value; this.markNeedsPaint(); } } TableBorder _border; public List rowDecorations { get { return this._rowDecorations ?? new List(); } set { if (this._rowDecorations == value) { return; } this._rowDecorations = value; if (this._rowDecorationPainters != null) { foreach (BoxPainter painter in this._rowDecorationPainters) { painter?.Dispose(); } } if (this._rowDecorations != null) { this._rowDecorationPainters = new List(); for (int i = 0; i < this._rowDecorations.Count; i++) { this._rowDecorationPainters.Add(null); } } else { this._rowDecorationPainters = null; } } } List _rowDecorations; List _rowDecorationPainters; public ImageConfiguration configuration { get { return this._configuration; } set { D.assert(value != null); if (value == this._configuration) { return; } this._configuration = value; this.markNeedsPaint(); } } ImageConfiguration _configuration; public TableCellVerticalAlignment defaultVerticalAlignment { get { return this._defaultVerticalAlignment; } set { if (this._defaultVerticalAlignment == value) { return; } this._defaultVerticalAlignment = value; this.markNeedsLayout(); } } TableCellVerticalAlignment _defaultVerticalAlignment; public TextBaseline? textBaseline { get { return this._textBaseline; } set { if (this._textBaseline == value) { return; } this._textBaseline = value; this.markNeedsLayout(); } } TextBaseline? _textBaseline; public override void setupParentData(RenderObject child) { if (!(child.parentData is TableCellParentData)) { child.parentData = new TableCellParentData(); } } public void setFlatChildren(int columns, List cells) { if (cells == this._children && columns == this._columns) { return; } D.assert(columns >= 0); if (columns == 0 || cells.isEmpty()) { D.assert(cells == null || cells.isEmpty()); this._columns = columns; if (this._children.isEmpty()) { D.assert(this._rows == 0); return; } foreach (RenderBox oldChild in this._children) { if (oldChild != null) { this.dropChild(oldChild); } } this._rows = 0; this._children.Clear(); this.markNeedsLayout(); return; } D.assert(cells != null); D.assert(cells.Count % columns == 0); HashSet lostChildren = new HashSet(); int y, x; for (y = 0; y < this._rows; y++) { for (x = 0; x < this._columns; x++) { int xyOld = x + y * this._columns; int xyNew = x + y * columns; if (this._children[xyOld] != null && (x >= columns || xyNew >= cells.Count || this._children[xyOld] != cells[xyNew])) { lostChildren.Add(this._children[xyOld]); } } } y = 0; while (y * columns < cells.Count) { for (x = 0; x < columns; x++) { int xyNew = x + y * columns; int xyOld = x + y * this._columns; if (cells[xyNew] != null && (x >= this._columns || y >= this._rows || this._children[xyOld] != cells[xyNew])) { if (lostChildren.Contains(cells[xyNew])) { lostChildren.Remove(cells[xyNew]); } else { this.adoptChild(cells[xyNew]); } } } y += 1; } foreach (RenderBox child in lostChildren) { this.dropChild(child); } this._columns = columns; this._rows = cells.Count / columns; this._children = cells; D.assert(this._children.Count == this.rows * this.columns); this.markNeedsLayout(); } void setChildren(List> cells) { if (cells == null) { this.setFlatChildren(0, null); return; } foreach (RenderBox oldChild in this._children) { if (oldChild != null) { this.dropChild(oldChild); } } this._children.Clear(); this._columns = cells.isNotEmpty() ? cells.First().Count : 0; this._rows = 0; foreach (List row in cells) { this.addRow(row); } D.assert(this._children.Count == this.rows * this.columns); } void addRow(List cells) { D.assert(cells.Count == this.columns); D.assert(this._children.Count == this.rows * this.columns); this._rows += 1; this._children.AddRange(cells); foreach (RenderBox cell in cells) { if (cell != null) { this.adoptChild(cell); } } this.markNeedsLayout(); } public void setChild(int x, int y, RenderBox value) { D.assert(x >= 0 && x < this.columns && y >= 0 && y < this.rows); D.assert(this._children.Count == this.rows * this.columns); int xy = x + y * this.columns; RenderBox oldChild = this._children[xy]; if (oldChild == value) { return; } if (oldChild != null) { this.dropChild(oldChild); } this._children[xy] = value; if (value != null) { this.adoptChild(value); } } public override void attach(object owner) { base.attach(owner); foreach (RenderBox child in this._children) { child?.attach(owner); } } public override void detach() { base.detach(); if (this._rowDecorationPainters != null) { foreach (BoxPainter painter in this._rowDecorationPainters) { painter?.Dispose(); } this._rowDecorationPainters = null; } foreach (RenderBox child in this._children) { child?.detach(); } } public override void visitChildren(RenderObjectVisitor visitor) { D.assert(this._children.Count == this.rows * this.columns); foreach (RenderBox child in this._children) { if (child != null) { visitor(child); } } } protected override float computeMinIntrinsicWidth(float height) { D.assert(this._children.Count == this.rows * this.columns); float totalMinWidth = 0.0f; for (int x = 0; x < this.columns; x++) { TableColumnWidth columnWidth = this._columnWidths.getOrDefault(x) ?? this.defaultColumnWidth; List columnCells = this.column(x); totalMinWidth += columnWidth.minIntrinsicWidth(columnCells, float.PositiveInfinity); } return totalMinWidth; } protected override float computeMaxIntrinsicWidth(float height) { D.assert(this._children.Count == this.rows * this.columns); float totalMaxWidth = 0.0f; for (int x = 0; x < this.columns; x++) { TableColumnWidth columnWidth = this._columnWidths.getOrDefault(x) ?? this.defaultColumnWidth; List columnCells = this.column(x); totalMaxWidth += columnWidth.maxIntrinsicWidth(columnCells, float.PositiveInfinity); } return totalMaxWidth; } protected override float computeMinIntrinsicHeight(float width) { D.assert(this._children.Count == this.rows * this.columns); List widths = this._computeColumnWidths(BoxConstraints.tightForFinite(width: width)); float rowTop = 0.0f; for (int y = 0; y < this.rows; y++) { float rowHeight = 0.0f; for (int x = 0; x < this.columns; x++) { int xy = x + y * this.columns; RenderBox child = this._children[xy]; if (child != null) { rowHeight = Mathf.Max(rowHeight, child.getMaxIntrinsicHeight(widths[x])); } } rowTop += rowHeight; } return rowTop; } protected override float computeMaxIntrinsicHeight(float width) { return this.computeMinIntrinsicHeight(width); } float? _baselineDistance; protected override float? computeDistanceToActualBaseline(TextBaseline baseline) { return this._baselineDistance; } List column(int x) { List ret = new List(); for (int y = 0; y < this.rows; y++) { int xy = x + y * this.columns; RenderBox child = this._children[xy]; if (child != null) { ret.Add(child); } } return ret; } List row(int y) { List ret = new List(); int start = y * this.columns; int end = (y + 1) * this.columns; for (int xy = start; xy < end; xy++) { RenderBox child = this._children[xy]; if (child != null) { ret.Add(child); } } return ret; } List _computeColumnWidths(BoxConstraints constraints) { D.assert(constraints != null); D.assert(this._children.Count == this.rows * this.columns); List widths = new List(); List minWidths = new List(); List flexes = new List(); for (int i = 0; i < this.columns; i++) { widths.Add(0.0f); minWidths.Add(0.0f); flexes.Add(null); } float tableWidth = 0.0f; float? unflexedTableWidth = 0.0f; float totalFlex = 0.0f; for (int x = 0; x < this.columns; x++) { TableColumnWidth columnWidth = this._columnWidths.getOrDefault(x) ?? this.defaultColumnWidth; List columnCells = this.column(x); float maxIntrinsicWidth = columnWidth.maxIntrinsicWidth(columnCells, constraints.maxWidth); D.assert(maxIntrinsicWidth.isFinite()); D.assert(maxIntrinsicWidth >= 0.0f); widths[x] = maxIntrinsicWidth; tableWidth += maxIntrinsicWidth; float minIntrinsicWidth = columnWidth.minIntrinsicWidth(columnCells, constraints.maxWidth); D.assert(minIntrinsicWidth.isFinite()); D.assert(minIntrinsicWidth >= 0.0f); minWidths[x] = minIntrinsicWidth; D.assert(maxIntrinsicWidth >= minIntrinsicWidth); float? flex = columnWidth.flex(columnCells); if (flex != null) { D.assert(flex.Value.isFinite()); D.assert(flex.Value > 0.0f); flexes[x] = flex; totalFlex += flex.Value; } else { unflexedTableWidth += maxIntrinsicWidth; } } float maxWidthConstraint = constraints.maxWidth; float minWidthConstraint = constraints.minWidth; if (totalFlex > 0.0f) { float targetWidth = 0.0f; if (maxWidthConstraint.isFinite()) { targetWidth = maxWidthConstraint; } else { targetWidth = minWidthConstraint; } if (tableWidth < targetWidth) { float remainingWidth = targetWidth - unflexedTableWidth.Value; D.assert(remainingWidth.isFinite()); D.assert(remainingWidth >= 0.0f); for (int x = 0; x < this.columns; x++) { if (flexes[x] != null) { float flexedWidth = remainingWidth * flexes[x].Value / totalFlex; D.assert(flexedWidth.isFinite()); D.assert(flexedWidth >= 0.0f); if (widths[x] < flexedWidth) { float delta = flexedWidth - widths[x]; tableWidth += delta; widths[x] = flexedWidth; } } } D.assert(tableWidth >= targetWidth); } } else if (tableWidth < minWidthConstraint) { float delta = (minWidthConstraint - tableWidth) / this.columns; for (int x = 0; x < this.columns; x++) { widths[x] += delta; } tableWidth = minWidthConstraint; } D.assert(() => { unflexedTableWidth = null; return true; }); if (tableWidth > maxWidthConstraint) { float deficit = tableWidth - maxWidthConstraint; int availableColumns = this.columns; float minimumDeficit = 0.00000001f; while (deficit > 0.0f && totalFlex > minimumDeficit) { float newTotalFlex = 0.0f; for (int x = 0; x < this.columns; x++) { if (flexes[x] != null) { float newWidth = widths[x] - deficit * flexes[x].Value / totalFlex; D.assert(newWidth.isFinite()); if (newWidth <= minWidths[x]) { deficit -= widths[x] - minWidths[x]; widths[x] = minWidths[x]; flexes[x] = null; availableColumns -= 1; } else { deficit -= widths[x] - newWidth; widths[x] = newWidth; newTotalFlex += flexes[x].Value; } D.assert(widths[x] >= 0.0f); } } totalFlex = newTotalFlex; } if (deficit > 0.0f) { do { float delta = deficit / availableColumns; int newAvailableColumns = 0; for (int x = 0; x < this.columns; x++) { float availableDelta = widths[x] - minWidths[x]; if (availableDelta > 0.0f) { if (availableDelta <= delta) { deficit -= widths[x] - minWidths[x]; widths[x] = minWidths[x]; } else { deficit -= availableDelta; widths[x] -= availableDelta; newAvailableColumns += 1; } } } availableColumns = newAvailableColumns; } while (deficit > 0.0f && availableColumns > 0); } } return widths; } readonly List _rowTops = new List(); List _columnLefts; Rect getRowBox(int row) { D.assert(row >= 0); D.assert(row < this.rows); return Rect.fromLTRB(0.0f, this._rowTops[row], this.size.width, this._rowTops[row + 1]); } protected override void performLayout() { int rows = this.rows; int columns = this.columns; D.assert(this._children.Count == rows * columns); if (rows * columns == 0) { this.size = this.constraints.constrain(new Size(0.0f, 0.0f)); return; } List widths = this._computeColumnWidths(this.constraints); List positions = new List(); float tableWidth = 0.0f; positions.Add(0.0f); for (int x = 1; x < columns; x++) { positions.Add(positions[x - 1] + widths[x - 1]); } this._columnLefts = positions; tableWidth = positions.Last() + widths.Last(); this._rowTops.Clear(); this._baselineDistance = null; float rowTop = 0.0f; for (int y = 0; y < rows; y++) { this._rowTops.Add(rowTop); float rowHeight = 0.0f; bool haveBaseline = false; float beforeBaselineDistance = 0.0f; float afterBaselineDistance = 0.0f; List baselines = new List(); for (int i = 0; i < columns; i++) { baselines.Add(null); } for (int x = 0; x < columns; x++) { int xy = x + y * columns; RenderBox child = this._children[xy]; if (child != null) { TableCellParentData childParentData = (TableCellParentData) child.parentData; D.assert(childParentData != null); childParentData.x = x; childParentData.y = y; switch (childParentData.verticalAlignment ?? this.defaultVerticalAlignment) { case TableCellVerticalAlignment.baseline: { D.assert(this.textBaseline != null); child.layout(BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true); float? childBaseline = child.getDistanceToBaseline(this.textBaseline.Value, onlyReal: true); if (childBaseline != null) { beforeBaselineDistance = Mathf.Max(beforeBaselineDistance, childBaseline.Value); afterBaselineDistance = Mathf.Max(afterBaselineDistance, child.size.height - childBaseline.Value); baselines[x] = childBaseline.Value; haveBaseline = true; } else { rowHeight = Mathf.Max(rowHeight, child.size.height); childParentData.offset = new Offset(positions[x], rowTop); } break; } case TableCellVerticalAlignment.top: case TableCellVerticalAlignment.middle: case TableCellVerticalAlignment.bottom: { child.layout(BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true); rowHeight = Mathf.Max(rowHeight, child.size.height); break; } case TableCellVerticalAlignment.fill: { break; } } } } if (haveBaseline) { if (y == 0) { this._baselineDistance = beforeBaselineDistance; } rowHeight = Mathf.Max(rowHeight, beforeBaselineDistance + afterBaselineDistance); } for (int x = 0; x < columns; x++) { int xy = x + y * columns; RenderBox child = this._children[xy]; if (child != null) { TableCellParentData childParentData = (TableCellParentData) child.parentData; switch (childParentData.verticalAlignment ?? this.defaultVerticalAlignment) { case TableCellVerticalAlignment.baseline: { if (baselines[x] != null) { childParentData.offset = new Offset(positions[x], rowTop + beforeBaselineDistance - baselines[x].Value); } break; } case TableCellVerticalAlignment.top: { childParentData.offset = new Offset(positions[x], rowTop); break; } case TableCellVerticalAlignment.middle: { childParentData.offset = new Offset(positions[x], rowTop + (rowHeight - child.size.height) / 2.0f); break; } case TableCellVerticalAlignment.bottom: { childParentData.offset = new Offset(positions[x], rowTop + rowHeight - child.size.height); break; } case TableCellVerticalAlignment.fill: { child.layout(BoxConstraints.tightFor(width: widths[x], height: rowHeight)); childParentData.offset = new Offset(positions[x], rowTop); break; } } } } rowTop += rowHeight; } this._rowTops.Add(rowTop); this.size = this.constraints.constrain(new Size(tableWidth, rowTop)); D.assert(this._rowTops.Count == rows + 1); } protected override bool hitTestChildren(HitTestResult result, Offset position = null) { D.assert(this._children.Count == this.rows * this.columns); for (int index = this._children.Count - 1; index >= 0; index--) { RenderBox child = this._children[index]; if (child != null) { BoxParentData childParentData = (BoxParentData) child.parentData; if (child.hitTest(result, position: position - childParentData.offset)) { return true; } } } return false; } public override void paint(PaintingContext context, Offset offset) { D.assert(this._children.Count == this.rows * this.columns); if (this.rows * this.columns == 0) { if (this.border != null) { Rect borderRect = Rect.fromLTWH(offset.dx, offset.dy, this.size.width, 0.0f); this.border.paint(context.canvas, borderRect, rows: new List(), columns: new List()); } return; } D.assert(this._rowTops.Count == this.rows + 1); if (this._rowDecorations != null) { Canvas canvas = context.canvas; for (int y = 0; y < this.rows; y++) { if (this._rowDecorations.Count <= y) { break; } if (this._rowDecorations[y] != null) { this._rowDecorationPainters[y] = this._rowDecorationPainters[y] ?? this._rowDecorations[y].createBoxPainter(this.markNeedsPaint); this._rowDecorationPainters[y].paint( canvas, new Offset(offset.dx, offset.dy + this._rowTops[y]), this.configuration.copyWith( size: new Size(this.size.width, this._rowTops[y + 1] - this._rowTops[y]) ) ); } } } for (int index = 0; index < this._children.Count; index++) { RenderBox child = this._children[index]; if (child != null) { BoxParentData childParentData = (BoxParentData) child.parentData; context.paintChild(child, childParentData.offset + offset); } } D.assert(this._rows == this._rowTops.Count - 1); D.assert(this._columns == this._columnLefts.Count); if (this.border != null) { Rect borderRect = Rect.fromLTWH(offset.dx, offset.dy, this.size.width, this._rowTops.Last()); List rows = this._rowTops.GetRange(1, this._rowTops.Count - 2); List columns = this._columnLefts.GetRange(1, this._columnLefts.Count - 1); this.border.paint(context.canvas, borderRect, rows: rows, columns: columns); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("border", this.border, defaultValue: null)); properties.add(new DiagnosticsProperty>("specified column widths", this._columnWidths, level: this._columnWidths.isEmpty() ? DiagnosticLevel.hidden : DiagnosticLevel.info)); properties.add(new DiagnosticsProperty("default column width", this.defaultColumnWidth)); properties.add(new MessageProperty("table size", $"{this.columns}*{this.rows}")); properties.add(new EnumerableProperty("column offsets", this._columnLefts, ifNull: "unknown")); properties.add(new EnumerableProperty("row offsets", this._rowTops, ifNull: "unknown")); } public override List debugDescribeChildren() { if (this._children.isEmpty()) { return new List {DiagnosticsNode.message("table is empty")}; } List children = new List(); for (int y = 0; y < this.rows; y++) { for (int x = 0; x < this.columns; x++) { int xy = x + y * this.columns; RenderBox child = this._children[xy]; string name = $"child ({x}, {y})"; if (child != null) { children.Add(child.toDiagnosticsNode(name: name)); } else { children.Add(new DiagnosticsProperty(name, null, ifNull: "is null", showSeparator: false)); } } } return children; } } }