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

Fix ListWheelScrollView gestures and paint coordinates in tests
This commit is contained in:
nt4f04uNd 2023-03-30 14:15:26 +06:00 committed by GitHub
parent 9dc4f83965
commit 3a7daaba78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 181 additions and 25 deletions

View file

@ -10,6 +10,7 @@ import 'package:vector_math/vector_math_64.dart' show Matrix4;
import 'box.dart';
import 'layer.dart';
import 'object.dart';
import 'proxy_box.dart';
import 'viewport.dart';
import 'viewport_offset.dart';
@ -55,6 +56,17 @@ class ListWheelParentData extends ContainerBoxParentData<RenderBox> {
///
/// This must be maintained by the [ListWheelChildManager].
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.
@ -964,12 +976,14 @@ class RenderListWheelViewport
Matrix4 cylindricalTransform,
Offset offsetToCenter,
) {
final Offset paintOriginOffset = offset + offsetToCenter;
// Paint child cylindrically, without [overAndUnderCenterOpacity].
void painter(PaintingContext context, Offset offset) {
context.paintChild(
child,
// Paint everything in the center (e.g. angle = 0), then transform.
offset + offsetToCenter,
paintOriginOffset,
);
}
@ -985,6 +999,12 @@ class RenderListWheelViewport
// Pre-transform painting function.
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
@ -1014,12 +1034,33 @@ class RenderListWheelViewport
return result;
}
/// This returns the matrices relative to the **untransformed plane's viewport
/// painting coordinates** system.
static bool _debugAssertValidPaintTransform(ListWheelParentData parentData) {
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
void applyPaintTransform(RenderBox child, Matrix4 transform) {
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
@ -1031,7 +1072,36 @@ class RenderListWheelViewport
}
@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
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) {

View file

@ -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(
mode: CupertinoDatePickerMode.date,
@ -600,6 +600,18 @@ void main() {
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(
CupertinoApp(
home: CupertinoPageScaffold(
@ -614,9 +626,13 @@ void main() {
),
);
double decemberX = tester.getBottomLeft(find.text('December')).dx;
double octoberX = tester.getBottomLeft(find.text('October')).dx;
final double distance = octoberX - decemberX;
// The wheel does not bend outwards.
for (final String month in visibleMonthsExceptTheCenter) {
expect(
tester.getBottomLeft(find.text(centerMonth)).dx,
lessThan(tester.getBottomLeft(find.text(month)).dx),
);
}
await tester.pumpWidget(
CupertinoApp(
@ -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.
expect(
distance >= (octoberX - decemberX),
true,
);
for (final String month in visibleMonthsExceptTheCenter) {
expect(
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 {

View file

@ -118,7 +118,7 @@ void main() {
expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '1').first),
const Offset(0.0, 175.0),
offsetMoreOrLessEquals(const Offset(0.0, 170.0), epsilon: 0.5),
);
expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '0').first),
@ -347,7 +347,7 @@ void main() {
// The item that was in the center now moved a bit.
expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '10')),
const Offset(200.0, 280.0),
const Offset(200.0, 250.0),
);
await tester.pumpAndSettle();
@ -366,7 +366,7 @@ void main() {
expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '10')).dy,
// It's down by 100.0 now.
moreOrLessEquals(350.0, epsilon: 0.5),
moreOrLessEquals(340.0, epsilon: 0.5),
);
expect(selectedItems, <int>[9]);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));

View file

@ -170,14 +170,14 @@ void main() {
// The first item is at the center of the viewport.
expect(
tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
const Offset(0.0, 250.0),
tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
);
// The last item is just before the first item.
expect(
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);
@ -186,7 +186,7 @@ void main() {
// We have passed the end of the list, the list should have looped back.
expect(
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() {
await tester.pump();
expect(
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.
@ -227,7 +227,7 @@ void main() {
await tester.pump();
expect(
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() {
await tester.pumpAndSettle();
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);
});
});
}