layer.dart 37.5 KB
Newer Older
1 2 3 4
// Copyright 2015 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.

5
import 'dart:collection';
6
import 'dart:ui' as ui show ImageFilter, Picture, SceneBuilder;
7
import 'dart:ui' show Offset;
8

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/painting.dart';
11
import 'package:vector_math/vector_math_64.dart';
12

13
import 'debug.dart';
14
import 'node.dart';
15

16
/// A composited layer.
17 18 19 20
///
/// During painting, the render tree generates a tree of composited layers that
/// are uploaded into the engine and displayed by the compositor. This class is
/// the base class for all composited layers.
21 22 23 24 25
///
/// Most layers can have their properties mutated, and layers can be moved to
/// different parents. The scene must be explicitly recomposited after such
/// changes are made; the layer tree does not maintain its own dirty state.
///
26
/// To composite the tree, create a [SceneBuilder] object, pass it to the
27
/// root [Layer] object's [addToScene] method, and then call
28 29
/// [SceneBuilder.build] to obtain a [Scene]. A [Scene] can then be painted
/// using [Window.render].
30 31 32 33 34 35 36 37 38 39 40 41 42 43
///
/// See also:
///
///  * [RenderView.compositeFrame], which implements this recomposition protocol
///    for painting [RenderObject] trees on the the display.
abstract class Layer extends AbstractNode with TreeDiagnosticsMixin {
  /// This layer's parent in the layer tree.
  ///
  /// The [parent] of the root node in the layer tree is null.
  ///
  /// Only subclasses of [ContainerLayer] can have children in the layer tree.
  /// All other layer classes are used for leaves in the layer tree.
  @override
  ContainerLayer get parent => super.parent;
44

45
  /// This layer's next sibling in the parent layer's child list.
46
  Layer get nextSibling => _nextSibling;
47
  Layer _nextSibling;
48

49
  /// This layer's previous sibling in the parent layer's child list.
50
  Layer get previousSibling => _previousSibling;
51
  Layer _previousSibling;
52

53
  /// Removes this layer from its parent layer's child list.
54
  @mustCallSuper
55 56
  void remove() {
    parent?._removeChild(this);
57
  }
58

59
  /// Replaces this layer with the given layer in the parent layer's child list.
Hixie's avatar
Hixie committed
60
  void replaceWith(Layer newLayer) {
61 62 63
    assert(parent != null);
    assert(attached == parent.attached);
    assert(newLayer.parent == null);
Hixie's avatar
Hixie committed
64 65
    assert(newLayer._nextSibling == null);
    assert(newLayer._previousSibling == null);
66 67
    assert(!newLayer.attached);
    newLayer._nextSibling = nextSibling;
Hixie's avatar
Hixie committed
68
    if (_nextSibling != null)
69 70
      _nextSibling._previousSibling = newLayer;
    newLayer._previousSibling = previousSibling;
Hixie's avatar
Hixie committed
71
    if (_previousSibling != null)
72
      _previousSibling._nextSibling = newLayer;
73 74 75 76 77 78 79
    assert(() {
      Layer node = this;
      while (node.parent != null)
        node = node.parent;
      assert(node != newLayer); // indicates we are about to create a cycle
      return true;
    });
80 81 82 83 84 85
    parent.adoptChild(newLayer);
    assert(newLayer.attached == parent.attached);
    if (parent.firstChild == this)
      parent._firstChild = newLayer;
    if (parent.lastChild == this)
      parent._lastChild = newLayer;
Hixie's avatar
Hixie committed
86 87
    _nextSibling = null;
    _previousSibling = null;
88 89
    parent.dropChild(this);
    assert(!attached);
Hixie's avatar
Hixie committed
90
  }
91

92
  /// Override this method to upload this layer to the engine.
93
  ///
94
  /// The `layerOffset` is the accumulated offset of this layer's parent from the
95
  /// origin of the builder's coordinate system.
96
  void addToScene(ui.SceneBuilder builder, Offset layerOffset);
97

98 99 100 101
  /// The object responsible for creating this layer.
  ///
  /// Defaults to the value of [RenderObject.debugCreator] for the render object
  /// that created this layer. Used in debug messages.
102
  dynamic debugCreator;
103

104 105 106
  @override
  String toString() => '${super.toString()}${ owner == null ? " DETACHED" : ""}';

107
  @override
108 109 110 111
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<Object>('owner', owner, hidden: parent != null, defaultValue: null));
    description.add(new DiagnosticsProperty<dynamic>('creator', debugCreator, defaultValue: null));
