layer.dart 13.3 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 6
import 'dart:ui' as ui show Picture, SceneBuilder;
import 'dart:ui' show Offset;
7

8
import 'package:flutter/painting.dart';
9
import 'package:vector_math/vector_math_64.dart';
10
import 'package:mojo_services/mojo/ui/layouts.mojom.dart' as mojom;
11

12
import 'debug.dart';
13

14 15 16 17 18
/// A composited layer
///
/// 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.
19
abstract class Layer {
20
  /// This layer's parent in the layer tree
21
  ContainerLayer get parent => _parent;
22
  ContainerLayer _parent;
23

24
  /// This layer's next sibling in the parent layer's child list
25
  Layer get nextSibling => _nextSibling;
26
  Layer _nextSibling;
27

28
  /// This layer's previous sibling in the parent layer's child list
29
  Layer get previousSibling => _previousSibling;
30
  Layer _previousSibling;
31

32
  /// Removes this layer from its parent layer's child list
33 34
  void detach() {
    if (_parent != null)
35
      _parent._remove(this);
36
  }
37 38

  /// Replaces this layer with the given layer in the parent layer's child list
Hixie's avatar
Hixie committed
39 40 41 42 43 44 45 46 47 48 49
  void replaceWith(Layer newLayer) {
    assert(_parent != null);
    assert(newLayer._parent == null);
    assert(newLayer._nextSibling == null);
    assert(newLayer._previousSibling == null);
    newLayer._nextSibling = _nextSibling;
    if (_nextSibling != null)
      newLayer._nextSibling._previousSibling = newLayer;
    newLayer._previousSibling = _previousSibling;
    if (_previousSibling != null)
      newLayer._previousSibling._nextSibling = newLayer;
50 51 52 53 54 55 56
    assert(() {
      Layer node = this;
      while (node.parent != null)
        node = node.parent;
      assert(node != newLayer); // indicates we are about to create a cycle
      return true;
    });
Hixie's avatar
Hixie committed
57 58 59 60 61 62 63 64 65
    newLayer._parent = _parent;
    if (_parent._firstChild == this)
      _parent._firstChild = newLayer;
    if (_parent._lastChild == this)
      _parent._lastChild = newLayer;
    _nextSibling = null;
    _previousSibling = null;
    _parent = null;
  }
66

67 68 69 70
  /// Override this function to upload this layer to the engine
  ///
  /// The layerOffset is the accumulated offset of this layer's parent from the
  /// origin of the builder's coordinate system.
71
  void addToScene(ui.SceneBuilder builder, Offset layerOffset);
72 73 74 75 76 77 78 79

  String toString() => '$runtimeType';

  dynamic debugOwner;

  String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
    String result = '$prefixLineOne$this\n';
    final String childrenDescription = debugDescribeChildren(prefixOtherLines);
80 81 82 83
    final String descriptionPrefix = childrenDescription != '' ? '$prefixOtherLines \u2502 ' : '$prefixOtherLines   ';
    List<String> description = <String>[];
    debugFillDescription(description);
    result += description.map((String description) => "$descriptionPrefix$description\n").join();
84 85 86 87 88 89
    if (childrenDescription == '')
      result += '$prefixOtherLines\n';
    result += childrenDescription;
    return result;
  }

90
  void debugFillDescription(List<String> description) {
91
    if (debugOwner != null)
92
      description.add('owner: $debugOwner');
93 94 95
  }

  String debugDescribeChildren(String prefix) => '';
96 97
}

98
/// A composited layer containing a [Picture]
99
class PictureLayer extends Layer {
100 101 102
  /// The picture recorded for this layer
  ///
  /// The picture's coodinate system matches this layer's coodinate system
103
  ui.Picture picture;
104

105
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
106
    builder.addPicture(layerOffset, picture);
107
  }
108 109
}

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
class ChildSceneLayer extends Layer {
  ChildSceneLayer({ this.offset, this.layoutInfo });

  Offset offset;
  mojom.ViewLayoutInfo layoutInfo;

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.addChildScene(
      offset + layerOffset,
      layoutInfo.size.width,
      layoutInfo.size.height,
      layoutInfo.sceneToken.value
    );
  }

125 126 127 128 129 130
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('offset: $offset');
    description.add('physicalWidth: ${layoutInfo.size.width}');
    description.add('physicalHeight: ${layoutInfo.size.height}');
    description.add('sceneToken.value: ${layoutInfo.sceneToken.value}');
131 132 133
  }
}

134
/// A layer that indicates to the compositor that it should display
135 136 137
/// certain performance statistics within it.
class PerformanceOverlayLayer extends Layer {
  PerformanceOverlayLayer({
138
    this.overlayRect,
139 140
    this.optionsMask,
    this.rasterizerThreshold
141
  });
142

143 144
  /// The rectangle in this layer's coodinate system that the overlay should occupy.
  Rect overlayRect;
145

146
  /// A mask specifying the statistics to display.
147 148 149
  final int optionsMask;

  final int rasterizerThreshold;
150

151
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
152
    assert(optionsMask != null);
153
    builder.addPerformanceOverlay(optionsMask, overlayRect.shift(layerOffset));
154
    builder.setRasterizerTracingThreshold(rasterizerThreshold);
155 156 157 158
  }
}


159
/// A composited layer that has a list of children
160
class ContainerLayer extends Layer {
161
  /// The first composited layer in this layer's child list
162
  Layer get firstChild => _firstChild;
163
  Layer _firstChild;
164

165
  /// The last composited layer in this layer's child list
166
  Layer get lastChild => _lastChild;
167
  Layer _lastChild;
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

  bool _debugUltimatePreviousSiblingOf(Layer child, { Layer equals }) {
    while (child._previousSibling != null) {
      assert(child._previousSibling != child);
      child = child._previousSibling;
    }
    return child == equals;
  }

  bool _debugUltimateNextSiblingOf(Layer child, { Layer equals }) {
    while (child._nextSibling != null) {
      assert(child._nextSibling != child);
      child = child._nextSibling;
    }
    return child == equals;
  }

185 186
  /// Adds the given layer to the end of this layer's child list
  void append(Layer child) {
187 188 189 190 191 192
    assert(child != this);
    assert(child != _firstChild);
    assert(child != _lastChild);
    assert(child._parent == null);
    assert(child._nextSibling == null);
    assert(child._previousSibling == null);
193 194 195 196 197 198 199
    assert(() {
      Layer node = this;
      while (node.parent != null)
        node = node.parent;
      assert(node != child); // indicates we are about to create a cycle
      return true;
    });
200
    child._parent = this;
201 202 203 204 205 206
    child._previousSibling = _lastChild;
    if (_lastChild != null)
      _lastChild._nextSibling = child;
    _lastChild = child;
    if (_firstChild == null)
      _firstChild = child;
207 208
  }

209
  void _remove(Layer child) {
Hixie's avatar
Hixie committed
210
    assert(child._parent == this);
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
    assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
    if (child._previousSibling == null) {
      assert(_firstChild == child);
      _firstChild = child._nextSibling;
    } else {
      child._previousSibling._nextSibling = child._nextSibling;
    }
    if (child._nextSibling == null) {
      assert(_lastChild == child);
      _lastChild = child._previousSibling;
    } else {
      child._nextSibling._previousSibling = child._previousSibling;
    }
    child._previousSibling = null;
    child._nextSibling = null;
    child._parent = null;
  }
Hixie's avatar
Hixie committed
229

230
  /// Removes all of this layer's children from its child list
231 232 233 234 235 236 237 238 239 240 241 242 243
  void removeAllChildren() {
    Layer child = _firstChild;
    while (child != null) {
      Layer next = child.nextSibling;
      child._previousSibling = null;
      child._nextSibling = null;
      child._parent = null;
      child = next;
    }
    _firstChild = null;
    _lastChild = null;
  }

244
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
245
    addChildrenToScene(builder, layerOffset);
Hixie's avatar
Hixie committed
246 247
  }

248
  /// Uploads all of this layer's children to the engine
249
  void addChildrenToScene(ui.SceneBuilder builder, Offset childOffset) {
250
    Layer child = _firstChild;
Hixie's avatar
Hixie committed
251
    while (child != null) {
252
      child.addToScene(builder, childOffset);
Hixie's avatar
Hixie committed
253 254 255
      child = child.nextSibling;
    }
  }
256

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
  String debugDescribeChildren(String prefix) {
    String result = '$prefix \u2502\n';
    if (_firstChild != null) {
      Layer child = _firstChild;
      int count = 1;
      while (child != _lastChild) {
        result += '${child.toStringDeep("$prefix \u251C\u2500child $count: ", "$prefix \u2502")}';
        count += 1;
        child = child._nextSibling;
      }
      if (child != null) {
        assert(child == _lastChild);
        result += '${child.toStringDeep("$prefix \u2514\u2500child $count: ", "$prefix  ")}';
      }
    }
    return result;
  }
Hixie's avatar
Hixie committed
274 275
}

276 277 278 279 280 281 282 283 284 285
class OffsetLayer extends ContainerLayer {
  OffsetLayer({ this.offset: Offset.zero });

  /// Offset from parent in the parent's coordinate system.
  Offset offset;

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    addChildrenToScene(builder, offset + layerOffset);
  }

