// Copyright 2017 The Chromium 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 '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.layer, throwsAssertionError); expect(boundary.isRepaintBoundary, isTrue); expect(boundary.layer, isNotNull); expect(boundary.layer.attached, isTrue); // this time it painted... root.opacity = 0.0; pumpFrame(phase: EnginePhase.paint); expect(inner.isRepaintBoundary, isFalse); expect(() => inner.layer, throwsAssertionError); expect(boundary.isRepaintBoundary, isTrue); expect(boundary.layer, isNotNull); expect(boundary.layer.attached, isFalse); // this time it did not. root.opacity = 0.5; pumpFrame(phase: EnginePhase.paint); expect(inner.isRepaintBoundary, isFalse); expect(() => inner.layer, throwsAssertionError); expect(boundary.isRepaintBoundary, isTrue); expect(boundary.layer, isNotNull); expect(boundary.layer.attached, isTrue); // this time it did again! }); test('layer subtree dirtiness is correctly computed', () { 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 where b and j are dirty: // a____ // / \ // (x)b___ c // / \ \ | // d e f g // / \ | // h i j(x) 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); a.debugMarkClean(); b.markNeedsAddToScene(); c.debugMarkClean(); d.debugMarkClean(); e.debugMarkClean(); f.debugMarkClean(); g.debugMarkClean(); h.debugMarkClean(); i.debugMarkClean(); j.markNeedsAddToScene(); a.updateSubtreeNeedsAddToScene(); expect(a.debugSubtreeNeedsAddToScene, true); expect(b.debugSubtreeNeedsAddToScene, true); expect(c.debugSubtreeNeedsAddToScene, true); expect(g.debugSubtreeNeedsAddToScene, true); expect(j.debugSubtreeNeedsAddToScene, true); expect(d.debugSubtreeNeedsAddToScene, false); expect(e.debugSubtreeNeedsAddToScene, false); expect(f.debugSubtreeNeedsAddToScene, false); expect(h.debugSubtreeNeedsAddToScene, false); expect(i.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); }); void checkNeedsAddToScene(Layer layer, void mutateCallback()) { layer.debugMarkClean(); layer.updateSubtreeNeedsAddToScene(); expect(layer.debugSubtreeNeedsAddToScene, false); mutateCallback(); layer.updateSubtreeNeedsAddToScene(); expect(layer.debugSubtreeNeedsAddToScene, true); } 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; }); }); final 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 ShaderMaskLayer fields triggers needsAddToScene', () { const Gradient gradient = RadialGradient(colors: <Color>[Color(0), Color(1)]); 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(0), shadowColor: const Color(0)); checkNeedsAddToScene(layer, () { final Path newPath = Path(); newPath.addRect(unitRect); layer.clipPath = newPath; }); checkNeedsAddToScene(layer, () { layer.elevation = 1; }); checkNeedsAddToScene(layer, () { layer.color = const Color(1); }); checkNeedsAddToScene(layer, () { layer.shadowColor = const Color(1); }); }); }