layer.dart 47.7 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:async';
6
import 'dart:collection';
7
import 'dart:ui' as ui show Image, ImageFilter, Picture, Scene, SceneBuilder;
8
import 'dart:ui' show Offset;
9

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

14 15
import 'debug.dart';

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
///
/// See also:
///
///  * [RenderView.compositeFrame], which implements this recomposition protocol
34
///    for painting [RenderObject] trees on the display.
35
abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
36 37 38 39 40 41 42 43
  /// 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
    assert(() {
      Layer node = this;
      while (node.parent != null)
        node = node.parent;
      assert(node != newLayer); // indicates we are about to create a cycle
      return true;
79
    }());
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 93 94 95 96 97 98 99 100 101 102 103 104
  /// Returns the value of [S] that corresponds to the point described by
  /// [regionOffset].
  ///
  /// Returns null if no matching region is found.
  ///
  /// The main way for a value to be assigned here is by pushing an
  /// [AnnotatedRegionLayer] into the layer tree.
  ///
  /// See also:
  ///
  ///   * [AnnotatedRegionLayer], for placing values in the layer tree.
  S find<S>(Offset regionOffset);

105
  /// Override this method to upload this layer to the engine.
106
  ///
107
  /// The `layerOffset` is the accumulated offset of this layer's parent from the
108
  /// origin of the builder's coordinate system.
109
  void addToScene(ui.SceneBuilder builder, Offset layerOffset);
110

111 112 113 114
  /// 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.
115
  dynamic debugCreator;
116

117
  @override
118
  String toStringShort() => '${super.toStringShort()}${ owner == null ? " DETACHED" : ""}';
119

120
  @override
121 122 123 124
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Object>('owner', owner, level: parent != null ? DiagnosticLevel.hidden : DiagnosticLevel.info, defaultValue: null));
    properties.add(new DiagnosticsProperty<dynamic>('creator', debugCreator, defaultValue: null, level: DiagnosticLevel.debug));
125
  }
126 127
}

128 129 130
/// A composited layer containing a [Picture].
///
/// Picture layers are always leaves in the layer tree.
131
class PictureLayer extends Layer {
132
  /// Creates a leaf layer for the layer tree.
133 134 135 136 137
  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
138
  /// [debugDumpLayerTree] (which can be triggered by pressing "L" when using
139 140 141 142
  /// "flutter run" at the console), which can help debug why certain drawing
  /// commands are being culled.
  final Rect canvasBounds;

143
  /// The picture recorded for this layer.
144
  ///
145
  /// The picture's coordinate system matches this layer's coordinate system.
146 147 148
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
149
  ui.Picture picture;
150

151 152 153 154 155
  /// 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.
156 157 158
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
159 160 161 162 163 164 165 166
  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.
167 168 169
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
170 171
  bool willChangeHint = false;

172
  @override
173
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
174
    builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
175
  }
176 177

  @override
178 179 180
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Rect>('paint bounds', canvasBounds));
181
  }
182 183 184

  @override
  S find<S>(Offset regionOffset) => null;
185 186
}

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
/// A composited layer that maps a backend texture to a rectangle.
///
/// Backend textures are images that can be applied (mapped) to an area of the
/// Flutter view. They are created, managed, and updated using a
/// platform-specific texture registry. This is typically done by a plugin
/// that integrates with host platform video player, camera, or OpenGL APIs,
/// or similar image sources.
///
/// A texture layer refers to its backend texture using an integer ID. Texture
/// IDs are obtained from the texture registry and are scoped to the Flutter
/// view. Texture IDs may be reused after deregistration, at the discretion
/// of the registry. The use of texture IDs currently unknown to the registry
/// will silently result in a blank rectangle.
///
/// Once inserted into the layer tree, texture layers are repainted autonomously
/// as dictated by the backend (e.g. on arrival of a video frame). Such
/// repainting generally does not involve executing Dart code.
///
/// Texture layers are always leaves in the layer tree.
///
/// See also:
///
/// * <https://docs.flutter.io/javadoc/io/flutter/view/TextureRegistry.html>
///   for how to create and manage backend textures on Android.
/// * <https://docs.flutter.io/objcdoc/Protocols/FlutterTextureRegistry.html>
///   for how to create and manage backend textures on iOS.
class TextureLayer extends Layer {
  /// Creates a texture layer bounded by [rect] and with backend texture
  /// identified by [textureId].
  TextureLayer({
    @required this.rect,
    @required this.textureId,
  }): assert(rect != null), assert(textureId != null);

