Commit baf06515 authored by Hixie's avatar Hixie

Table widget

- Add x and y to the parent data, in case it's useful. In point of fact,
  I ended up not needing it.

- Make columnWidths settable rather than having a setter so that the
  code in the widgets layer is more idiomatic.

- Teach setFlatChildren about avoiding unnecessary drop/adopt loops.

- Assert that the child list length is correct more aggressively so that
  we catch the dumb bug I had more quickly next time.

- Improve the toString() to handle empty tables better.

- Resort the imports in framework.dart.

- Introduce a LocalKey for cases where you don't want to allow use of
  GlobalKey.

- Make inflateWidget and deactivateChild public for subclass use.

- Table widget.

- Cell widget.

- TableRow fake widget.
parent a5ee6e11
...@@ -131,7 +131,7 @@ class RenderParagraph extends RenderBox { ...@@ -131,7 +131,7 @@ class RenderParagraph extends RenderBox {
@override @override
String debugDescribeChildren(String prefix) { String debugDescribeChildren(String prefix) {
return '$prefix \u2558\u2550\u2566\u2550\u2550 text \u2550\u2550\u2550\n' 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 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n'
'$prefix\n'; '$prefix\n';
} }
......
...@@ -15,21 +15,19 @@ export 'package:flutter/painting.dart'; ...@@ -15,21 +15,19 @@ export 'package:flutter/painting.dart';
export 'package:flutter/rendering.dart' show export 'package:flutter/rendering.dart' show
Axis, Axis,
BoxConstraints, BoxConstraints,
CrossAxisAlignment,
CustomClipper, CustomClipper,
CustomPainter, CustomPainter,
FixedColumnCountGridDelegate, FixedColumnCountGridDelegate,
CrossAxisAlignment,
FlexDirection, FlexDirection,
MainAxisAlignment,
FractionalOffsetTween, FractionalOffsetTween,
GridDelegate, GridDelegate,
GridDelegateWithInOrderChildPlacement, GridDelegateWithInOrderChildPlacement,
GridSpecification, GridSpecification,
HitTestBehavior, HitTestBehavior,
MainAxisAlignment,
MaxTileWidthGridDelegate, MaxTileWidthGridDelegate,
MultiChildLayoutDelegate, MultiChildLayoutDelegate,
SingleChildLayoutDelegate,
RenderObjectPainter,
PaintingContext, PaintingContext,
PlainTextSpan, PlainTextSpan,
PointerCancelEvent, PointerCancelEvent,
...@@ -42,7 +40,9 @@ export 'package:flutter/rendering.dart' show ...@@ -42,7 +40,9 @@ export 'package:flutter/rendering.dart' show
PointerUpEvent, PointerUpEvent,
PointerUpEventListener, PointerUpEventListener,
RelativeRect, RelativeRect,
RenderObjectPainter,
ShaderCallback, ShaderCallback,
SingleChildLayoutDelegate,
ValueChanged, ValueChanged,
ViewportAnchor, ViewportAnchor,
ViewportDimensions, ViewportDimensions,
......
...@@ -20,6 +20,8 @@ export 'package:flutter/services.dart' show FlutterError; ...@@ -20,6 +20,8 @@ export 'package:flutter/services.dart' show FlutterError;
/// original Widget's Key. /// original Widget's Key.
/// ///
/// Keys must be unique amongst the Elements with the same parent. /// Keys must be unique amongst the Elements with the same parent.
///
/// Subclasses of Key should either subclass [LocalKey] or [GlobalKey].
abstract class Key { abstract class Key {
/// Construct a ValueKey<String> with the given String. /// Construct a ValueKey<String> with the given String.
/// This is the simplest way to create keys. /// This is the simplest way to create keys.
...@@ -29,12 +31,18 @@ abstract class Key { ...@@ -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 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. /// 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 /// For example, a ValueKey<String> is equal to another ValueKey<String> if
/// their values match. /// their values match.
class ValueKey<T> extends Key { class ValueKey<T> extends LocalKey {
const ValueKey(this.value) : super.constructor(); const ValueKey(this.value);
final T value; final T value;
...@@ -54,8 +62,8 @@ class ValueKey<T> extends Key { ...@@ -54,8 +62,8 @@ class ValueKey<T> extends Key {
} }
/// A [Key] that is only equal to itself. /// A [Key] that is only equal to itself.
class UniqueKey extends Key { class UniqueKey extends LocalKey {
const UniqueKey() : super.constructor(); const UniqueKey();
@override @override
String toString() => '[$hashCode]'; String toString() => '[$hashCode]';
...@@ -65,8 +73,8 @@ class UniqueKey extends Key { ...@@ -65,8 +73,8 @@ class UniqueKey extends Key {
/// ///
/// Used to tie the identity of a Widget to the identity of an object used to /// Used to tie the identity of a Widget to the identity of an object used to
/// generate that Widget. /// generate that Widget.
class ObjectKey extends Key { class ObjectKey extends LocalKey {
const ObjectKey(this.value) : super.constructor(); const ObjectKey(this.value);
final Object value; final Object value;
...@@ -744,7 +752,7 @@ abstract class Element implements BuildContext { ...@@ -744,7 +752,7 @@ abstract class Element implements BuildContext {
Element updateChild(Element child, Widget newWidget, dynamic newSlot) { Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) { if (newWidget == null) {
if (child != null) if (child != null)
_deactivateChild(child); deactivateChild(child);
return null; return null;
} }
if (child != null) { if (child != null) {
...@@ -760,10 +768,10 @@ abstract class Element implements BuildContext { ...@@ -760,10 +768,10 @@ abstract class Element implements BuildContext {
assert(child.widget == newWidget); assert(child.widget == newWidget);
return child; return child;
} }
_deactivateChild(child); deactivateChild(child);
assert(child._parent == null); assert(child._parent == null);
} }
return _inflateWidget(newWidget, newSlot); return inflateWidget(newWidget, newSlot);
} }
static void finalizeTree() { static void finalizeTree() {
...@@ -870,7 +878,7 @@ abstract class Element implements BuildContext { ...@@ -870,7 +878,7 @@ abstract class Element implements BuildContext {
return element; return element;
} }
Element _inflateWidget(Widget newWidget, dynamic newSlot) { Element inflateWidget(Widget newWidget, dynamic newSlot) {
Key key = newWidget.key; Key key = newWidget.key;
if (key is GlobalKey) { if (key is GlobalKey) {
Element newChild = _retakeInactiveElement(key, newWidget); Element newChild = _retakeInactiveElement(key, newWidget);
...@@ -901,7 +909,7 @@ abstract class Element implements BuildContext { ...@@ -901,7 +909,7 @@ abstract class Element implements BuildContext {
}); });
} }
void _deactivateChild(Element child) { void deactivateChild(Element child) {
assert(child != null); assert(child != null);
assert(child._parent == this); assert(child._parent == this);
child._parent = null; child._parent = null;
...@@ -1339,7 +1347,7 @@ abstract class ComponentElement extends BuildableElement { ...@@ -1339,7 +1347,7 @@ abstract class ComponentElement extends BuildableElement {
@override @override
bool detachChild(Element child) { bool detachChild(Element child) {
assert(child == _child); assert(child == _child);
_deactivateChild(_child); deactivateChild(_child);
_child = null; _child = null;
return true; return true;
} }
...@@ -1733,7 +1741,7 @@ abstract class RenderObjectElement extends BuildableElement { ...@@ -1733,7 +1741,7 @@ abstract class RenderObjectElement extends BuildableElement {
if (oldChild.widget.key != null) if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key] = oldChild; oldKeyedChildren[oldChild.widget.key] = oldChild;
else else
_deactivateChild(oldChild); deactivateChild(oldChild);
} }
oldChildrenTop += 1; oldChildrenTop += 1;
} }
...@@ -1795,7 +1803,7 @@ abstract class RenderObjectElement extends BuildableElement { ...@@ -1795,7 +1803,7 @@ abstract class RenderObjectElement extends BuildableElement {
if (haveOldChildren && oldKeyedChildren.isNotEmpty) { if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
for (Element oldChild in oldKeyedChildren.values) { for (Element oldChild in oldKeyedChildren.values) {
if (detachedChildren == null || !detachedChildren.contains(oldChild)) if (detachedChildren == null || !detachedChildren.contains(oldChild))
_deactivateChild(oldChild); deactivateChild(oldChild);
} }
} }
...@@ -1897,7 +1905,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement { ...@@ -1897,7 +1905,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement {
@override @override
bool detachChild(Element child) { bool detachChild(Element child) {
assert(child == _child); assert(child == _child);
_deactivateChild(_child); deactivateChild(_child);
_child = null; _child = null;
return true; return true;
} }
...@@ -1984,7 +1992,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement { ...@@ -1984,7 +1992,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
@override @override
bool detachChild(Element child) { bool detachChild(Element child) {
_detachedChildren.add(child); _detachedChildren.add(child);
_deactivateChild(child); deactivateChild(child);
return true; return true;
} }
...@@ -1994,7 +2002,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement { ...@@ -1994,7 +2002,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
_children = new List<Element>(widget.children.length); _children = new List<Element>(widget.children.length);
Element previousChild; Element previousChild;
for (int i = 0; i < _children.length; ++i) { 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; _children[i] = newChild;
previousChild = newChild; previousChild = newChild;
} }
......
// 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');
}
}
...@@ -44,6 +44,7 @@ export 'src/widgets/scrollable_list.dart'; ...@@ -44,6 +44,7 @@ export 'src/widgets/scrollable_list.dart';
export 'src/widgets/scrollable.dart'; export 'src/widgets/scrollable.dart';
export 'src/widgets/semantics_debugger.dart'; export 'src/widgets/semantics_debugger.dart';
export 'src/widgets/status_transitions.dart'; export 'src/widgets/status_transitions.dart';
export 'src/widgets/table.dart';
export 'src/widgets/title.dart'; export 'src/widgets/title.dart';
export 'src/widgets/transitions.dart'; export 'src/widgets/transitions.dart';
export 'src/widgets/unique_widget.dart'; export 'src/widgets/unique_widget.dart';
......
...@@ -55,4 +55,20 @@ void main() { ...@@ -55,4 +55,20 @@ void main() {
expect(table.size, equals(new Size(800.0, 230.0))); 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);
});
} }
This diff is collapsed.
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