112
  }
113 114
}

115 116 117
/// A composited layer containing a [Picture].
///
/// Picture layers are always leaves in the layer tree.
118
class PictureLayer extends Layer {
119
  /// Creates a leaf layer for the layer tree.
120 121 122 123 124 125 126 127 128 129
  PictureLayer(this.canvasBounds);

  /// The bounds that were used for the canvas that drew this layer's [picture].
  ///
  /// This is purely advisory. It is included in the information dumped with
  /// [dumpLayerTree] (which can be triggered by pressing "L" when using
  /// "flutter run" at the console), which can help debug why certain drawing
  /// commands are being culled.
  final Rect canvasBounds;

130
  /// The picture recorded for this layer.
131
  ///
132 133 134 135
  /// The picture's coodinate system matches this layer's coodinate system.
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
136
  ui.Picture picture;
137

138 139 140 141 142
  /// Hints that the painting in this layer is complex and would benefit from
  /// caching.
  ///
  /// If this hint is not set, the compositor will apply its own heuristics to
  /// decide whether the this layer is complex enough to benefit from caching.
143 144 145
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
146 147 148 149 150 151 152 153
  bool isComplexHint = false;

  /// Hints that the painting in this layer is likely to change next frame.
  ///
  /// This hint tells the compositor not to cache this layer because the cache
  /// will not be used in the future. If this hint is not set, the compositor
  /// will apply its own heuristics to decide whether this layer is likely to be
  /// reused in the future.
154 155 156
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
157 158
  bool willChangeHint = false;

159
  @override
160
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
161
    builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
162
  }
163 164

  @override
165 166 167
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<Rect>('paint bounds', canvasBounds));
168
  }
169 170
}

171
/// A layer that indicates to the compositor that it should display
172
/// certain performance statistics within it.
173 174
///
/// Performance overlay layers are always leaves in the layer tree.
175
class PerformanceOverlayLayer extends Layer {
176
  /// Creates a layer that displays a performance overlay.
177
  PerformanceOverlayLayer({
178 179 180 181
    @required this.overlayRect,
    @required this.optionsMask,
    @required this.rasterizerThreshold,
    @required this.checkerboardRasterCacheImages,
182
    @required this.checkerboardOffscreenLayers,
183
  });
184

185
  /// The rectangle in this layer's coordinate system that the overlay should occupy.
186 187 188
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
189
  Rect overlayRect;
190

191 192
  /// The mask is created by shifting 1 by the index of the specific
  /// [PerformanceOverlayOption] to enable.
193 194
  final int optionsMask;

195 196 197
  /// The rasterizer threshold is an integer specifying the number of frame
  /// intervals that the rasterizer must miss before it decides that the frame
  /// is suitable for capturing an SkPicture trace for further analysis.
198
  final int rasterizerThreshold;
199

200 201 202 203 204 205 206 207 208 209 210 211 212
  /// Whether the raster cache should checkerboard cached entries.
  ///
  /// The compositor can sometimes decide to cache certain portions of the
  /// widget hierarchy. Such portions typically don't change often from frame to
  /// frame and are expensive to render. This can speed up overall rendering. However,
  /// there is certain upfront cost to constructing these cache entries. And, if
  /// the cache entries are not used very often, this cost may not be worth the
  /// speedup in rendering of subsequent frames. If the developer wants to be certain
  /// that populating the raster cache is not causing stutters, this option can be
  /// set. Depending on the observations made, hints can be provided to the compositor
  /// that aid it in making better decisions about caching.
  final bool checkerboardRasterCacheImages;

213 214 215 216 217 218 219 220 221 222
  /// Whether the compositor should checkerboard layers that are rendered to offscreen
  /// bitmaps. This can be useful for debugging rendering performance.
  ///
  /// Render target switches are caused by using opacity layers (via a [FadeTransition] or
  /// [Opacity] widget), clips, shader mask layers, etc. Selecting a new render target
  /// and merging it with the rest of the scene has a performance cost. This can sometimes
  /// be avoided by using equivalent widgets that do not require these layers (for example,
  /// replacing an [Opacity] widget with an [widgets.Image] using a [BlendMode]).
  final bool checkerboardOffscreenLayers;

223
  @override
224
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
225
    assert(optionsMask != null);
226
    builder.addPerformanceOverlay(optionsMask, overlayRect.shift(layerOffset));
227
    builder.setRasterizerTracingThreshold(rasterizerThreshold);
228
    builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
229
    builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers);
230 231 232
  }
}

233 234 235 236 237
/// A composited layer that has a list of children.
///
/// A [ContainerLayer] instance merely takes a list of children and inserts them
/// into the composited rendering in order. There are subclasses of
/// [ContainerLayer] which apply more elaborate effects in the process.
238
class ContainerLayer extends Layer {
239
  /// The first composited layer in this layer's child list.
240
  Layer get firstChild => _firstChild;
241
  Layer _firstChild;
242

243
  /// The last composited layer in this layer's child list.
244
  Layer get lastChild => _lastChild;
245
  Layer _lastChild;
246 247

  bool _debugUltimatePreviousSiblingOf(Layer child, { Layer equals }) {
248 249 250 251 252
    assert(child.attached == attached);
    while (child.previousSibling != null) {
      assert(child.previousSibling != child);
      child = child.previousSibling;
      assert(child.attached == attached);
253 254 255 256 257
    }
    return child == equals;
  }

  bool _debugUltimateNextSiblingOf(Layer child, { Layer equals }) {
258
    assert(child.attached == attached);
259 260 261
    while (child._nextSibling != null) {
      assert(child._nextSibling != child);
      child = child._nextSibling;
262
      assert(child.attached == attached);
263 264 265 266
    }
    return child == equals;
  }

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
  @override
  void attach(Object owner) {
    super.attach(owner);
    Layer child = firstChild;
    while (child != null) {
      child.attach(owner);
      child = child.nextSibling;
    }
  }

  @override
  void detach() {
    super.detach();
    Layer child = firstChild;
    while (child != null) {
      child.detach();
      child = child.nextSibling;
    }
  }

  /// Adds the given layer to the end of this layer's child list.
288
  void append(Layer child) {
289
    assert(child != this);
290 291 292 293 294 295
    assert(child != firstChild);
    assert(child != lastChild);
    assert(child.parent == null);
    assert(!child.attached);
    assert(child.nextSibling == null);
    assert(child.previousSibling == null);
296 297 298 299 300 301 302
    assert(() {
      Layer node = this;
      while (node.parent != null)
        node = node.parent;
      assert(node != child); // indicates we are about to create a cycle
      return true;
    });
303 304 305 306
    adoptChild(child);
    child._previousSibling = lastChild;
    if (lastChild != null)
      lastChild._nextSibling = child;
307
    _lastChild = child;
308
    _firstChild ??= child;
309
    assert(child.attached == attached);
310 311
  }

312 313 314 315 316 317
  // Implementation of [Layer.remove].
  void _removeChild(Layer child) {
    assert(child.parent == this);
    assert(child.attached == attached);
    assert(_debugUltimatePreviousSiblingOf(child, equals: firstChild));
    assert(_debugUltimateNextSiblingOf(child, equals: lastChild));
318 319 320 321
    if (child._previousSibling == null) {
      assert(_firstChild == child);
      _firstChild = child._nextSibling;
    } else {
322
      child._previousSibling._nextSibling = child.nextSibling;
323 324
    }
    if (child._nextSibling == null) {
325 326
      assert(lastChild == child);
      _lastChild = child.previousSibling;
327
    } else {
328
      child.nextSibling._previousSibling = child.previousSibling;
329
    }
330 331 332 333 334
    assert((firstChild == null) == (lastChild == null));
    assert(firstChild == null || firstChild.attached == attached);
    assert(lastChild == null || lastChild.attached == attached);
    assert(firstChild == null || _debugUltimateNextSiblingOf(firstChild, equals: lastChild));
    assert(lastChild == null || _debugUltimatePreviousSiblingOf(lastChild, equals: firstChild));
335 336
    child._previousSibling = null;
    child._nextSibling = null;
337 338
    dropChild(child);
    assert(!child.attached);
339
  }
Hixie's avatar
Hixie committed
340

341
  /// Removes all of this layer's children from its child list.
342
  void removeAllChildren() {
343
    Layer child = firstChild;
344
    while (child != null) {
345
      final Layer next = child.nextSibling;
346 347
      child._previousSibling = null;
      child._nextSibling = null;
348 349
      assert(child.attached == attached);
      dropChild(child);
350 351 352 353 354 355
      child = next;
    }
    _firstChild = null;
    _lastChild = null;
  }

356
  @override
357
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
358
    addChildrenToScene(builder, layerOffset);
Hixie's avatar
Hixie committed
359 360
  }

361 362 363 364
  /// Uploads all of this layer's children to the engine.
  ///
  /// This method is typically used by [addToScene] to insert the children into
  /// the scene. Subclasses of [ContainerLayer] typically override [addToScene]
365
  /// to apply effects to the scene using the [SceneBuilder] API, then insert
366 367
  /// their children using [addChildrenToScene], then reverse the aforementioned
  /// effects before returning from [addToScene].
368
  void addChildrenToScene(ui.SceneBuilder builder, Offset childOffset) {
369
    Layer child = firstChild;
Hixie's avatar
Hixie committed
370
    while (child != null) {
371
      child.addToScene(builder, childOffset);
Hixie's avatar
Hixie committed
372 373 374
      child = child.nextSibling;
    }
  }
375

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
  /// Applies the transform that would be applied when compositing the given
  /// child to the given matrix.
  ///
  /// Specifically, this should apply the transform that is applied to child's
  /// _origin_. When using [applyTransform] with a chain of layers, results will
  /// be unreliable unless the deepest layer in the chain collapses the
  /// `layerOffset` in [addToScene] to zero, meaning that it passes
  /// [Offset.zero] to its children, and bakes any incoming `layerOffset` into
  /// the [SceneBuilder] as (for instance) a transform (which is then also
  /// included in the transformation applied by [applyTransform]).
  ///
  /// For example, if [addToScene] applies the `layerOffset` and then
  /// passes [Offset.zero] to the children, then it should be included in the
  /// transform applied here, whereas if [addToScene] just passes the
  /// `layerOffset` to the child, then it should not be included in the
  /// transform applied here.
  ///
  /// This method is only valid immediately after [addToScene] has been called,
  /// before any of the properties have been changed.
  ///
  /// The default implementation does nothing, since [ContainerLayer], by
  /// default, composits its children at the origin of the [ContainerLayer]
  /// itself.
  ///
  /// The `child` argument should generally not be null, since in principle a
  /// layer could transform each child independently. However, certain layers
  /// may explicitly allow null as a value, for example if they know that they
  /// transform all their children identically.
  ///
  /// The `transform` argument must not be null.
  ///
  /// Used by [FollowerLayer] to transform its child to a [LeaderLayer]'s
  /// position.
  void applyTransform(Layer child, Matrix4 transform) {
    assert(child != null);
    assert(transform != null);
  }

414
  @override
415 416
  List<DiagnosticsNode> debugDescribeChildren() {
    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
417
    if (firstChild == null)
418
      return children;
419 420
    Layer child = firstChild;
    int count = 1;
421 422 423 424
    while (true) {
      children.add(child.toDiagnosticsNode(name: 'child $count'));
      if (child == lastChild)
        break;
425 426 427
      count += 1;
      child = child.nextSibling;
    }
428
    return children;
429
  }
Hixie's avatar
Hixie committed
430 431
}

432 433 434 435 436 437 438 439 440
/// A layer that is displayed at an offset from its parent layer.
///
/// Offset layers are key to efficient repainting because they are created by
/// repaint boundaries in the [RenderObject] tree (see
/// [RenderObject.isRepaintBoundary]). When a render object that is a repaint
/// boundary is asked to paint at given offset in a [PaintingContext], the
/// render object first checks whether it needs to repaint itself. If not, it
/// reuses its existing [OffsetLayer] (and its entire subtree) by mutating its
/// [offset] property, cutting off the paint walk.
441
class OffsetLayer extends ContainerLayer {
442 443
  /// Creates an offset layer.
  ///
444 445
  /// By default, [offset] is zero. It must be non-null before the compositing
  /// phase of the pipeline.
446 447 448
  OffsetLayer({ this.offset: Offset.zero });

  /// Offset from parent in the parent's coordinate system.
449 450 451
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
452 453 454
  ///
  /// The [offset] property must be non-null before the compositing phase of the
  /// pipeline.
455 456
  Offset offset;

457
  @override
458 459 460 461
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    addChildrenToScene(builder, offset + layerOffset);
  }

462
  @override
463 464 465
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<Offset>('offset', offset));
466 467 468
  }
}

469
/// A composite layer that clips its children using a rectangle.
Hixie's avatar
Hixie committed
470
class ClipRectLayer extends ContainerLayer {
471 472 473 474
  /// Creates a layer with a rectangular clip.
  ///
  /// The [clipRect] property must be non-null before the compositing phase of
  /// the pipeline.
475
  ClipRectLayer({ this.clipRect });
Hixie's avatar
Hixie committed
476

477 478 479 480
  /// The rectangle to clip in the parent's coordinate system.
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
Hixie's avatar
Hixie committed
481 482
  Rect clipRect;

483
  @override
484
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
485 486
    builder.pushClipRect(clipRect.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
487
    builder.pop();
Hixie's avatar
Hixie committed
488 489
  }

490
  @override
491 492 493
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<Rect>('clipRect', clipRect));
494
  }
495
}
Hixie's avatar
Hixie committed
496

497
/// A composite layer that clips its children using a rounded rectangle.
Hixie's avatar
Hixie committed
498
class ClipRRectLayer extends ContainerLayer {
499 500 501 502
  /// Creates a layer with a rounded-rectangular clip.
  ///
  /// The [clipRRect] property must be non-null before the compositing phase of
  /// the pipeline.
503
  ClipRRectLayer({ this.clipRRect });
504

505 506 507 508
  /// The rounded-rect to clip in the parent's coordinate system.
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
509
  RRect clipRRect;
Hixie's avatar
Hixie committed
510

511
  @override
512
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
513 514
    builder.pushClipRRect(clipRRect.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
515
    builder.pop();
Hixie's avatar
Hixie committed
516
  }
517

518
  @override
519 520 521
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<RRect>('clipRRect', clipRRect));
522
  }
Hixie's avatar
Hixie committed
523 524
}

525
/// A composite layer that clips its children using a path.
Hixie's avatar
Hixie committed
526
class ClipPathLayer extends ContainerLayer {
527 528 529 530
  /// Creates a layer with a path-based clip.
  ///
  /// The [clipPath] property must be non-null before the compositing phase of
  /// the pipeline.
531
  ClipPathLayer({ this.clipPath });
532

533 534 535 536
  /// The path to clip in the parent's coordinate system.
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
Hixie's avatar
Hixie committed
537 538
  Path clipPath;

539
  @override
540
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
541 542
    builder.pushClipPath(clipPath.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
543
    builder.pop();
Hixie's avatar
Hixie committed
544
  }
545

546
  @override
547 548 549
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<Path>('clipPath', clipPath));
550
  }
551 552
}

553 554 555 556 557
/// A composited layer that applies a given transformation matrix to its
/// children.
///
/// This class inherits from [OffsetLayer] to make it one of the layers that
/// can be used at the root of a [RenderObject] hierarchy.
558
class TransformLayer extends OffsetLayer {
559 560
  /// Creates a transform layer.
  ///
561 562 563
  /// The [transform] and [offset] properties must be non-null before the
  /// compositing phase of the pipeline.
  TransformLayer({ this.transform, Offset offset: Offset.zero }) : super(offset: offset);
564

565 566 567 568
  /// The matrix to apply.
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
569 570 571 572 573
  ///
  /// This transform is applied before [offset], if both are set.
  ///
  /// The [transform] property must be non-null before the compositing phase of
  /// the pipeline.
574
  Matrix4 transform;
575

576 577
  Matrix4 _lastEffectiveTransform;

578
  @override
579
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
580 581 582 583 584
    _lastEffectiveTransform = transform;
    final Offset totalOffset = offset + layerOffset;
    if (totalOffset != Offset.zero) {
      _lastEffectiveTransform = new Matrix4.translationValues(totalOffset.dx, totalOffset.dy, 0.0)
        ..multiply(_lastEffectiveTransform);
585
    }
586
    builder.pushTransform(_lastEffectiveTransform.storage);
587 588
    addChildrenToScene(builder, Offset.zero);
    builder.pop();
589
  }
590

591 592 593 594 595 596 597
  @override
  void applyTransform(Layer child, Matrix4 transform) {
    assert(child != null);
    assert(transform != null);
    transform.multiply(_lastEffectiveTransform);
  }

598
  @override
599 600 601
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new TransformProperty('transform', transform));
602
  }
603 604
}

605
/// A composited layer that makes its children partially transparent.
606
class OpacityLayer extends ContainerLayer {
607 608 609 610
  /// Creates an opacity layer.
  ///
  /// The [alpha] property must be non-null before the compositing phase of
  /// the pipeline.
611
  OpacityLayer({ this.alpha });
612

613
  /// The amount to multiply into the alpha channel.
614 615 616
  ///
  /// The opacity is expressed as an integer from 0 to 255, where 0 is fully
  /// transparent and 255 is fully opaque.
617 618 619
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
620 621
  int alpha;

622
  @override
623
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
Adam Barth's avatar
Adam Barth committed
624
    builder.pushOpacity(alpha);
625
    addChildrenToScene(builder, layerOffset);
626
    builder.pop();
627
  }
628

629
  @override
630 631 632
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new IntProperty('alpha', alpha));
633
  }
634
}
635

636
/// A composited layer that applies a shader to its children.
637
class ShaderMaskLayer extends ContainerLayer {
638 639
  /// Creates a shader mask layer.
  ///
640
  /// The [shader], [maskRect], and [blendMode] properties must be non-null
641
  /// before the compositing phase of the pipeline.
642
  ShaderMaskLayer({ this.shader, this.maskRect, this.blendMode });
643 644

  /// The shader to apply to the children.
645 646 647
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
648
  Shader shader;
649 650

  /// The size of the shader.
651 652 653
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
654 655
  Rect maskRect;

656
  /// The blend mode to apply when blending the shader with the children.
657 658 659
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
660
  BlendMode blendMode;
661

662
  @override
663
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
664
    builder.pushShaderMask(shader, maskRect.shift(layerOffset), blendMode);
665
    addChildrenToScene(builder, layerOffset);
666 667 668
    builder.pop();
  }

669
  @override
670 671 672 673 674
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<Shader>('shader', shader));
    description.add(new DiagnosticsProperty<Rect>('maskRect', maskRect));
    description.add(new DiagnosticsProperty<BlendMode>('blendMode', blendMode));
675 676
  }
}
677 678 679

/// A composited layer that applies a filter to the existing contents of the scene.
class BackdropFilterLayer extends ContainerLayer {
680 681 682 683
  /// Creates a backdrop filter layer.
  ///
  /// The [filter] property must be non-null before the compositing phase of the
  /// pipeline.
684 685 686
  BackdropFilterLayer({ this.filter });

  /// The filter to apply to the existing contents of the scene.
687 688 689
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
690 691 692 693 694 695 696 697 698
  ui.ImageFilter filter;

  @override
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.pushBackdropFilter(filter);
    addChildrenToScene(builder, layerOffset);
    builder.pop();
  }
}
699

700 701 702 703 704
/// A composited layer that uses a physical model to producing lighting effects.
///
/// For example, the layer casts a shadow according to its geometry and the
/// relative position of lights and other physically modelled objects in the
/// scene.
705
class PhysicalModelLayer extends ContainerLayer {
706 707 708 709
  /// Creates a composited layer that uses a physical model to producing
  /// lighting effects.
  ///
  /// The [clipRRect], [elevation], and [color] arguments must not be null.
710 711 712 713
  PhysicalModelLayer({
    @required this.clipRRect,
    @required this.elevation,
    @required this.color,
714 715 716
  }) : assert(clipRRect != null),
       assert(elevation != null),
       assert(color != null);
717

718 719 720 721
  /// The rounded-rect to clip in the parent's coordinate system.
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
722 723 724
  RRect clipRRect;

  /// The z-coordinate at which to place this physical object.
725 726 727
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
728
  double elevation;
729 730

  /// The background color.
731 732 733
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
734 735 736 737 738 739
  Color color;

  @override
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.pushPhysicalModel(
      rrect: clipRRect.shift(layerOffset),
740
      elevation: elevation,
741 742 743 744 745 746 747
      color: color,
    );
    addChildrenToScene(builder, layerOffset);
    builder.pop();
  }

  @override
748 749 750 751 752
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<RRect>('clipRRect', clipRRect));
    description.add(new DoubleProperty('elevation', elevation));
    description.add(new DiagnosticsProperty<Color>('color', color));
753 754
  }
}
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773

/// An object that a [LeaderLayer] can register with.
///
/// An instance of this class should be provided as the [LeaderLayer.link] and
/// the [FollowerLayer.link] properties to cause the [FollowerLayer] to follow
/// the [LeaderLayer].
///
/// See also:
///
///  * [CompositedTransformTarget], the widget that creates a [LeaderLayer].
///  * [CompositedTransformFollower], the widget that creates a [FollowerLayer].
///  * [RenderLeaderLayer] and [RenderFollowerLayer], the corresponding
///    render objects.
class LayerLink {
  /// The currently-registered [LeaderLayer], if any.
  LeaderLayer get leader => _leader;
  LeaderLayer _leader;

  @override
774
  String toString() => '${describeIdentity(this)}(${ _leader != null ? "<linked>" : "<dangling>" })';
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
}

/// A composited layer that can be followed by a [FollowerLayer].
///
/// This layer collapses the accumulated offset into a transform and passes
/// [Offset.zero] to its child layers in the [addToScene]/[addChildrenToScene]
/// methods, so that [applyTransform] will work reliably.
class LeaderLayer extends ContainerLayer {
  /// Creates a leader layer.
  ///
  /// The [link] property must not be null, and must not have been provided to
  /// any other [LeaderLayer] layers that are [attached] to the layer tree at
  /// the same time.
  ///
  /// The [offset] property must be non-null before the compositing phase of the
  /// pipeline.
  LeaderLayer({ @required this.link, this.offset: Offset.zero }) : assert(link != null);

