layer_annotations_test.dart 27 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('PhysicalModelLayer.findAllAnnotations respects clipPath', () {
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
    // 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);

453 454
    final Layer root = _withBackgroundAnnotation(
      1000,
455 456 457 458 459 460 461 462 463 464 465 466 467 468
      _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),
          ),
469
        ],
470 471 472 473
      ).build(),
    );

    expect(
474
      root.findAllAnnotations<int>(insidePosition).entries.toList(),
475 476 477 478 479
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1, localPosition: insidePosition),
      ]),
    );
    expect(
480
      root.findAllAnnotations<int>(outsidePosition).entries.toList(),
481 482 483 484 485 486
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition),
      ]),
    );
  });

487
  test('LeaderLayer.findAllAnnotations respects offset', () {
488 489 490
    const Offset insidePosition = Offset(-5, 5);
    const Offset outsidePosition = Offset(5, 5);

491 492
    final Layer root = _withBackgroundAnnotation(
      1000,
493 494 495 496 497 498 499
      _Layers(
        LeaderLayer(
          link: LayerLink(),
          offset: const Offset(-10, 0),
        ),
        children: <Object>[
          _TestAnnotatedLayer(1, opaque: true, size: const Size(10, 10)),
500
        ],
501 502 503 504
      ).build(),
    );

    expect(
505
      root.findAllAnnotations<int>(insidePosition).entries.toList(),
506 507 508 509 510
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1, localPosition: Offset(5, 5)),
      ]),
    );
    expect(
511
      root.findAllAnnotations<int>(outsidePosition).entries.toList(),
512 513 514 515 516 517
      _equalToAnnotationResult<int>(<AnnotationEntry<int>>[
        const AnnotationEntry<int>(annotation: 1000, localPosition: outsidePosition),
      ]),
    );
  });

518 519 520 521 522 523 524 525 526
  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(
527
          AnnotatedRegionLayer<int>(1),
528 529 530 531 532
          children: <Object>[
            _TestAnnotatedLayer(2, opaque: false),
          ],
        ).build(),
      );
533

534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
      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(),
      );
560

561 562 563 564 565 566 567 568 569
      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),
        ]),
      );
    },
  );
570

571
  test('AnnotatedRegionLayer.findAllAnnotations has default opacity as false', () {
572 573
    const Offset position = Offset(5, 5);

574 575
    final Layer root = _withBackgroundAnnotation(
      1000,
576 577 578 579
      _Layers(
        AnnotatedRegionLayer<int>(1),
        children: <Object>[
          _TestAnnotatedLayer(2, opaque: false),
580
        ],
581 582 583 584
      ).build(),
    );

    expect(
585
      root.findAllAnnotations<int>(position).entries.toList(),
586 587 588 589 590 591 592 593
      _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),
      ]),
    );
  });

594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
  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(),
      );
609

610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
      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(
629
          AnnotatedRegionLayer<int>(1, size: Size.zero),
630 631 632 633 634
          children: <Object>[
            _TestAnnotatedLayer(2, opaque: true),
          ],
        ).build(),
      );
635

636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
      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(
654
          AnnotatedRegionLayer<int>(1),
655 656 657 658 659
          children: <Object>[
            _TestAnnotatedLayer(2, opaque: false),
          ],
        ).build(),
      );
660

661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
      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(),
      );
687

688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
      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),
712
          ),
713 714 715 716 717 718 719 720 721 722 723 724
          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(),
      );
725

726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
      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(),
      );
757

758 759 760 761 762 763 764 765 766
      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),
        ]),
      );
    },
  );
767 768
}

769 770
/// A [ContainerLayer] that contains a stack of layers: `layer` in the front,
/// and another layer annotated with `value` in the back.
771 772 773
///
/// It is a utility function that helps checking the opacity returned by
/// [Layer.findAnnotations].
774
Layer _withBackgroundAnnotation(int value, Layer layer) {
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
  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.
790
  final List<Object>? children;
791 792 793 794 795 796 797 798
  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) {
799 800
      for (final Object child in children!) {
        late Layer layer;
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
        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 {
817 818
  _TestAnnotatedLayer(
    this.value, {
819
    required this.opaque,
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844
    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].
845
  final Size? size;
846 847

  @override
848
  EngineLayer? addToScene(SceneBuilder builder) {
849 850 851 852 853 854 855
    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
856
  bool findAnnotations<S extends Object>(
857 858
    AnnotationResult<S> result,
    Offset localPosition, {
859
    required bool onlyFirst,
860
  }) {
861
    if (S != int) {
862
      return false;
863 864
    }
    if (size != null && !(offset & size!).contains(localPosition)) {
865
      return false;
866
    }
867
    final Object untypedValue = value;
868
    final S typedValue = untypedValue as S;
869 870 871 872 873
    result.add(AnnotationEntry<S>(annotation: typedValue, localPosition: localPosition));
    return opaque;
  }
}

874 875 876 877 878 879 880 881 882 883
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,
}) {
884 885 886
  return pairwiseCompare<AnnotationEntry<int>, AnnotationEntry<int>>(
    list,
    (AnnotationEntry<int> a, AnnotationEntry<int> b) {
887 888 889
      return a.annotation == b.annotation
          && _almostEqual(a.localPosition.dx, b.localPosition.dx, maxCoordinateRelativeDiff)
          && _almostEqual(a.localPosition.dy, b.localPosition.dy, maxCoordinateRelativeDiff);
890 891 892 893
    },
    'equal to',
  );
}