layer_annotations_test.dart 25.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11
// 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/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vector_math/vector_math_64.dart';

void main() {
12
  test('ContainerLayer.findAllAnnotations returns all results from its children', () {
13 14 15 16 17 18
    final Layer root = _Layers(
      ContainerLayer(),
      children: <Object>[
        _TestAnnotatedLayer(1, opaque: false),
        _TestAnnotatedLayer(2, opaque: false),
        _TestAnnotatedLayer(3, opaque: false),
19
      ],
20 21 22
    ).build();

    expect(
23
      root.findAllAnnotations<int>(Offset.zero).entries.toList(),
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
      _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),
39
      ],
40 41
    ).build();

42
    final int result = root.find<int>(Offset.zero)!;
43
    expect(result, 3);
44 45
  });

46
  test('ContainerLayer.findAllAnnotations returns empty result when finding nothing', () {
47 48 49 50 51 52
    final Layer root = _Layers(
      ContainerLayer(),
      children: <Object>[
        _TestAnnotatedLayer(1, opaque: false),
        _TestAnnotatedLayer(2, opaque: false),
        _TestAnnotatedLayer(3, opaque: false),
53
      ],
54 55
    ).build();

56
    expect(root.findAllAnnotations<double>(Offset.zero).entries.isEmpty, isTrue);
57 58 59 60 61 62 63 64 65
  });

  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),
66
      ],
67 68 69 70 71
    ).build();

    expect(root.find<double>(Offset.zero), isNull);
  });

72
  test('ContainerLayer.findAllAnnotations stops at the first opaque child', () {
73 74 75 76 77 78
    final Layer root = _Layers(
      ContainerLayer(),
      children: <Object>[
        _TestAnnotatedLayer(1, opaque: false),
        _TestAnnotatedLayer(2, opaque: true),
        _TestAnnotatedLayer(3, opaque: false),
79
      ],
80 81 82
    ).build();

    expect(
83
      root.findAllAnnotations<int>(Offset.zero).entries.toList(),
84
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
85 86
        const AnnotationEntry<int>(annotation: 3, localPosition: Offset.zero),
        const AnnotationEntry<int>(annotation: 2, localPosition: Offset.zero),
87 88 89 90
      ]),
    );
  });

91
  test("ContainerLayer.findAllAnnotations returns children's opacity (true)", () {
92 93
    final Layer root = _withBackgroundAnnotation(
      1000,
94 95 96 97
      _Layers(
        ContainerLayer(),
        children: <Object>[
          _TestAnnotatedLayer(2, opaque: true),
98
        ],
99 100 101 102
      ).build(),
    );

    expect(
103
      root.findAllAnnotations<int>(Offset.zero).entries.toList(),
104
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
105
        const AnnotationEntry<int>(annotation: 2, localPosition: Offset.zero),
106 107 108 109
      ]),
    );
  });

110
  test("ContainerLayer.findAllAnnotations returns children's opacity (false)", () {
111 112
    final Layer root = _withBackgroundAnnotation(
      1000,
113 114 115 116 117 118 119 120 121
      _Layers(
        ContainerLayer(),
        children: <Object>[
          _TestAnnotatedLayer(2, opaque: false),
        ],
      ).build(),
    );

    expect(
122
      root.findAllAnnotations<int>(Offset.zero).entries.toList(),
123
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
124 125
        const AnnotationEntry<int>(annotation: 2, localPosition: Offset.zero),
        const AnnotationEntry<int>(annotation: 1000, localPosition: Offset.zero),
126 127 128 129
      ]),
    );
  });

130
  test('ContainerLayer.findAllAnnotations returns false as opacity when finding nothing', () {
131 132
    final Layer root = _withBackgroundAnnotation(
      1000,
133 134 135 136 137 138 139 140 141
      _Layers(
        ContainerLayer(),
        children: <Object>[
          _TestAnnotatedLayer(2, opaque: false, size: Size.zero),
        ],
      ).build(),
    );

    expect(
142
      root.findAllAnnotations<int>(Offset.zero).entries.toList(),
143
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
144
        const AnnotationEntry<int>(annotation: 1000, localPosition: Offset.zero),
145 146 147 148
      ]),
    );
  });

149
  test('OffsetLayer.findAllAnnotations respects offset', () {
150 151 152
    const Offset insidePosition = Offset(-5, 5);
    const Offset outsidePosition = Offset(5, 5);

153 154
    final Layer root = _withBackgroundAnnotation(
      1000,
155 156 157 158
      _Layers(
        OffsetLayer(offset: const Offset(-10, 0)),
        children: <Object>[
          _TestAnnotatedLayer(1, opaque: true, size: const Size(10, 10)),
159
        ],
160 161 162 163
      ).build(),
    );

    expect(
164
      root.findAllAnnotations<int>(insidePosition).entries.toList(),
165 166 167 168 169
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1, localPosition: Offset(5, 5)),
      ]),
    );
    expect(
170
      root.findAllAnnotations<int>(outsidePosition).entries.toList(),
171 172 173 174 175 176
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: Offset(5, 5)),
      ]),
    );
  });

177
  test('ClipRectLayer.findAllAnnotations respects clipRect', () {
178 179 180
    const Offset insidePosition = Offset(11, 11);
    const Offset outsidePosition = Offset(19, 19);

181 182
    final Layer root = _withBackgroundAnnotation(
      1000,
183 184 185 186 187 188 189 190 191
      _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),
          ),
192
        ],
193 194 195 196
      ).build(),
    );

    expect(
197
      root.findAllAnnotations<int>(insidePosition).entries.toList(),
198 199 200 201 202
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1, localPosition: insidePosition),
      ]),
    );
    expect(
203
      root.findAllAnnotations<int>(outsidePosition).entries.toList(),
204 205 206 207 208 209
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition),
      ]),
    );
  });

210
  test('ClipRRectLayer.findAllAnnotations respects clipRRect', () {
211 212 213 214 215 216 217 218 219 220
    // 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);

221 222
    final Layer root = _withBackgroundAnnotation(
      1000,
223 224 225 226 227 228 229 230 231
      _Layers(
        ClipRRectLayer(clipRRect: rrect),
        children: <Object>[
          _TestAnnotatedLayer(
            1,
            opaque: true,
            size: const Size(10, 10),
            offset: const Offset(10, 10),
          ),
232
        ],
233 234 235 236
      ).build(),
    );

    expect(
237
      root.findAllAnnotations<int>(insidePosition).entries.toList(),
238 239 240 241 242
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1, localPosition: insidePosition),
      ]),
    );
    expect(
243
      root.findAllAnnotations<int>(outsidePosition).entries.toList(),
244 245 246 247 248 249
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition),
      ]),
    );
  });

250
  test('ClipPathLayer.findAllAnnotations respects clipPath', () {
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
    // 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);

266 267
    final Layer root = _withBackgroundAnnotation(
      1000,
268 269 270 271 272 273 274 275 276
      _Layers(
        ClipPathLayer(clipPath: path),
        children: <Object>[
          _TestAnnotatedLayer(
            1,
            opaque: true,
            size: const Size(10, 10),
            offset: const Offset(10, 10),
          ),
277
        ],
278 279 280 281
      ).build(),
    );

    expect(
282
      root.findAllAnnotations<int>(insidePosition).entries.toList(),
283 284 285 286 287
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1, localPosition: insidePosition),
      ]),
    );
    expect(
288
      root.findAllAnnotations<int>(outsidePosition).entries.toList(),
289 290 291 292 293 294
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition),
      ]),
    );
  });

295
  test('TransformLayer.findAllAnnotations respects transform', () {
296 297
    // Matrix `transform` enlarges the target by (2x, 4x), then shift it by
    // (10, 20).
298
    final Matrix4 transform = Matrix4.diagonal3Values(2, 4, 1)..setTranslation(Vector3(10, 20, 0));
299 300 301 302 303
    // 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);

304 305
    final Layer root = _withBackgroundAnnotation(
      1000,
306 307 308 309 310 311 312 313 314
      _Layers(
        TransformLayer(transform: transform),
        children: <Object>[
          _TestAnnotatedLayer(
            1,
            opaque: true,
            size: const Size(10, 10),
            offset: const Offset(10, 10),
          ),
315
        ],
316 317 318 319
      ).build(),
    );

    expect(
320
      root.findAllAnnotations<int>(insidePosition).entries.toList(),
321 322 323 324 325
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1, localPosition: Offset(15, 15)),
      ]),
    );
    expect(
326
      root.findAllAnnotations<int>(outsidePosition).entries.toList(),
327 328 329 330 331 332
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition),
      ]),
    );
  });

333 334 335 336 337 338 339
  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);

340 341
    final Layer root = _withBackgroundAnnotation(
      0,
342 343 344 345 346 347 348 349 350
      _Layers(
        TransformLayer(transform: transform),
        children: <Object>[
          _TestAnnotatedLayer(
            1,
            opaque: true,
            size: const Size(30, 40),
            offset: const Offset(10, 20),
          ),
351
        ],
352 353 354 355
      ).build(),
    );

    void expectOneAnnotation({
356 357 358
      required Offset globalPosition,
      required int value,
      required Offset localPosition,
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
    }) {
      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),
    );
  });

416
  test('TransformLayer.findAllAnnotations skips when transform is irreversible', () {
417 418
    final Matrix4 transform = Matrix4.diagonal3Values(1, 0, 1);

419 420
    final Layer root = _withBackgroundAnnotation(
      1000,
421 422 423 424
      _Layers(
        TransformLayer(transform: transform),
        children: <Object>[
          _TestAnnotatedLayer(1, opaque: true),
425
        ],
426 427 428 429
      ).build(),
    );

    expect(
430
      root.findAllAnnotations<int>(Offset.zero).entries.toList(),
431 432 433 434 435 436
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: Offset.zero),
      ]),
    );
  });