286 287 288
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('offset: $offset');
289 290 291 292
  }
}


293
/// A composite layer that clips its children using a rectangle
Hixie's avatar
Hixie committed
294
class ClipRectLayer extends ContainerLayer {
295
  ClipRectLayer({ this.clipRect });
Hixie's avatar
Hixie committed
296

297
  /// The rectangle to clip in the parent's coordinate system
Hixie's avatar
Hixie committed
298
  Rect clipRect;
299 300
  // TODO(abarth): Why is the rectangle in the parent's coordinate system
  // instead of in the coordinate system of this layer?
Hixie's avatar
Hixie committed
301

302
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
303 304
    builder.pushClipRect(clipRect.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
305
    builder.pop();
Hixie's avatar
Hixie committed
306 307
  }

308 309 310
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('clipRect: $clipRect');
311
  }
312
}
Hixie's avatar
Hixie committed
313

314
/// A composite layer that clips its children using a rounded rectangle
Hixie's avatar
Hixie committed
315
class ClipRRectLayer extends ContainerLayer {
316
  ClipRRectLayer({ this.clipRRect });
317 318

  /// The rounded-rect to clip in the parent's coordinate system
319
  RRect clipRRect;
320 321
  // TODO(abarth): Why is the rounded-rect in the parent's coordinate system
  // instead of in the coordinate system of this layer?
Hixie's avatar
Hixie committed
322

323
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
324 325
    builder.pushClipRRect(clipRRect.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
326
    builder.pop();
Hixie's avatar
Hixie committed
327
  }
328

329 330 331
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('clipRRect: $clipRRect');
332
  }
Hixie's avatar
Hixie committed
333 334
}

335
/// A composite layer that clips its children using a path
Hixie's avatar
Hixie committed
336
class ClipPathLayer extends ContainerLayer {
337
  ClipPathLayer({ this.clipPath });
338 339

  /// The path to clip in the parent's coordinate system
Hixie's avatar
Hixie committed
340
  Path clipPath;
341 342
  // TODO(abarth): Why is the path in the parent's coordinate system instead of
  // in the coordinate system of this layer?
Hixie's avatar
Hixie committed
343

344
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
345 346
    builder.pushClipPath(clipPath.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
347
    builder.pop();
Hixie's avatar
Hixie committed
348
  }
349

350 351 352
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('clipPath: $clipPath');
353
  }
354 355
}

356
/// A composited layer that applies a transformation matrix to its children
357 358
class TransformLayer extends OffsetLayer {
  TransformLayer({ Offset offset: Offset.zero, this.transform }): super(offset: offset);
359

360
  /// The matrix to apply
361
  Matrix4 transform;
362

363
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
364 365 366 367 368
    Matrix4 offsetTransform = new Matrix4.identity();
    offsetTransform.translate(offset.dx + layerOffset.dx, offset.dy + layerOffset.dy);
    builder.pushTransform((offsetTransform * transform).storage);
    addChildrenToScene(builder, Offset.zero);
    builder.pop();
369
  }
370

371 372 373 374
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('transform:');
    description.addAll(debugDescribeTransform(transform));
375
  }
376 377
}

378
/// A composited layer that makes its children partially transparent
379
class OpacityLayer extends ContainerLayer {
380
  OpacityLayer({ this.alpha });
381 382 383 384 385

  /// The amount to multiply into the alpha channel
  ///
  /// The opacity is expressed as an integer from 0 to 255, where 0 is fully
  /// transparent and 255 is fully opaque.
386 387
  int alpha;

388
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
Adam Barth's avatar
Adam Barth committed
389
    builder.pushOpacity(alpha);
390
    addChildrenToScene(builder, layerOffset);
391
    builder.pop();
392
  }
393

394 395 396
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('alpha: $alpha');
397
  }
398
}
399 400 401

/// A composited layer that applies a shader to hits children.
class ShaderMaskLayer extends ContainerLayer {
402
  ShaderMaskLayer({ this.shader, this.maskRect, this.transferMode });
403 404

  /// The shader to apply to the children.
405
  Shader shader;
406 407 408 409 410 411 412 413

  /// The size of the shader.
  Rect maskRect;

  /// The tranfer mode to apply when blending the shader with the children.
  TransferMode transferMode;

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
414 415
    builder.pushShaderMask(shader, maskRect.shift(layerOffset), transferMode);
    addChildrenToScene(builder, layerOffset);
416 417 418
    builder.pop();
  }

419 420 421 422 423
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('shader: $shader');
    description.add('maskRect: $maskRect');
    description.add('transferMode: $transferMode');
424 425
  }
}