  /// The object with which this layer should register.
  ///
  /// The link will be established when this layer is [attach]ed, and will be
  /// cleared when this layer is [detach]ed.
  final LayerLink link;

  /// Offset from parent in the parent's coordinate system.
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
  ///
  /// The [offset] property must be non-null before the compositing phase of the
  /// pipeline.
  Offset offset;

  @override
  void attach(Object owner) {
    super.attach(owner);
    assert(link.leader == null);
    _lastOffset = null;
    link._leader = this;
  }

  @override
  void detach() {
    assert(link.leader == this);
    link._leader = null;
    _lastOffset = null;
    super.detach();
  }

  /// The offset the last time this layer was composited.
  ///
  /// This is reset to null when the layer is attached or detached, to help
  /// catch cases where the follower layer ends up before the leader layer, but
  /// not every case can be detected.
  Offset _lastOffset;

  @override
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    assert(offset != null);
    _lastOffset = offset + layerOffset;
    if (_lastOffset != Offset.zero)
      builder.pushTransform(new Matrix4.translationValues(_lastOffset.dx, _lastOffset.dy, 0.0).storage);
    addChildrenToScene(builder, Offset.zero);
    if (_lastOffset != Offset.zero)
      builder.pop();
  }

  /// Applies the transform that would be applied when compositing the given
  /// child to the given matrix.
  ///
  /// See [ContainerLayer.applyTransform] for details.
  ///
  /// The `child` argument may be null, as the same transform is applied to all
  /// children.
  @override
  void applyTransform(Layer child, Matrix4 transform) {
    assert(_lastOffset != null);
    if (_lastOffset != Offset.zero)
      transform.translate(_lastOffset.dx, _lastOffset.dy);
  }

  @override
857 858 859 860
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<Offset>('offset', offset));
    description.add(new DiagnosticsProperty<LayerLink>('link', link));
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
  }
}

/// A composited layer that applies a transformation matrix to its children such
/// that they are positioned to match a [LeaderLayer].
///
/// If any of the ancestors of this layer have a degenerate matrix (e.g. scaling
/// by zero), then the [FollowerLayer] will not be able to transform its child
/// to the coordinate space of the [Leader].
///
/// A [linkedOffset] property can be provided to further offset the child layer
/// from the leader layer, for example if the child is to follow the linked
/// layer at a distance rather than directly overlapping it.
class FollowerLayer extends ContainerLayer {
  /// Creates a follower layer.
  ///
  /// The [link] property must not be null.
  ///
  /// The [unlinkedOffset], [linkedOffset], and [showWhenUnlinked] properties
  /// must be non-null before the compositing phase of the pipeline.
  FollowerLayer({
    @required this.link,
    this.showWhenUnlinked: true,
    this.unlinkedOffset: Offset.zero,
    this.linkedOffset: Offset.zero,
  }) : assert(link != null);

  /// The link to the [LeaderLayer].
  ///
  /// The same object should be provided to a [LeaderLayer] that is earlier in
  /// the layer tree. When this layer is composited, it will apply a transform
  /// that moves its children to match the position of the [LeaderLayer].
  final LayerLink link;

  /// Whether to show the layer's contents when the [link] does not point to a
  /// [LeaderLayer].
  ///
  /// When the layer is linked, children layers are positioned such that they
  /// have the same global position as the linked [LeaderLayer].
  ///
  /// When the layer is not linked, then: if [showWhenUnlinked] is true,
  /// children are positioned as if the [FollowerLayer] was a [ContainerLayer];
  /// if it is false, then children are hidden.
  ///
  /// The [showWhenUnlinked] property must be non-null before the compositing
  /// phase of the pipeline.
  bool showWhenUnlinked;

  /// Offset from parent in the parent's coordinate system, used when the layer
  /// is not linked to a [LeaderLayer].
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
  ///
  /// The [unlinkedOffset] property must be non-null before the compositing
  /// phase of the pipeline.
  ///
  /// See also:
  ///
  ///  * [linkedOffset], for when the layers are linked.
  Offset unlinkedOffset;

