Chromium Code Reviews| Index: sky/sdk/lib/widgets/block_viewport.dart |
| diff --git a/sky/sdk/lib/widgets/block_viewport.dart b/sky/sdk/lib/widgets/block_viewport.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..ad010b469aa7326dfccf3d315d7a3cd331db3ad7 |
| --- /dev/null |
| +++ b/sky/sdk/lib/widgets/block_viewport.dart |
| @@ -0,0 +1,257 @@ |
| +// 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 '../rendering/block.dart'; |
| +import '../rendering/box.dart'; |
| +import '../rendering/object.dart'; |
| +import 'widget.dart'; |
| + |
| +// return null if index is greater than index of last entry |
| +typedef Widget IndexedBuilder(int index); |
| + |
| +class _Key { |
| + const _Key(this.type, this.key); |
| + factory _Key.fromWidget(Widget widget) => new _Key(widget.runtimeType, widget.key); |
| + final Type type; |
| + final String key; |
| + bool operator ==(_Key other) => other is _Key && other.type == type && other.key == key; |
| + int get hashCode => 373 * 37 * type.hashCode + key.hashCode; |
| +} |
| + |
| +class BlockViewport extends RenderObjectWrapper { |
| + BlockViewport({ IndexedBuilder this.builder, this.startOffset, this.token, String key }) |
|
abarth-chromium
2015/07/08 21:56:20
Is IndexedBuilder needed here?
|
| + : super(key: key); |
| + |
| + IndexedBuilder builder; |
| + double startOffset; |
| + Object token; |
|
abarth-chromium
2015/07/08 21:56:20
final?
|
| + |
| + RenderBlockViewport get root => super.root; |
| + RenderBlockViewport createNode() => new RenderBlockViewport(); |
| + |
| + Map<_Key, Widget> _childrenByKey = new Map<_Key, Widget>(); |
| + |
| + void walkChildren(WidgetTreeWalker walker) { |
| + for (Widget child in _childrenByKey.values) |
| + walker(child); |
| + } |
| + |
| + static const omit = const Object(); // used as a slot when it's not yet time to attach the child |
| + |
| + void insertChildRoot(RenderObjectWrapper child, dynamic slot) { |
| + if (slot == omit) |
| + return; |
| + final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer |
| + assert(slot == null || slot is RenderObject); |
| + assert(root is ContainerRenderObjectMixin); |
| + root.add(child.root, before: slot); |
| + assert(root == this.root); // TODO(ianh): Remove this once the analyzer is cleverer |
| + } |
| + |
| + void detachChildRoot(RenderObjectWrapper child) { |
| + final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer |
| + assert(root is ContainerRenderObjectMixin); |
| + if (child.root.parent != root) |
| + return; // probably had slot == omit when inserted |
| + root.remove(child.root); |
| + assert(root == this.root); // TODO(ianh): Remove this once the analyzer is cleverer |
| + } |
| + |
| + void remove() { |
| + for (var child in _childrenByKey.values) { |
| + assert(child != null); |
| + removeChild(child); |
| + } |
| + super.remove(); |
| + } |
| + |
| + void didMount() { |
| + root.callback = layout; |
| + super.didMount(); |
| + } |
| + |
| + void didUnmount() { |
| + root.callback = null; |
| + super.didUnmount(); |
| + } |
| + |
| + // _offsets contains the offsets of each child from the top of the |
| + // list up to the last one we've ever created, and the offset of the |
| + // end of the last one. If there's no children, then the only offset |
| + // is 0.0. |
| + List<double> _offsets = <double>[0.0]; |
| + |
| + int _findIndexForOffsetBeforeOrAt(double offset) { |
| + int left = 0; |
| + int right = _offsets.length-1; |
|
abarth-chromium
2015/07/08 21:56:20
We usually put spaces around binary operators like
|
| + while (right >= left) { |
| + int middle = left + ((right-left) ~/ 2); |
| + if (_offsets[middle] < offset) { |
| + left = middle+1; |
| + } else if (_offsets[middle] > offset) { |
| + right = middle-1; |
| + } else { |
| + return middle; |
| + } |
| + } |
| + return right; |
| + } |
|
abarth-chromium
2015/07/08 21:56:20
Seems like a function we should factor out to some
|
| + |
| + void syncRenderObject(BlockViewport old) { |
| + super.syncRenderObject(old); |
| + root.markNeedsLayout(); |
|
abarth-chromium
2015/07/08 21:56:20
Do we need to do a layout every time we sync? I w
|
| + } |
| + |
| + Widget _getWidget(int index, BoxConstraints innerConstraints) { |
| + LayoutCallbackBuilderHandle handle = enterLayoutCallbackBuilder(); |
| + try { |
| + assert(index >= 0); |
| + Widget widget = builder == null ? null : builder(index); |
| + if (widget == null) |
| + return null; |
| + assert(widget.key != null); // items in lists must have keys |
| + final _Key key = new _Key.fromWidget(widget); |
| + Widget oldWidget = _childrenByKey[key]; |
| + widget = syncChild(widget, oldWidget, omit); |
| + if (oldWidget != null) |
| + _childrenByKey[key] = widget; |
| + if (index >= _offsets.length-1) { |
| + assert(index == _offsets.length-1); |
| + final double widgetStartOffset = _offsets[index]; |
| + RenderBox widgetRoot = widget.root; |
| + assert(widgetRoot is RenderBox); |
| + final double widgetEndOffset = widgetStartOffset + widgetRoot.getMaxIntrinsicHeight(innerConstraints); |
| + _offsets.add(widgetEndOffset); |
| + } |
| + return widget; |
| + } finally { |
| + exitLayoutCallbackBuilder(handle); |
| + } |
| + } |
| + |
| + void layout(BoxConstraints constraints) { |
| + Map<_Key, Widget> newChildren = new Map<_Key, Widget>(); |
| + Map<int, Widget> builtChildren = new Map<int, Widget>(); |
| + |
| + double height = root.size.height; |
| + double endOffset = startOffset + height; |
| + BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth); |
|
abarth-chromium
2015/07/08 21:56:20
There's a bug here if constraints.minWidth is grea
|
| + |
| + int startIndex; |
| + bool haveChildren; |
| + if (startOffset <= 0.0) { |
| + startIndex = 0; |
| + if (_offsets.length > 1) { |
| + haveChildren = true; |
| + } else { |
| + Widget widget = _getWidget(startIndex, innerConstraints); |
| + if (widget != null) { |
| + newChildren[new _Key.fromWidget(widget)] = widget; |
| + builtChildren[startIndex] = widget; |
| + haveChildren = true; |
| + } else { |
| + haveChildren = false; |
| + } |
| + } |
| + } else { |
| + startIndex = _findIndexForOffsetBeforeOrAt(startOffset); |
| + if (startIndex == _offsets.length-1) { |
| + // We don't have an offset on the list that is beyond the start offset. |
| + assert(_offsets.last <= startOffset); |
| + // Fill the list until this isn't true or until we know that the |
| + // list is complete (and thus we are overscrolled). |
| + while (true) { |
| + Widget widget = _getWidget(startIndex, innerConstraints); |
| + if (widget == null) |
| + break; |
| + _Key widgetKey = new _Key.fromWidget(widget); |
| + if (_offsets.last > startOffset) { |
| + newChildren[widgetKey] = widget; |
| + builtChildren[startIndex] = widget; |
| + break; |
| + } |
| + if (!_childrenByKey.containsKey(widgetKey)) { |
| + // we don't actually need this one, release it |
| + syncChild(null, widget, null); |
| + } // else we'll get rid of it later, when we remove old children |
| + startIndex += 1; |
| + assert(startIndex == _offsets.length-1); |
| + } |
| + if (_offsets.last > startOffset) { |
| + // If we're here, we have at least one child, so our list has |
| + // at least two offsets, the top of the child and the bottom |
| + // of the child. |
| + assert(_offsets.length >= 2); |
| + assert(startIndex == _offsets.length-2); |
| + haveChildren = true; |
| + } else { |
| + // If we're here, there are no children to show. |
| + haveChildren = false; |
| + } |
| + } else { |
| + haveChildren = true; |
| + } |
| + } |
| + assert(haveChildren != null); |
| + |
| + assert(startIndex >= 0); |
| + assert(startIndex < _offsets.length); |
| + |
| + int index = startIndex; |
| + if (haveChildren) { |
| + // Build all the widgets we need. |
| + root.startOffset = _offsets[index]-startOffset; |
| + while (_offsets[index] < endOffset) { |
| + if (!builtChildren.containsKey(index)) { |
| + Widget widget = _getWidget(index, innerConstraints); |
| + if (widget == null) |
| + break; // reached the end of the list |
| + newChildren[new _Key.fromWidget(widget)] = widget; |
| + builtChildren[index] = widget; |
| + } |
| + assert(builtChildren[index] != null); |
| + index += 1; |
| + } |
| + } |
| + |
| + // Remove any old children. |
| + for (_Key oldChildKey in _childrenByKey.keys) { |
| + if (!newChildren.containsKey(oldChildKey)) |
| + syncChild(null, _childrenByKey[oldChildKey], null); // calls detachChildRoot() |
| + } |
| + |
| + if (haveChildren) { |
| + // Place all our children in our RenderObject. |
| + // All the children we are placing are in builtChildren and newChildren. |
| + // We will walk them backwards so we can set the siblings at the same time. |
| + RenderBox nextSibling = null; |
| + while (index > startIndex) { |
| + index -= 1; |
| + Widget widget = builtChildren[index]; |
| + if (widget.root.parent == root) { |
| + root.move(widget.root, before: nextSibling); |
| + } else { |
| + assert(widget.root.parent == null); |
| + root.add(widget.root, before: nextSibling); |
| + } |
| + widget.updateSlot(nextSibling); |
| + nextSibling = widget.root; |
| + } |
| + } |
| + |
| + _childrenByKey = newChildren; |
| + } |
| + |
| + bool retainStatefulNodeIfPossible(BlockViewport newNode) { |
| + doRetainStatefulNode(newNode); |
| + startOffset = newNode.startOffset; |
| + if (token != newNode.token || builder != newNode.builder) { |
| + builder = newNode.builder; |
| + token = newNode.token; |
| + _offsets = <double>[0.0]; |
| + } |
| + return true; |
| + } |
| + |
| +} |