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> {
..addListener(_handleResizeProgressChanged);
_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() {
......@@ -220,6 +225,9 @@ class DismissableState extends State<Dismissable> {
Widget build(BuildContext context) {
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>(
_directionIsYAxis ? _size.width : _size.height,
end: 0.0,
......
......@@ -66,13 +66,18 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
widget.onInvalidatorAvailable(invalidate);
}
/// _childOffsets 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 are no children, then the only offset is 0.0. 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.
List<double> _childOffsets = <double>[0.0];
/// _childExtents contains the extents of each child from the top of the list
/// up to the last one we've ever created.
final List<double> _childExtents = <double>[];
/// _childOffsets contains the offsets of the top 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. 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.
bool _didReachLastChild = false;
......@@ -111,8 +116,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
/// Forget all the known child offsets.
void _resetCache() {
_childExtents.clear();
_childOffsets.clear();
_childOffsets.add(0.0);
_didReachLastChild = false;
_childOffsets = <double>[0.0];
_invalidIndices.clear();
}
......@@ -273,8 +280,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final Element newElement = _inflateOrUpdateWidget(newWidget);
// Update the offsets based on the newElement's dimensions.
final double newOffset = _getElementExtent(newElement, innerConstraints);
_childOffsets.add(_childOffsets[index] + newOffset);
final double newExtent = _getElementExtent(newElement, innerConstraints);
_childExtents.add(newExtent);
_childOffsets.add(_childOffsets[index] + newExtent);
assert(_childExtents.length == _childOffsets.length - 1);
return newElement;
}
......@@ -308,21 +317,13 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
/// This compares the offsets we had for an element with its current
/// intrinsic dimensions.
bool _debugIsSameSize(Element element, int index, BoxConstraints constraints) {
assert(_invalidIndices.isEmpty);
BoxConstraints innerConstraints = _getInnerConstraints(constraints);
// We multiple both sides by 32 and then round to avoid floating
// point errors. (You have to round, not truncate, because otherwise
// if the error is on either side of an integer, you'll magnify it
// rather than hiding it.)
// This is an issue because we don't actually record the raw data
// (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();
double newExtent = _getElementExtent(element, innerConstraints);
bool result = _childExtents[index] == newExtent;
if (!result)
print("Element $element at index $index was size ${_childExtents[index]} but is now size ${newExtent} yet no invalidate() was received to that effect");
return result;
}
/// This is the core lazy-build algorithm. It builds widgets incrementally
......@@ -357,18 +358,19 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final BoxConstraints innerConstraints = _getInnerConstraints(constraints);
// Before doing the actual layout, fix the offsets for the widgets whose
// size or type has changed.
// size has apparently changed.
if (!isValid) {
assert(_childOffsets.length > 0);
assert(_childOffsets.length == _childExtents.length + 1);
List<int> invalidIndices = _invalidIndices.toList();
invalidIndices.sort();
for (int i = 0; i < invalidIndices.length; i += 1) {
// Determine the indices for this pass.
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
int endIndex;
int endIndex; // the last index into _childOffsets that we want to update this round
if (i == invalidIndices.length - 1) {
// This is the last invalid index. Update all the remaining entries in _childOffsets.
endIndex = _childOffsets.length - 1;
......@@ -385,11 +387,10 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
final Element newElement = _getElement(widgetIndex, innerConstraints);
// Update the offsets based on the newElement's dimensions.
final double newOffset = _getElementExtent(newElement, innerConstraints);
final double oldOffset = _childOffsets[widgetIndex + 1] - _childOffsets[widgetIndex];
final double offsetDelta = newOffset - oldOffset;
_childExtents[widgetIndex] = _getElementExtent(newElement, innerConstraints);
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.
final _ChildKey key = new _ChildKey.fromWidget(newElement.widget);
......@@ -418,7 +419,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
startIndex = 0;
// If we're scrolled up past the top, then our first visible widget, if
// any, is the first widget.
if (_childOffsets.length > 1) {
if (_childExtents.length > 0) {
haveChildren = true;
} else {
final Element element = _getElementAtLastKnownOffset(startIndex, innerConstraints);
......@@ -435,7 +436,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
// We're at some sane (not higher than the top) scroll offset.
// See if we can already find the offset in our cache.
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.
haveChildren = true;
} else {
......@@ -465,7 +466,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
_childrenByKey.remove(key);
updateChild(element, null, null);
startIndex += 1;
assert(startIndex == _childOffsets.length - 1);
assert(startIndex == _childExtents.length);
}
assert(haveChildren == _childOffsets.last > widget.startOffset);
assert(() {
......@@ -475,8 +476,9 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
// 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(_childOffsets.length >= 2);
assert(startIndex == _childOffsets.length - 2);
assert(_childExtents.length >= 1);
assert(_childOffsets.length == _childExtents.length + 1);
assert(startIndex == _childExtents.length - 1);
}
return true;
});
......@@ -485,7 +487,7 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
assert(haveChildren != null);
assert(haveChildren || _didReachLastChild || endOffset < 0.0);
assert(startIndex >= 0);
assert(startIndex < _childOffsets.length);
assert(startIndex < _childExtents.length);
// Build the other widgets that are visible.
int index = startIndex;
......@@ -509,10 +511,12 @@ class MixedViewportElement extends RenderObjectElement<MixedViewport> {
_didReachLastChild = true;
break;
}
if (index == _childOffsets.length-1) {
if (index == _childExtents.length) {
// Remember this element's offset.
final double newOffset = _getElementExtent(element, innerConstraints);
_childOffsets.add(_childOffsets[index] + newOffset);
final double newExtent = _getElementExtent(element, innerConstraints);
_childExtents.add(newExtent);
_childOffsets.add(_childOffsets[index] + newExtent);
assert(_childOffsets.length == _childExtents.length + 1);
} else {
// Verify that it hasn't changed size.
// If this assertion fires, it means you didn't call "invalidate"
......
......@@ -9,8 +9,6 @@ import 'package:vector_math/vector_math.dart';
export 'package:sky/animation.dart' show Direction;
// TODO(abarth): TransitionProxy
abstract class TransitionComponent extends StatefulComponent {
TransitionComponent({
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