  /// Bounding rectangle of this layer.
  final Rect rect;

  /// The identity of the backend texture.
  final int textureId;

  @override
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    final Rect shiftedRect = rect.shift(layerOffset);
    builder.addTexture(
      textureId,
      offset: shiftedRect.topLeft,
      width: shiftedRect.width,
      height: shiftedRect.height,
    );
  }
237 238 239

  @override
  S find<S>(Offset regionOffset) => null;
240 241
}

242
/// A layer that indicates to the compositor that it should display
243
/// certain performance statistics within it.
244 245
///
/// Performance overlay layers are always leaves in the layer tree.
246
class PerformanceOverlayLayer extends Layer {
247
  /// Creates a layer that displays a performance overlay.
248
  PerformanceOverlayLayer({
249 250 251 252
    @required this.overlayRect,
    @required this.optionsMask,
    @required this.rasterizerThreshold,
    @required this.checkerboardRasterCacheImages,
253
    @required this.checkerboardOffscreenLayers,
254
  });
255

256
  /// The rectangle in this layer's coordinate system that the overlay should occupy.
257 258 259
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
260
  Rect overlayRect;
261

262 263
  /// The mask is created by shifting 1 by the index of the specific
  /// [PerformanceOverlayOption] to enable.
264 265
  final int optionsMask;

266 267 268
  /// 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.
269
  final int rasterizerThreshold;
270

271 272 273 274 275 276 277 278 279 280 281 282 283
  /// 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;

284 285 286 287 288 289 290 291 292 293
  /// 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;

294
  @override
295
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
296
    assert(optionsMask != null);
297
    builder.addPerformanceOverlay(optionsMask, overlayRect.shift(layerOffset));
298
    builder.setRasterizerTracingThreshold(rasterizerThreshold);
299
    builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
300
    builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers);
301
  }
302 303 304

  @override
  S find<S>(Offset regionOffset) => null;
305 306
}

307 308 309 310 311
/// 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.
312
class ContainerLayer extends Layer {
313
  /// The first composited layer in this layer's child list.
314
  Layer get firstChild => _firstChild;
315
  Layer _firstChild;
316

317
  /// The last composited layer in this layer's child list.
318
  Layer get lastChild => _lastChild;
319
  Layer _lastChild;
320 321

  bool _debugUltimatePreviousSiblingOf(Layer child, { Layer equals }) {
322 323 324 325 326
    assert(child.attached == attached);
    while (child.previousSibling != null) {
      assert(child.previousSibling != child);
      child = child.previousSibling;
      assert(child.attached == attached);
327 328 329 330 331
    }
    return child == equals;
  }

  bool _debugUltimateNextSiblingOf(Layer child, { Layer equals }) {
332
    assert(child.attached == attached);
333 334 335
    while (child._nextSibling != null) {
      assert(child._nextSibling != child);
      child = child._nextSibling;
336
      assert(child.attached == attached);
337 338 339 340
    }
    return child == equals;
  }

341 342 343 344 345 346 347 348 349 350 351 352 353
  @override
  S find<S>(Offset regionOffset) {
    Layer current = lastChild;
    while (current != null) {
      final Object value = current.find<S>(regionOffset);
      if (value != null) {
        return value;
      }
      current = current.previousSibling;
    }
    return null;
  }

354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
  @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.
375
  void append(Layer child) {
376
    assert(child != this);
377 378 379 380 381 382
    assert(child != firstChild);
    assert(child != lastChild);
    assert(child.parent == null);
    assert(!child.attached);
    assert(child.nextSibling == null);
    assert(child.previousSibling == null);
383 384 385 386 387 388
    assert(() {
      Layer node = this;
      while (node.parent != null)
        node = node.parent;
      assert(node != child); // indicates we are about to create a cycle
      return true;
389
    }());
390 391 392 393
    adoptChild(child);
    child._previousSibling = lastChild;
    if (lastChild != null)
      lastChild._nextSibling = child;
394
    _lastChild = child;
395
    _firstChild ??= child;
396
    assert(child.attached == attached);
397 398
  }

399 400 401 402 403 404
  // 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));
405 406 407 408
    if (child._previousSibling == null) {
      assert(_firstChild == child);
      _firstChild = child._nextSibling;
    } else {
409
      child._previousSibling._nextSibling = child.nextSibling;
410 411
    }
    if (child._nextSibling == null) {
412 413
      assert(lastChild == child);
      _lastChild = child.previousSibling;
414
    } else {
415
      child.nextSibling._previousSibling = child.previousSibling;
416
    }
417 418 419 420 421
    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));
