mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* Update project.pbxproj files to say Flutter rather than Chromium Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright. * Update the copyright notice checker to require a standard notice on all files * Update copyrights on Dart files. (This was a mechanical commit.) * Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine. Some were already marked "The Flutter Authors", not clear why. Their dates have been normalized. Some were missing the blank line after the license. Some were randomly different in trivial ways for no apparent reason (e.g. missing the trailing period). * Clean up the copyrights in non-Dart files. (Manual edits.) Also, make sure templates don't have copyrights. * Fix some more ORGANIZATIONNAMEs
576 lines
20 KiB
Dart
576 lines
20 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/foundation.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'rendering_tester.dart';
|
|
|
|
void main() {
|
|
test('non-painted layers are detached', () {
|
|
RenderObject boundary, inner;
|
|
final RenderOpacity root = RenderOpacity(
|
|
child: boundary = RenderRepaintBoundary(
|
|
child: inner = RenderDecoratedBox(
|
|
decoration: const BoxDecoration(),
|
|
),
|
|
),
|
|
);
|
|
layout(root, phase: EnginePhase.paint);
|
|
expect(inner.isRepaintBoundary, isFalse);
|
|
expect(inner.debugLayer, null);
|
|
expect(boundary.isRepaintBoundary, isTrue);
|
|
expect(boundary.debugLayer, isNotNull);
|
|
expect(boundary.debugLayer.attached, isTrue); // this time it painted...
|
|
|
|
root.opacity = 0.0;
|
|
pumpFrame(phase: EnginePhase.paint);
|
|
expect(inner.isRepaintBoundary, isFalse);
|
|
expect(inner.debugLayer, null);
|
|
expect(boundary.isRepaintBoundary, isTrue);
|
|
expect(boundary.debugLayer, isNotNull);
|
|
expect(boundary.debugLayer.attached, isFalse); // this time it did not.
|
|
|
|
root.opacity = 0.5;
|
|
pumpFrame(phase: EnginePhase.paint);
|
|
expect(inner.isRepaintBoundary, isFalse);
|
|
expect(inner.debugLayer, null);
|
|
expect(boundary.isRepaintBoundary, isTrue);
|
|
expect(boundary.debugLayer, isNotNull);
|
|
expect(boundary.debugLayer.attached, isTrue); // this time it did again!
|
|
});
|
|
|
|
test('updateSubtreeNeedsAddToScene propagates Layer.alwaysNeedsAddToScene up the tree', () {
|
|
final ContainerLayer a = ContainerLayer();
|
|
final ContainerLayer b = ContainerLayer();
|
|
final ContainerLayer c = ContainerLayer();
|
|
final _TestAlwaysNeedsAddToSceneLayer d = _TestAlwaysNeedsAddToSceneLayer();
|
|
final ContainerLayer e = ContainerLayer();
|
|
final ContainerLayer f = ContainerLayer();
|
|
|
|
// Tree structure:
|
|
// a
|
|
// / \
|
|
// b c
|
|
// / \
|
|
// (x)d e
|
|
// /
|
|
// f
|
|
a.append(b);
|
|
a.append(c);
|
|
b.append(d);
|
|
b.append(e);
|
|
d.append(f);
|
|
|
|
a.debugMarkClean();
|
|
b.debugMarkClean();
|
|
c.debugMarkClean();
|
|
d.debugMarkClean();
|
|
e.debugMarkClean();
|
|
f.debugMarkClean();
|
|
|
|
expect(a.debugSubtreeNeedsAddToScene, false);
|
|
expect(b.debugSubtreeNeedsAddToScene, false);
|
|
expect(c.debugSubtreeNeedsAddToScene, false);
|
|
expect(d.debugSubtreeNeedsAddToScene, false);
|
|
expect(e.debugSubtreeNeedsAddToScene, false);
|
|
expect(f.debugSubtreeNeedsAddToScene, false);
|
|
|
|
a.updateSubtreeNeedsAddToScene();
|
|
|
|
expect(a.debugSubtreeNeedsAddToScene, true);
|
|
expect(b.debugSubtreeNeedsAddToScene, true);
|
|
expect(c.debugSubtreeNeedsAddToScene, false);
|
|
expect(d.debugSubtreeNeedsAddToScene, true);
|
|
expect(e.debugSubtreeNeedsAddToScene, false);
|
|
expect(f.debugSubtreeNeedsAddToScene, false);
|
|
});
|
|
|
|
test('updateSubtreeNeedsAddToScene propagates Layer._needsAddToScene up the tree', () {
|
|
final ContainerLayer a = ContainerLayer();
|
|
final ContainerLayer b = ContainerLayer();
|
|
final ContainerLayer c = ContainerLayer();
|
|
final ContainerLayer d = ContainerLayer();
|
|
final ContainerLayer e = ContainerLayer();
|
|
final ContainerLayer f = ContainerLayer();
|
|
final ContainerLayer g = ContainerLayer();
|
|
final List<ContainerLayer> allLayers = <ContainerLayer>[a, b, c, d, e, f, g];
|
|
|
|
// The tree is like the following where b and j are dirty:
|
|
// a____
|
|
// / \
|
|
// (x)b___ c
|
|
// / \ \ |
|
|
// d e f g(x)
|
|
a.append(b);
|
|
a.append(c);
|
|
b.append(d);
|
|
b.append(e);
|
|
b.append(f);
|
|
c.append(g);
|
|
|
|
for (ContainerLayer layer in allLayers) {
|
|
expect(layer.debugSubtreeNeedsAddToScene, true);
|
|
}
|
|
|
|
for (ContainerLayer layer in allLayers) {
|
|
layer.debugMarkClean();
|
|
}
|
|
|
|
for (ContainerLayer layer in allLayers) {
|
|
expect(layer.debugSubtreeNeedsAddToScene, false);
|
|
}
|
|
|
|
b.markNeedsAddToScene();
|
|
a.updateSubtreeNeedsAddToScene();
|
|
|
|
expect(a.debugSubtreeNeedsAddToScene, true);
|
|
expect(b.debugSubtreeNeedsAddToScene, true);
|
|
expect(c.debugSubtreeNeedsAddToScene, false);
|
|
expect(d.debugSubtreeNeedsAddToScene, false);
|
|
expect(e.debugSubtreeNeedsAddToScene, false);
|
|
expect(f.debugSubtreeNeedsAddToScene, false);
|
|
expect(g.debugSubtreeNeedsAddToScene, false);
|
|
|
|
g.markNeedsAddToScene();
|
|
a.updateSubtreeNeedsAddToScene();
|
|
|
|
expect(a.debugSubtreeNeedsAddToScene, true);
|
|
expect(b.debugSubtreeNeedsAddToScene, true);
|
|
expect(c.debugSubtreeNeedsAddToScene, true);
|
|
expect(d.debugSubtreeNeedsAddToScene, false);
|
|
expect(e.debugSubtreeNeedsAddToScene, false);
|
|
expect(f.debugSubtreeNeedsAddToScene, false);
|
|
expect(g.debugSubtreeNeedsAddToScene, true);
|
|
|
|
a.buildScene(SceneBuilder());
|
|
for (ContainerLayer layer in allLayers) {
|
|
expect(layer.debugSubtreeNeedsAddToScene, false);
|
|
}
|
|
});
|
|
|
|
test('leader and follower layers are always dirty', () {
|
|
final LayerLink link = LayerLink();
|
|
final LeaderLayer leaderLayer = LeaderLayer(link: link);
|
|
final FollowerLayer followerLayer = FollowerLayer(link: link);
|
|
leaderLayer.debugMarkClean();
|
|
followerLayer.debugMarkClean();
|
|
leaderLayer.updateSubtreeNeedsAddToScene();
|
|
followerLayer.updateSubtreeNeedsAddToScene();
|
|
expect(leaderLayer.debugSubtreeNeedsAddToScene, true);
|
|
expect(followerLayer.debugSubtreeNeedsAddToScene, true);
|
|
});
|
|
|
|
test('depthFirstIterateChildren', () {
|
|
final ContainerLayer a = ContainerLayer();
|
|
final ContainerLayer b = ContainerLayer();
|
|
final ContainerLayer c = ContainerLayer();
|
|
final ContainerLayer d = ContainerLayer();
|
|
final ContainerLayer e = ContainerLayer();
|
|
final ContainerLayer f = ContainerLayer();
|
|
final ContainerLayer g = ContainerLayer();
|
|
|
|
final PictureLayer h = PictureLayer(Rect.zero);
|
|
final PictureLayer i = PictureLayer(Rect.zero);
|
|
final PictureLayer j = PictureLayer(Rect.zero);
|
|
|
|
// The tree is like the following:
|
|
// a____
|
|
// / \
|
|
// b___ c
|
|
// / \ \ |
|
|
// d e f g
|
|
// / \ |
|
|
// h i j
|
|
a.append(b);
|
|
a.append(c);
|
|
b.append(d);
|
|
b.append(e);
|
|
b.append(f);
|
|
d.append(h);
|
|
d.append(i);
|
|
c.append(g);
|
|
g.append(j);
|
|
|
|
expect(
|
|
a.depthFirstIterateChildren(),
|
|
<Layer>[b, d, h, i, e, f, c, g, j],
|
|
);
|
|
|
|
d.remove();
|
|
// a____
|
|
// / \
|
|
// b___ c
|
|
// \ \ |
|
|
// e f g
|
|
// |
|
|
// j
|
|
expect(
|
|
a.depthFirstIterateChildren(),
|
|
<Layer>[b, e, f, c, g, j],
|
|
);
|
|
});
|
|
|
|
void checkNeedsAddToScene(Layer layer, void mutateCallback()) {
|
|
layer.debugMarkClean();
|
|
layer.updateSubtreeNeedsAddToScene();
|
|
expect(layer.debugSubtreeNeedsAddToScene, false);
|
|
mutateCallback();
|
|
layer.updateSubtreeNeedsAddToScene();
|
|
expect(layer.debugSubtreeNeedsAddToScene, true);
|
|
}
|
|
|
|
List<String> _getDebugInfo(Layer layer) {
|
|
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
|
layer.debugFillProperties(builder);
|
|
return builder.properties
|
|
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
|
.map((DiagnosticsNode node) => node.toString()).toList();
|
|
}
|
|
|
|
test('ClipRectLayer prints clipBehavior in debug info', () {
|
|
expect(_getDebugInfo(ClipRectLayer()), contains('clipBehavior: Clip.hardEdge'));
|
|
expect(
|
|
_getDebugInfo(ClipRectLayer(clipBehavior: Clip.antiAliasWithSaveLayer)),
|
|
contains('clipBehavior: Clip.antiAliasWithSaveLayer'),
|
|
);
|
|
});
|
|
|
|
test('ClipRRectLayer prints clipBehavior in debug info', () {
|
|
expect(_getDebugInfo(ClipRRectLayer()), contains('clipBehavior: Clip.antiAlias'));
|
|
expect(
|
|
_getDebugInfo(ClipRRectLayer(clipBehavior: Clip.antiAliasWithSaveLayer)),
|
|
contains('clipBehavior: Clip.antiAliasWithSaveLayer'),
|
|
);
|
|
});
|
|
|
|
test('ClipPathLayer prints clipBehavior in debug info', () {
|
|
expect(_getDebugInfo(ClipPathLayer()), contains('clipBehavior: Clip.antiAlias'));
|
|
expect(
|
|
_getDebugInfo(ClipPathLayer(clipBehavior: Clip.antiAliasWithSaveLayer)),
|
|
contains('clipBehavior: Clip.antiAliasWithSaveLayer'),
|
|
);
|
|
});
|
|
|
|
test('mutating PictureLayer fields triggers needsAddToScene', () {
|
|
final PictureLayer pictureLayer = PictureLayer(Rect.zero);
|
|
checkNeedsAddToScene(pictureLayer, () {
|
|
final PictureRecorder recorder = PictureRecorder();
|
|
pictureLayer.picture = recorder.endRecording();
|
|
});
|
|
|
|
pictureLayer.isComplexHint = false;
|
|
checkNeedsAddToScene(pictureLayer, () {
|
|
pictureLayer.isComplexHint = true;
|
|
});
|
|
|
|
pictureLayer.willChangeHint = false;
|
|
checkNeedsAddToScene(pictureLayer, () {
|
|
pictureLayer.willChangeHint = true;
|
|
});
|
|
});
|
|
|
|
const Rect unitRect = Rect.fromLTRB(0, 0, 1, 1);
|
|
|
|
test('mutating PerformanceOverlayLayer fields triggers needsAddToScene', () {
|
|
final PerformanceOverlayLayer layer = PerformanceOverlayLayer(
|
|
overlayRect: Rect.zero, optionsMask: 0, rasterizerThreshold: 0,
|
|
checkerboardRasterCacheImages: false, checkerboardOffscreenLayers: false);
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.overlayRect = unitRect;
|
|
});
|
|
});
|
|
|
|
test('mutating OffsetLayer fields triggers needsAddToScene', () {
|
|
final OffsetLayer layer = OffsetLayer();
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.offset = const Offset(1, 1);
|
|
});
|
|
});
|
|
|
|
test('mutating ClipRectLayer fields triggers needsAddToScene', () {
|
|
final ClipRectLayer layer = ClipRectLayer(clipRect: Rect.zero);
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.clipRect = unitRect;
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.clipBehavior = Clip.antiAliasWithSaveLayer;
|
|
});
|
|
});
|
|
|
|
test('mutating ClipRRectLayer fields triggers needsAddToScene', () {
|
|
final ClipRRectLayer layer = ClipRRectLayer(clipRRect: RRect.zero);
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.clipRRect = RRect.fromRectAndRadius(unitRect, const Radius.circular(0));
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.clipBehavior = Clip.antiAliasWithSaveLayer;
|
|
});
|
|
});
|
|
|
|
test('mutating ClipPath fields triggers needsAddToScene', () {
|
|
final ClipPathLayer layer = ClipPathLayer(clipPath: Path());
|
|
checkNeedsAddToScene(layer, () {
|
|
final Path newPath = Path();
|
|
newPath.addRect(unitRect);
|
|
layer.clipPath = newPath;
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.clipBehavior = Clip.antiAliasWithSaveLayer;
|
|
});
|
|
});
|
|
|
|
test('mutating OpacityLayer fields triggers needsAddToScene', () {
|
|
final OpacityLayer layer = OpacityLayer(alpha: 0);
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.alpha = 1;
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.offset = const Offset(1, 1);
|
|
});
|
|
});
|
|
|
|
test('mutating ColorFilterLayer fields triggers needsAddToScene', () {
|
|
final ColorFilterLayer layer = ColorFilterLayer(
|
|
colorFilter: const ColorFilter.mode(Color(0xFFFF0000), BlendMode.color),
|
|
);
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.colorFilter = const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color);
|
|
});
|
|
});
|
|
|
|
test('mutating ShaderMaskLayer fields triggers needsAddToScene', () {
|
|
const Gradient gradient = RadialGradient(colors: <Color>[Color(0x00000000), Color(0x00000001)]);
|
|
final Shader shader = gradient.createShader(Rect.zero);
|
|
final ShaderMaskLayer layer = ShaderMaskLayer(shader: shader, maskRect: Rect.zero, blendMode: BlendMode.clear);
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.maskRect = unitRect;
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.blendMode = BlendMode.color;
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.shader = gradient.createShader(unitRect);
|
|
});
|
|
});
|
|
|
|
test('mutating BackdropFilterLayer fields triggers needsAddToScene', () {
|
|
final BackdropFilterLayer layer = BackdropFilterLayer(filter: ImageFilter.blur());
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.filter = ImageFilter.blur(sigmaX: 1.0);
|
|
});
|
|
});
|
|
|
|
test('mutating PhysicalModelLayer fields triggers needsAddToScene', () {
|
|
final PhysicalModelLayer layer = PhysicalModelLayer(
|
|
clipPath: Path(), elevation: 0, color: const Color(0x00000000), shadowColor: const Color(0x00000000));
|
|
checkNeedsAddToScene(layer, () {
|
|
final Path newPath = Path();
|
|
newPath.addRect(unitRect);
|
|
layer.clipPath = newPath;
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.elevation = 1;
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.color = const Color(0x00000001);
|
|
});
|
|
checkNeedsAddToScene(layer, () {
|
|
layer.shadowColor = const Color(0x00000001);
|
|
});
|
|
});
|
|
|
|
group('PhysicalModelLayer checks elevations', () {
|
|
/// Adds the layers to a container where A paints before B.
|
|
///
|
|
/// Expects there to be `expectedErrorCount` errors. Checking elevations is
|
|
/// enabled by default.
|
|
void _testConflicts(
|
|
PhysicalModelLayer layerA,
|
|
PhysicalModelLayer layerB, {
|
|
@required int expectedErrorCount,
|
|
bool enableCheck = true,
|
|
}) {
|
|
assert(expectedErrorCount != null);
|
|
assert(enableCheck || expectedErrorCount == 0, 'Cannot disable check and expect non-zero error count.');
|
|
final OffsetLayer container = OffsetLayer();
|
|
container.append(layerA);
|
|
container.append(layerB);
|
|
debugCheckElevationsEnabled = enableCheck;
|
|
debugDisableShadows = false;
|
|
int errors = 0;
|
|
if (enableCheck) {
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
errors++;
|
|
};
|
|
}
|
|
container.buildScene(SceneBuilder());
|
|
expect(errors, expectedErrorCount);
|
|
debugCheckElevationsEnabled = false;
|
|
}
|
|
|
|
// Tests:
|
|
//
|
|
// ───────────── (LayerA, paints first)
|
|
// │ ───────────── (LayerB, paints second)
|
|
// │ │
|
|
// ───────────────────────────
|
|
test('Overlapping layers at wrong elevation', () {
|
|
final PhysicalModelLayer layerA = PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(0, 0, 20, 20)),
|
|
elevation: 3.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
final PhysicalModelLayer layerB =PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(10, 10, 20, 20)),
|
|
elevation: 2.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
_testConflicts(layerA, layerB, expectedErrorCount: 1);
|
|
});
|
|
|
|
// Tests:
|
|
//
|
|
// ───────────── (LayerA, paints first)
|
|
// │ ───────────── (LayerB, paints second)
|
|
// │ │
|
|
// ───────────────────────────
|
|
//
|
|
// Causes no error if check is disabled.
|
|
test('Overlapping layers at wrong elevation, check disabled', () {
|
|
final PhysicalModelLayer layerA = PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(0, 0, 20, 20)),
|
|
elevation: 3.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
final PhysicalModelLayer layerB =PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(10, 10, 20, 20)),
|
|
elevation: 2.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
_testConflicts(layerA, layerB, expectedErrorCount: 0, enableCheck: false);
|
|
});
|
|
|
|
// Tests:
|
|
//
|
|
// ────────── (LayerA, paints first)
|
|
// │ ─────────── (LayerB, paints second)
|
|
// │ │
|
|
// ────────────────────────────
|
|
test('Non-overlapping layers at wrong elevation', () {
|
|
final PhysicalModelLayer layerA = PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(0, 0, 20, 20)),
|
|
elevation: 3.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
final PhysicalModelLayer layerB =PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(20, 20, 20, 20)),
|
|
elevation: 2.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
_testConflicts(layerA, layerB, expectedErrorCount: 0);
|
|
});
|
|
|
|
// Tests:
|
|
//
|
|
// ─────── (Child of A, paints second)
|
|
// │
|
|
// ─────────── (LayerA, paints first)
|
|
// │ ──────────── (LayerB, paints third)
|
|
// │ │
|
|
// ────────────────────────────
|
|
test('Non-overlapping layers at wrong elevation, child at lower elevation', () {
|
|
final PhysicalModelLayer layerA = PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(0, 0, 20, 20)),
|
|
elevation: 3.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
|
|
layerA.append(PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(2, 2, 10, 10)),
|
|
elevation: 1.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
));
|
|
|
|
final PhysicalModelLayer layerB =PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(20, 20, 20, 20)),
|
|
elevation: 2.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
_testConflicts(layerA, layerB, expectedErrorCount: 0);
|
|
});
|
|
|
|
// Tests:
|
|
//
|
|
// ─────────── (Child of A, paints second, overflows)
|
|
// │ ──────────── (LayerB, paints third)
|
|
// ─────────── │ (LayerA, paints first)
|
|
// │ │
|
|
// │ │
|
|
// ────────────────────────────
|
|
//
|
|
// Which fails because the overflowing child overlaps something that paints
|
|
// after it at a lower elevation.
|
|
test('Child overflows parent and overlaps another physical layer', () {
|
|
final PhysicalModelLayer layerA = PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(0, 0, 20, 20)),
|
|
elevation: 3.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
|
|
layerA.append(PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(15, 15, 25, 25)),
|
|
elevation: 2.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
));
|
|
|
|
final PhysicalModelLayer layerB =PhysicalModelLayer(
|
|
clipPath: Path()..addRect(const Rect.fromLTWH(20, 20, 20, 20)),
|
|
elevation: 4.0,
|
|
color: const Color(0x00000000),
|
|
shadowColor: const Color(0x00000000),
|
|
);
|
|
|
|
_testConflicts(layerA, layerB, expectedErrorCount: 1);
|
|
});
|
|
}, skip: isBrowser);
|
|
|
|
test('ContainerLayer.toImage can render interior layer', () {
|
|
final OffsetLayer parent = OffsetLayer();
|
|
final OffsetLayer child = OffsetLayer();
|
|
final OffsetLayer grandChild = OffsetLayer();
|
|
child.append(grandChild);
|
|
parent.append(child);
|
|
|
|
// This renders the layers and generates engine layers.
|
|
parent.buildScene(SceneBuilder());
|
|
|
|
// Causes grandChild to pass its engine layer as `oldLayer`
|
|
grandChild.toImage(const Rect.fromLTRB(0, 0, 10, 10));
|
|
|
|
// Ensure we can render the same scene again after rendering an interior
|
|
// layer.
|
|
parent.buildScene(SceneBuilder());
|
|
}, skip: isBrowser); // TODO(yjbanov): `toImage` doesn't work on the Web: https://github.com/flutter/flutter/issues/42767
|
|
}
|
|
|
|
class _TestAlwaysNeedsAddToSceneLayer extends ContainerLayer {
|
|
@override
|
|
bool get alwaysNeedsAddToScene => true;
|
|
}
|