mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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
978 lines
28 KiB
Dart
978 lines
28 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.
|
|
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'dart:math' as math;
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:vector_math/vector_math_64.dart';
|
|
|
|
void main() {
|
|
testWidgets('Transform origin', (WidgetTester tester) async {
|
|
var didReceiveTap = false;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Stack(
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 100.0,
|
|
left: 100.0,
|
|
child: Container(width: 100.0, height: 100.0, color: const Color(0xFF0000FF)),
|
|
),
|
|
Positioned(
|
|
top: 100.0,
|
|
left: 100.0,
|
|
child: SizedBox(
|
|
width: 100.0,
|
|
height: 100.0,
|
|
child: Transform(
|
|
transform: Matrix4.diagonal3Values(0.5, 0.5, 1.0),
|
|
origin: const Offset(100.0, 50.0),
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
didReceiveTap = true;
|
|
},
|
|
child: Container(color: const Color(0xFF00FFFF)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(110.0, 110.0));
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(190.0, 150.0));
|
|
expect(didReceiveTap, isTrue);
|
|
});
|
|
|
|
testWidgets('Transform alignment', (WidgetTester tester) async {
|
|
var didReceiveTap = false;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Stack(
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 100.0,
|
|
left: 100.0,
|
|
child: Container(width: 100.0, height: 100.0, color: const Color(0xFF0000FF)),
|
|
),
|
|
Positioned(
|
|
top: 100.0,
|
|
left: 100.0,
|
|
child: SizedBox(
|
|
width: 100.0,
|
|
height: 100.0,
|
|
child: Transform(
|
|
transform: Matrix4.diagonal3Values(0.5, 0.5, 1.0),
|
|
alignment: Alignment.centerRight,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
didReceiveTap = true;
|
|
},
|
|
child: Container(color: const Color(0xFF00FFFF)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(110.0, 110.0));
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(190.0, 150.0));
|
|
expect(didReceiveTap, isTrue);
|
|
});
|
|
|
|
testWidgets('Transform AlignmentDirectional alignment', (WidgetTester tester) async {
|
|
var didReceiveTap = false;
|
|
|
|
Widget buildFrame(TextDirection textDirection, AlignmentGeometry alignment) {
|
|
return Directionality(
|
|
textDirection: textDirection,
|
|
child: Stack(
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 100.0,
|
|
left: 100.0,
|
|
child: Container(width: 100.0, height: 100.0, color: const Color(0xFF0000FF)),
|
|
),
|
|
Positioned(
|
|
top: 100.0,
|
|
left: 100.0,
|
|
child: SizedBox(
|
|
width: 100.0,
|
|
height: 100.0,
|
|
child: Transform(
|
|
transform: Matrix4.diagonal3Values(0.5, 0.5, 1.0),
|
|
alignment: alignment,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
didReceiveTap = true;
|
|
},
|
|
child: Container(color: const Color(0xFF00FFFF)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(TextDirection.ltr, AlignmentDirectional.centerEnd));
|
|
didReceiveTap = false;
|
|
await tester.tapAt(const Offset(110.0, 110.0));
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(190.0, 150.0));
|
|
expect(didReceiveTap, isTrue);
|
|
|
|
await tester.pumpWidget(buildFrame(TextDirection.rtl, AlignmentDirectional.centerStart));
|
|
didReceiveTap = false;
|
|
await tester.tapAt(const Offset(110.0, 110.0));
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(190.0, 150.0));
|
|
expect(didReceiveTap, isTrue);
|
|
|
|
await tester.pumpWidget(buildFrame(TextDirection.ltr, AlignmentDirectional.centerStart));
|
|
didReceiveTap = false;
|
|
await tester.tapAt(const Offset(190.0, 150.0));
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(110.0, 150.0));
|
|
expect(didReceiveTap, isTrue);
|
|
|
|
await tester.pumpWidget(buildFrame(TextDirection.rtl, AlignmentDirectional.centerEnd));
|
|
didReceiveTap = false;
|
|
await tester.tapAt(const Offset(190.0, 150.0));
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(110.0, 150.0));
|
|
expect(didReceiveTap, isTrue);
|
|
});
|
|
|
|
testWidgets('Transform offset + alignment', (WidgetTester tester) async {
|
|
var didReceiveTap = false;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Stack(
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 100.0,
|
|
left: 100.0,
|
|
child: Container(width: 100.0, height: 100.0, color: const Color(0xFF0000FF)),
|
|
),
|
|
Positioned(
|
|
top: 100.0,
|
|
left: 100.0,
|
|
child: SizedBox(
|
|
width: 100.0,
|
|
height: 100.0,
|
|
child: Transform(
|
|
transform: Matrix4.diagonal3Values(0.5, 0.5, 1.0),
|
|
origin: const Offset(100.0, 0.0),
|
|
alignment: Alignment.centerLeft,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
didReceiveTap = true;
|
|
},
|
|
child: Container(color: const Color(0xFF00FFFF)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(110.0, 110.0));
|
|
expect(didReceiveTap, isFalse);
|
|
await tester.tapAt(const Offset(190.0, 150.0));
|
|
expect(didReceiveTap, isTrue);
|
|
});
|
|
|
|
testWidgets('Composited transform offset', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Center(
|
|
child: SizedBox(
|
|
width: 400.0,
|
|
height: 300.0,
|
|
child: ClipRect(
|
|
child: Transform(
|
|
transform: Matrix4.diagonal3Values(0.5, 0.5, 1.0),
|
|
child: RepaintBoundary(child: Container(color: const Color(0xFF00FF00))),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final List<Layer> layers = tester.layers..retainWhere((Layer layer) => layer is TransformLayer);
|
|
expect(layers.length, 2);
|
|
// The first transform is from the render view.
|
|
final layer = layers[1] as TransformLayer;
|
|
final Matrix4 transform = layer.transform!;
|
|
expect(transform.getTranslation(), equals(Vector3(100.0, 75.0, 0.0)));
|
|
});
|
|
|
|
testWidgets('Transform.rotate', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Transform.rotate(
|
|
angle: math.pi / 2.0,
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
final List<Layer> layers = tester.layers..retainWhere((Layer layer) => layer is TransformLayer);
|
|
expect(layers.length, 2);
|
|
// The first transform is from the render view.
|
|
final layer = layers[1] as TransformLayer;
|
|
final Matrix4 transform = layer.transform!;
|
|
expect(transform.storage, <dynamic>[
|
|
moreOrLessEquals(0.0),
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
-1.0,
|
|
moreOrLessEquals(0.0),
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
700.0,
|
|
-100.0,
|
|
0.0,
|
|
1.0,
|
|
]);
|
|
});
|
|
|
|
testWidgets('applyPaintTransform of Transform in Padding', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 30.0, top: 20.0, right: 50.0, bottom: 70.0),
|
|
child: Transform(
|
|
transform: Matrix4.diagonal3Values(2.0, 2.0, 2.0),
|
|
child: const Placeholder(),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(30.0, 20.0));
|
|
});
|
|
|
|
testWidgets('Transform.translate', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Transform.translate(
|
|
offset: const Offset(100.0, 50.0),
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
// This should not cause a transform layer to be inserted.
|
|
final List<Layer> layers = tester.layers..retainWhere((Layer layer) => layer is TransformLayer);
|
|
expect(layers.length, 1); // only the render view
|
|
expect(tester.getTopLeft(find.byType(Container)), const Offset(100.0, 50.0));
|
|
});
|
|
|
|
testWidgets('Transform.scale', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Transform.scale(scale: 2.0, child: RepaintBoundary(child: Container())),
|
|
);
|
|
|
|
final List<Layer> layers = tester.layers..retainWhere((Layer layer) => layer is TransformLayer);
|
|
expect(layers.length, 2);
|
|
// The first transform is from the render view.
|
|
final layer = layers[1] as TransformLayer;
|
|
final Matrix4 transform = layer.transform!;
|
|
expect(transform.storage, <dynamic>[
|
|
// These are column-major, not row-major.
|
|
2.0, 0.0, 0.0, 0.0,
|
|
0.0, 2.0, 0.0, 0.0,
|
|
0.0, 0.0, 1.0, 0.0,
|
|
-400.0, -300.0, 0.0, 1.0, // it's 1600x1200, centered in an 800x600 square
|
|
]);
|
|
});
|
|
|
|
testWidgets('Transform with nan value short-circuits rendering', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Transform(
|
|
transform: Matrix4.identity()..storage[0] = double.nan,
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(tester.layers, hasLength(1));
|
|
});
|
|
|
|
testWidgets('Transform with inf value short-circuits rendering', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Transform(
|
|
transform: Matrix4.identity()..storage[0] = double.infinity,
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(tester.layers, hasLength(1));
|
|
});
|
|
|
|
testWidgets('Transform with -inf value short-circuits rendering', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Transform(
|
|
transform: Matrix4.identity()..storage[0] = double.negativeInfinity,
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(tester.layers, hasLength(1));
|
|
});
|
|
|
|
testWidgets('Transform.rotate does not remove layers due to singular short-circuit', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
Transform.rotate(
|
|
angle: math.pi / 2,
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(tester.layers, hasLength(3));
|
|
});
|
|
|
|
testWidgets('Transform.rotate creates nice rotation matrices for 0, 90, 180, 270 degrees', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
Transform.rotate(
|
|
angle: math.pi / 2,
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.layers[1],
|
|
isA<TransformLayer>().having(
|
|
(TransformLayer layer) => layer.transform,
|
|
'transform',
|
|
equals(
|
|
Matrix4.fromList(<double>[
|
|
0.0,
|
|
-1.0,
|
|
0.0,
|
|
700.0,
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
-100.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
])..transpose(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Transform.rotate(
|
|
angle: math.pi,
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.layers[1],
|
|
isA<TransformLayer>().having(
|
|
(TransformLayer layer) => layer.transform,
|
|
'transform',
|
|
equals(
|
|
Matrix4.fromList(<double>[
|
|
-1.0,
|
|
0.0,
|
|
0.0,
|
|
800.0,
|
|
0.0,
|
|
-1.0,
|
|
0.0,
|
|
600.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
])..transpose(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Transform.rotate(
|
|
angle: 3 * math.pi / 2,
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.layers[1],
|
|
isA<TransformLayer>().having(
|
|
(TransformLayer layer) => layer.transform,
|
|
'transform',
|
|
equals(
|
|
Matrix4.fromList(<double>[
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
100.0,
|
|
-1.0,
|
|
0.0,
|
|
0.0,
|
|
700.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
])..transpose(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(Transform.rotate(angle: 0, child: RepaintBoundary(child: Container())));
|
|
|
|
// No transform layer created
|
|
expect(tester.layers[1], isA<OffsetLayer>());
|
|
expect(tester.layers, hasLength(2));
|
|
});
|
|
|
|
testWidgets('Transform.scale with 0.0 does not paint child layers', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Transform.scale(scale: 0.0, child: RepaintBoundary(child: Container())),
|
|
);
|
|
|
|
expect(tester.layers, hasLength(1)); // root transform layer
|
|
|
|
await tester.pumpWidget(
|
|
Transform.scale(scaleX: 0.0, child: RepaintBoundary(child: Container())),
|
|
);
|
|
|
|
expect(tester.layers, hasLength(1));
|
|
|
|
await tester.pumpWidget(
|
|
Transform.scale(scaleY: 0.0, child: RepaintBoundary(child: Container())),
|
|
);
|
|
|
|
expect(tester.layers, hasLength(1));
|
|
|
|
await tester.pumpWidget(
|
|
Transform.scale(
|
|
scale: 0.01, // small but non-zero
|
|
child: RepaintBoundary(child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(tester.layers, hasLength(3));
|
|
});
|
|
|
|
testWidgets('Translated child into translated box - hit test', (WidgetTester tester) async {
|
|
final GlobalKey key1 = GlobalKey();
|
|
var pointerDown = false;
|
|
await tester.pumpWidget(
|
|
Transform.translate(
|
|
offset: const Offset(100.0, 50.0),
|
|
child: Transform.translate(
|
|
offset: const Offset(1000.0, 1000.0),
|
|
child: Listener(
|
|
onPointerDown: (PointerDownEvent event) {
|
|
pointerDown = true;
|
|
},
|
|
child: Container(key: key1, color: const Color(0xFF000000)),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(pointerDown, isFalse);
|
|
await tester.tap(find.byKey(key1));
|
|
expect(pointerDown, isTrue);
|
|
});
|
|
|
|
Widget generateTransform(bool needsCompositing, double angle) {
|
|
final Widget customPaint = CustomPaint(painter: TestRectPainter());
|
|
return Transform(
|
|
transform: MatrixUtils.createCylindricalProjectionTransform(
|
|
radius: 100,
|
|
angle: angle,
|
|
perspective: 0.003,
|
|
),
|
|
// A RepaintBoundary child forces the Transform to needsCompositing
|
|
child: needsCompositing ? RepaintBoundary(child: customPaint) : customPaint,
|
|
);
|
|
}
|
|
|
|
testWidgets(
|
|
'3D transform renders the same with or without needsCompositing',
|
|
(WidgetTester tester) async {
|
|
for (double angle = 0; angle <= math.pi / 4; angle += 0.01) {
|
|
await tester.pumpWidget(RepaintBoundary(child: generateTransform(true, angle)));
|
|
final RenderBox renderBox = tester.binding.renderView.child!;
|
|
final layer = renderBox.debugLayer! as OffsetLayer;
|
|
final ui.Image imageWithCompositing = await layer.toImage(renderBox.paintBounds);
|
|
addTearDown(imageWithCompositing.dispose);
|
|
|
|
await tester.pumpWidget(RepaintBoundary(child: generateTransform(false, angle)));
|
|
await expectLater(
|
|
find.byType(RepaintBoundary).first,
|
|
matchesReferenceImage(imageWithCompositing),
|
|
);
|
|
}
|
|
},
|
|
skip: isBrowser, // due to https://github.com/flutter/flutter/issues/49857
|
|
);
|
|
|
|
List<double> extractMatrix(ui.ImageFilter? filter) {
|
|
final List<String> numbers = filter.toString().split('[').last.split(']').first.split(',');
|
|
return numbers.map<double>((String str) => double.parse(str.trim())).toList();
|
|
}
|
|
|
|
testWidgets('Transform.translate with FilterQuality produces filter layer', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
Transform.translate(
|
|
offset: const Offset(25.0, 25.0),
|
|
filterQuality: FilterQuality.low,
|
|
child: const SizedBox(width: 100, height: 100),
|
|
),
|
|
);
|
|
expect(tester.layers.whereType<ImageFilterLayer>().length, 1);
|
|
final ImageFilterLayer layer = tester.layers.whereType<ImageFilterLayer>().first;
|
|
expect(extractMatrix(layer.imageFilter), <double>[
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
25.0,
|
|
25.0,
|
|
0.0,
|
|
1.0,
|
|
]);
|
|
});
|
|
|
|
testWidgets('Transform.scale with FilterQuality produces filter layer', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
Transform.scale(
|
|
scale: 3.14159,
|
|
filterQuality: FilterQuality.low,
|
|
child: const SizedBox(width: 100, height: 100),
|
|
),
|
|
);
|
|
expect(tester.layers.whereType<ImageFilterLayer>().length, 1);
|
|
final ImageFilterLayer layer = tester.layers.whereType<ImageFilterLayer>().first;
|
|
expect(extractMatrix(layer.imageFilter), <double>[
|
|
3.14159,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
3.14159,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
-856.636,
|
|
-642.477,
|
|
0.0,
|
|
1.0,
|
|
]);
|
|
});
|
|
|
|
testWidgets('Transform.rotate with FilterQuality produces filter layer', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
Transform.rotate(
|
|
angle: math.pi / 4,
|
|
filterQuality: FilterQuality.low,
|
|
child: const SizedBox(width: 100, height: 100),
|
|
),
|
|
);
|
|
expect(tester.layers.whereType<ImageFilterLayer>().length, 1);
|
|
final ImageFilterLayer layer = tester.layers.whereType<ImageFilterLayer>().first;
|
|
expect(extractMatrix(layer.imageFilter), <dynamic>[
|
|
moreOrLessEquals(0.7071067811865476),
|
|
moreOrLessEquals(0.7071067811865475),
|
|
0.0,
|
|
0.0,
|
|
moreOrLessEquals(-0.7071067811865475),
|
|
moreOrLessEquals(0.7071067811865476),
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
moreOrLessEquals(329.28932188134524),
|
|
moreOrLessEquals(-194.97474683058329),
|
|
0.0,
|
|
1.0,
|
|
]);
|
|
});
|
|
|
|
testWidgets('Offset Transform.rotate with FilterQuality produces filter layer', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
SizedBox(
|
|
width: 400,
|
|
height: 400,
|
|
child: Center(
|
|
child: Transform.rotate(
|
|
angle: math.pi / 4,
|
|
filterQuality: FilterQuality.low,
|
|
child: const SizedBox(width: 100, height: 100),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.layers.whereType<ImageFilterLayer>().length, 1);
|
|
final ImageFilterLayer layer = tester.layers.whereType<ImageFilterLayer>().first;
|
|
expect(extractMatrix(layer.imageFilter), <dynamic>[
|
|
moreOrLessEquals(0.7071067811865476),
|
|
moreOrLessEquals(0.7071067811865475),
|
|
0.0,
|
|
0.0,
|
|
moreOrLessEquals(-0.7071067811865475),
|
|
moreOrLessEquals(0.7071067811865476),
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
0.0,
|
|
moreOrLessEquals(329.28932188134524),
|
|
moreOrLessEquals(-194.97474683058329),
|
|
0.0,
|
|
1.0,
|
|
]);
|
|
});
|
|
|
|
testWidgets('Transform layers update to match child and filterQuality', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
Transform.rotate(
|
|
angle: math.pi / 4,
|
|
filterQuality: FilterQuality.low,
|
|
child: const SizedBox(width: 100, height: 100),
|
|
),
|
|
);
|
|
expect(tester.layers.whereType<ImageFilterLayer>(), hasLength(1));
|
|
|
|
await tester.pumpWidget(
|
|
Transform.rotate(angle: math.pi / 4, child: const SizedBox(width: 100, height: 100)),
|
|
);
|
|
expect(tester.layers.whereType<ImageFilterLayer>(), isEmpty);
|
|
|
|
await tester.pumpWidget(Transform.rotate(angle: math.pi / 4, filterQuality: FilterQuality.low));
|
|
expect(tester.layers.whereType<ImageFilterLayer>(), isEmpty);
|
|
|
|
await tester.pumpWidget(
|
|
Transform.rotate(
|
|
angle: math.pi / 4,
|
|
filterQuality: FilterQuality.low,
|
|
child: const SizedBox(width: 100, height: 100),
|
|
),
|
|
);
|
|
expect(tester.layers.whereType<ImageFilterLayer>(), hasLength(1));
|
|
});
|
|
|
|
testWidgets('Transform layers with filterQuality golden', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: GridView.count(
|
|
crossAxisCount: 3,
|
|
children: <Widget>[
|
|
Transform.rotate(
|
|
angle: math.pi / 6,
|
|
child: Center(
|
|
child: Container(width: 100, height: 20, color: const Color(0xffffff00)),
|
|
),
|
|
),
|
|
Transform.scale(
|
|
scale: 1.5,
|
|
child: Center(
|
|
child: Container(width: 100, height: 20, color: const Color(0xffffff00)),
|
|
),
|
|
),
|
|
Transform.translate(
|
|
offset: const Offset(20.0, 60.0),
|
|
child: Center(
|
|
child: Container(width: 100, height: 20, color: const Color(0xffffff00)),
|
|
),
|
|
),
|
|
Transform.rotate(
|
|
angle: math.pi / 6,
|
|
filterQuality: FilterQuality.low,
|
|
child: Center(
|
|
child: Container(width: 100, height: 20, color: const Color(0xff00ff00)),
|
|
),
|
|
),
|
|
Transform.scale(
|
|
scale: 1.5,
|
|
filterQuality: FilterQuality.low,
|
|
child: Center(
|
|
child: Container(width: 100, height: 20, color: const Color(0xff00ff00)),
|
|
),
|
|
),
|
|
Transform.translate(
|
|
offset: const Offset(20.0, 60.0),
|
|
filterQuality: FilterQuality.low,
|
|
child: Center(
|
|
child: Container(width: 100, height: 20, color: const Color(0xff00ff00)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
await expectLater(
|
|
find.byType(GridView),
|
|
matchesGoldenFile('transform_golden.BitmapRotate.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
"Transform.scale() does not accept all three 'scale', 'scaleX' and 'scaleY' parameters to be non-null",
|
|
(WidgetTester tester) async {
|
|
await expectLater(() {
|
|
tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: Transform.scale(
|
|
scale: 1.0,
|
|
scaleX: 1.0,
|
|
scaleY: 1.0,
|
|
child: const SizedBox(height: 100, width: 100),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}, throwsAssertionError);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
"Transform.scale() needs at least one of 'scale', 'scaleX' and 'scaleY' to be non-null, otherwise throws AssertionError",
|
|
(WidgetTester tester) async {
|
|
await expectLater(() {
|
|
tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(child: Transform.scale(child: const SizedBox(height: 100, width: 100))),
|
|
),
|
|
);
|
|
}, throwsAssertionError);
|
|
},
|
|
);
|
|
|
|
testWidgets("Transform.scale() scales widget uniformly with 'scale' parameter", (
|
|
WidgetTester tester,
|
|
) async {
|
|
const scale = 1.5;
|
|
const double height = 100;
|
|
const double width = 150;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: SizedBox(
|
|
height: 400,
|
|
width: 400,
|
|
child: Center(
|
|
child: Transform.scale(
|
|
scale: scale,
|
|
child: Container(height: height, width: width, decoration: const BoxDecoration()),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
const target = Size(width * scale, height * scale);
|
|
|
|
expect(
|
|
tester.getBottomRight(find.byType(Container)),
|
|
target.bottomRight(tester.getTopLeft(find.byType(Container))),
|
|
);
|
|
});
|
|
|
|
testWidgets("Transform.scale() scales widget according to 'scaleX' and 'scaleY'", (
|
|
WidgetTester tester,
|
|
) async {
|
|
const scaleX = 1.5;
|
|
const scaleY = 1.2;
|
|
const double height = 100;
|
|
const double width = 150;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: SizedBox(
|
|
height: 400,
|
|
width: 400,
|
|
child: Center(
|
|
child: Transform.scale(
|
|
scaleX: scaleX,
|
|
scaleY: scaleY,
|
|
child: Container(height: height, width: width, decoration: const BoxDecoration()),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
const target = Size(width * scaleX, height * scaleY);
|
|
|
|
expect(
|
|
tester.getBottomRight(find.byType(Container)),
|
|
target.bottomRight(tester.getTopLeft(find.byType(Container))),
|
|
);
|
|
});
|
|
|
|
testWidgets('Transform.flip does flip child correctly', (WidgetTester tester) async {
|
|
const topRight = Offset(60, 20);
|
|
const bottomLeft = Offset(20, 60);
|
|
const bottomRight = Offset(60, 60);
|
|
|
|
var tappedRed = false;
|
|
|
|
const Widget square = SizedBox.square(dimension: 40);
|
|
final Widget child = Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
GestureDetector(
|
|
onTap: () => tappedRed = true,
|
|
child: const ColoredBox(color: Color(0xffff0000), child: square),
|
|
),
|
|
const ColoredBox(color: Color(0xff00ff00), child: square),
|
|
],
|
|
),
|
|
const Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
ColoredBox(color: Color(0xff0000ff), child: square),
|
|
ColoredBox(color: Color(0xffeeff00), child: square),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Transform.flip(flipX: true, child: child),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tapAt(topRight);
|
|
|
|
expect(tappedRed, isTrue, reason: 'Transform.flip cannot flipX');
|
|
|
|
tappedRed = false;
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Transform.flip(flipY: true, child: child),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tapAt(bottomLeft);
|
|
|
|
expect(tappedRed, isTrue, reason: 'Transform.flip cannot flipY');
|
|
|
|
tappedRed = false;
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Transform.flip(flipX: true, flipY: true, child: child),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tapAt(bottomRight);
|
|
|
|
expect(tappedRed, isTrue, reason: 'Transform.flip cannot flipX and flipY together');
|
|
});
|
|
}
|
|
|
|
class TestRectPainter extends CustomPainter {
|
|
@override
|
|
void paint(ui.Canvas canvas, ui.Size size) {
|
|
canvas.drawRect(
|
|
const Offset(200, 200) & const Size(10, 10),
|
|
Paint()..color = const Color(0xFFFF0000),
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(CustomPainter oldDelegate) => true;
|
|
}
|