422 423
    child._previousSibling = null;
    child._nextSibling = null;
424 425
    dropChild(child);
    assert(!child.attached);
426
  }
Hixie's avatar
Hixie committed
427

428
  /// Removes all of this layer's children from its child list.
429
  void removeAllChildren() {
430
    Layer child = firstChild;
431
    while (child != null) {
432
      final Layer next = child.nextSibling;
433 434
      child._previousSibling = null;
      child._nextSibling = null;
435 436
      assert(child.attached == attached);
      dropChild(child);
437 438 439 440 441 442
      child = next;
    }
    _firstChild = null;
    _lastChild = null;
  }

443
  @override
444
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
445
    addChildrenToScene(builder, layerOffset);
Hixie's avatar
Hixie committed
446 447
  }

448 449 450 451
  /// 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]
452
  /// to apply effects to the scene using the [SceneBuilder] API, then insert
453 454
  /// their children using [addChildrenToScene], then reverse the aforementioned
  /// effects before returning from [addToScene].
455
  void addChildrenToScene(ui.SceneBuilder builder, Offset childOffset) {
456
    Layer child = firstChild;
Hixie's avatar
Hixie committed
457
    while (child != null) {
458
      child.addToScene(builder, childOffset);
Hixie's avatar
Hixie committed
459 460 461
      child = child.nextSibling;
    }
  }
462

463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
  /// 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
484
  /// default, composites its children at the origin of the [ContainerLayer]
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
  /// 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);
  }

501
  @override
502 503
  List<DiagnosticsNode> debugDescribeChildren() {
    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
504
    if (firstChild == null)
505
      return children;
506 507
    Layer child = firstChild;
    int count = 1;
508 509 510 511
    while (true) {
      children.add(child.toDiagnosticsNode(name: 'child $count'));
      if (child == lastChild)
        break;
512 513 514
      count += 1;
      child = child.nextSibling;
    }
515
    return children;
516
  }
Hixie's avatar
Hixie committed
517 518
}

519 520 521 522 523 524 525 526 527
/// 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.
528
class OffsetLayer extends ContainerLayer {
529 530
  /// Creates an offset layer.
  ///
531 532
  /// By default, [offset] is zero. It must be non-null before the compositing
  /// phase of the pipeline.
533
  OffsetLayer({ this.offset = Offset.zero });
534 535

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

544 545 546 547 548
  @override
  S find<S>(Offset regionOffset) {
    return super.find<S>(regionOffset - offset);
  }

549
  @override
550 551 552 553
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    addChildrenToScene(builder, offset + layerOffset);
  }

554
  @override
555 556 557
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Offset>('offset', offset));
558
  }
559 560 561

  /// Capture an image of the current state of this layer and its children.
  ///
562 563 564
  /// The returned [ui.Image] has uncompressed raw RGBA bytes, will be offset
  /// by the top-left corner of [bounds], and have dimensions equal to the size
  /// of [bounds] multiplied by [pixelRatio].
565 566 567 568 569 570 571 572 573 574 575
  ///
  /// The [pixelRatio] describes the scale between the logical pixels and the
  /// size of the output image. It is independent of the
  /// [window.devicePixelRatio] for the device, so specifying 1.0 (the default)
  /// will give you a 1:1 mapping between logical pixels and the output pixels
  /// in the image.
  ///
  /// See also:
  ///
  ///  * [RenderRepaintBoundary.toImage] for a similar API at the render object level.
  ///  * [dart:ui.Scene.toImage] for more information about the image returned.
