flutter_flutter/packages/flutter/test/rendering/layer_annotations_test.dart
Kate Lovett 9d96df2364
Modernize framework lints (#179089)
WIP

Commits separated as follows:
- Update lints in analysis_options files
- Run `dart fix --apply`
- Clean up leftover analysis issues 
- Run `dart format .` in the right places.

Local analysis and testing passes. Checking CI now.

Part of https://github.com/flutter/flutter/issues/178827
- Adoption of flutter_lints in examples/api coming in a separate change
(cc @loic-sharma)

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-11-26 01:10:39 +00:00

763 lines
24 KiB
Dart

// 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/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.zero),
const AnnotationEntry<int>(annotation: 2, localPosition: Offset.zero),
]),
);
});
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.zero),
]),
);
});
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.zero),
const AnnotationEntry<int>(annotation: 1000, localPosition: Offset.zero),
]),
);
});
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.zero),
]),
);
});
test('OffsetLayer.findAllAnnotations respects offset', () {
const insidePosition = Offset(-5, 5);
const 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 insidePosition = Offset(11, 11);
const 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.fromRectAndRadius(
const Offset(10, 10) & const Size(10, 10),
const Radius.circular(4),
);
const insidePosition = Offset(12, 12);
const 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 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 insidePosition = Offset(11, 11);
const 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 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 insidePosition = Offset(40, 80);
const 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 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 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('LeaderLayer.findAllAnnotations respects offset', () {
const insidePosition = Offset(-5, 5);
const 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 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 append to the list '
'and return the given opacity (true) during a successful hit', () {
const 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 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 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 position = Offset(5, 5);
final Layer root = _withBackgroundAnnotation(
1000,
_Layers(
AnnotatedRegionLayer<int>(1, 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 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 add to children's opacity "
'during a successful hit if it is opaque', () {
const 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 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 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!) {
late 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) {
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 extends Object>(
AnnotationResult<S> result,
Offset localPosition, {
required bool onlyFirst,
}) {
if (S != int) {
return false;
}
if (size != null && !(offset & size!).contains(localPosition)) {
return false;
}
final Object untypedValue = value;
final 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');
}