| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:ui'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:vector_math/vector_math_64.dart'; |
| |
| void main() { |
| test('ContainerLayer.findAllAnnotations returns all results from its children', () { |
| final Layer root = _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(1, opaque: false), |
| _TestAnnotatedLayer(2, opaque: false), |
| _TestAnnotatedLayer(3, opaque: false), |
| ] |
| ).build(); |
| |
| expect( |
| root.findAllAnnotations<int>(Offset.zero).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 3, localPosition: Offset.zero), |
| const AnnotationEntry<int>(annotation: 2, localPosition: Offset.zero), |
| const AnnotationEntry<int>(annotation: 1, localPosition: Offset.zero), |
| ]), |
| ); |
| }); |
| |
| test('ContainerLayer.find returns the first result from its children', () { |
| final Layer root = _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(1, opaque: false), |
| _TestAnnotatedLayer(2, opaque: false), |
| _TestAnnotatedLayer(3, opaque: false), |
| ] |
| ).build(); |
| |
| final int result = root.find<int>(Offset.zero); |
| expect(result, 3); |
| }); |
| |
| test('ContainerLayer.findAllAnnotations returns empty result when finding nothing', () { |
| final Layer root = _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(1, opaque: false), |
| _TestAnnotatedLayer(2, opaque: false), |
| _TestAnnotatedLayer(3, opaque: false), |
| ] |
| ).build(); |
| |
| expect(root.findAllAnnotations<double>(Offset.zero).entries.isEmpty, isTrue); |
| }); |
| |
| test('ContainerLayer.find returns null when finding nothing', () { |
| final Layer root = _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(1, opaque: false), |
| _TestAnnotatedLayer(2, opaque: false), |
| _TestAnnotatedLayer(3, opaque: false), |
| ] |
| ).build(); |
| |
| expect(root.find<double>(Offset.zero), isNull); |
| }); |
| |
| test('ContainerLayer.findAllAnnotations stops at the first opaque child', () { |
| final Layer root = _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(1, opaque: false), |
| _TestAnnotatedLayer(2, opaque: true), |
| _TestAnnotatedLayer(3, opaque: false), |
| ] |
| ).build(); |
| |
| expect( |
| root.findAllAnnotations<int>(Offset.zero).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 3, localPosition: Offset(0, 0)), |
| const AnnotationEntry<int>(annotation: 2, localPosition: Offset(0, 0)), |
| ]), |
| ); |
| }); |
| |
| test("ContainerLayer.findAllAnnotations returns children's opacity (true)", () { |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: true), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(Offset.zero).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: Offset(0, 0)), |
| ]), |
| ); |
| }); |
| |
| test("ContainerLayer.findAllAnnotations returns children's opacity (false)", () { |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false), |
| ], |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(Offset.zero).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: Offset(0, 0)), |
| const AnnotationEntry<int>(annotation: 1000, localPosition: Offset(0, 0)), |
| ]), |
| ); |
| }); |
| |
| test('ContainerLayer.findAllAnnotations returns false as opacity when finding nothing', () { |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false, size: Size.zero), |
| ], |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(Offset.zero).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: Offset(0, 0)), |
| ]), |
| ); |
| }); |
| |
| test('OffsetLayer.findAllAnnotations respects offset', () { |
| const Offset insidePosition = Offset(-5, 5); |
| const Offset outsidePosition = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| OffsetLayer(offset: const Offset(-10, 0)), |
| children: <Object>[ |
| _TestAnnotatedLayer(1, opaque: true, size: const Size(10, 10)), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(insidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1, localPosition: Offset(5, 5)), |
| ]), |
| ); |
| expect( |
| root.findAllAnnotations<int>(outsidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: Offset(5, 5)), |
| ]), |
| ); |
| }); |
| |
| test('ClipRectLayer.findAllAnnotations respects clipRect', () { |
| const Offset insidePosition = Offset(11, 11); |
| const Offset outsidePosition = Offset(19, 19); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| ClipRectLayer(clipRect: const Offset(10, 10) & const Size(5, 5)), |
| children: <Object>[ |
| _TestAnnotatedLayer( |
| 1, |
| opaque: true, |
| size: const Size(10, 10), |
| offset: const Offset(10, 10), |
| ), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(insidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1, localPosition: insidePosition), |
| ]), |
| ); |
| expect( |
| root.findAllAnnotations<int>(outsidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition), |
| ]), |
| ); |
| }); |
| |
| test('ClipRRectLayer.findAllAnnotations respects clipRRect', () { |
| // For a curve of radius 4 centered at (4, 4), |
| // location (1, 1) is outside, while (2, 2) is inside. |
| // Here we shift this RRect by (10, 10). |
| final RRect rrect = RRect.fromRectAndRadius( |
| const Offset(10, 10) & const Size(10, 10), |
| const Radius.circular(4), |
| ); |
| const Offset insidePosition = Offset(12, 12); |
| const Offset outsidePosition = Offset(11, 11); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| ClipRRectLayer(clipRRect: rrect), |
| children: <Object>[ |
| _TestAnnotatedLayer( |
| 1, |
| opaque: true, |
| size: const Size(10, 10), |
| offset: const Offset(10, 10), |
| ), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(insidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1, localPosition: insidePosition), |
| ]), |
| ); |
| expect( |
| root.findAllAnnotations<int>(outsidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition), |
| ]), |
| ); |
| }); |
| |
| test('ClipPathLayer.findAllAnnotations respects clipPath', () { |
| // For this triangle, location (1, 1) is inside, while (2, 2) is outside. |
| // 2 |
| // ————— |
| // | / |
| // | / |
| // 2 |/ |
| final Path originalPath = Path(); |
| originalPath.lineTo(2, 0); |
| originalPath.lineTo(0, 2); |
| originalPath.close(); |
| // Shift this clip path by (10, 10). |
| final Path path = originalPath.shift(const Offset(10, 10)); |
| const Offset insidePosition = Offset(11, 11); |
| const Offset outsidePosition = Offset(12, 12); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| ClipPathLayer(clipPath: path), |
| children: <Object>[ |
| _TestAnnotatedLayer( |
| 1, |
| opaque: true, |
| size: const Size(10, 10), |
| offset: const Offset(10, 10), |
| ), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(insidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1, localPosition: insidePosition), |
| ]), |
| ); |
| expect( |
| root.findAllAnnotations<int>(outsidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition), |
| ]), |
| ); |
| }); |
| |
| test('TransformLayer.findAllAnnotations respects transform', () { |
| // Matrix `transform` enlarges the target by (2x, 4x), then shift it by |
| // (10, 20). |
| final Matrix4 transform = Matrix4.diagonal3Values(2, 4, 1) |
| ..setTranslation(Vector3(10, 20, 0)); |
| // The original region is Offset(10, 10) & Size(10, 10) |
| // The transformed region is Offset(30, 60) & Size(20, 40) |
| const Offset insidePosition = Offset(40, 80); |
| const Offset outsidePosition = Offset(20, 40); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| TransformLayer(transform: transform), |
| children: <Object>[ |
| _TestAnnotatedLayer( |
| 1, |
| opaque: true, |
| size: const Size(10, 10), |
| offset: const Offset(10, 10), |
| ), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(insidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1, localPosition: Offset(15, 15)), |
| ]), |
| ); |
| expect( |
| root.findAllAnnotations<int>(outsidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition), |
| ]), |
| ); |
| }); |
| |
| 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: <Object>[ |
| _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<int>(globalPosition).entries.toList(), |
| _equalToAnnotationResult<int>( |
| <AnnotationEntry<int>>[ |
| AnnotationEntry<int>(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 = _withBackgroundAnnotation(1000, |
| _Layers( |
| TransformLayer(transform: transform), |
| children: <Object>[ |
| _TestAnnotatedLayer(1, opaque: true), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(Offset.zero).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: Offset.zero), |
| ]), |
| ); |
| }); |
| |
| test('PhysicalModelLayer.findAllAnnotations respects clipPath', () { |
| // For this triangle, location (1, 1) is inside, while (2, 2) is outside. |
| // 2 |
| // ————— |
| // | / |
| // | / |
| // 2 |/ |
| final Path originalPath = Path(); |
| originalPath.lineTo(2, 0); |
| originalPath.lineTo(0, 2); |
| originalPath.close(); |
| // Shift this clip path by (10, 10). |
| final Path path = originalPath.shift(const Offset(10, 10)); |
| const Offset insidePosition = Offset(11, 11); |
| const Offset outsidePosition = Offset(12, 12); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| PhysicalModelLayer( |
| clipPath: path, |
| elevation: 10, |
| color: const Color.fromARGB(0, 0, 0, 0), |
| shadowColor: const Color.fromARGB(0, 0, 0, 0), |
| ), |
| children: <Object>[ |
| _TestAnnotatedLayer( |
| 1, |
| opaque: true, |
| size: const Size(10, 10), |
| offset: const Offset(10, 10), |
| ), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(insidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1, localPosition: insidePosition), |
| ]), |
| ); |
| expect( |
| root.findAllAnnotations<int>(outsidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition), |
| ]), |
| ); |
| }); |
| |
| |
| test('LeaderLayer.findAllAnnotations respects offset', () { |
| const Offset insidePosition = Offset(-5, 5); |
| const Offset outsidePosition = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| LeaderLayer( |
| link: LayerLink(), |
| offset: const Offset(-10, 0), |
| ), |
| children: <Object>[ |
| _TestAnnotatedLayer(1, opaque: true, size: const Size(10, 10)), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(insidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1, localPosition: Offset(5, 5)), |
| ]), |
| ); |
| expect( |
| root.findAllAnnotations<int>(outsidePosition).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition), |
| ]), |
| ); |
| }); |
| |
| test('AnnotatedRegionLayer.findAllAnnotations should append to the list ' |
| 'and return the given opacity (false) during a successful hit', () { |
| const Offset position = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>(1, opaque: false), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1000, localPosition: position), |
| ]), |
| ); |
| }); |
| |
| test('AnnotatedRegionLayer.findAllAnnotations should append to the list ' |
| 'and return the given opacity (true) during a successful hit', () { |
| const Offset position = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>(1, opaque: true), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1, localPosition: position), |
| ]), |
| ); |
| }); |
| |
| test('AnnotatedRegionLayer.findAllAnnotations has default opacity as false', () { |
| const Offset position = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>(1), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1000, localPosition: position), |
| ]), |
| ); |
| }); |
| |
| test('AnnotatedRegionLayer.findAllAnnotations should still check children and return ' |
| "children's opacity (false) during a failed hit", () { |
| const Offset position = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>(1, opaque: true, size: Size.zero), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false), |
| ] |
| ).build(), |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1000, localPosition: position), |
| ]), |
| ); |
| }); |
| |
| test('AnnotatedRegionLayer.findAllAnnotations should still check children and return ' |
| "children's opacity (true) during a failed hit", () { |
| const Offset position = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>(1, opaque: false, size: Size.zero), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: true), |
| ] |
| ).build() |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| ]), |
| ); |
| }); |
| |
| test("AnnotatedRegionLayer.findAllAnnotations should not add to children's opacity " |
| 'during a successful hit if it is not opaque', () { |
| const Offset position = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>(1, opaque: false), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false), |
| ] |
| ).build() |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1000, localPosition: position), |
| ]), |
| ); |
| }); |
| |
| test("AnnotatedRegionLayer.findAllAnnotations should add to children's opacity " |
| 'during a successful hit if it is opaque', () { |
| const Offset position = Offset(5, 5); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>(1, opaque: true), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false), |
| ] |
| ).build() |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1, localPosition: position), |
| ]), |
| ); |
| }); |
| |
| test('AnnotatedRegionLayer.findAllAnnotations should clip its annotation ' |
| 'using size and offset (positive)', () { |
| // The target position would have fallen outside if not for the offset. |
| const Offset position = Offset(100, 100); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>( |
| 1, |
| size: const Size(20, 20), |
| offset: const Offset(90, 90), |
| ), |
| children: <Object>[ |
| _TestAnnotatedLayer( |
| 2, |
| opaque: false, |
| // Use this offset to make sure AnnotatedRegionLayer's offset |
| // does not affect its children. |
| offset: const Offset(20, 20), |
| size: const Size(110, 110), |
| ), |
| ] |
| ).build() |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1, localPosition: Offset(10, 10)), |
| const AnnotationEntry<int>(annotation: 1000, localPosition: position), |
| ]), |
| ); |
| }); |
| |
| test('AnnotatedRegionLayer.findAllAnnotations should clip its annotation ' |
| 'using size and offset (negative)', () { |
| // The target position would have fallen inside if not for the offset. |
| const Offset position = Offset(10, 10); |
| |
| final Layer root = _withBackgroundAnnotation(1000, |
| _Layers( |
| AnnotatedRegionLayer<int>( |
| 1, |
| size: const Size(20, 20), |
| offset: const Offset(90, 90), |
| ), |
| children: <Object>[ |
| _TestAnnotatedLayer(2, opaque: false, size: const Size(110, 110)), |
| ] |
| ).build() |
| ); |
| |
| expect( |
| root.findAllAnnotations<int>(position).entries.toList(), |
| _equalToAnnotationResult<int>(<AnnotationEntry<int>>[ |
| const AnnotationEntry<int>(annotation: 2, localPosition: position), |
| const AnnotationEntry<int>(annotation: 1000, localPosition: position), |
| ]), |
| ); |
| }); |
| } |
| |
| /// 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]. |
| Layer _withBackgroundAnnotation(int value, Layer layer) { |
| return _Layers( |
| ContainerLayer(), |
| children: <Object>[ |
| _TestAnnotatedLayer(value, opaque: false), |
| layer, |
| ], |
| ).build(); |
| } |
| |
| // A utility class that helps building a layer tree. |
| class _Layers { |
| _Layers(this.root, {this.children}); |
| |
| final ContainerLayer root; |
| // Each element must be instance of Layer or _Layers. |
| final List<Object> children; |
| bool _assigned = false; |
| |
| // Build the layer tree by calling each child's `build`, then append children |
| // to [root]. Returns the root. |
| Layer build() { |
| assert(!_assigned); |
| _assigned = true; |
| if (children != null) { |
| for (final Object child in children) { |
| Layer layer; |
| if (child is Layer) { |
| layer = child; |
| } else if (child is _Layers) { |
| layer = child.build(); |
| } else { |
| assert(false, 'Element of _Layers.children must be instance of Layer or _Layers'); |
| } |
| root.append(layer); |
| } |
| } |
| return root; |
| } |
| } |
| |
| // This layer's [findAnnotation] can be controlled by the given arguments. |
| class _TestAnnotatedLayer extends Layer { |
| _TestAnnotatedLayer(this.value, { |
| @required this.opaque, |
| this.offset = Offset.zero, |
| this.size, |
| }); |
| |
| // The value added to result in [findAnnotations] during a successful hit. |
| final int value; |
| |
| // The return value of [findAnnotations] during a successful hit. |
| final bool opaque; |
| |
| /// The [offset] is optionally used to translate the clip region for the |
| /// hit-testing of [find] by [offset]. |
| /// |
| /// If not provided, offset defaults to [Offset.zero]. |
| /// |
| /// Ignored if [size] is not set. |
| final Offset offset; |
| |
| /// The [size] is optionally used to clip the hit-testing of [find]. |
| /// |
| /// If not provided, all offsets are considered to be contained within this |
| /// layer, unless an ancestor layer applies a clip. |
| /// |
| /// If [offset] is set, then the offset is applied to the size region before |
| /// hit testing in [find]. |
| final Size size; |
| |
| @override |
| EngineLayer addToScene(SceneBuilder builder, [Offset layerOffset = Offset.zero]) { |
| return null; |
| } |
| |
| // This implementation is hit when the type is `int` and position is within |
| // [offset] & [size]. If it is hit, it adds [value] to result and returns |
| // [opaque]; otherwise it directly returns false. |
| @override |
| bool findAnnotations<S>( |
| AnnotationResult<S> result, |
| Offset localPosition, { |
| bool onlyFirst, |
| }) { |
| if (S != int) |
| return false; |
| if (size != null && !(offset & size).contains(localPosition)) |
| return false; |
| final Object untypedValue = value; |
| final S typedValue = untypedValue as S; |
| result.add(AnnotationEntry<S>(annotation: typedValue, localPosition: localPosition)); |
| return opaque; |
| } |
| } |
| |
| bool _almostEqual(double a, double b, double maxRelativeDiff) { |
| assert(maxRelativeDiff >= 0); |
| assert(maxRelativeDiff < 1); |
| return (a - b).abs() <= a.abs() * maxRelativeDiff; |
| } |
| |
| Matcher _equalToAnnotationResult<T>( |
| List<AnnotationEntry<int>> list, { |
| double maxCoordinateRelativeDiff = 0, |
| }) { |
| return pairwiseCompare<AnnotationEntry<int>, AnnotationEntry<int>>( |
| list, |
| (AnnotationEntry<int> a, AnnotationEntry<int> b) { |
| return a.annotation == b.annotation |
| && _almostEqual(a.localPosition.dx, b.localPosition.dx, maxCoordinateRelativeDiff) |
| && _almostEqual(a.localPosition.dy, b.localPosition.dy, maxCoordinateRelativeDiff); |
| }, |
| 'equal to', |
| ); |
| } |