576
  Future<ui.Image> toImage(Rect bounds, {double pixelRatio = 1.0}) async {
577
    assert(bounds != null);
578 579
    assert(pixelRatio != null);
    final ui.SceneBuilder builder = new ui.SceneBuilder();
580 581
    final Matrix4 transform = new Matrix4.translationValues(bounds.left - offset.dx, bounds.top - offset.dy, 0.0);
    transform.scale(pixelRatio, pixelRatio);
582 583 584 585 586 587 588
    builder.pushTransform(transform.storage);
    addToScene(builder, Offset.zero);
    final ui.Scene scene = builder.build();
    try {
      // Size is rounded up to the next pixel to make sure we don't clip off
      // anything.
      return await scene.toImage(
589 590
        (pixelRatio * bounds.width).ceil(),
        (pixelRatio * bounds.height).ceil(),
591 592 593 594 595
      );
    } finally {
      scene.dispose();
    }
  }
596 597
}

598
/// A composite layer that clips its children using a rectangle.
599 600 601 602
///
/// When debugging, setting [debugDisableClipLayers] to true will cause this
/// layer to be skipped (directly replaced by its children). This can be helpful
/// to track down the cause of performance problems.
Hixie's avatar
Hixie committed
603
class ClipRectLayer extends ContainerLayer {
604 605 606 607
  /// Creates a layer with a rectangular clip.
  ///
  /// The [clipRect] property must be non-null before the compositing phase of
  /// the pipeline.
608
  ClipRectLayer({ this.clipRect });
Hixie's avatar
Hixie committed
609

610 611 612 613
  /// 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
614 615
  Rect clipRect;

616 617 618 619 620 621 622
  @override
  S find<S>(Offset regionOffset) {
    if (!clipRect.contains(regionOffset))
      return null;
    return super.find<S>(regionOffset);
  }

623
  @override
624
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
625 626 627 628 629 630
    bool enabled = true;
    assert(() {
      enabled = !debugDisableClipLayers;
      return true;
    }());
    if (enabled)
631
      builder.pushClipRect(clipRect.shift(layerOffset));
632
    addChildrenToScene(builder, layerOffset);
633
    if (enabled)
634
      builder.pop();
Hixie's avatar
Hixie committed
635 636
  }

637
  @override
638 639 640
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Rect>('clipRect', clipRect));
641
  }
642
}
Hixie's avatar
Hixie committed
643

644
/// A composite layer that clips its children using a rounded rectangle.
645 646 647 648
///
/// When debugging, setting [debugDisableClipLayers] to true will cause this
/// layer to be skipped (directly replaced by its children). This can be helpful
/// to track down the cause of performance problems.
Hixie's avatar
Hixie committed
649
class ClipRRectLayer extends ContainerLayer {
650 651 652 653
  /// Creates a layer with a rounded-rectangular clip.
  ///
  /// The [clipRRect] property must be non-null before the compositing phase of
  /// the pipeline.
654
  ClipRRectLayer({ this.clipRRect });
655

656 657 658 659
  /// 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]).
660
  RRect clipRRect;
Hixie's avatar
Hixie committed
661

662 663 664 665 666 667 668
  @override
  S find<S>(Offset regionOffset) {
    if (!clipRRect.contains(regionOffset))
      return null;
    return super.find<S>(regionOffset);
  }

669
  @override
670
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
671 672 673 674 675 676
    bool enabled = true;
    assert(() {
      enabled = !debugDisableClipLayers;
      return true;
    }());
    if (enabled)
677
      builder.pushClipRRect(clipRRect.shift(layerOffset));
678
    addChildrenToScene(builder, layerOffset);
679
    if (enabled)
680
      builder.pop();
Hixie's avatar
Hixie committed
681
  }
682

683
  @override
684 685 686
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<RRect>('clipRRect', clipRRect));
687
  }
Hixie's avatar
Hixie committed
688 689
}

