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 {
@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';
}
......
......@@ -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,
......
......@@ -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;
}
......
// 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';
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';
......
......@@ -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);
});
}
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