  /// Offset from the origin of the leader layer to the origin of the child
  /// layers, used when the layer is linked to a [LeaderLayer].
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
  ///
  /// The [linkedOffset] property must be non-null before the compositing phase
  /// of the pipeline.
  ///
  /// See also:
  ///
  ///  * [unlinkedOffset], for when the layer is not linked.
  Offset linkedOffset;

  Offset _lastOffset;
  Matrix4 _lastTransform;

  /// The transform that was used during the last composition phase.
  ///
  /// If the [link] was not linked to a [LeaderLayer], or if this layer has
  /// a degerenate matrix applied, then this will be null.
  ///
  /// This method returns a new [Matrix4] instance each time it is invoked.
  Matrix4 getLastTransform() {
    if (_lastTransform == null)
      return null;
    final Matrix4 result = new Matrix4.translationValues(-_lastOffset.dx, -_lastOffset.dy, 0.0);
    result.multiply(_lastTransform);
    return result;
  }

  /// Call [applyTransform] for each layer in the provided list.
  ///
  /// The list is in reverse order (deepest first). The first layer will be
  /// treated as the child of the second, and so forth. The first layer in the
  /// list won't have [applyTransform] called on it. The first layer may be
  /// null.
  Matrix4 _collectTransformForLayerChain(List<ContainerLayer> layers) {
    // Initialize our result matrix.
    final Matrix4 result = new Matrix4.identity();
    // Apply each layer to the matrix in turn, starting from the last layer,
    // and providing the previous layer as the child.
    for (int index = layers.length - 1; index > 0; index -= 1)
      layers[index].applyTransform(layers[index - 1], result);
    return result;
  }

  /// Populate [_lastTransform] given the current state of the tree.
  void _establishTransform() {
    assert(link != null);
    _lastTransform = null;
    // Check to see if we are linked.
    if (link.leader == null)
      return;
    // If we're linked, check the link is valid.
    assert(link.leader.owner == owner, 'Linked LeaderLayer anchor is not in the same layer tree as the FollowerLayer.');
    assert(link.leader._lastOffset != null, 'LeaderLayer anchor must come before FollowerLayer in paint order, but the reverse was true.');
    // Collect all our ancestors into a Set so we can recognize them.
    final Set<Layer> ancestors = new HashSet<Layer>();
    Layer ancestor = parent;
    while (ancestor != null) {
      ancestors.add(ancestor);
      ancestor = ancestor.parent;
    }
    // Collect all the layers from a hypothetical child (null) of the target
    // layer up to the common ancestor layer.
    ContainerLayer layer = link.leader;
    final List<ContainerLayer> forwardLayers = <ContainerLayer>[null, layer];
    do {
      layer = layer.parent;
      forwardLayers.add(layer);
    } while (!ancestors.contains(layer));
    ancestor = layer;
    // Collect all the layers from this layer up to the common ancestor layer.
    layer = this;
    final List<ContainerLayer> inverseLayers = <ContainerLayer>[layer];
    do {
      layer = layer.parent;
      inverseLayers.add(layer);
    } while (layer != ancestor);
    // Establish the forward and backward matrices given these lists of layers.
    final Matrix4 forwardTransform = _collectTransformForLayerChain(forwardLayers);
    final Matrix4 inverseTransform = _collectTransformForLayerChain(inverseLayers);
    if (inverseTransform.invert() == 0.0) {
      // We are in a degenerate transform, so there's not much we can do.
      return;
    }
    // Combine the matrices and store the result.
    inverseTransform.multiply(forwardTransform);
    inverseTransform.translate(linkedOffset.dx, linkedOffset.dy);
    _lastTransform = inverseTransform;
  }

  @override
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    assert(link != null);
    assert(showWhenUnlinked != null);
    if (link.leader == null && !showWhenUnlinked) {
      _lastTransform = null;
      _lastOffset = null;
      return;
    }
    _establishTransform();
    if (_lastTransform != null) {
      builder.pushTransform(_lastTransform.storage);
      addChildrenToScene(builder, Offset.zero);
      builder.pop();
      _lastOffset = unlinkedOffset + layerOffset;
    } else {
      _lastOffset = null;
      addChildrenToScene(builder, unlinkedOffset + layerOffset);
    }
  }

  @override
  void applyTransform(Layer child, Matrix4 transform) {
    assert(child != null);
    assert(transform != null);
    if (_lastTransform != null)
      transform.multiply(_lastTransform);
  }

  @override
1046 1047 1048 1049
  void debugFillProperties(List<DiagnosticsNode> description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<LayerLink>('link', link));
    description.add(new TransformProperty('transform', getLastTransform(), defaultValue: null));
1050 1051
  }
}