blob: f7b8aa5ac6b39ae13a3bc7c00c190d8952144fc0 [file] [log] [blame]
// 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',
);
}