Merge pull request #2961 from Hixie/table

Table widget
This commit is contained in:
Ian Hickson 2016-03-29 13:20:13 -07:00
commit f4603f7615
8 changed files with 748 additions and 83 deletions

View file

@ -131,7 +131,7 @@ class RenderParagraph extends RenderBox {
@override
String debugDescribeChildren(String prefix) {
return '$prefix \u2558\u2550\u2566\u2550\u2550 text \u2550\u2550\u2550\n'
'${text.toString("$prefix \u2551 ")}\n'
'${text.toString("$prefix \u2551 ")}' // TextSpan includes a newline
'$prefix \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n'
'$prefix\n';
}

View file

@ -12,6 +12,12 @@ import 'object.dart';
class TableCellParentData extends BoxParentData {
TableCellVerticalAlignment verticalAlignment;
/// The column that the child was in the last time it was laid out.
int x;
/// The row that the child was in the last time it was laid out.
int y;
@override
String toString() => '${super.toString()}; $verticalAlignment';
}
@ -383,6 +389,15 @@ class RenderTable extends RenderBox {
for (int x = 0; x < columnsToCopy; x += 1)
_children[x + y * columns] = oldChildren[x + y * oldColumns];
}
if (oldColumns > columns) {
for (int y = 0; y < rows; y += 1) {
for (int x = columns; x < oldColumns; x += 1) {
int xy = x + y * oldColumns;
if (oldChildren[xy] != null)
dropChild(oldChildren[xy]);
}
}
}
markNeedsLayout();
}
@ -393,14 +408,21 @@ class RenderTable extends RenderBox {
assert(value >= 0);
if (value == rows)
return;
if (_rows > value) {
for (int xy = columns * value; xy < _children.length; xy += 1) {
if (_children[xy] != null)
dropChild(_children[xy]);
}
}
_rows = value;
_children.length = columns * rows;
markNeedsLayout();
}
Map<int, TableColumnWidth> _columnWidths;
void setColumnWidths(Map<int, TableColumnWidth> value) {
assert(value != null);
Map<int, TableColumnWidth> get columnWidths => new Map<int, TableColumnWidth>.unmodifiable(_columnWidths);
void set columnWidths(Map<int, TableColumnWidth> value) {
value ??= new HashMap<int, TableColumnWidth>();
if (_columnWidths == value)
return;
_columnWidths = value;
@ -460,16 +482,19 @@ class RenderTable extends RenderBox {
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) {
// consider the case of a newly empty table
if (columns == 0 || cells.length == 0) {
assert(cells == null || cells.length == 0);
if (_children.length == 0)
_columns = columns;
if (_children.length == 0) {
assert(_rows == 0);
return;
_columns = 0;
}
for (RenderBox oldChild in cells) {
if (oldChild != null)
dropChild(oldChild);
}
_rows = 0;
_children.clear();
markNeedsLayout();
@ -477,34 +502,55 @@ class RenderTable extends RenderBox {
}
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);
// remove cells that are moving away
for (int y = 0; y < _columns; y += 1) {
for (int x = 0; x < _rows; x += 1) {
int xyOld = x + y * _columns;
int xyNew = x + y * columns;
if (_children[xyOld] != null && (x >= columns || xyNew >= cells.length || _children[xyOld] != cells[xyNew]))
dropChild(_children[xyOld]);
}
}
// adopt cells that are arriving
int y = 0;
while (y * columns < cells.length) {
for (int x = 0; x < columns; x += 1) {
int xyNew = x + y * columns;
int xyOld = x + y * _columns;
if (cells[xyNew] != null && (x >= _columns || y >= _rows || _children[xyOld] != cells[xyNew]))
adoptChild(cells[xyNew]);
}
y += 1;
}
// update our internal values
_columns = columns;
_rows = cells.length ~/ columns;
_children = cells.toList();
assert(_children.length == rows * columns);
markNeedsLayout();
}
void setChildren(List<List<RenderBox>> cells) {
// TODO(ianh): Make this smarter, like setFlatChildren
if (cells == null) {
setFlatChildren(0, null);
return;
}
for (RenderBox oldChild in cells) {
for (RenderBox oldChild in _children) {
if (oldChild != null)
dropChild(oldChild);
}
_children.clear();
_columns = cells.length > 0 ? cells.first.length : 0;
_rows = 0;
_children.clear();
for (List<RenderBox> row in cells)
addRow(row);
assert(_children.length == rows * columns);
}
void addRow(List<RenderBox> cells) {
assert(cells.length == columns);
assert(_children.length == rows * columns);
_rows += 1;
_children.addAll(cells);
for (RenderBox cell in cells) {
@ -518,6 +564,7 @@ class RenderTable extends RenderBox {
assert(x != null);
assert(y != null);
assert(x >= 0 && x < columns && y >= 0 && y < rows);
assert(_children.length == rows * columns);
final int xy = x + y * columns;
RenderBox oldChild = _children[xy];
if (oldChild != null)
@ -543,6 +590,7 @@ class RenderTable extends RenderBox {
@override
void visitChildren(RenderObjectVisitor visitor) {
assert(_children.length == rows * columns);
for (RenderBox child in _children) {
if (child != null)
visitor(child);
@ -552,6 +600,7 @@ class RenderTable extends RenderBox {
@override
double getMinIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
assert(_children.length == rows * columns);
double totalMinWidth = 0.0;
for (int x = 0; x < columns; x += 1) {
TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
@ -564,6 +613,7 @@ class RenderTable extends RenderBox {
@override
double getMaxIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
assert(_children.length == rows * columns);
double totalMaxWidth = 0.0;
for (int x = 0; x < columns; x += 1) {
TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
@ -578,6 +628,7 @@ class RenderTable extends RenderBox {
// 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);
assert(_children.length == rows * columns);
final List<double> widths = computeColumnWidths(constraints);
double rowTop = 0.0;
for (int y = 0; y < rows; y += 1) {
@ -626,6 +677,7 @@ class RenderTable extends RenderBox {
}
List<double> computeColumnWidths(BoxConstraints constraints) {
assert(_children.length == rows * columns);
final List<double> widths = new List<double>(columns);
final List<double> flexes = new List<double>(columns);
double totalMinWidth = 0.0;
@ -677,7 +729,10 @@ class RenderTable extends RenderBox {
@override
void performLayout() {
assert(_children.length == rows * columns);
if (rows * columns == 0) {
// TODO(ianh): if columns is zero, this should be zero width
// TODO(ianh): if columns is not zero, this should be based on the column width specifications
size = constraints.constrain(const Size(double.INFINITY, 0.0));
return;
}
@ -704,6 +759,8 @@ class RenderTable extends RenderBox {
RenderBox child = _children[xy];
if (child != null) {
TableCellParentData childParentData = child.parentData;
childParentData.x = x;
childParentData.y = y;
switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
case TableCellVerticalAlignment.baseline:
assert(textBaseline != null);
@ -769,6 +826,7 @@ class RenderTable extends RenderBox {
@override
bool hitTestChildren(HitTestResult result, { Point position }) {
assert(_children.length == rows * columns);
for (int index = _children.length - 1; index >= 0; index -= 1) {
RenderBox child = _children[index];
if (child != null) {
@ -784,6 +842,7 @@ class RenderTable extends RenderBox {
@override
void paint(PaintingContext context, Offset offset) {
assert(_children.length == rows * columns);
if (rows * columns == 0)
return;
assert(_rowTops.length == rows);
@ -797,37 +856,39 @@ class RenderTable extends RenderBox {
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;
if (border != null) {
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);
}
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();
}
@ -851,21 +912,25 @@ class RenderTable extends RenderBox {
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")}');
if (lastIndex < 0) {
result.writeln('$prefix \u2514\u2500table is empty');
} else {
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 {
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');
if (xy < lastIndex) {
result.writeln('$prefix \u251C\u2500child ($x, $y) is null');
} else {
result.writeln('$prefix \u2514\u2500child ($x, $y) is null');
}
}
}
}

View file

@ -15,21 +15,19 @@ export 'package:flutter/painting.dart';
export 'package:flutter/rendering.dart' show
Axis,
BoxConstraints,
CrossAxisAlignment,
CustomClipper,
CustomPainter,
FixedColumnCountGridDelegate,
CrossAxisAlignment,
FlexDirection,
MainAxisAlignment,
FractionalOffsetTween,
GridDelegate,
GridDelegateWithInOrderChildPlacement,
GridSpecification,
HitTestBehavior,
MainAxisAlignment,
MaxTileWidthGridDelegate,
MultiChildLayoutDelegate,
SingleChildLayoutDelegate,
RenderObjectPainter,
PaintingContext,
PlainTextSpan,
PointerCancelEvent,
@ -42,7 +40,9 @@ export 'package:flutter/rendering.dart' show
PointerUpEvent,
PointerUpEventListener,
RelativeRect,
RenderObjectPainter,
ShaderCallback,
SingleChildLayoutDelegate,
ValueChanged,
ViewportAnchor,
ViewportDimensions,

View file

@ -20,6 +20,8 @@ export 'package:flutter/services.dart' show FlutterError;
/// original Widget's Key.
///
/// Keys must be unique amongst the Elements with the same parent.
///
/// Subclasses of Key should either subclass [LocalKey] or [GlobalKey].
abstract class Key {
/// Construct a ValueKey<String> with the given String.
/// This is the simplest way to create keys.
@ -29,12 +31,18 @@ abstract class Key {
const Key.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor
}
/// A key that is not a [GlobalKey].
abstract class LocalKey extends Key {
/// Default constructor, used by subclasses.
const LocalKey() : super.constructor();
}
/// A kind of [Key] that uses a value of a particular type to identify itself.
///
/// For example, a ValueKey<String> is equal to another ValueKey<String> if
/// their values match.
class ValueKey<T> extends Key {
const ValueKey(this.value) : super.constructor();
class ValueKey<T> extends LocalKey {
const ValueKey(this.value);
final T value;
@ -54,8 +62,8 @@ class ValueKey<T> extends Key {
}
/// A [Key] that is only equal to itself.
class UniqueKey extends Key {
const UniqueKey() : super.constructor();
class UniqueKey extends LocalKey {
const UniqueKey();
@override
String toString() => '[$hashCode]';
@ -65,8 +73,8 @@ class UniqueKey extends Key {
///
/// Used to tie the identity of a Widget to the identity of an object used to
/// generate that Widget.
class ObjectKey extends Key {
const ObjectKey(this.value) : super.constructor();
class ObjectKey extends LocalKey {
const ObjectKey(this.value);
final Object value;
@ -744,7 +752,7 @@ abstract class Element implements BuildContext {
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
_deactivateChild(child);
deactivateChild(child);
return null;
}
if (child != null) {
@ -760,10 +768,10 @@ abstract class Element implements BuildContext {
assert(child.widget == newWidget);
return child;
}
_deactivateChild(child);
deactivateChild(child);
assert(child._parent == null);
}
return _inflateWidget(newWidget, newSlot);
return inflateWidget(newWidget, newSlot);
}
static void finalizeTree() {
@ -870,7 +878,7 @@ abstract class Element implements BuildContext {
return element;
}
Element _inflateWidget(Widget newWidget, dynamic newSlot) {
Element inflateWidget(Widget newWidget, dynamic newSlot) {
Key key = newWidget.key;
if (key is GlobalKey) {
Element newChild = _retakeInactiveElement(key, newWidget);
@ -901,7 +909,7 @@ abstract class Element implements BuildContext {
});
}
void _deactivateChild(Element child) {
void deactivateChild(Element child) {
assert(child != null);
assert(child._parent == this);
child._parent = null;
@ -1339,7 +1347,7 @@ abstract class ComponentElement extends BuildableElement {
@override
bool detachChild(Element child) {
assert(child == _child);
_deactivateChild(_child);
deactivateChild(_child);
_child = null;
return true;
}
@ -1733,7 +1741,7 @@ abstract class RenderObjectElement extends BuildableElement {
if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key] = oldChild;
else
_deactivateChild(oldChild);
deactivateChild(oldChild);
}
oldChildrenTop += 1;
}
@ -1795,7 +1803,7 @@ abstract class RenderObjectElement extends BuildableElement {
if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
for (Element oldChild in oldKeyedChildren.values) {
if (detachedChildren == null || !detachedChildren.contains(oldChild))
_deactivateChild(oldChild);
deactivateChild(oldChild);
}
}
@ -1897,7 +1905,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement {
@override
bool detachChild(Element child) {
assert(child == _child);
_deactivateChild(_child);
deactivateChild(_child);
_child = null;
return true;
}
@ -1984,7 +1992,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
@override
bool detachChild(Element child) {
_detachedChildren.add(child);
_deactivateChild(child);
deactivateChild(child);
return true;
}
@ -1994,7 +2002,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
_children = new List<Element>(widget.children.length);
Element previousChild;
for (int i = 0; i < _children.length; ++i) {
Element newChild = _inflateWidget(widget.children[i], previousChild);
Element newChild = inflateWidget(widget.children[i], previousChild);
_children[i] = newChild;
previousChild = newChild;
}

View file

@ -0,0 +1,231 @@
// 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 'package:flutter/rendering.dart';
import 'debug.dart';
import 'framework.dart';
export 'package:flutter/rendering.dart' show
FixedColumnWidth,
FlexColumnWidth,
FractionColumnWidth,
IntrinsicColumnWidth,
MaxColumnWidth,
MinColumnWidth,
TableBorder,
TableCellVerticalAlignment,
TableColumnWidth;
class TableRow {
const TableRow({ this.key, this.children });
final LocalKey key;
final List<Widget> children;
}
class _TableElementRow {
const _TableElementRow({ this.key, this.children });
final LocalKey key;
final List<Element> children;
}
/// Uses the table layout algorithm for its children.
///
/// For details about the table layout algorithm, see [RenderTable].
/// To control the alignment of children, see [TableCell].
class Table extends RenderObjectWidget {
Table({
Key key,
this.children: const <TableRow>[],
this.columnWidths,
this.defaultColumnWidth: const FlexColumnWidth(1.0),
this.border,
this.defaultVerticalAlignment: TableCellVerticalAlignment.top,
this.textBaseline
}) : super(key: key) {
assert(children != null);
assert(defaultColumnWidth != null);
assert(defaultVerticalAlignment != null);
assert(() {
List<Widget> flatChildren = children.expand((TableRow row) => row.children).toList(growable: false);
return !debugChildrenHaveDuplicateKeys(this, flatChildren);
});
assert(!children.any((TableRow row1) => row1.key != null && children.any((TableRow row2) => row1 != row2 && row1.key == row2.key)));
}
final List<TableRow> children;
final Map<int, TableColumnWidth> columnWidths;
final TableColumnWidth defaultColumnWidth;
final TableBorder border;
final TableCellVerticalAlignment defaultVerticalAlignment;
final TextBaseline textBaseline;
@override
_TableElement createElement() => new _TableElement(this);
@override
RenderTable createRenderObject(BuildContext context) {
return new RenderTable(
columns: children.length > 0 ? children[0].children.length : 0,
rows: children.length,
columnWidths: columnWidths,
defaultColumnWidth: defaultColumnWidth,
border: border,
defaultVerticalAlignment: defaultVerticalAlignment,
textBaseline: textBaseline
);
}
@override
void updateRenderObject(BuildContext context, RenderTable renderObject) {
assert(renderObject.columns == (children.length > 0 ? children[0].children.length : 0));
assert(renderObject.rows == children.length);
renderObject
..columnWidths = columnWidths
..defaultColumnWidth = defaultColumnWidth
..border = border
..defaultVerticalAlignment = defaultVerticalAlignment
..textBaseline = textBaseline;
}
}
class _TableElement extends RenderObjectElement {
_TableElement(Table widget) : super(widget);
@override
Table get widget => super.widget;
@override
RenderTable get renderObject => super.renderObject;
// This class ignores the child's slot entirely.
// Instead of doing incremental updates to the child list, it replaces the entire list each frame.
List<_TableElementRow> _children = const<_TableElementRow>[];
bool _debugWillReattachChildren = false;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(!_debugWillReattachChildren);
assert(() { _debugWillReattachChildren = true; return true; });
_children = widget.children.map((TableRow row) {
return new _TableElementRow(
key: row.key,
children: row.children.map((Widget child) => inflateWidget(child, null)).toList(growable: false)
);
}).toList(growable: false);
assert(() { _debugWillReattachChildren = false; return true; });
_updateRenderObjectChildren();
}
@override
void insertChildRenderObject(RenderObject child, Element slot) {
assert(_debugWillReattachChildren);
renderObject.setupParentData(child);
}
@override
void moveChildRenderObject(RenderObject child, dynamic slot) {
assert(_debugWillReattachChildren);
}
@override
void removeChildRenderObject(RenderObject child) {
assert(_debugWillReattachChildren);
TableCellParentData childParentData = child.parentData;
renderObject.setChild(childParentData.x, childParentData.y, null);
}
final Set<Element> _detachedChildren = new HashSet<Element>();
@override
void update(Table newWidget) {
assert(!_debugWillReattachChildren);
assert(() { _debugWillReattachChildren = true; return true; });
Map<LocalKey, List<Element>> oldKeyedRows = new Map<LocalKey, List<Element>>.fromIterable(
_children.where((_TableElementRow row) => row.key != null),
key: (_TableElementRow row) => row.key,
value: (_TableElementRow row) => row.children
);
Iterator<_TableElementRow> oldUnkeyedRows = _children.where((_TableElementRow row) => row.key == null).iterator;
List<_TableElementRow> newChildren = <_TableElementRow>[];
Set<List<Element>> taken = new Set<List<Element>>();
for (TableRow row in newWidget.children) {
List<Element> oldChildren;
if (row.key != null && oldKeyedRows.containsKey(row.key)) {
oldChildren = oldKeyedRows[row.key];
taken.add(oldChildren);
} else if (row.key == null && oldUnkeyedRows.moveNext()) {
oldChildren = oldUnkeyedRows.current.children;
} else {
oldChildren = const <Element>[];
}
newChildren.add(new _TableElementRow(
key: row.key,
children: updateChildren(oldChildren, row.children, detachedChildren: _detachedChildren)
));
}
while (oldUnkeyedRows.moveNext())
updateChildren(oldUnkeyedRows.current.children, const <Widget>[], detachedChildren: _detachedChildren);
for (List<Element> oldChildren in oldKeyedRows.values.where((List<Element> list) => !taken.contains(list)))
updateChildren(oldChildren, const <Widget>[], detachedChildren: _detachedChildren);
assert(() { _debugWillReattachChildren = false; return true; });
_children = newChildren;
_updateRenderObjectChildren();
_detachedChildren.clear();
super.update(newWidget);
assert(widget == newWidget);
}
void _updateRenderObjectChildren() {
assert(renderObject != null);
renderObject.setFlatChildren(
_children.length > 0 ? _children[0].children.length : 0,
_children.expand((_TableElementRow row) => row.children.map((Element child) => child.renderObject)).toList()
);
}
@override
void visitChildren(ElementVisitor visitor) {
for (Element child in _children.expand((_TableElementRow row) => row.children)) {
if (!_detachedChildren.contains(child))
visitor(child);
}
}
@override
bool detachChild(Element child) {
_detachedChildren.add(child);
deactivateChild(child);
return true;
}
}
class TableCell extends ParentDataWidget<Table> {
TableCell({ Key key, this.verticalAlignment, Widget child })
: super(key: key, child: child);
final TableCellVerticalAlignment verticalAlignment;
@override
void applyParentData(RenderObject renderObject) {
final TableCellParentData parentData = renderObject.parentData;
if (parentData.verticalAlignment != verticalAlignment) {
parentData.verticalAlignment = verticalAlignment;
AbstractNode targetParent = renderObject.parent;
if (targetParent is RenderObject)
targetParent.markNeedsLayout();
}
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('verticalAlignment: $verticalAlignment');
}
}

View file

@ -44,6 +44,7 @@ export 'src/widgets/scrollable_list.dart';
export 'src/widgets/scrollable.dart';
export 'src/widgets/semantics_debugger.dart';
export 'src/widgets/status_transitions.dart';
export 'src/widgets/table.dart';
export 'src/widgets/title.dart';
export 'src/widgets/transitions.dart';
export 'src/widgets/unique_widget.dart';

View file

@ -55,4 +55,20 @@ void main() {
expect(table.size, equals(new Size(800.0, 230.0)));
});
test('Table test: removing cells', () {
RenderTable table;
RenderBox child;
table = new RenderTable(
columns: 5,
rows: 5
);
table.setChild(4, 4, child = sizedBox(10.0, 10.0));
layout(table);
expect(child.attached, isTrue);
table.rows = 4;
expect(child.attached, isFalse);
});
}

View file

@ -0,0 +1,344 @@
// 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_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:test/test.dart';
void main() {
test('Table widget - control test', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(
new Table(
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('AAAAAA'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('EEE'), new Text('F')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('III')
]
),
]
)
);
RenderBox boxA = tester.findText('AAAAAA').findRenderObject();
RenderBox boxD = tester.findText('D').findRenderObject();
RenderBox boxG = tester.findText('G').findRenderObject();
RenderBox boxB = tester.findText('B').findRenderObject();
expect(boxA.size, equals(boxD.size));
expect(boxA.size, equals(boxG.size));
expect(boxA.size, equals(boxB.size));
});
});
test('Table widget - changing table dimensions', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(
new Table(
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('A'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('E'), new Text('F')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('I')
]
),
]
)
);
RenderBox boxA1 = tester.findText('A').findRenderObject();
RenderBox boxG1 = tester.findText('G').findRenderObject();
expect(boxA1, isNotNull);
expect(boxG1, isNotNull);
tester.pumpWidget(
new Table(
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('a'), new Text('b'), new Text('c'), new Text('d')
]
),
new TableRow(
children: <Widget>[
new Text('e'), new Text('f'), new Text('g'), new Text('h')
]
),
]
)
);
RenderBox boxA2 = tester.findText('a').findRenderObject();
RenderBox boxG2 = tester.findText('g').findRenderObject();
expect(boxA2, isNotNull);
expect(boxG2, isNotNull);
expect(boxA1, equals(boxA2));
expect(boxG1, isNot(equals(boxG2)));
});
});
test('Table widget - repump test', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(
new Table(
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('AAAAAA'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('EEE'), new Text('F')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('III')
]
),
]
)
);
tester.pumpWidget(
new Table(
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('AAA'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('E'), new Text('FFFFFF')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('III')
]
),
]
)
);
RenderBox boxA = tester.findText('AAA').findRenderObject();
RenderBox boxD = tester.findText('D').findRenderObject();
RenderBox boxG = tester.findText('G').findRenderObject();
RenderBox boxB = tester.findText('B').findRenderObject();
expect(boxA.size, equals(boxD.size));
expect(boxA.size, equals(boxG.size));
expect(boxA.size, equals(boxB.size));
});
});
test('Table widget - intrinsic sizing test', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(
new Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('AAA'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('E'), new Text('FFFFFF')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('III')
]
),
]
)
);
RenderBox boxA = tester.findText('AAA').findRenderObject();
RenderBox boxD = tester.findText('D').findRenderObject();
RenderBox boxG = tester.findText('G').findRenderObject();
RenderBox boxB = tester.findText('B').findRenderObject();
expect(boxA.size, equals(boxD.size));
expect(boxA.size, equals(boxG.size));
expect(boxA.size.width, greaterThan(boxB.size.width));
expect(boxA.size.height, equals(boxB.size.height));
});
});
test('Table widget - intrinsic sizing test, resizing', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(
new Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('AAAAAA'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('EEE'), new Text('F')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('III')
]
),
]
)
);
tester.pumpWidget(
new Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('A'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('EEE'), new Text('F')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('III')
]
),
]
)
);
RenderBox boxA = tester.findText('A').findRenderObject();
RenderBox boxD = tester.findText('D').findRenderObject();
RenderBox boxG = tester.findText('G').findRenderObject();
RenderBox boxB = tester.findText('B').findRenderObject();
expect(boxA.size, equals(boxD.size));
expect(boxA.size, equals(boxG.size));
expect(boxA.size.width, lessThan(boxB.size.width));
expect(boxA.size.height, equals(boxB.size.height));
});
});
test('Table widget - intrinsic sizing test, changing column widths', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(
new Table(
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('AAA'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('E'), new Text('FFFFFF')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('III')
]
),
]
)
);
tester.pumpWidget(
new Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('AAA'), new Text('B'), new Text('C')
]
),
new TableRow(
children: <Widget>[
new Text('D'), new Text('E'), new Text('FFFFFF')
]
),
new TableRow(
children: <Widget>[
new Text('G'), new Text('H'), new Text('III')
]
),
]
)
);
RenderBox boxA = tester.findText('AAA').findRenderObject();
RenderBox boxD = tester.findText('D').findRenderObject();
RenderBox boxG = tester.findText('G').findRenderObject();
RenderBox boxB = tester.findText('B').findRenderObject();
expect(boxA.size, equals(boxD.size));
expect(boxA.size, equals(boxG.size));
expect(boxA.size.width, greaterThan(boxB.size.width));
expect(boxA.size.height, equals(boxB.size.height));
});
});
test('Table widget - moving test', () {
testWidgets((WidgetTester tester) {
List<BuildContext> contexts = <BuildContext>[];
tester.pumpWidget(
new Table(
children: <TableRow>[
new TableRow(
key: new ValueKey<int>(1),
children: <Widget>[
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
contexts.add(context);
return new Text('A');
}
)
]
),
new TableRow(
children: <Widget>[
new Text('b')
]
),
]
)
);
tester.pumpWidget(
new Table(
children: <TableRow>[
new TableRow(
children: <Widget>[
new Text('b')
]
),
new TableRow(
key: new ValueKey<int>(1),
children: <Widget>[
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
contexts.add(context);
return new Text('A');
}
)
]
),
]
)
);
expect(contexts.length, equals(2));
expect(contexts[0], equals(contexts[1]));
});
});
// TODO(ianh): Test handling of TableCell object
}