Commit a765507c authored by Hixie's avatar Hixie

fn3: Port Card Collection demo

Also:

- Make Dismissable report when it starts squashing, since otherwise we
  don't invalidate the list early enough and it gets mad that it wasn't
  told one of its children had changed size.

- Have Dismissable check that it gets removed after it's dismissed, to
  avoid having lots of redundant widgets around.

- Start tracking the height of each child of a MixedViewport, so that we
  don't accumulate floating point errors when a child jiggles up and down.

- Have _childOffsets reuse its storage space rather than newing up a new
  copy each time we reset the cache.

- Avoid double-updating child sizes when handling mixed viewport invalidations.
parent af8019bf
...@@ -102,6 +102,11 @@ class DismissableState extends State<Dismissable> { ...@@ -102,6 +102,11 @@ class DismissableState extends State<Dismissable> {
..addListener(_handleResizeProgressChanged); ..addListener(_handleResizeProgressChanged);
_resizePerformance.play(); _resizePerformance.play();
}); });
// Our squash curve (ease) does not return v=0.0 for t=0.0, so we
// technically resize on the first frame. To make sure this doesn't confuse
// any other widgets (like MixedViewport, which checks for this kind of
// thing), we report a resize straight away.
_maybeCallOnResized();
} }
void _handleResizeProgressChanged() { void _handleResizeProgressChanged() {
...@@ -220,6 +225,9 @@ class DismissableState extends State<Dismissable> { ...@@ -220,6 +225,9 @@ class DismissableState extends State<Dismissable> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_resizePerformance != null) { if (_resizePerformance != null) {
// make sure you remove this widget once it's been dismissed!
assert(_resizePerformance.status == AnimationStatus.forward);
AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>( AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>(
_directionIsYAxis ? _size.width : _size.height, _directionIsYAxis ? _size.width : _size.height,
end: 0.0, end: 0.0,
......
...@@ -66,13 +66,18 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -66,13 +66,18 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
widget.onInvalidatorAvailable(invalidate); widget.onInvalidatorAvailable(invalidate);
} }
/// _childOffsets contains the offsets of each child from the top of the list /// _childExtents contains the extents 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 /// up to the last one we've ever created.
/// last one. If there are no children, then the only offset is 0.0. The final List<double> _childExtents = <double>[];
/// offset of the end of the last child created (the actual last child, if
/// didReachLastChild is true), is also the distance from the top (left) of /// _childOffsets contains the offsets of the top of each child from the top
/// the first child to the bottom (right) of the last child created. /// of the list up to the last one we've ever created, and the offset of the
List<double> _childOffsets = <double>[0.0]; /// end of the last one. The first value is always 0.0. If there are no
/// children, that is the only value. The offset of the end of the last child
/// created (the actual last child, if didReachLastChild is true), is also the
/// distance from the top (left) of the first child to the bottom (right) of
/// the last child created.
final List<double> _childOffsets = <double>[0.0];
/// Whether childOffsets includes the offset of the last child. /// Whether childOffsets includes the offset of the last child.
bool _didReachLastChild = false; bool _didReachLastChild = false;
...@@ -111,8 +116,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -111,8 +116,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
/// Forget all the known child offsets. /// Forget all the known child offsets.
void _resetCache() { void _resetCache() {
_childExtents.clear();
_childOffsets.clear();
_childOffsets.add(0.0);
_didReachLastChild = false; _didReachLastChild = false;
_childOffsets = <double>[0.0];
_invalidIndices.clear(); _invalidIndices.clear();
} }
...@@ -273,8 +280,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -273,8 +280,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final Element newElement = _inflateOrUpdateWidget(newWidget); final Element newElement = _inflateOrUpdateWidget(newWidget);
// Update the offsets based on the newElement's dimensions. // Update the offsets based on the newElement's dimensions.
final double newOffset = _getElementExtent(newElement, innerConstraints); final double newExtent = _getElementExtent(newElement, innerConstraints);
_childOffsets.add(_childOffsets[index] + newOffset); _childExtents.add(newExtent);
_childOffsets.add(_childOffsets[index] + newExtent);
assert(_childExtents.length == _childOffsets.length - 1);
return newElement; return newElement;
} }
...@@ -308,21 +317,13 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -308,21 +317,13 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
/// This compares the offsets we had for an element with its current /// This compares the offsets we had for an element with its current
/// intrinsic dimensions. /// intrinsic dimensions.
bool _debugIsSameSize(Element element, int index, BoxConstraints constraints) { bool _debugIsSameSize(Element element, int index, BoxConstraints constraints) {
assert(_invalidIndices.isEmpty);
BoxConstraints innerConstraints = _getInnerConstraints(constraints); BoxConstraints innerConstraints = _getInnerConstraints(constraints);
// We multiple both sides by 32 and then round to avoid floating double newExtent = _getElementExtent(element, innerConstraints);
// point errors. (You have to round, not truncate, because otherwise bool result = _childExtents[index] == newExtent;
// if the error is on either side of an integer, you'll magnify it if (!result)
// rather than hiding it.) print("Element $element at index $index was size ${_childExtents[index]} but is now size ${newExtent} yet no invalidate() was received to that effect");
// This is an issue because we don't actually record the raw data return result;
// (the intrinsic dimensions), we record the offsets.
// The offsets therefore accumulate floating point errors. The
// errors are far too small to make the slightest diffference, but
// they're big enough to trip the assertion if we don't do this.
// We multiply by 32 so that we notice errors up to 1/32nd of a
// logical pixel. I'm assuming 32x resolution displays aren't going
// to happen. When I'm invariably proved wrong, just bump this up to
// a higher power of two.
return ((_childOffsets[index+1] - _childOffsets[index]) * 32.0).round() == (_getElementExtent(element, innerConstraints) * 32.0).round();
} }
/// This is the core lazy-build algorithm. It builds widgets incrementally /// This is the core lazy-build algorithm. It builds widgets incrementally
...@@ -357,18 +358,19 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -357,18 +358,19 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final BoxConstraints innerConstraints = _getInnerConstraints(constraints); final BoxConstraints innerConstraints = _getInnerConstraints(constraints);
// Before doing the actual layout, fix the offsets for the widgets whose // Before doing the actual layout, fix the offsets for the widgets whose
// size or type has changed. // size has apparently changed.
if (!isValid) { if (!isValid) {
assert(_childOffsets.length > 0); assert(_childOffsets.length > 0);
assert(_childOffsets.length == _childExtents.length + 1);
List<int> invalidIndices = _invalidIndices.toList(); List<int> invalidIndices = _invalidIndices.toList();
invalidIndices.sort(); invalidIndices.sort();
for (int i = 0; i < invalidIndices.length; i += 1) { for (int i = 0; i < invalidIndices.length; i += 1) {
// Determine the indices for this pass. // Determine the indices for this pass.
final int widgetIndex = invalidIndices[i]; final int widgetIndex = invalidIndices[i];
if (widgetIndex >= _childOffsets.length-1) if (widgetIndex >= _childExtents.length)
break; // we don't have that child, so there's nothing to invalidate break; // we don't have that child, so there's nothing to invalidate
int endIndex; int endIndex; // the last index into _childOffsets that we want to update this round
if (i == invalidIndices.length - 1) { if (i == invalidIndices.length - 1) {
// This is the last invalid index. Update all the remaining entries in _childOffsets. // This is the last invalid index. Update all the remaining entries in _childOffsets.
endIndex = _childOffsets.length - 1; endIndex = _childOffsets.length - 1;
...@@ -385,11 +387,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -385,11 +387,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final Element newElement = _getElement(widgetIndex, innerConstraints); final Element newElement = _getElement(widgetIndex, innerConstraints);
// Update the offsets based on the newElement's dimensions. // Update the offsets based on the newElement's dimensions.
final double newOffset = _getElementExtent(newElement, innerConstraints); _childExtents[widgetIndex] = _getElementExtent(newElement, innerConstraints);
final double oldOffset = _childOffsets[widgetIndex + 1] - _childOffsets[widgetIndex];
final double offsetDelta = newOffset - oldOffset;
for (int j = widgetIndex + 1; j <= endIndex; j++) for (int j = widgetIndex + 1; j <= endIndex; j++)
_childOffsets[j] += offsetDelta; _childOffsets[j] = _childOffsets[j - 1] + _childExtents[j - 1];
assert(_childOffsets.length == _childExtents.length + 1);
// Decide if it's visible. // Decide if it's visible.
final _ChildKey key = new _ChildKey.fromWidget(newElement.widget); final _ChildKey key = new _ChildKey.fromWidget(newElement.widget);
...@@ -418,7 +419,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -418,7 +419,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
startIndex = 0; startIndex = 0;
// If we're scrolled up past the top, then our first visible widget, if // If we're scrolled up past the top, then our first visible widget, if
// any, is the first widget. // any, is the first widget.
if (_childOffsets.length > 1) { if (_childExtents.length > 0) {
haveChildren = true; haveChildren = true;
} else { } else {
final Element element = _getElementAtLastKnownOffset(startIndex, innerConstraints); final Element element = _getElementAtLastKnownOffset(startIndex, innerConstraints);
...@@ -435,7 +436,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -435,7 +436,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
// We're at some sane (not higher than the top) scroll offset. // We're at some sane (not higher than the top) scroll offset.
// See if we can already find the offset in our cache. // See if we can already find the offset in our cache.
startIndex = _findIndexForOffsetBeforeOrAt(widget.startOffset); startIndex = _findIndexForOffsetBeforeOrAt(widget.startOffset);
if (startIndex < _childOffsets.length - 1) { if (startIndex < _childExtents.length) {
// We already know of a child that would be visible at this offset. // We already know of a child that would be visible at this offset.
haveChildren = true; haveChildren = true;
} else { } else {
...@@ -465,7 +466,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -465,7 +466,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
_childrenByKey.remove(key); _childrenByKey.remove(key);
updateChild(element, null, null); updateChild(element, null, null);
startIndex += 1; startIndex += 1;
assert(startIndex == _childOffsets.length - 1); assert(startIndex == _childExtents.length);
} }
assert(haveChildren == _childOffsets.last > widget.startOffset); assert(haveChildren == _childOffsets.last > widget.startOffset);
assert(() { assert(() {
...@@ -475,8 +476,9 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -475,8 +476,9 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
// If we're here, we have at least one child, so our list has // 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 // at least two offsets, the top of the child and the bottom
// of the child. // of the child.
assert(_childOffsets.length >= 2); assert(_childExtents.length >= 1);
assert(startIndex == _childOffsets.length - 2); assert(_childOffsets.length == _childExtents.length + 1);
assert(startIndex == _childExtents.length - 1);
} }
return true; return true;
}); });
...@@ -485,7 +487,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -485,7 +487,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
assert(haveChildren != null); assert(haveChildren != null);
assert(haveChildren || _didReachLastChild || endOffset < 0.0); assert(haveChildren || _didReachLastChild || endOffset < 0.0);
assert(startIndex >= 0); assert(startIndex >= 0);
assert(startIndex < _childOffsets.length); assert(startIndex < _childExtents.length);
// Build the other widgets that are visible. // Build the other widgets that are visible.
int index = startIndex; int index = startIndex;
...@@ -509,10 +511,12 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -509,10 +511,12 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
_didReachLastChild = true; _didReachLastChild = true;
break; break;
} }
if (index == _childOffsets.length-1) { if (index == _childExtents.length) {
// Remember this element's offset. // Remember this element's offset.
final double newOffset = _getElementExtent(element, innerConstraints); final double newExtent = _getElementExtent(element, innerConstraints);
_childOffsets.add(_childOffsets[index] + newOffset); _childExtents.add(newExtent);
_childOffsets.add(_childOffsets[index] + newExtent);
assert(_childOffsets.length == _childExtents.length + 1);
} else { } else {
// Verify that it hasn't changed size. // Verify that it hasn't changed size.
// If this assertion fires, it means you didn't call "invalidate" // If this assertion fires, it means you didn't call "invalidate"
......
...@@ -9,8 +9,6 @@ import 'package:vector_math/vector_math.dart'; ...@@ -9,8 +9,6 @@ import 'package:vector_math/vector_math.dart';
export 'package:sky/animation.dart' show Direction; export 'package:sky/animation.dart' show Direction;
// TODO(abarth): TransitionProxy
abstract class TransitionComponent extends StatefulComponent { abstract class TransitionComponent extends StatefulComponent {
TransitionComponent({ TransitionComponent({
Key key, Key key,
......
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