Unverified Commit 3a7daaba authored by nt4f04uNd's avatar nt4f04uNd Committed by GitHub

Fix ListWheelScrollView gestures and paint coordinates in tests (#121342)

Fix ListWheelScrollView gestures and paint coordinates in tests
parent 9dc4f839
...@@ -10,6 +10,7 @@ import 'package:vector_math/vector_math_64.dart' show Matrix4; ...@@ -10,6 +10,7 @@ import 'package:vector_math/vector_math_64.dart' show Matrix4;
import 'box.dart'; import 'box.dart';
import 'layer.dart'; import 'layer.dart';
import 'object.dart'; import 'object.dart';
import 'proxy_box.dart';
import 'viewport.dart'; import 'viewport.dart';
import 'viewport_offset.dart'; import 'viewport_offset.dart';
...@@ -55,6 +56,17 @@ class ListWheelParentData extends ContainerBoxParentData<RenderBox> { ...@@ -55,6 +56,17 @@ class ListWheelParentData extends ContainerBoxParentData<RenderBox> {
/// ///
/// This must be maintained by the [ListWheelChildManager]. /// This must be maintained by the [ListWheelChildManager].
int? index; int? index;
/// Transform applied to this child during painting.
///
/// Can be used to find the local bounds of this child in the viewport,
/// and then use it, for example, in hit testing.
///
/// May be null if child was laid out, but not painted
/// by the parent, but normally this shouldn't happen,
/// because [RenderListWheelViewport] paints all of the
/// children it has laid out.
Matrix4? transform;
} }
/// Render, onto a wheel, a bigger sequential set of objects inside this viewport. /// Render, onto a wheel, a bigger sequential set of objects inside this viewport.
...@@ -964,12 +976,14 @@ class RenderListWheelViewport ...@@ -964,12 +976,14 @@ class RenderListWheelViewport
Matrix4 cylindricalTransform, Matrix4 cylindricalTransform,
Offset offsetToCenter, Offset offsetToCenter,
) { ) {
final Offset paintOriginOffset = offset + offsetToCenter;
// Paint child cylindrically, without [overAndUnderCenterOpacity]. // Paint child cylindrically, without [overAndUnderCenterOpacity].
void painter(PaintingContext context, Offset offset) { void painter(PaintingContext context, Offset offset) {
context.paintChild( context.paintChild(
child, child,
// Paint everything in the center (e.g. angle = 0), then transform. // Paint everything in the center (e.g. angle = 0), then transform.
offset + offsetToCenter, paintOriginOffset,
); );
} }
...@@ -985,6 +999,12 @@ class RenderListWheelViewport ...@@ -985,6 +999,12 @@ class RenderListWheelViewport
// Pre-transform painting function. // Pre-transform painting function.
overAndUnderCenterOpacity == 1 ? painter : opacityPainter, overAndUnderCenterOpacity == 1 ? painter : opacityPainter,
); );
final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
// Save the final transform that accounts both for the offset and cylindrical transform.
final Matrix4 transform = _centerOriginTransform(cylindricalTransform)
..translate(paintOriginOffset.dx, paintOriginOffset.dy);
childParentData.transform = transform;
} }
/// Return the Matrix4 transformation that would zoom in content in the /// Return the Matrix4 transformation that would zoom in content in the
...@@ -1014,12 +1034,33 @@ class RenderListWheelViewport ...@@ -1014,12 +1034,33 @@ class RenderListWheelViewport
return result; return result;
} }
/// This returns the matrices relative to the **untransformed plane's viewport static bool _debugAssertValidPaintTransform(ListWheelParentData parentData) {
/// painting coordinates** system. if (parentData.transform == null) {
throw FlutterError(
'Child paint transform happened to be null. \n'
'$RenderListWheelViewport normally paints all of the children it has laid out. \n'
'Did you forget to update the $ListWheelParentData.transform during the paint() call? \n'
'If this is intetional, change or remove this assertion accordingly.'
);
}
return true;
}
static bool _debugAssertValidHitTestOffsets(String context, Offset offset1, Offset offset2) {
if (offset1 != offset2) {
throw FlutterError("$context - hit test expected values didn't match: $offset1 != $offset2");
}
return true;
}
@override @override
void applyPaintTransform(RenderBox child, Matrix4 transform) { void applyPaintTransform(RenderBox child, Matrix4 transform) {
final ListWheelParentData parentData = child.parentData! as ListWheelParentData; final ListWheelParentData parentData = child.parentData! as ListWheelParentData;
transform.translate(0.0, _getUntransformedPaintingCoordinateY(parentData.offset.dy)); final Matrix4? paintTransform = parentData.transform;
assert(_debugAssertValidPaintTransform(parentData));
if (paintTransform != null) {
transform.multiply(paintTransform);
}
} }
@override @override
...@@ -1031,7 +1072,36 @@ class RenderListWheelViewport ...@@ -1031,7 +1072,36 @@ class RenderListWheelViewport
} }
@override @override
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) => false; bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
RenderBox? child = lastChild;
while (child != null) {
final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
final Matrix4? transform = childParentData.transform;
assert(_debugAssertValidPaintTransform(childParentData));
final bool isHit = result.addWithPaintTransform(
transform: transform,
position: position,
hitTest: (BoxHitTestResult result, Offset transformed) {
assert(() {
if (transform == null) {
return _debugAssertValidHitTestOffsets('Null transform', transformed, position);
}
final Matrix4? inverted = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform));
if (inverted == null) {
return _debugAssertValidHitTestOffsets('Null inverted transform', transformed, position);
}
return _debugAssertValidHitTestOffsets('MatrixUtils.transformPoint', transformed, MatrixUtils.transformPoint(inverted, position));
}());
return child!.hitTest(result, position: transformed);
},
);
if (isHit) {
return true;
}
child = childParentData.previousSibling;
}
return false;
}
@override @override
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) { RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) {
......
...@@ -592,7 +592,7 @@ void main() { ...@@ -592,7 +592,7 @@ void main() {
); );
}); });
testWidgets('width of wheel in background does not increase at large widths', (WidgetTester tester) async { testWidgets('wheel does not bend outwards', (WidgetTester tester) async {
final Widget dateWidget = CupertinoDatePicker( final Widget dateWidget = CupertinoDatePicker(
mode: CupertinoDatePickerMode.date, mode: CupertinoDatePickerMode.date,
...@@ -600,6 +600,18 @@ void main() { ...@@ -600,6 +600,18 @@ void main() {
initialDateTime: DateTime(2018, 1, 1, 10, 30), initialDateTime: DateTime(2018, 1, 1, 10, 30),
); );
const String centerMonth = 'January';
const List<String> visibleMonthsExceptTheCenter = <String>[
'September',
'October',
'November',
'December',
'February',
'March',
'April',
'May',
];
await tester.pumpWidget( await tester.pumpWidget(
CupertinoApp( CupertinoApp(
home: CupertinoPageScaffold( home: CupertinoPageScaffold(
...@@ -614,9 +626,13 @@ void main() { ...@@ -614,9 +626,13 @@ void main() {
), ),
); );
double decemberX = tester.getBottomLeft(find.text('December')).dx; // The wheel does not bend outwards.
double octoberX = tester.getBottomLeft(find.text('October')).dx; for (final String month in visibleMonthsExceptTheCenter) {
final double distance = octoberX - decemberX; expect(
tester.getBottomLeft(find.text(centerMonth)).dx,
lessThan(tester.getBottomLeft(find.text(month)).dx),
);
}
await tester.pumpWidget( await tester.pumpWidget(
CupertinoApp( CupertinoApp(
...@@ -632,14 +648,13 @@ void main() { ...@@ -632,14 +648,13 @@ void main() {
), ),
); );
decemberX = tester.getBottomLeft(find.text('December')).dx;
octoberX = tester.getBottomLeft(find.text('October')).dx;
// The wheel does not bend outwards at large widths. // The wheel does not bend outwards at large widths.
expect( for (final String month in visibleMonthsExceptTheCenter) {
distance >= (octoberX - decemberX), expect(
true, tester.getBottomLeft(find.text(centerMonth)).dx,
); lessThan(tester.getBottomLeft(find.text(month)).dx),
);
}
}); });
testWidgets('picker automatically scrolls away from invalid date on month change', (WidgetTester tester) async { testWidgets('picker automatically scrolls away from invalid date on month change', (WidgetTester tester) async {
......
...@@ -118,7 +118,7 @@ void main() { ...@@ -118,7 +118,7 @@ void main() {
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '1').first), tester.getTopLeft(find.widgetWithText(SizedBox, '1').first),
const Offset(0.0, 175.0), offsetMoreOrLessEquals(const Offset(0.0, 170.0), epsilon: 0.5),
); );
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '0').first), tester.getTopLeft(find.widgetWithText(SizedBox, '0').first),
...@@ -347,7 +347,7 @@ void main() { ...@@ -347,7 +347,7 @@ void main() {
// The item that was in the center now moved a bit. // The item that was in the center now moved a bit.
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '10')), tester.getTopLeft(find.widgetWithText(SizedBox, '10')),
const Offset(200.0, 280.0), const Offset(200.0, 250.0),
); );
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -366,7 +366,7 @@ void main() { ...@@ -366,7 +366,7 @@ void main() {
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '10')).dy, tester.getTopLeft(find.widgetWithText(SizedBox, '10')).dy,
// It's down by 100.0 now. // It's down by 100.0 now.
moreOrLessEquals(350.0, epsilon: 0.5), moreOrLessEquals(340.0, epsilon: 0.5),
); );
expect(selectedItems, <int>[9]); expect(selectedItems, <int>[9]);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
......
...@@ -170,14 +170,14 @@ void main() { ...@@ -170,14 +170,14 @@ void main() {
// The first item is at the center of the viewport. // The first item is at the center of the viewport.
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '0')), tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
const Offset(0.0, 250.0), offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
); );
// The last item is just before the first item. // The last item is just before the first item.
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '9')), tester.getTopLeft(find.widgetWithText(SizedBox, '9')),
const Offset(0.0, 150.0), offsetMoreOrLessEquals(const Offset(200.0, 150.0), epsilon: 15.0),
); );
controller.jumpTo(1000.0); controller.jumpTo(1000.0);
...@@ -186,7 +186,7 @@ void main() { ...@@ -186,7 +186,7 @@ void main() {
// We have passed the end of the list, the list should have looped back. // We have passed the end of the list, the list should have looped back.
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '0')), tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
const Offset(0.0, 250.0), offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
); );
}); });
...@@ -219,7 +219,7 @@ void main() { ...@@ -219,7 +219,7 @@ void main() {
await tester.pump(); await tester.pump();
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '-1000')), tester.getTopLeft(find.widgetWithText(SizedBox, '-1000')),
const Offset(0.0, 250.0), offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
); );
// Can be scrolled infinitely for positive indexes. // Can be scrolled infinitely for positive indexes.
...@@ -227,7 +227,7 @@ void main() { ...@@ -227,7 +227,7 @@ void main() {
await tester.pump(); await tester.pump();
expect( expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '1000')), tester.getTopLeft(find.widgetWithText(SizedBox, '1000')),
const Offset(0.0, 250.0), offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
); );
}); });
...@@ -1537,4 +1537,75 @@ void main() { ...@@ -1537,4 +1537,75 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(controller.offset, 700.0); expect(controller.offset, 700.0);
}); });
group('gestures', () {
testWidgets('ListWheelScrollView allows taps for on its children', (WidgetTester tester) async {
final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10);
final List<int> children = List<int>.generate(100, (int index) => index);
final List<int> paintedChildren = <int>[];
final Set<int> tappedChildren = <int>{};
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ListWheelScrollView(
controller: controller,
itemExtent: 100,
children: children
.map((int index) => GestureDetector(
key: ValueKey<int>(index),
onTap: () {
tappedChildren.add(index);
},
child: SizedBox(
width: 100,
height: 100,
child: CustomPaint(
painter: TestCallbackPainter(onPaint: () {
paintedChildren.add(index);
}),
),
),
))
.toList(),
),
),
);
// Screen is 600px tall. Item 10 is in the center and each item is 100px tall.
expect(paintedChildren, <int>[7, 8, 9, 10, 11, 12, 13]);
for (final int child in paintedChildren) {
await tester.tap(find.byKey(ValueKey<int>(child)));
}
expect(tappedChildren, paintedChildren);
});
testWidgets('ListWheelScrollView allows for horizontal drags on its children', (WidgetTester tester) async {
final PageController pageController = PageController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ListWheelScrollView(
itemExtent: 100,
children: <Widget>[
PageView(
controller: pageController,
children: List<int>.generate(100, (int index) => index)
.map((int index) => Text(index.toString()))
.toList(),
),
],
),
),
);
expect(pageController.page, 0.0);
await tester.drag(find.byType(PageView), const Offset(-800, 0));
expect(pageController.page, 1.0);
});
});
} }
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