690
/// A composite layer that clips its children using a path.
691 692 693 694
///
/// When debugging, setting [debugDisableClipLayers] to true will cause this
/// layer to be skipped (directly replaced by its children). This can be helpful
/// to track down the cause of performance problems.
Hixie's avatar
Hixie committed
695
class ClipPathLayer extends ContainerLayer {
696 697 698 699
  /// Creates a layer with a path-based clip.
  ///
  /// The [clipPath] property must be non-null before the compositing phase of
  /// the pipeline.
700
  ClipPathLayer({ this.clipPath });
701

702 703 704 705
  /// 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
706 707
  Path clipPath;

708 709 710 711 712 713 714
  @override
  S find<S>(Offset regionOffset) {
    if (!clipPath.contains(regionOffset))
      return null;
    return super.find<S>(regionOffset);
  }

715
  @override
716
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
717 718 719 720 721 722
    bool enabled = true;
    assert(() {
      enabled = !debugDisableClipLayers;
      return true;
    }());
    if (enabled)
723
      builder.pushClipPath(clipPath.shift(layerOffset));
724
    addChildrenToScene(builder, layerOffset);
725
    if (enabled)
726
      builder.pop();
Hixie's avatar
Hixie committed
727
  }
728 729
}

730 731 732 733 734
/// 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.
735
class TransformLayer extends OffsetLayer {
736 737
  /// Creates a transform layer.
  ///
738 739
  /// The [transform] and [offset] properties must be non-null before the
  /// compositing phase of the pipeline.
740 741 742
  TransformLayer({ Matrix4 transform, Offset offset = Offset.zero })
    : _transform = transform,
      super(offset: offset);
743

744 745 746 747
  /// The matrix to apply.
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
748 749 750 751 752
  ///
  /// This transform is applied before [offset], if both are set.
  ///
  /// The [transform] property must be non-null before the compositing phase of
  /// the pipeline.
753 754 755 756 757 758
  Matrix4 get transform => _transform;
  Matrix4 _transform;
  set transform(Matrix4 value) {
    if (value == _transform)
      return;
    _transform = value;
759
    _inverseDirty = true;
760
  }
761

762
  Matrix4 _lastEffectiveTransform;
763
  Matrix4 _invertedTransform;
764
  bool _inverseDirty = true;
765

766
  @override
767
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
768 769 770 771 772
    _lastEffectiveTransform = transform;
    final Offset totalOffset = offset + layerOffset;
    if (totalOffset != Offset.zero) {
      _lastEffectiveTransform = new Matrix4.translationValues(totalOffset.dx, totalOffset.dy, 0.0)
        ..multiply(_lastEffectiveTransform);
773
    }
774
    builder.pushTransform(_lastEffectiveTransform.storage);
775 776
    addChildrenToScene(builder, Offset.zero);
    builder.pop();
777
  }
778

779 780
  @override
  S find<S>(Offset regionOffset) {
781 782 783 784 785 786
    if (_inverseDirty) {
      _invertedTransform = Matrix4.tryInvert(transform);
      _inverseDirty = false;
    }
    if (_invertedTransform == null)
      return null;
787 788 789 790 791
    final Vector4 vector = new Vector4(regionOffset.dx, regionOffset.dy, 0.0, 1.0);
    final Vector4 result = _invertedTransform.transform(vector);
    return super.find<S>(new Offset(result[0], result[1]));
  }

792 793 794 795 796 797 798
  @override
  void applyTransform(Layer child, Matrix4 transform) {
    assert(child != null);
    assert(transform != null);
    transform.multiply(_lastEffectiveTransform);
  }

799
  @override
800 801 802
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new TransformProperty('transform', transform));
803
  }
804 805
}

806
/// A composited layer that makes its children partially transparent.
807 808 809 810
///
/// When debugging, setting [debugDisableOpacityLayers] to true will cause this
/// layer to be skipped (directly replaced by its children). This can be helpful
/// to track down the cause of performance problems.
811
class OpacityLayer extends ContainerLayer {
812 813 814 815
  /// Creates an opacity layer.
  ///
  /// The [alpha] property must be non-null before the compositing phase of
  /// the pipeline.
816
  OpacityLayer({ this.alpha });
817

818
  /// The amount to multiply into the alpha channel.
819 820 821
  ///
  /// The opacity is expressed as an integer from 0 to 255, where 0 is fully
  /// transparent and 255 is fully opaque.
822 823 824
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
825 826
  int alpha;

827
  @override
828
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
829 830 831 832 833 834
    bool enabled = true;
    assert(() {
      enabled = !debugDisableOpacityLayers;
      return true;
    }());
    if (enabled)
835
      builder.pushOpacity(alpha);
836
    addChildrenToScene(builder, layerOffset);
837
    if (enabled)
838
      builder.pop();
839
  }
