Unverified Commit 7d6ffc7c authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Add rect logic to getOffsetToReveal when onlySlivers is true (#64295)

parent e30e795a
...@@ -692,10 +692,24 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -692,10 +692,24 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
@override @override
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect rect }) { RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect rect }) {
double leadingScrollOffset = 0.0; // Steps to convert `rect` (from a RenderBox coordinate system) to its
double targetMainAxisExtent; // scroll offset within this viewport (not in the exact order):
rect ??= target.paintBounds; //
// 1. Pick the outmost RenderBox (between which, and the viewport, there is
// nothing but RenderSlivers) as an intermediate reference frame
// (the `pivot`), convert `rect` to that coordinate space.
//
// 2. Convert `rect` from the `pivot` coordinate space to its sliver
// parent's sliver coordinate system (i.e., to a scroll offset), based on
// the axis direction and growth direction of the parent.
//
// 3. Convert the scroll offset to its sliver parent's coordinate space
// using `childScrollOffset`, until we reach the viewport.
//
// 4. Make the final conversion from the outmost sliver to the viewport
// using `scrollOffsetOf`.
double leadingScrollOffset = 0.0;
// Starting at `target` and walking towards the root: // Starting at `target` and walking towards the root:
// - `child` will be the last object before we reach this viewport, and // - `child` will be the last object before we reach this viewport, and
// - `pivot` will be the last RenderBox before we reach this viewport. // - `pivot` will be the last RenderBox before we reach this viewport.
...@@ -717,89 +731,116 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -717,89 +731,116 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
child = parent; child = parent;
} }
// `rect` in the new intermediate coordinate system.
Rect rectLocal;
// Our new reference frame render object's main axis extent.
double pivotExtent;
GrowthDirection growthDirection;
// `leadingScrollOffset` is currently the scrollOffset of our new reference
// frame (`pivot` or `target`), within `child`.
if (pivot != null) { if (pivot != null) {
assert(pivot.parent != null); assert(pivot.parent != null);
assert(pivot.parent != this); assert(pivot.parent != this);
assert(pivot != this); assert(pivot != this);
assert(pivot.parent is RenderSliver); // TODO(abarth): Support other kinds of render objects besides slivers. assert(pivot.parent is RenderSliver); // TODO(abarth): Support other kinds of render objects besides slivers.
final RenderSliver pivotParent = pivot.parent as RenderSliver; final RenderSliver pivotParent = pivot.parent as RenderSliver;
growthDirection = pivotParent.constraints.growthDirection;
final Matrix4 transform = target.getTransformTo(pivot); switch (axis) {
final Rect bounds = MatrixUtils.transformRect(transform, rect); case Axis.horizontal:
pivotExtent = pivot.size.width;
final GrowthDirection growthDirection = pivotParent.constraints.growthDirection;
switch (applyGrowthDirectionToAxisDirection(axisDirection, growthDirection)) {
case AxisDirection.up:
double offset;
switch (growthDirection) {
case GrowthDirection.forward:
offset = bounds.bottom;
break;
case GrowthDirection.reverse:
offset = bounds.top;
break;
}
leadingScrollOffset += pivot.size.height - offset;
targetMainAxisExtent = bounds.height;
break;
case AxisDirection.right:
double offset;
switch (growthDirection) {
case GrowthDirection.forward:
offset = bounds.left;
break;
case GrowthDirection.reverse:
offset = bounds.right;
break;
}
leadingScrollOffset += offset;
targetMainAxisExtent = bounds.width;
break;
case AxisDirection.down:
double offset;
switch (growthDirection) {
case GrowthDirection.forward:
offset = bounds.top;
break;
case GrowthDirection.reverse:
offset = bounds.bottom;
break;
}
leadingScrollOffset += offset;
targetMainAxisExtent = bounds.height;
break; break;
case AxisDirection.left: case Axis.vertical:
double offset; pivotExtent = pivot.size.height;
switch (growthDirection) {
case GrowthDirection.forward:
offset = bounds.right;
break;
case GrowthDirection.reverse:
offset = bounds.left;
break;
}
leadingScrollOffset += pivot.size.width - offset;
targetMainAxisExtent = bounds.width;
break; break;
} }
rect ??= target.paintBounds;
rectLocal = MatrixUtils.transformRect(target.getTransformTo(pivot), rect);
} else if (onlySlivers) { } else if (onlySlivers) {
// `pivot` does not exist. We'll have to make up one from `target`, the
// innermost sliver.
final RenderSliver targetSliver = target as RenderSliver; final RenderSliver targetSliver = target as RenderSliver;
targetMainAxisExtent = targetSliver.geometry.scrollExtent; growthDirection = targetSliver.constraints.growthDirection;
// TODO(LongCatIsLooong): make sure this works if `targetSliver` is a
// persistent header, when #56413 relands.
pivotExtent = targetSliver.geometry.scrollExtent;
if (rect == null) {
switch (axis) {
case Axis.horizontal:
rect = Rect.fromLTWH(
0, 0,
targetSliver.geometry.scrollExtent,
targetSliver.constraints.crossAxisExtent,
);
break;
case Axis.vertical:
rect = Rect.fromLTWH(
0, 0,
targetSliver.constraints.crossAxisExtent,
targetSliver.geometry.scrollExtent,
);
break;
}
}
rectLocal = rect;
} else { } else {
return RevealedOffset(offset: offset.pixels, rect: rect); return RevealedOffset(offset: offset.pixels, rect: rect);
} }
assert(pivotExtent != null);
assert(rect != null);
assert(rectLocal != null);
assert(growthDirection != null);
assert(child.parent == this); assert(child.parent == this);
assert(child is RenderSliver); assert(child is RenderSliver);
final RenderSliver sliver = child as RenderSliver; final RenderSliver sliver = child as RenderSliver;
final double extentOfPinnedSlivers = maxScrollObstructionExtentBefore(sliver);
double targetMainAxisExtent;
// The scroll offset within `child` to `rect`.
switch (applyGrowthDirectionToAxisDirection(axisDirection, growthDirection)) {
case AxisDirection.up:
leadingScrollOffset += pivotExtent - rectLocal.bottom;
targetMainAxisExtent = rectLocal.height;
break;
case AxisDirection.right:
leadingScrollOffset += rectLocal.left;
targetMainAxisExtent = rectLocal.width;
break;
case AxisDirection.down:
leadingScrollOffset += rectLocal.top;
targetMainAxisExtent = rectLocal.height;
break;
case AxisDirection.left:
leadingScrollOffset += pivotExtent - rectLocal.right;
targetMainAxisExtent = rectLocal.width;
break;
}
// The scroll offset in the viewport to `rect`.
leadingScrollOffset = scrollOffsetOf(sliver, leadingScrollOffset); leadingScrollOffset = scrollOffsetOf(sliver, leadingScrollOffset);
// This step assumes the viewport's layout is up-to-date, i.e., if
// offset.pixels is changed after the last performLayout, the new scroll
// position will not be accounted for.
final Matrix4 transform = target.getTransformTo(this);
Rect targetRect = MatrixUtils.transformRect(transform, rect);
final double extentOfPinnedSlivers = maxScrollObstructionExtentBefore(sliver);
switch (sliver.constraints.growthDirection) { switch (sliver.constraints.growthDirection) {
case GrowthDirection.forward: case GrowthDirection.forward:
leadingScrollOffset -= extentOfPinnedSlivers; leadingScrollOffset -= extentOfPinnedSlivers;
break; break;
case GrowthDirection.reverse: case GrowthDirection.reverse:
// Nothing to do. // If child's growth direction is reverse, when viewport.offset is
// `leadingScrollOffset`, it is positioned just outside of the leading
// edge of the viewport.
switch (axis) {
case Axis.vertical:
leadingScrollOffset -= targetRect.height;
break;
case Axis.horizontal:
leadingScrollOffset -= targetRect.width;
break;
}
break; break;
} }
...@@ -816,9 +857,6 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -816,9 +857,6 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
final double targetOffset = leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment; final double targetOffset = leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment;
final double offsetDifference = offset.pixels - targetOffset; final double offsetDifference = offset.pixels - targetOffset;
final Matrix4 transform = target.getTransformTo(this);
Rect targetRect = MatrixUtils.transformRect(transform, rect);
switch (axisDirection) { switch (axisDirection) {
case AxisDirection.down: case AxisDirection.down:
targetRect = targetRect.translate(0.0, offsetDifference); targetRect = targetRect.translate(0.0, offsetDifference);
......
...@@ -220,7 +220,7 @@ void main() { ...@@ -220,7 +220,7 @@ void main() {
); );
children.add(sliver); children.add(sliver);
return SliverPadding( return SliverPadding(
padding: const EdgeInsets.all(22.0), padding: const EdgeInsets.only(top: 22.0, bottom: 23.0),
sliver: sliver, sliver: sliver,
); );
}), }),
...@@ -234,10 +234,16 @@ void main() { ...@@ -234,10 +234,16 @@ void main() {
final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false)); final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false));
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0); RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
expect(revealed.offset, 5 * (100 + 22 + 22) + 22); expect(revealed.offset, 5 * (100 + 22 + 23) + 22);
revealed = viewport.getOffsetToReveal(target, 1.0); revealed = viewport.getOffsetToReveal(target, 1.0);
expect(revealed.offset, 5 * (100 + 22 + 22) + 22 - 100); expect(revealed.offset, 5 * (100 + 22 + 23) + 22 - 100);
// With rect specified.
revealed = viewport.getOffsetToReveal(target, 0.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, 5 * (100 + 22 + 23) + 22 + 2);
revealed = viewport.getOffsetToReveal(target, 1.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, 5 * (100 + 22 + 23) + 22 - (200 - 4));
}); });
testWidgets('Viewport getOffsetToReveal Sliver - right', (WidgetTester tester) async { testWidgets('Viewport getOffsetToReveal Sliver - right', (WidgetTester tester) async {
...@@ -261,7 +267,7 @@ void main() { ...@@ -261,7 +267,7 @@ void main() {
); );
children.add(sliver); children.add(sliver);
return SliverPadding( return SliverPadding(
padding: const EdgeInsets.all(22.0), padding: const EdgeInsets.only(left: 22.0, right: 23.0),
sliver: sliver, sliver: sliver,
); );
}), }),
...@@ -275,10 +281,16 @@ void main() { ...@@ -275,10 +281,16 @@ void main() {
final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false)); final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false));
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0); RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
expect(revealed.offset, 5 * (100 + 22 + 22) + 22); expect(revealed.offset, 5 * (100 + 22 + 23) + 22);
revealed = viewport.getOffsetToReveal(target, 1.0); revealed = viewport.getOffsetToReveal(target, 1.0);
expect(revealed.offset, 5 * (100 + 22 + 22) + 22 - 100); expect(revealed.offset, 5 * (100 + 22 + 23) + 22 - 100);
// With rect specified.
revealed = viewport.getOffsetToReveal(target, 0.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, 5 * (100 + 22 + 23) + 22 + 1);
revealed = viewport.getOffsetToReveal(target, 1.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, 5 * (100 + 22 + 23) + 22 - (200 - 3));
}); });
testWidgets('Viewport getOffsetToReveal Sliver - up', (WidgetTester tester) async { testWidgets('Viewport getOffsetToReveal Sliver - up', (WidgetTester tester) async {
...@@ -302,7 +314,7 @@ void main() { ...@@ -302,7 +314,7 @@ void main() {
); );
children.add(sliver); children.add(sliver);
return SliverPadding( return SliverPadding(
padding: const EdgeInsets.all(22.0), padding: const EdgeInsets.only(top: 22.0, bottom: 23.0),
sliver: sliver, sliver: sliver,
); );
}), }),
...@@ -316,17 +328,25 @@ void main() { ...@@ -316,17 +328,25 @@ void main() {
final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false)); final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false));
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0); RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
expect(revealed.offset, 5 * (100 + 22 + 22) + 22); // Does not include the bottom padding of children[5] thus + 23 instead of + 22.
expect(revealed.offset, 5 * (100 + 22 + 23) + 23);
revealed = viewport.getOffsetToReveal(target, 1.0); revealed = viewport.getOffsetToReveal(target, 1.0);
expect(revealed.offset, 5 * (100 + 22 + 22) + 22 - 100); expect(revealed.offset, 5 * (100 + 22 + 23) + 23 - 100);
// With rect specified.
revealed = viewport.getOffsetToReveal(target, 0.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, 5 * (100 + 22 + 23) + 23 + (100 - 4));
revealed = viewport.getOffsetToReveal(target, 1.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, - 200 + 6 * (100 + 22 + 23) - 22 - 2);
}); });
testWidgets('Viewport getOffsetToReveal Sliver - up - reverse growth', (WidgetTester tester) async { testWidgets('Viewport getOffsetToReveal Sliver - up - reverse growth', (WidgetTester tester) async {
const Key centerKey = ValueKey<String>('center'); const Key centerKey = ValueKey<String>('center');
const EdgeInsets padding = EdgeInsets.only(top: 22.0, bottom: 23.0);
final Widget centerSliver = SliverPadding( final Widget centerSliver = SliverPadding(
key: centerKey, key: centerKey,
padding: const EdgeInsets.all(22.0), padding: padding,
sliver: SliverToBoxAdapter( sliver: SliverToBoxAdapter(
child: Container( child: Container(
height: 100.0, height: 100.0,
...@@ -339,7 +359,7 @@ void main() { ...@@ -339,7 +359,7 @@ void main() {
child: const Text('Tile lower'), child: const Text('Tile lower'),
); );
final Widget lowerSliver = SliverPadding( final Widget lowerSliver = SliverPadding(
padding: const EdgeInsets.all(22.0), padding: padding,
sliver: SliverToBoxAdapter( sliver: SliverToBoxAdapter(
child: lowerItem, child: lowerItem,
), ),
...@@ -370,13 +390,20 @@ void main() { ...@@ -370,13 +390,20 @@ void main() {
revealed = viewport.getOffsetToReveal(target, 1.0); revealed = viewport.getOffsetToReveal(target, 1.0);
expect(revealed.offset, - 100 - 22 - 100); expect(revealed.offset, - 100 - 22 - 100);
// With rect specified.
revealed = viewport.getOffsetToReveal(target, 0.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, - 22 - 4);
revealed = viewport.getOffsetToReveal(target, 1.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, -200 - 22 - 2);
}); });
testWidgets('Viewport getOffsetToReveal Sliver - left - reverse growth', (WidgetTester tester) async { testWidgets('Viewport getOffsetToReveal Sliver - left - reverse growth', (WidgetTester tester) async {
const Key centerKey = ValueKey<String>('center'); const Key centerKey = ValueKey<String>('center');
const EdgeInsets padding = EdgeInsets.only(left: 22.0, right: 23.0);
final Widget centerSliver = SliverPadding( final Widget centerSliver = SliverPadding(
key: centerKey, key: centerKey,
padding: const EdgeInsets.all(22.0), padding: padding,
sliver: SliverToBoxAdapter( sliver: SliverToBoxAdapter(
child: Container( child: Container(
width: 100.0, width: 100.0,
...@@ -389,7 +416,7 @@ void main() { ...@@ -389,7 +416,7 @@ void main() {
child: const Text('Tile lower'), child: const Text('Tile lower'),
); );
final Widget lowerSliver = SliverPadding( final Widget lowerSliver = SliverPadding(
padding: const EdgeInsets.all(22.0), padding: padding,
sliver: SliverToBoxAdapter( sliver: SliverToBoxAdapter(
child: lowerItem, child: lowerItem,
), ),
...@@ -421,6 +448,12 @@ void main() { ...@@ -421,6 +448,12 @@ void main() {
revealed = viewport.getOffsetToReveal(target, 1.0); revealed = viewport.getOffsetToReveal(target, 1.0);
expect(revealed.offset, - 100 - 22 - 200); expect(revealed.offset, - 100 - 22 - 200);
// With rect specified.
revealed = viewport.getOffsetToReveal(target, 0.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, - 22 - 3);
revealed = viewport.getOffsetToReveal(target, 1.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, - 300 - 22 - 1);
}); });
testWidgets('Viewport getOffsetToReveal Sliver - left', (WidgetTester tester) async { testWidgets('Viewport getOffsetToReveal Sliver - left', (WidgetTester tester) async {
...@@ -445,7 +478,7 @@ void main() { ...@@ -445,7 +478,7 @@ void main() {
); );
children.add(sliver); children.add(sliver);
return SliverPadding( return SliverPadding(
padding: const EdgeInsets.all(22.0), padding: const EdgeInsets.only(left: 22.0, right: 23.0),
sliver: sliver, sliver: sliver,
); );
}), }),
...@@ -459,10 +492,16 @@ void main() { ...@@ -459,10 +492,16 @@ void main() {
final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false)); final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false));
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0); RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
expect(revealed.offset, 5 * (100 + 22 + 22) + 22); expect(revealed.offset, 5 * (100 + 22 + 23) + 23);
revealed = viewport.getOffsetToReveal(target, 1.0); revealed = viewport.getOffsetToReveal(target, 1.0);
expect(revealed.offset, 5 * (100 + 22 + 22) + 22 - 100); expect(revealed.offset, 5 * (100 + 22 + 23) + 23 - 100);
// With rect specified.
revealed = viewport.getOffsetToReveal(target, 0.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, 6 * (100 + 22 + 23) - 22 - 3);
revealed = viewport.getOffsetToReveal(target, 1.0, rect: const Rect.fromLTRB(1, 2, 3, 4));
expect(revealed.offset, -200 + 6 * (100 + 22 + 23) - 22 - 1);
}); });
testWidgets('Nested Viewports showOnScreen', (WidgetTester tester) async { testWidgets('Nested Viewports showOnScreen', (WidgetTester tester) async {
......
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