diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index 0a6673af10..f940491868 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -1655,9 +1655,8 @@ class TransformLayer extends OffsetLayer { } if (_invertedTransform == null) return null; - final Vector4 vector = Vector4(localPosition.dx, localPosition.dy, 0.0, 1.0); - final Vector4 result = _invertedTransform.transform(vector); - return Offset(result[0], result[1]); + + return MatrixUtils.transformPoint(_invertedTransform, localPosition); } @override diff --git a/packages/flutter/test/rendering/layer_annotations_test.dart b/packages/flutter/test/rendering/layer_annotations_test.dart index 832d8ffe83..dbb8c78f98 100644 --- a/packages/flutter/test/rendering/layer_annotations_test.dart +++ b/packages/flutter/test/rendering/layer_annotations_test.dart @@ -90,7 +90,7 @@ void main() { }); test('ContainerLayer.findAllAnnotations returns children\'s opacity (true)', () { - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( ContainerLayer(), children: [ @@ -108,7 +108,7 @@ void main() { }); test('ContainerLayer.findAllAnnotations returns children\'s opacity (false)', () { - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( ContainerLayer(), children: [ @@ -127,7 +127,7 @@ void main() { }); test('ContainerLayer.findAllAnnotations returns false as opacity when finding nothing', () { - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( ContainerLayer(), children: [ @@ -148,7 +148,7 @@ void main() { const Offset insidePosition = Offset(-5, 5); const Offset outsidePosition = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( OffsetLayer(offset: const Offset(-10, 0)), children: [ @@ -175,7 +175,7 @@ void main() { const Offset insidePosition = Offset(11, 11); const Offset outsidePosition = Offset(19, 19); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( ClipRectLayer(clipRect: const Offset(10, 10) & const Size(5, 5)), children: [ @@ -214,7 +214,7 @@ void main() { const Offset insidePosition = Offset(12, 12); const Offset outsidePosition = Offset(11, 11); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( ClipRRectLayer(clipRRect: rrect), children: [ @@ -258,7 +258,7 @@ void main() { const Offset insidePosition = Offset(11, 11); const Offset outsidePosition = Offset(12, 12); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( ClipPathLayer(clipPath: path), children: [ @@ -296,7 +296,7 @@ void main() { const Offset insidePosition = Offset(40, 80); const Offset outsidePosition = Offset(20, 40); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( TransformLayer(transform: transform), children: [ @@ -324,10 +324,92 @@ void main() { ); }); + test('TransformLayer.findAllAnnotations correctly transforms with perspective', () { + // Test the 4 corners of a transformed annotated region. + final Matrix4 transform = Matrix4.identity() + ..setEntry(3, 2, 0.005) + ..rotateX(-0.2) + ..rotateY(0.2); + + final Layer root = _withBackgroundAnnotation(0, + _Layers( + TransformLayer(transform: transform), + children: [ + _TestAnnotatedLayer( + 1, + opaque: true, + size: const Size(30, 40), + offset: const Offset(10, 20), + ), + ] + ).build(), + ); + + void expectOneAnnotation({ + @required Offset globalPosition, + @required int value, + @required Offset localPosition, + }) { + expect( + root.findAllAnnotations(globalPosition).entries.toList(), + _equalToAnnotationResult( + >[ + AnnotationEntry(annotation: value, localPosition: localPosition), + ], + maxCoordinateRelativeDiff: 0.005, + ), + ); + } + + expectOneAnnotation( + globalPosition: const Offset(10.0, 19.7), + value: 0, + localPosition: const Offset(10.0, 19.7), + ); + expectOneAnnotation( + globalPosition: const Offset(10.1, 19.8), + value: 1, + localPosition: const Offset(10.0, 20.0), + ); + + expectOneAnnotation( + globalPosition: const Offset(10.5, 62.8), + value: 0, + localPosition: const Offset(10.5, 62.8), + ); + expectOneAnnotation( + globalPosition: const Offset(10.6, 62.7), + value: 1, + localPosition: const Offset(10.1, 59.9), + ); + + expectOneAnnotation( + globalPosition: const Offset(42.6, 40.8), + value: 0, + localPosition: const Offset(42.6, 40.8), + ); + expectOneAnnotation( + globalPosition: const Offset(42.5, 40.9), + value: 1, + localPosition: const Offset(39.9, 40.0), + ); + + expectOneAnnotation( + globalPosition: const Offset(43.5, 63.5), + value: 0, + localPosition: const Offset(43.5, 63.5), + ); + expectOneAnnotation( + globalPosition: const Offset(43.4, 63.4), + value: 1, + localPosition: const Offset(39.9, 59.9), + ); + }); + test('TransformLayer.findAllAnnotations skips when transform is irreversible', () { final Matrix4 transform = Matrix4.diagonal3Values(1, 0, 1); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( TransformLayer(transform: transform), children: [ @@ -360,7 +442,7 @@ void main() { const Offset insidePosition = Offset(11, 11); const Offset outsidePosition = Offset(12, 12); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( PhysicalModelLayer( clipPath: path, @@ -398,7 +480,7 @@ void main() { const Offset insidePosition = Offset(-5, 5); const Offset outsidePosition = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( LeaderLayer( link: LayerLink(), @@ -428,7 +510,7 @@ void main() { 'and return the given opacity (false) during a successful hit', () { const Offset position = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer(1, opaque: false), children: [ @@ -451,7 +533,7 @@ void main() { 'and return the given opacity (true) during a successful hit', () { const Offset position = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer(1, opaque: true), children: [ @@ -472,7 +554,7 @@ void main() { test('AnnotatedRegionLayer.findAllAnnotations has default opacity as false', () { const Offset position = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer(1), children: [ @@ -495,7 +577,7 @@ void main() { 'children\'s opacity (false) during a failed hit', () { const Offset position = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer(1, opaque: true, size: Size.zero), children: [ @@ -517,7 +599,7 @@ void main() { 'children\'s opacity (true) during a failed hit', () { const Offset position = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer(1, opaque: false, size: Size.zero), children: [ @@ -538,7 +620,7 @@ void main() { 'during a successful hit if it is not opaque', () { const Offset position = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer(1, opaque: false), children: [ @@ -561,7 +643,7 @@ void main() { 'during a successful hit if it is opaque', () { const Offset position = Offset(5, 5); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer(1, opaque: true), children: [ @@ -584,7 +666,7 @@ void main() { // The target position would have fallen outside if not for the offset. const Offset position = Offset(100, 100); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer( 1, @@ -619,7 +701,7 @@ void main() { // The target position would have fallen inside if not for the offset. const Offset position = Offset(10, 10); - final Layer root = _appendAnnotationIfNotOpaque(1000, + final Layer root = _withBackgroundAnnotation(1000, _Layers( AnnotatedRegionLayer( 1, @@ -642,14 +724,12 @@ void main() { }); } -/// Append `value` to the result of the annotations test of `layer` if and only -/// if it is opaque at the given location. +/// A [ContainerLayer] that contains a stack of layers: `layer` in the front, +/// and another layer annotated with `value` in the back. /// /// It is a utility function that helps checking the opacity returned by /// [Layer.findAnnotations]. -/// Technically it is a [ContainerLayer] that contains `layer` followed by -/// another layer annotated with `value`. -Layer _appendAnnotationIfNotOpaque(int value, Layer layer) { +Layer _withBackgroundAnnotation(int value, Layer layer) { return _Layers( ContainerLayer(), children: [ @@ -746,11 +826,22 @@ class _TestAnnotatedLayer extends Layer { } } -Matcher _equalToAnnotationResult(List> list) { +bool _almostEqual(double a, double b, double maxRelativeDiff) { + assert(maxRelativeDiff >= 0); + assert(maxRelativeDiff < 1); + return (a - b).abs() <= a.abs() * maxRelativeDiff; +} + +Matcher _equalToAnnotationResult( + List> list, { + double maxCoordinateRelativeDiff = 0, +}) { return pairwiseCompare, AnnotationEntry>( list, (AnnotationEntry a, AnnotationEntry b) { - return a.annotation == b.annotation && a.localPosition == b.localPosition; + return a.annotation == b.annotation + && _almostEqual(a.localPosition.dx, b.localPosition.dx, maxCoordinateRelativeDiff) + && _almostEqual(a.localPosition.dy, b.localPosition.dy, maxCoordinateRelativeDiff); }, 'equal to', );