437
  test('LeaderLayer.findAllAnnotations respects offset', () {
438 439 440
    const Offset insidePosition = Offset(-5, 5);
    const Offset outsidePosition = Offset(5, 5);

441 442
    final Layer root = _withBackgroundAnnotation(
      1000,
443 444 445 446 447 448 449
      _Layers(
        LeaderLayer(
          link: LayerLink(),
          offset: const Offset(-10, 0),
        ),
        children: <Object>[
          _TestAnnotatedLayer(1, opaque: true, size: const Size(10, 10)),
450
        ],
451 452 453 454
      ).build(),
    );

    expect(
455
      root.findAllAnnotations<int>(insidePosition).entries.toList(),
456 457 458 459 460
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1, localPosition: Offset(5, 5)),
      ]),
    );
    expect(
461
      root.findAllAnnotations<int>(outsidePosition).entries.toList(),
462 463 464 465 466 467
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition),
      ]),
    );
  });

468 469 470 471 472 473 474 475 476
  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(
477
          AnnotatedRegionLayer<int>(1),
478 479 480 481 482
          children: <Object>[
            _TestAnnotatedLayer(2, opaque: false),
          ],
        ).build(),
      );
483

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
      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(),
      );
510

511 512 513 514 515 516 517 518 519
      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),
        ]),
      );
    },
  );
520

521
  test('AnnotatedRegionLayer.findAllAnnotations has default opacity as false', () {
522 523
    const Offset position = Offset(5, 5);

524 525
    final Layer root = _withBackgroundAnnotation(
      1000,
526 527 528 529
      _Layers(
        AnnotatedRegionLayer<int>(1),
        children: <Object>[
          _TestAnnotatedLayer(2, opaque: false),
530
        ],
531 532 533 534
      ).build(),
    );

    expect(
535
      root.findAllAnnotations<int>(position).entries.toList(),
536 537 538 539 540 541 542 543
      _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),
      ]),
    );
  });

544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
  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(),
      );
559

560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
      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(
579
          AnnotatedRegionLayer<int>(1, size: Size.zero),
580 581 582 583 584
          children: <Object>[
            _TestAnnotatedLayer(2, opaque: true),
          ],
        ).build(),
      );
585

586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
      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(
604
          AnnotatedRegionLayer<int>(1),
605 606 607 608 609
          children: <Object>[
            _TestAnnotatedLayer(2, opaque: false),
          ],
        ).build(),
      );
610

611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
      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(),
      );
637

638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
      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),
662
          ),
663 664 665 666 667 668 669 670 671 672 673 674
          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(),
      );
675

676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
      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(),
      );
707

708 709 710 711 712 713 714 715 716
      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),
        ]),
      );
    },
  );
717 718
}

719 720
/// A [ContainerLayer] that contains a stack of layers: `layer` in the front,
/// and another layer annotated with `value` in the back.
721 722 723
///
/// It is a utility function that helps checking the opacity returned by
/// [Layer.findAnnotations].
724
Layer _withBackgroundAnnotation(int value, Layer layer) {
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739
  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.
740
  final List<Object>? children;
741 742 743 744 745 746 747 748
  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) {
749 750
      for (final Object child in children!) {
        late Layer layer;
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
        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 {
767 768
  _TestAnnotatedLayer(
    this.value, {
769
    required this.opaque,
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
    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].
795
  final Size? size;
796 797

  @override
798
  EngineLayer? addToScene(SceneBuilder builder) {
799 800 801 802 803 804 805
    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
806
  bool findAnnotations<S extends Object>(
807 808
    AnnotationResult<S> result,
    Offset localPosition, {
809
    required bool onlyFirst,
810
  }) {
811
    if (S != int) {
812
      return false;
813 814
    }
    if (size != null && !(offset & size!).contains(localPosition)) {
815
      return false;
816
    }
817
    final Object untypedValue = value;
818
    final S typedValue = untypedValue as S;
819 820 821 822 823
    result.add(AnnotationEntry<S>(annotation: typedValue, localPosition: localPosition));
    return opaque;
  }
}

824 825 826 827 828 829 830 831 832 833
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,
}) {
834 835 836
  return pairwiseCompare<AnnotationEntry<int>, AnnotationEntry<int>>(
    list,
    (AnnotationEntry<int> a, AnnotationEntry<int> b) {
837 838 839
      return a.annotation == b.annotation
          && _almostEqual(a.localPosition.dx, b.localPosition.dx, maxCoordinateRelativeDiff)
          && _almostEqual(a.localPosition.dy, b.localPosition.dy, maxCoordinateRelativeDiff);
840 841 842 843
    },
    'equal to',
  );
}