Commit 6af9e6c9 authored by Hixie's avatar Hixie

RenderTable

parent 8cbeb2e9
......@@ -44,6 +44,7 @@ export 'src/rendering/rotated_box.dart';
export 'src/rendering/semantics.dart';
export 'src/rendering/shifted_box.dart';
export 'src/rendering/stack.dart';
export 'src/rendering/table.dart';
export 'src/rendering/view.dart';
export 'src/rendering/viewport.dart';
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'dart:math' as math;
import 'box.dart';
import 'object.dart';
/// Parent data used by [RenderTable] for its children.
class TableCellParentData extends BoxParentData {
TableCellVerticalAlignment verticalAlignment;
@override
String toString() => '${super.toString()}; $verticalAlignment';
}
/// Base class to describe how wide a column in a [RenderTable] should be.
abstract class TableColumnWidth {
const TableColumnWidth();
double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth);
double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth);
double flex(Iterable<RenderBox> cells) => null;
@override
String toString() => '$runtimeType';
}
/// Sizes the column according to the intrinsic dimensions of all the
/// cells in that column.
///
/// This is a very expensive way to size a column.
class IntrinsicColumnWidth extends TableColumnWidth {
const IntrinsicColumnWidth();
@override
double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
double result = 0.0;
for (RenderBox cell in cells)
result = math.max(result, cell.getMinIntrinsicWidth(const BoxConstraints()));
return result;
}
@override
double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
double result = 0.0;
for (RenderBox cell in cells)
result = math.max(result, cell.getMaxIntrinsicWidth(const BoxConstraints()));
return result;
}
}
/// Sizes the column to a specific number of pixels.
///
/// This is the cheapest way to size a column.
class FixedColumnWidth extends TableColumnWidth {
const FixedColumnWidth(this.value);
final double value;
@override
double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
return value;
}
@override
double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
return value;
}
@override
String toString() => '$runtimeType($value)';
}
/// Sizes the column to a fraction of the table's constraints' maxWidth.
///
/// This is a cheap way to size a column.
class FractionColumnWidth extends TableColumnWidth {
const FractionColumnWidth(this.value);
final double value;
@override
double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
if (!containerWidth.isFinite)
return 0.0;
return value * containerWidth;
}
@override
double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
if (!containerWidth.isFinite)
return 0.0;
return value * containerWidth;
}
@override
String toString() => '$runtimeType($value)';
}
/// Sizes the column by taking a part of the remaining space once all
/// the other columns have been laid out.
///
/// For example, if two columns have FlexColumnWidth(), then half the
/// space will go to one and half the space will go to the other.
///
/// This is a cheap way to size a column.
class FlexColumnWidth extends TableColumnWidth {
const FlexColumnWidth([this.value = 1.0]);
final double value;
@override
double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
return 0.0;
}
@override
double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
return 0.0;
}
@override
double flex(Iterable<RenderBox> cells) {
return value;
}
@override
String toString() => '$runtimeType($value)';
}
/// Sizes the column such that it is the size that is the maximum of
/// two column width specifications.
///
/// For example, to have a column be 10% of the container width or
/// 100px, whichever is bigger, you could use:
///
/// const MaxColumnWidth(const FixedColumnWidth(100.0), FractionColumnWidth(0.1))
///
/// Both specifications are evaluated, so if either specification is
/// expensive, so is this.
class MaxColumnWidth extends TableColumnWidth {
const MaxColumnWidth(this.a, this.b);
final TableColumnWidth a;
final TableColumnWidth b;
@override
double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
return math.max(
a.minIntrinsicWidth(cells, containerWidth),
b.minIntrinsicWidth(cells, containerWidth)
);
}
@override
double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
return math.max(
a.maxIntrinsicWidth(cells, containerWidth),
b.maxIntrinsicWidth(cells, containerWidth)
);
}
@override
double flex(Iterable<RenderBox> cells) {
double aFlex = a.flex(cells);
if (aFlex == null)
return b.flex(cells);
return math.max(aFlex, b.flex(cells));
}
@override
String toString() => '$runtimeType($a, $b)';
}
/// Sizes the column such that it is the size that is the minimum of
/// two column width specifications.
///
/// For example, to have a column be 10% of the container width but
/// never bigger than 100px, you could use:
///
/// const MinColumnWidth(const FixedColumnWidth(100.0), FractionColumnWidth(0.1))
///
/// Both specifications are evaluated, so if either specification is
/// expensive, so is this.
class MinColumnWidth extends TableColumnWidth {
const MinColumnWidth(this.a, this.b); // at most as big as a or b
final TableColumnWidth a;
final TableColumnWidth b;
@override
double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
return math.min(
a.minIntrinsicWidth(cells, containerWidth),
b.minIntrinsicWidth(cells, containerWidth)
);
}
@override
double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
return math.min(
a.maxIntrinsicWidth(cells, containerWidth),
b.maxIntrinsicWidth(cells, containerWidth)
);
}
@override
double flex(Iterable<RenderBox> cells) {
double aFlex = a.flex(cells);
if (aFlex == null)
return b.flex(cells);
return math.min(aFlex, b.flex(cells));
}
@override
String toString() => '$runtimeType($a, $b)';
}
/// Border specification for [RenderTable].
///
/// This is like [Border], with the addition of two sides: the inner
/// horizontal borders and the inner vertical borders.
class TableBorder extends Border {
const TableBorder({
BorderSide top: BorderSide.none,
BorderSide right: BorderSide.none,
BorderSide bottom: BorderSide.none,
BorderSide left: BorderSide.none,
this.horizontalInside: BorderSide.none,
this.verticalInside: BorderSide.none
}) : super(
top: top,
right: right,
bottom: bottom,
left: left
);
factory TableBorder.all({
Color color: const Color(0xFF000000),
double width: 1.0
}) {
final BorderSide side = new BorderSide(color: color, width: width);
return new TableBorder(top: side, right: side, bottom: side, left: side, horizontalInside: side, verticalInside: side);
}
factory TableBorder.symmetric({
BorderSide inside: BorderSide.none,
BorderSide outside: BorderSide.none
}) {
return new TableBorder(
top: outside,
right: outside,
bottom: outside,
left: outside,
horizontalInside: inside,
verticalInside: inside
);
}
final BorderSide horizontalInside;
final BorderSide verticalInside;
@override
TableBorder scale(double t) {
return new TableBorder(
top: top.copyWith(width: t * top.width),
right: right.copyWith(width: t * right.width),
bottom: bottom.copyWith(width: t * bottom.width),
left: left.copyWith(width: t * left.width),
horizontalInside: horizontalInside.copyWith(width: t * horizontalInside.width),
verticalInside: verticalInside.copyWith(width: t * verticalInside.width)
);
}
static TableBorder lerp(TableBorder a, TableBorder b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return b.scale(t);
if (b == null)
return a.scale(1.0 - t);
return new TableBorder(
top: BorderSide.lerp(a.top, b.top, t),
right: BorderSide.lerp(a.right, b.right, t),
bottom: BorderSide.lerp(a.bottom, b.bottom, t),
left: BorderSide.lerp(a.left, b.left, t),
horizontalInside: BorderSide.lerp(a.horizontalInside, b.horizontalInside, t),
verticalInside: BorderSide.lerp(a.verticalInside, b.verticalInside, t)
);
}
@override
bool operator ==(dynamic other) {
if (super != other)
return false;
final TableBorder typedOther = other;
return horizontalInside == typedOther.horizontalInside &&
verticalInside == typedOther.verticalInside;
}
@override
int get hashCode => hashValues(super.hashCode, horizontalInside, verticalInside);
@override
String toString() => 'TableBorder($top, $right, $bottom, $left, $horizontalInside, $verticalInside)';
}
/// Vertical alignment options for cells in [RenderTable] objects.
///
/// This is specified using [TableCellParentData] objects on the
/// [RenderObject.parentData] of the children of the [RenderTable].
enum TableCellVerticalAlignment {
/// Cells with this alignment are placed with their top at the top of the row.
top,
/// Cells with this alignment are vertically centered in the row.
middle,
/// Cells with this alignment are placed with their bottom at the bottom of the row.
bottom,
/// Cells with this alignment are aligned such that they all share the same
/// baseline. Cells with no baseline are top-aligned instead. The baseline
/// used is specified by [RenderTable.baseline]. It is not valid to use the
/// baseline value if [RenderTable.baseline] is not specified.
baseline,
/// Cells with this alignment are sized to be as tall as the row, then made to fit the row.
/// If all the cells have this alignment, then the row will have zero height.
fill
}
/// A table where the columns and rows are sized to fit the contents of the cells.
class RenderTable extends RenderBox {
RenderTable({
int columns,
int rows,
Map<int, TableColumnWidth> columnWidths,
TableColumnWidth defaultColumnWidth: const FlexColumnWidth(1.0),
TableBorder border,
TableCellVerticalAlignment defaultVerticalAlignment: TableCellVerticalAlignment.top,
TextBaseline textBaseline,
List<List<RenderBox>> children
}) {
assert(columns == null || columns >= 0);
assert(rows == null || rows >= 0);
assert(rows == null || children == null);
assert(defaultColumnWidth != null);
_columns = columns ?? (children != null && children.length > 0 ? children.first.length : 0);
_rows = rows ?? 0;
_children = new List<RenderBox>()..length = _columns * _rows;
_columnWidths = columnWidths ?? new HashMap<int, TableColumnWidth>();
_defaultColumnWidth = defaultColumnWidth;
_border = border;
_defaultVerticalAlignment = defaultVerticalAlignment;
_textBaseline = textBaseline;
if (children != null) {
for (List<RenderBox> row in children)
addRow(row);
}
}
// TODO(ianh): Add a 'decoration' field to the children's parent data, to paint on each cell.
// Children are stored in row-major order.
// _children.length must be rows * columns
List<RenderBox> _children = const <RenderBox>[];
int get columns => _columns;
int _columns;
void set columns(int value) {
assert(value != null);
assert(value >= 0);
if (value == columns)
return;
int oldColumns = columns;
List<RenderBox> oldChildren = _children;
_columns = value;
_children = new List<RenderBox>()..length = columns * rows;
int columnsToCopy = math.min(columns, oldColumns);
for (int y = 0; y < rows; y += 1) {
for (int x = 0; x < columnsToCopy; x += 1)
_children[x + y * columns] = oldChildren[x + y * oldColumns];
}
markNeedsLayout();
}
int get rows => _rows;
int _rows;
void set rows(int value) {
assert(value != null);
assert(value >= 0);
if (value == rows)
return;
_rows = value;
_children.length = columns * rows;
markNeedsLayout();
}
Map<int, TableColumnWidth> _columnWidths;
void setColumnWidths(Map<int, TableColumnWidth> value) {
assert(value != null);
if (_columnWidths == value)
return;
_columnWidths = value;
markNeedsLayout();
}
void setColumnWidth(int column, TableColumnWidth value) {
if (_columnWidths[column] == value)
return;
_columnWidths[column] = value;
markNeedsLayout();
}
TableColumnWidth get defaultColumnWidth => _defaultColumnWidth;
TableColumnWidth _defaultColumnWidth;
void set defaultColumnWidth(TableColumnWidth value) {
assert(value != null);
if (defaultColumnWidth == value)
return;
_defaultColumnWidth = value;
markNeedsLayout();
}
TableBorder get border => _border;
TableBorder _border;
void set border(TableBorder value) {
if (border == value)
return;
_border = value;
markNeedsPaint();
}
TableCellVerticalAlignment get defaultVerticalAlignment => _defaultVerticalAlignment;
TableCellVerticalAlignment _defaultVerticalAlignment;
void set defaultVerticalAlignment (TableCellVerticalAlignment value) {
if (_defaultVerticalAlignment == value)
return;
_defaultVerticalAlignment = value;
markNeedsLayout();
}
TextBaseline get textBaseline => _textBaseline;
TextBaseline _textBaseline;
void set textBaseline (TextBaseline value) {
if (_textBaseline == value)
return;
_textBaseline = value;
markNeedsLayout();
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! TableCellParentData)
child.parentData = new TableCellParentData();
}
void setFlatChildren(int columns, List<RenderBox> cells) {
if (cells == _children && columns == _columns)
return;
for (RenderBox oldChild in cells) {
if (oldChild != null)
dropChild(oldChild);
}
assert(columns >= 0);
if (columns == 0) {
assert(cells == null || cells.length == 0);
if (_children.length == 0)
return;
_columns = 0;
_rows = 0;
_children.clear();
markNeedsLayout();
return;
}
assert(cells != null);
assert(cells.length % columns == 0);
_columns = columns;
_rows = cells.length % columns;
_children = cells;
for (RenderBox cell in cells) {
if (cell != null)
adoptChild(cell);
}
markNeedsLayout();
}
void setChildren(List<List<RenderBox>> cells) {
if (cells == null) {
setFlatChildren(0, null);
return;
}
for (RenderBox oldChild in cells) {
if (oldChild != null)
dropChild(oldChild);
}
_columns = cells.length > 0 ? cells.first.length : 0;
_rows = 0;
_children.clear();
for (List<RenderBox> row in cells)
addRow(row);
}
void addRow(List<RenderBox> cells) {
assert(cells.length == columns);
_rows += 1;
_children.addAll(cells);
for (RenderBox cell in cells) {
if (cell != null)
adoptChild(cell);
}
markNeedsLayout();
}
void setChild(int x, int y, RenderBox value) {
assert(x != null);
assert(y != null);
assert(x >= 0 && x < columns && y >= 0 && y < rows);
final int xy = x + y * columns;
RenderBox oldChild = _children[xy];
if (oldChild != null)
dropChild(oldChild);
_children[xy] = value;
if (value != null)
adoptChild(value);
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
for (RenderBox child in _children)
child?.attach(owner);
}
@override
void detach() {
super.detach();
for (RenderBox child in _children)
child?.detach();
}
@override
void visitChildren(RenderObjectVisitor visitor) {
for (RenderBox child in _children) {
if (child != null)
visitor(child);
}
}
@override
double getMinIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
double totalMinWidth = 0.0;
for (int x = 0; x < columns; x += 1) {
TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
Iterable<RenderBox> columnCells = column(x);
totalMinWidth += columnWidth.minIntrinsicWidth(columnCells, constraints.maxWidth);
}
return constraints.constrainWidth(totalMinWidth);
}
@override
double getMaxIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
double totalMaxWidth = 0.0;
for (int x = 0; x < columns; x += 1) {
TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
Iterable<RenderBox> columnCells = column(x);
totalMaxWidth += columnWidth.maxIntrinsicWidth(columnCells, constraints.maxWidth);
}
return constraints.constrainWidth(totalMaxWidth);
}
@override
double getMinIntrinsicHeight(BoxConstraints constraints) {
// winner of the 2016 world's most expensive intrinsic dimension function award
// honorable mention, most likely to improve if taught about memoization award
assert(constraints.debugAssertIsNormalized);
final List<double> widths = computeColumnWidths(constraints);
double rowTop = 0.0;
for (int y = 0; y < rows; y += 1) {
double rowHeight = 0.0;
for (int x = 0; x < columns; x += 1) {
final int xy = x + y * columns;
RenderBox child = _children[xy];
if (child != null)
rowHeight = math.max(rowHeight, child.getMaxIntrinsicHeight(new BoxConstraints.tightFor(width: widths[x])));
}
rowTop += rowHeight;
}
return constraints.constrainHeight(rowTop);
}
@override
double getMaxIntrinsicHeight(BoxConstraints constraints) {
return getMinIntrinsicHeight(constraints);
}
double _baselineDistance;
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
// returns the baseline of the first cell that has a baseline in the first row
assert(!needsLayout);
return _baselineDistance;
}
Iterable<RenderBox> column(int x) sync* {
for (int y = 0; y < rows; y += 1) {
final int xy = x + y * columns;
RenderBox child = _children[xy];
if (child != null)
yield child;
}
}
Iterable<RenderBox> row(int y) sync* {
final int start = y * columns;
final int end = (y + 1) * columns;
for (int xy = start; xy < end; xy += 1) {
RenderBox child = _children[xy];
if (child != null)
yield child;
}
}
List<double> computeColumnWidths(BoxConstraints constraints) {
final List<double> widths = new List<double>(columns);
final List<double> flexes = new List<double>(columns);
double totalMinWidth = 0.0;
double totalMaxWidth = constraints.maxWidth.isFinite ? constraints.maxWidth : 0.0;
double totalFlex = 0.0;
for (int x = 0; x < columns; x += 1) {
TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
Iterable<RenderBox> columnCells = column(x);
double minIntrinsicWidth = columnWidth.minIntrinsicWidth(columnCells, constraints.maxWidth);
widths[x] = minIntrinsicWidth;
totalMinWidth += minIntrinsicWidth;
if (!constraints.maxWidth.isFinite) {
double maxIntrinsicWidth = columnWidth.maxIntrinsicWidth(columnCells, constraints.maxWidth);
assert(minIntrinsicWidth <= maxIntrinsicWidth);
totalMaxWidth += maxIntrinsicWidth;
}
double flex = columnWidth.flex(columnCells);
if (flex != null) {
assert(flex != 0.0);
flexes[x] = flex;
totalFlex += flex;
}
}
assert(!widths.any((double value) => value == null));
// table is going to be the biggest of:
// - the incoming minimum width
// - the sum of the cells' minimum widths
// - the incoming maximum width if it is finite, or else the table's ideal shrink-wrap width
double tableWidth = math.max(constraints.minWidth, math.max(totalMinWidth, totalMaxWidth));
double remainingWidth = tableWidth - totalMinWidth;
if (remainingWidth > 0.0) {
if (totalFlex > 0.0) {
for (int x = 0; x < columns; x += 1) {
if (flexes[x] != null) {
widths[x] += math.max((flexes[x] / totalFlex) * remainingWidth - widths[x], 0.0);
}
}
} else {
for (int x = 0; x < columns; x += 1)
widths[x] += remainingWidth / columns;
}
}
return widths;
}
// cache the table geometry for painting purposes
List<double> _rowTops = <double>[];
List<double> _columnLefts;
@override
void performLayout() {
if (rows * columns == 0) {
size = constraints.constrain(const Size(double.INFINITY, 0.0));
return;
}
final List<double> widths = computeColumnWidths(constraints);
final List<double> positions = new List<double>(columns);
_rowTops.clear();
positions[0] = 0.0;
for (int x = 1; x < columns; x += 1)
positions[x] = positions[x-1] + widths[x-1];
_columnLefts = positions;
assert(!positions.any((double value) => value == null));
_baselineDistance = null;
// then, lay out each row
double rowTop = 0.0;
for (int y = 0; y < rows; y += 1) {
_rowTops.add(rowTop);
double rowHeight = 0.0;
bool haveBaseline = false;
double beforeBaselineDistance = 0.0;
double afterBaselineDistance = 0.0;
List<double> baselines = new List<double>(columns);
for (int x = 0; x < columns; x += 1) {
final int xy = x + y * columns;
RenderBox child = _children[xy];
if (child != null) {
TableCellParentData childParentData = child.parentData;
switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
case TableCellVerticalAlignment.baseline:
assert(textBaseline != null);
child.layout(new BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
double childBaseline = child.getDistanceToBaseline(textBaseline, onlyReal: true);
if (childBaseline != null) {
beforeBaselineDistance = math.max(beforeBaselineDistance, childBaseline);
afterBaselineDistance = math.max(afterBaselineDistance, child.size.height - childBaseline);
baselines[x] = childBaseline;
haveBaseline = true;
} else {
rowHeight = math.max(rowHeight, child.size.height);
childParentData.offset = new Offset(positions[x], rowTop);
}
break;
case TableCellVerticalAlignment.top:
case TableCellVerticalAlignment.middle:
case TableCellVerticalAlignment.bottom:
child.layout(new BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
rowHeight = math.max(rowHeight, child.size.height);
break;
case TableCellVerticalAlignment.fill:
break;
}
}
}
if (haveBaseline) {
if (y == 0)
_baselineDistance = beforeBaselineDistance;
rowHeight = math.max(rowHeight, beforeBaselineDistance + afterBaselineDistance);
}
for (int x = 0; x < columns; x += 1) {
final int xy = x + y * columns;
RenderBox child = _children[xy];
if (child != null) {
final TableCellParentData childParentData = child.parentData;
switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
case TableCellVerticalAlignment.baseline:
if (baselines[x] != null)
childParentData.offset = new Offset(positions[x], rowTop + beforeBaselineDistance - baselines[x]);
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.0);
break;
case TableCellVerticalAlignment.bottom:
childParentData.offset = new Offset(positions[x], rowTop + rowHeight - child.size.height);
break;
case TableCellVerticalAlignment.fill:
child.layout(new BoxConstraints.tightFor(width: widths[x], height: rowHeight));
childParentData.offset = new Offset(positions[x], rowTop);
break;
}
}
}
rowTop += rowHeight;
}
size = constraints.constrain(new Size(positions.last + widths.last, rowTop));
assert(_rowTops.length == rows);
}
@override
bool hitTestChildren(HitTestResult result, { Point position }) {
for (int index = _children.length - 1; index >= 0; index -= 1) {
RenderBox child = _children[index];
if (child != null) {
final BoxParentData childParentData = child.parentData;
Point transformed = new Point(position.x - childParentData.offset.dx,
position.y - childParentData.offset.dy);
if (child.hitTest(result, position: transformed))
return true;
}
}
return false;
}
@override
void paint(PaintingContext context, Offset offset) {
if (rows * columns == 0)
return;
assert(_rowTops.length == rows);
for (int index = 0; index < _children.length; index += 1) {
RenderBox child = _children[index];
if (child != null) {
final BoxParentData childParentData = child.parentData;
context.paintChild(child, childParentData.offset + offset);
}
}
Rect bounds = offset & size;
Canvas canvas = context.canvas;
canvas.saveLayer(bounds, new Paint());
switch (border.verticalInside.style) {
case BorderStyle.solid:
Paint paint = new Paint()
..color = border.verticalInside.color
..strokeWidth = border.verticalInside.width
..style = PaintingStyle.stroke;
Path path = new Path();
for (int x = 1; x < columns; x += 1) {
path.moveTo(bounds.left + _columnLefts[x], bounds.top);
path.lineTo(bounds.left + _columnLefts[x], bounds.bottom);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none: break;
}
switch (border.horizontalInside.style) {
case BorderStyle.solid:
Paint paint = new Paint()
..color = border.horizontalInside.color
..strokeWidth = border.horizontalInside.width
..style = PaintingStyle.stroke;
Path path = new Path();
for (int y = 1; y < rows; y += 1) {
path.moveTo(bounds.left, bounds.top + _rowTops[y]);
path.lineTo(bounds.right, bounds.top + _rowTops[y]);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none: break;
}
border.paint(canvas, bounds);
canvas.restore();
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (border != null)
description.add('border: $border');
if (_columnWidths.length > 0)
description.add('specified column widths: $_columnWidths');
description.add('default column width: $defaultColumnWidth');
description.add('table size: $columns\u00D7$rows');
if (!needsLayout) {
description.add('column offsets: ${ _columnLefts ?? "unknown" }');
description.add('row offsets: ${ _rowTops ?? "unknown" }');
}
}
@override
String debugDescribeChildren(String prefix) {
StringBuffer result = new StringBuffer();
result.writeln('$prefix \u2502');
int lastIndex = _children.length - 1;
for (int y = 0; y < rows; y += 1) {
for (int x = 0; x < columns; x += 1) {
final int xy = x + y * columns;
RenderBox child = _children[xy];
if (child != null) {
if (xy < lastIndex) {
result.write('${child.toStringDeep("$prefix \u251C\u2500child ($x, $y): ", "$prefix \u2502")}');
} else {
result.write('${child.toStringDeep("$prefix \u2514\u2500child ($x, $y): ", "$prefix ")}');
}
} else {
if (xy < lastIndex) {
result.writeln('$prefix \u251C\u2500child ($x, $y) is null');
} else {
result.writeln('$prefix \u2514\u2500child ($x, $y) is null');
}
}
}
}
return result.toString();
}
}
......@@ -14,6 +14,7 @@ import 'dart:collection';
/// [debugDumpApp]) and to the Dart [print] method can result in out-of-order
/// messages in the logs.
void debugPrint(String message) {
print(message);
_debugPrintBuffer.addAll(message.split('\n'));
if (!_debugPrintScheduled)
_debugPrintTask();
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';
import 'rendering_tester.dart';
RenderBox sizedBox(double width, double height) {
return new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tight(new Size(width, height))
);
}
void main() {
test('Table control test; tight', () {
RenderTable table;
layout(table = new RenderTable());
expect(table.size.width, equals(800.0));
expect(table.size.height, equals(600.0));
});
test('Table control test; loose', () {
RenderTable table;
layout(new RenderPositionedBox(child: table = new RenderTable()));
expect(table.size, equals(const Size(800.0, 0.0)));
});
test('Table test: combinations', () {
RenderTable table;
layout(new RenderPositionedBox(child: table = new RenderTable(
columns: 5,
rows: 5,
defaultColumnWidth: const IntrinsicColumnWidth(),
defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
textBaseline: TextBaseline.alphabetic
)));
expect(table.size, equals(const Size(800.0, 0.0)));
table.setChild(2, 4, sizedBox(100.0, 200.0));
pumpFrame();
expect(table.size, equals(new Size(800.0, 200.0)));
table.setChild(0, 0, sizedBox(10.0, 30.0));
table.setChild(1, 0, sizedBox(20.0, 20.0));
table.setChild(2, 0, sizedBox(30.0, 10.0));
pumpFrame();
expect(table.size, equals(new Size(800.0, 230.0)));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment