Commit 05839e51 authored by Adam Barth's avatar Adam Barth

updateChildren() needs to walk the list forward

This patch changes the framework to walk the child list forwards so that build
functions with global side effects do sensible things. Specifically, if you
have a number of autofocusable children, the first one the list will acquire
the focus because it gets built first now.

Fixes #182
parent 594e7000
...@@ -1294,59 +1294,62 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent ...@@ -1294,59 +1294,62 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
ChildType _firstChild; ChildType _firstChild;
ChildType _lastChild; ChildType _lastChild;
void _addToChildList(ChildType child, { ChildType before }) { void _insertIntoChildList(ChildType child, { ChildType after }) {
final ParentDataType childParentData = child.parentData; final ParentDataType childParentData = child.parentData;
assert(childParentData.nextSibling == null); assert(childParentData.nextSibling == null);
assert(childParentData.previousSibling == null); assert(childParentData.previousSibling == null);
_childCount += 1; _childCount += 1;
assert(_childCount > 0); assert(_childCount > 0);
if (before == null) { if (after == null) {
// append at the end (_lastChild) // insert at the start (_firstChild)
childParentData.previousSibling = _lastChild; childParentData.nextSibling = _firstChild;
if (_lastChild != null) { if (_firstChild != null) {
final ParentDataType _lastChildParentData = _lastChild.parentData; final ParentDataType _firstChildParentData = _firstChild.parentData;
_lastChildParentData.nextSibling = child; _firstChildParentData.previousSibling = child;
} }
_lastChild = child; _firstChild = child;
if (_firstChild == null) if (_lastChild == null)
_firstChild = child; _lastChild = child;
} else { } else {
assert(_firstChild != null); assert(_firstChild != null);
assert(_lastChild != null); assert(_lastChild != null);
assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); assert(_debugUltimatePreviousSiblingOf(after, equals: _firstChild));
assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); assert(_debugUltimateNextSiblingOf(after, equals: _lastChild));
final ParentDataType beforeParentData = before.parentData; final ParentDataType afterParentData = after.parentData;
if (beforeParentData.previousSibling == null) { if (afterParentData.nextSibling == null) {
// insert at the start (_firstChild); we'll end up with two or more children // insert at the end (_lastChild); we'll end up with two or more children
assert(before == _firstChild); assert(after == _lastChild);
childParentData.nextSibling = before; childParentData.previousSibling = after;
beforeParentData.previousSibling = child; afterParentData.nextSibling = child;
_firstChild = child; _lastChild = child;
} else { } else {
// insert in the middle; we'll end up with three or more children // insert in the middle; we'll end up with three or more children
// set up links from child to siblings // set up links from child to siblings
childParentData.previousSibling = beforeParentData.previousSibling; childParentData.nextSibling = afterParentData.nextSibling;
childParentData.nextSibling = before; childParentData.previousSibling = after;
// set up links from siblings to child // set up links from siblings to child
final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling.parentData; final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling.parentData;
final ParentDataType childNextSiblingParentData = childParentData.nextSibling.parentData; final ParentDataType childNextSiblingParentData = childParentData.nextSibling.parentData;
childPreviousSiblingParentData.nextSibling = child; childPreviousSiblingParentData.nextSibling = child;
childNextSiblingParentData.previousSibling = child; childNextSiblingParentData.previousSibling = child;
assert(beforeParentData.previousSibling == child); assert(afterParentData.nextSibling == child);
} }
} }
} }
/// Insert child into this render object's child list before the given child. /// Insert child into this render object's child list after the given child.
/// void insert(ChildType child, { ChildType after }) {
/// To insert a child at the end of the child list, omit the before parameter.
void add(ChildType child, { ChildType before }) {
assert(child != this); assert(child != this);
assert(before != this); assert(after != this);
assert(child != before); assert(child != after);
assert(child != _firstChild); assert(child != _firstChild);
assert(child != _lastChild); assert(child != _lastChild);
adoptChild(child); adoptChild(child);
_addToChildList(child, before: before); _insertIntoChildList(child, after: after);
}
/// Append child to the end of this render object's child list.
void add(ChildType child) {
insert(child, after: _lastChild);
} }
/// Add all the children to the end of this render object's child list. /// Add all the children to the end of this render object's child list.
...@@ -1411,16 +1414,16 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent ...@@ -1411,16 +1414,16 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
/// More efficient than removing and re-adding the child. Requires the child /// More efficient than removing and re-adding the child. Requires the child
/// to already be in the child list at some position. Pass null for before to /// to already be in the child list at some position. Pass null for before to
/// move the child to the end of the child list. /// move the child to the end of the child list.
void move(ChildType child, { ChildType before }) { void move(ChildType child, { ChildType after }) {
assert(child != this); assert(child != this);
assert(before != this); assert(after != this);
assert(child != before); assert(child != after);
assert(child.parent == this); assert(child.parent == this);
final ParentDataType childParentData = child.parentData; final ParentDataType childParentData = child.parentData;
if (childParentData.nextSibling == before) if (childParentData.previousSibling == after)
return; return;
_removeFromChildList(child); _removeFromChildList(child);
_addToChildList(child, before: before); _insertIntoChildList(child, after: after);
} }
void attach() { void attach() {
......
...@@ -1507,79 +1507,80 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1507,79 +1507,80 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// Widgets without keys might be synced but there is no guarantee. // Widgets without keys might be synced but there is no guarantee.
// The general approach is to sync the entire new list backwards, as follows: // The general approach is to sync the entire new list backwards, as follows:
// 1. Walk the lists from the top until you no longer have // 1. Walk the lists from the top, syncing nodes, until you no longer have
// matching nodes. We don't sync these yet, but we now know to // matching nodes.
// skip them below. We do this because at each sync we need to // 2. Walk the lists from the bottom, without syncing nodes, until you no
// pass the pointer to the new next widget as the slot, which // longer have matching nodes. We'll sync these nodes at the end. We
// we can't do until we've synced the next child. // don't sync them now because we want to sync all the nodes in order
// 2. Walk the lists from the bottom, syncing nodes, until you no // from beginning ot end.
// longer have matching nodes.
// At this point we narrowed the old and new lists to the point // At this point we narrowed the old and new lists to the point
// where the nodes no longer match. // where the nodes no longer match.
// 3. Walk the narrowed part of the old list to get the list of // 3. Walk the narrowed part of the old list to get the list of
// keys and sync null with non-keyed items. // keys and sync null with non-keyed items.
// 4. Walk the narrowed part of the new list backwards: // 4. Walk the narrowed part of the new list forwards:
// * Sync unkeyed items with null // * Sync unkeyed items with null
// * Sync keyed items with the source if it exists, else with null. // * Sync keyed items with the source if it exists, else with null.
// 5. Walk the top list again but backwards, syncing the nodes. // 5. Walk the bottom of the list again, syncing the nodes.
// 6. Sync null with any items in the list of keys that are still // 6. Sync null with any items in the list of keys that are still
// mounted. // mounted.
int childrenTop = 0; int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1; int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1; int oldChildrenBottom = oldChildren.length - 1;
// top of the lists List<Element> newChildren = oldChildren.length == newWidgets.length ?
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) { oldChildren : new List<Element>(newWidgets.length);
Element oldChild = oldChildren[childrenTop];
Widget newWidget = newWidgets[childrenTop]; Element previousChild;
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop];
Widget newWidget = newWidgets[newChildrenTop];
assert(oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
if (!Widget.canUpdate(oldChild.widget, newWidget)) if (!Widget.canUpdate(oldChild.widget, newWidget))
break; break;
childrenTop += 1; Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
} }
List<Element> newChildren = oldChildren.length == newWidgets.length ? // Scan the bottom of the list.
oldChildren : new List<Element>(newWidgets.length); while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element nextSibling;
// bottom of the lists
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenBottom]; Element oldChild = oldChildren[oldChildrenBottom];
Widget newWidget = newWidgets[newChildrenBottom]; Widget newWidget = newWidgets[newChildrenBottom];
assert(oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
if (!Widget.canUpdate(oldChild.widget, newWidget)) if (!Widget.canUpdate(oldChild.widget, newWidget))
break; break;
Element newChild = updateChild(oldChild, newWidget, nextSibling);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
newChildren[newChildrenBottom] = newChild;
nextSibling = newChild;
oldChildrenBottom -= 1; oldChildrenBottom -= 1;
newChildrenBottom -= 1; newChildrenBottom -= 1;
} }
// middle of the lists - old list // Scan the old children in the middle of the list.
bool haveOldNodes = childrenTop <= oldChildrenBottom; bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
Map<Key, Element> oldKeyedChildren; Map<Key, Element> oldKeyedChildren;
if (haveOldNodes) { if (haveOldChildren) {
oldKeyedChildren = new Map<Key, Element>(); oldKeyedChildren = new Map<Key, Element>();
while (childrenTop <= oldChildrenBottom) { while (oldChildrenTop <= oldChildrenBottom) {
Element oldChild = oldChildren[oldChildrenBottom]; Element oldChild = oldChildren[oldChildrenTop];
assert(oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
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);
oldChildrenBottom -= 1; oldChildrenTop += 1;
} }
} }
// middle of the lists - new list // Update the middle of the list.
while (childrenTop <= newChildrenBottom) { while (newChildrenTop <= newChildrenBottom) {
Element oldChild; Element oldChild;
Widget newWidget = newWidgets[newChildrenBottom]; Widget newWidget = newWidgets[newChildrenTop];
if (haveOldNodes) { if (haveOldChildren) {
Key key = newWidget.key; Key key = newWidget.key;
if (key != null) { if (key != null) {
oldChild = oldKeyedChildren[newWidget.key]; oldChild = oldKeyedChildren[newWidget.key];
...@@ -1596,32 +1597,38 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1596,32 +1597,38 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
} }
} }
assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget)); assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget));
Element newChild = updateChild(oldChild, newWidget, nextSibling); Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active); assert(newChild._debugLifecycleState == _ElementLifecycle.active);
assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active); assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active);
newChildren[newChildrenBottom] = newChild; newChildren[newChildrenTop] = newChild;
nextSibling = newChild; previousChild = newChild;
newChildrenBottom -= 1; newChildrenTop += 1;
} }
assert(oldChildrenBottom == newChildrenBottom);
assert(childrenTop == newChildrenBottom + 1);
// now sync the top of the list // We've scaned the whole list.
while (childrenTop > 0) { assert(oldChildrenTop == oldChildrenBottom + 1);
childrenTop -= 1; assert(newChildrenTop == newChildrenBottom + 1);
Element oldChild = oldChildren[childrenTop]; assert(newWidgets.length - newChildrenTop == oldChildren.length - oldChildrenTop);
newChildrenBottom = newWidgets.length - 1;
oldChildrenBottom = oldChildren.length - 1;
// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop];
assert(oldChild._debugLifecycleState == _ElementLifecycle.active); assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
Widget newWidget = newWidgets[childrenTop]; Widget newWidget = newWidgets[newChildrenTop];
assert(Widget.canUpdate(oldChild.widget, newWidget)); assert(Widget.canUpdate(oldChild.widget, newWidget));
Element newChild = updateChild(oldChild, newWidget, nextSibling); Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active); assert(newChild._debugLifecycleState == _ElementLifecycle.active);
assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active); assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active);
newChildren[childrenTop] = newChild; newChildren[newChildrenTop] = newChild;
nextSibling = newChild; previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
} }
// clean up any of the remaining middle nodes from the old list // clean up any of the remaining middle nodes from the old list
if (haveOldNodes && !oldKeyedChildren.isEmpty) { if (haveOldChildren && !oldKeyedChildren.isEmpty) {
for (Element oldChild in oldKeyedChildren.values) for (Element oldChild in oldKeyedChildren.values)
_deactivateChild(oldChild); _deactivateChild(oldChild);
} }
...@@ -1755,15 +1762,13 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1755,15 +1762,13 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
void insertChildRenderObject(RenderObject child, Element slot) { void insertChildRenderObject(RenderObject child, Element slot) {
final ContainerRenderObjectMixin renderObject = this.renderObject; final ContainerRenderObjectMixin renderObject = this.renderObject;
final RenderObject nextSibling = slot?.renderObject; renderObject.insert(child, after: slot?.renderObject);
renderObject.add(child, before: nextSibling);
assert(renderObject == this.renderObject); assert(renderObject == this.renderObject);
} }
void moveChildRenderObject(RenderObject child, dynamic slot) { void moveChildRenderObject(RenderObject child, dynamic slot) {
final ContainerRenderObjectMixin renderObject = this.renderObject; final ContainerRenderObjectMixin renderObject = this.renderObject;
final RenderObject nextSibling = slot?.renderObject; renderObject.move(child, after: slot?.renderObject);
renderObject.move(child, before: nextSibling);
assert(renderObject == this.renderObject); assert(renderObject == this.renderObject);
} }
...@@ -1797,7 +1802,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -1797,7 +1802,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
super.mount(parent, newSlot); super.mount(parent, newSlot);
_children = new List<Element>(widget.children.length); _children = new List<Element>(widget.children.length);
Element previousChild; Element previousChild;
for (int i = _children.length - 1; i >= 0; --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;
......
...@@ -118,14 +118,12 @@ abstract class _ViewportBaseElement<T extends _ViewportBase> extends RenderObjec ...@@ -118,14 +118,12 @@ abstract class _ViewportBaseElement<T extends _ViewportBase> extends RenderObjec
} }
void insertChildRenderObject(RenderObject child, Element slot) { void insertChildRenderObject(RenderObject child, Element slot) {
RenderObject nextSibling = slot?.renderObject; renderObject.insert(child, after: slot?.renderObject);
renderObject.add(child, before: nextSibling);
} }
void moveChildRenderObject(RenderObject child, Element slot) { void moveChildRenderObject(RenderObject child, Element slot) {
assert(child.parent == renderObject); assert(child.parent == renderObject);
RenderObject nextSibling = slot?.renderObject; renderObject.move(child, after: slot?.renderObject);
renderObject.move(child, before: nextSibling);
} }
void removeChildRenderObject(RenderObject child) { void removeChildRenderObject(RenderObject child) {
......
...@@ -552,14 +552,12 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -552,14 +552,12 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
if (haveChildren) { if (haveChildren) {
// Place all our children in our RenderObject. // Place all our children in our RenderObject.
// All the children we are placing are in builtChildren and newChildren. // All the children we are placing are in builtChildren and newChildren.
// We will walk them backwards so we can set the slots at the same time. Element previousChild = null;
Element nextSibling = null; for (int i = startIndex; i < index; ++i) {
while (index > startIndex) { final Element element = builtChildren[i];
index -= 1; if (element.slot != previousChild)
final Element element = builtChildren[index]; updateSlotForChild(element, previousChild);
if (element.slot != nextSibling) previousChild = element;
updateSlotForChild(element, nextSibling);
nextSibling = element;
} }
} }
...@@ -577,20 +575,19 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -577,20 +575,19 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
if (slot == _omit) if (slot == _omit)
return; return;
assert(slot == null || slot is Element); assert(slot == null || slot is Element);
RenderObject nextSibling = slot?.renderObject; renderObject.insert(child, after: slot?.renderObject);
renderObject.add(child, before: nextSibling);
} }
void moveChildRenderObject(RenderObject child, dynamic slot) { void moveChildRenderObject(RenderObject child, dynamic slot) {
if (slot == _omit) if (slot == _omit)
return; return;
assert(slot == null || slot is Element); assert(slot == null || slot is Element);
RenderObject nextSibling = slot?.renderObject; RenderObject previousSibling = slot?.renderObject;
assert(nextSibling == null || nextSibling.parent == renderObject); assert(previousSibling == null || previousSibling.parent == renderObject);
if (child.parent == renderObject) if (child.parent == renderObject)
renderObject.move(child, before: nextSibling); renderObject.move(child, after: previousSibling);
else else
renderObject.add(child, before: nextSibling); renderObject.insert(child, after: previousSibling);
} }
void removeChildRenderObject(RenderObject child) { void removeChildRenderObject(RenderObject child) {
......
...@@ -149,14 +149,12 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO ...@@ -149,14 +149,12 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
} }
void insertChildRenderObject(RenderObject child, Element slot) { void insertChildRenderObject(RenderObject child, Element slot) {
RenderObject nextSibling = slot?.renderObject; renderObject.insert(child, after: slot?.renderObject);
renderObject.add(child, before: nextSibling);
} }
void moveChildRenderObject(RenderObject child, Element slot) { void moveChildRenderObject(RenderObject child, Element slot) {
assert(child.parent == renderObject); assert(child.parent == renderObject);
RenderObject nextSibling = slot?.renderObject; renderObject.move(child, after: slot?.renderObject);
renderObject.move(child, before: nextSibling);
} }
void removeChildRenderObject(RenderObject child) { void removeChildRenderObject(RenderObject child) {
......
...@@ -36,17 +36,16 @@ void main() { ...@@ -36,17 +36,16 @@ void main() {
new Focus( new Focus(
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
// reverse these when you fix https://github.com/flutter/engine/issues/1495
new TestFocusable(
key: keyB,
no: 'b',
yes: 'B FOCUSED'
),
new TestFocusable( new TestFocusable(
key: keyA, key: keyA,
no: 'a', no: 'a',
yes: 'A FOCUSED' yes: 'A FOCUSED'
), ),
new TestFocusable(
key: keyB,
no: 'b',
yes: 'B FOCUSED'
),
] ]
) )
) )
......
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