840

841
  @override
842 843 844
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new IntProperty('alpha', alpha));
845
  }
846
}
847

848
/// A composited layer that applies a shader to its children.
849
class ShaderMaskLayer extends ContainerLayer {
850 851
  /// Creates a shader mask layer.
  ///
852
  /// The [shader], [maskRect], and [blendMode] properties must be non-null
853
  /// before the compositing phase of the pipeline.
854
  ShaderMaskLayer({ this.shader, this.maskRect, this.blendMode });
855 856

  /// The shader to apply to the children.
857 858 859
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
860
  Shader shader;
861 862

  /// The size of the shader.
863 864 865
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
866 867
  Rect maskRect;

868
  /// The blend mode to apply when blending the shader with the children.
869 870 871
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
872
  BlendMode blendMode;
873

874
  @override
875
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
876
    builder.pushShaderMask(shader, maskRect.shift(layerOffset), blendMode);
877
    addChildrenToScene(builder, layerOffset);
878 879 880
    builder.pop();
  }

881
  @override
882 883 884 885 886
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Shader>('shader', shader));
    properties.add(new DiagnosticsProperty<Rect>('maskRect', maskRect));
    properties.add(new DiagnosticsProperty<BlendMode>('blendMode', blendMode));
887 888
  }
}
889 890 891

/// A composited layer that applies a filter to the existing contents of the scene.
class BackdropFilterLayer extends ContainerLayer {
892 893 894 895
  /// Creates a backdrop filter layer.
  ///
  /// The [filter] property must be non-null before the compositing phase of the
  /// pipeline.
896 897 898
  BackdropFilterLayer({ this.filter });

  /// The filter to apply to the existing contents of the scene.
899 900 901
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
902 903 904 905 906 907 908 909 910
  ui.ImageFilter filter;

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

912 913 914 915 916
/// 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.
917 918 919 920
///
/// When debugging, setting [debugDisablePhysicalShapeLayers] to true will cause this
/// layer to be skipped (directly replaced by its children). This can be helpful
/// to track down the cause of performance problems.
921
class PhysicalModelLayer extends ContainerLayer {
922 923 924
  /// Creates a composited layer that uses a physical model to producing
  /// lighting effects.
  ///
925
  /// The [clipPath], [elevation], and [color] arguments must not be null.
926
  PhysicalModelLayer({
927
    @required this.clipPath,
928 929
    @required this.elevation,
    @required this.color,
930
    @required this.shadowColor,
931
  }) : assert(clipPath != null),
932
       assert(elevation != null),
933 934
       assert(color != null),
       assert(shadowColor != null);
935

936
  /// The path to clip in the parent's coordinate system.
937 938 939
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
940
  Path clipPath;
941 942

  /// The z-coordinate at which to place this physical object.
943 944 945
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
946 947 948 949 950 951
  ///
  /// In tests, the [debugDisableShadows] flag is set to true by default.
  /// Several widgets and render objects force all elevations to zero when this
  /// flag is set. For this reason, this property will often be set to zero in
  /// tests even if the layer should be raised. To verify the actual value,
  /// consider setting [debugDisableShadows] to false in your test.
952
  double elevation;
953 954

  /// The background color.
955 956 957
  ///
  /// The scene must be explicitly recomposited after this property is changed
  /// (as described at [Layer]).
958 959
  Color color;

960 961 962
  /// The shadow color.
  Color shadowColor;

963 964 965 966 967 968 969
  @override
  S find<S>(Offset regionOffset) {
    if (!clipPath.contains(regionOffset))
      return null;
    return super.find<S>(regionOffset);
  }

970 971
  @override
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
972 973 974 975 976 977
    bool enabled = true;
    assert(() {
      enabled = !debugDisablePhysicalShapeLayers;
      return true;
    }());
    if (enabled) {
978 979 980 981
      builder.pushPhysicalShape(
        path: clipPath.shift(layerOffset),
        elevation: elevation,
        color: color,
982
        shadowColor: shadowColor,
983
      );
984
    }
985
    addChildrenToScene(builder, layerOffset);
986
    if (enabled)
987
      builder.pop();
988 989 990
  }

  @override
991 992 993 994
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DoubleProperty('elevation', elevation));
    properties.add(new DiagnosticsProperty<Color>('color', color));
995 996
  }
}
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015

/// 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
1016
  String toString() => '${describeIdentity(this)}(${ _leader != null ? "<linked>" : "<dangling>" })';
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
}

/// 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.
1033
  LeaderLayer({ @required this.link, this.offset = Offset.zero }) : assert(link != null);
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072

  /// 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;

1073 1074 1075 1076 1077
  @override
  S find<S>(Offset regionOffset) {
    return super.find(regionOffset - offset);
  }

1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
  @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
1104 1105 1106 1107
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Offset>('offset', offset));
    properties.add(new DiagnosticsProperty<LayerLink>('link', link));
1108 1109 1110 1111 1112 1113 1114 1115
  }
}

/// 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
1116
/// to the coordinate space of the [LeaderLayer].
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129
///
/// 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,
1130 1131 1132
    this.showWhenUnlinked = true,
    this.unlinkedOffset = Offset.zero,
    this.linkedOffset = Offset.zero,
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185
  }) : 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;
1186
  Matrix4 _invertedTransform;
1187
  bool _inverseDirty = true;
1188 1189 1190 1191 1192 1193

  @override
  S find<S>(Offset regionOffset) {
    if (link.leader == null) {
      return showWhenUnlinked ? super.find<S>(regionOffset - unlinkedOffset) : null;
    }
1194 1195 1196
    if (_inverseDirty) {
      _invertedTransform = Matrix4.tryInvert(getLastTransform());
      _inverseDirty = false;
1197
    }
1198 1199
    if (_invertedTransform == null)
      return null;
1200 1201 1202 1203
    final Vector4 vector = new Vector4(regionOffset.dx, regionOffset.dy, 0.0, 1.0);
    final Vector4 result = _invertedTransform.transform(vector);
    return super.find<S>(new Offset(result[0] - linkedOffset.dx, result[1] - linkedOffset.dy));
  }
1204 1205 1206 1207

  /// The transform that was used during the last composition phase.
  ///
  /// If the [link] was not linked to a [LeaderLayer], or if this layer has
1208
  /// a degenerate matrix applied, then this will be null.
1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278
  ///
  /// 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;
1279
    _inverseDirty = true;
1280 1281 1282 1283 1284 1285 1286 1287 1288
  }

  @override
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    assert(link != null);
    assert(showWhenUnlinked != null);
    if (link.leader == null && !showWhenUnlinked) {
      _lastTransform = null;
      _lastOffset = null;
1289
      _inverseDirty = true;
1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301
      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);
    }
1302
    _inverseDirty = true;
1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313
  }

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

  @override
1314 1315 1316 1317
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<LayerLink>('link', link));
    properties.add(new TransformProperty('transform', getLastTransform(), defaultValue: null));
1318 1319
  }
}
1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362

/// A composited layer which annotates its children with a value.
///
/// These values can be retrieved using [Layer.find] with a given [Offset]. If
/// a [Size] is provided to this layer, then find will check if the provided
/// offset is within the bounds of the layer.
class AnnotatedRegionLayer<T> extends ContainerLayer {
  /// Creates a new layer annotated with [value] that clips to [size] if provided.
  ///
  /// The value provided cannot be null.
  AnnotatedRegionLayer(this.value, {this.size}) : assert(value != null);

  /// The value returned by [find] if the offset is contained within this layer.
  final T value;

  /// The [size] is optionally used to clip the hit-testing of [find].
  ///
  /// If not provided, all offsets are considered to be contained within this
  /// layer, unless an ancestor layer applies a clip.
  final Size size;

  @override
  S find<S>(Offset regionOffset) {
    final S result = super.find<S>(regionOffset);
    if (result != null)
      return result;
    if (size != null && !size.contains(regionOffset))
      return null;
    if (T == S) {
      final Object untypedResult = value;
      final S typedResult = untypedResult;
      return typedResult;
    }
    return super.find<S>(regionOffset);
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<T>('value', value));
    properties.add(new DiagnosticsProperty<Size>('size', size, defaultValue: null));
  }
}