layer.dart 12.6 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:ui' as ui;
6

7
import 'package:vector_math/vector_math_64.dart';
8

9
import 'basic_types.dart';
10
import 'debug.dart';
11 12 13

export 'basic_types.dart';

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
  Layer({ this.offset: Offset.zero });
21

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

25
  /// This layer's parent in the layer tree
26
  ContainerLayer get parent => _parent;
27
  ContainerLayer _parent;
28

29
  /// This layer's next sibling in the parent layer's child list
30
  Layer get nextSibling => _nextSibling;
31
  Layer _nextSibling;
32

33
  /// This layer's previous sibling in the parent layer's child list
34
  Layer get previousSibling => _previousSibling;
35
  Layer _previousSibling;
36

37
  /// Removes this layer from its parent layer's child list
38 39
  void detach() {
    if (_parent != null)
40
      _parent._remove(this);
41
  }
42 43

  /// Replaces this layer with the given layer in the parent layer's child list
Hixie's avatar
Hixie committed
44 45 46 47 48 49 50 51 52 53 54
  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;
55 56 57 58 59 60 61
    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
62 63 64 65 66 67 68 69 70
    newLayer._parent = _parent;
    if (_parent._firstChild == this)
      _parent._firstChild = newLayer;
    if (_parent._lastChild == this)
      _parent._lastChild = newLayer;
    _nextSibling = null;
    _previousSibling = null;
    _parent = null;
  }
71

72 73 74 75
  /// 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.
76
  void addToScene(ui.SceneBuilder builder, Offset layerOffset);
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101

  String toString() => '$runtimeType';

  dynamic debugOwner;

  String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
    String result = '$prefixLineOne$this\n';
    final String childrenDescription = debugDescribeChildren(prefixOtherLines);
    final String settingsPrefix = childrenDescription != '' ? '$prefixOtherLines \u2502 ' : '$prefixOtherLines   ';
    List<String> settings = <String>[];
    debugDescribeSettings(settings);
    result += settings.map((String setting) => "$settingsPrefix$setting\n").join();
    if (childrenDescription == '')
      result += '$prefixOtherLines\n';
    result += childrenDescription;
    return result;
  }

  void debugDescribeSettings(List<String> settings) {
    if (debugOwner != null)
      settings.add('owner: $debugOwner');
    settings.add('offset: $offset');
  }

  String debugDescribeChildren(String prefix) => '';
102 103
}

104
/// A composited layer containing a [Picture]
105
class PictureLayer extends Layer {
Hixie's avatar
Hixie committed
106
  PictureLayer({ Offset offset: Offset.zero, this.paintBounds })
107
    : super(offset: offset);
108

109 110 111 112
  /// The rectangle in this layer's coodinate system that bounds the recording
  ///
  /// The paint bounds are used to decide how much graphics memory to allocate
  /// when rasterizing this layer.
Hixie's avatar
Hixie committed
113
  Rect paintBounds;
114 115 116 117

  /// The picture recorded for this layer
  ///
  /// The picture's coodinate system matches this layer's coodinate system
118
  ui.Picture picture;
119

120
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
121
    builder.addPicture(offset + layerOffset, picture, paintBounds);
122 123
  }

124 125 126 127
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('paintBounds: $paintBounds');
  }
128 129
}

130 131 132 133 134 135
/// A layer that indicates to the compositor that it should display
/// certain statistics within it
class StatisticsLayer extends Layer {
  StatisticsLayer({
    Offset offset: Offset.zero,
    this.paintBounds,
136 137
    this.optionsMask,
    this.rasterizerThreshold
138 139 140 141 142 143
  }) : super(offset: offset);

  /// The rectangle in this layer's coodinate system that bounds the recording
  Rect paintBounds;

  /// A mask specifying the statistics to display
144 145 146
  final int optionsMask;

  final int rasterizerThreshold;
147

148
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
149
    assert(optionsMask != null);
150
    builder.addStatistics(optionsMask, paintBounds.shift(offset + layerOffset));
151
    builder.setRasterizerTracingThreshold(rasterizerThreshold);
152 153 154 155
  }
}


156
/// A composited layer that has a list of children
157
class ContainerLayer extends Layer {
158
  ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset);
159

160
  /// The first composited layer in this layer's child list
161
  Layer get firstChild => _firstChild;
162
  Layer _firstChild;
163

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

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

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

208
  void _remove(Layer child) {
Hixie's avatar
Hixie committed
209
    assert(child._parent == this);
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
    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
228

229
  /// Removes all of this layer's children from its child list
230 231 232 233 234 235 236 237 238 239 240 241 242
  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;
  }

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

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

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
  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
273 274
}

275
/// A composite layer that clips its children using a rectangle
Hixie's avatar
Hixie committed
276 277 278
class ClipRectLayer extends ContainerLayer {
  ClipRectLayer({ Offset offset: Offset.zero, this.clipRect }) : super(offset: offset);

279
  /// The rectangle to clip in the parent's coordinate system
Hixie's avatar
Hixie committed
280
  Rect clipRect;
281 282
  // 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
283

284
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
285 286 287
    Offset childOffset = offset + layerOffset;
    builder.pushClipRect(clipRect.shift(childOffset));
    addChildrenToScene(builder, childOffset);
288
    builder.pop();
Hixie's avatar
Hixie committed
289 290
  }

291 292 293 294
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('clipRect: $clipRect');
  }
295
}
Hixie's avatar
Hixie committed
296

297
/// A composite layer that clips its children using a rounded rectangle
Hixie's avatar
Hixie committed
298 299 300
class ClipRRectLayer extends ContainerLayer {
  ClipRRectLayer({ Offset offset: Offset.zero, this.bounds, this.clipRRect }) : super(offset: offset);

301
  /// Unused
Hixie's avatar
Hixie committed
302
  Rect bounds;
303 304 305
  // TODO(abarth): Remove.

  /// The rounded-rect to clip in the parent's coordinate system
306
  ui.RRect clipRRect;
307 308
  // 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
309

310
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
311 312 313
    Offset childOffset = offset + layerOffset;
    builder.pushClipRRect(clipRRect.shift(childOffset), bounds.shift(childOffset));
    addChildrenToScene(builder, childOffset);
314
    builder.pop();
Hixie's avatar
Hixie committed
315
  }
316

317 318 319 320 321
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('bounds: $bounds');
    settings.add('clipRRect: $clipRRect');
  }
Hixie's avatar
Hixie committed
322 323
}

324
/// A composite layer that clips its children using a path
Hixie's avatar
Hixie committed
325 326 327
class ClipPathLayer extends ContainerLayer {
  ClipPathLayer({ Offset offset: Offset.zero, this.bounds, this.clipPath }) : super(offset: offset);

328
  /// Unused
Hixie's avatar
Hixie committed
329
  Rect bounds;
330 331 332
  // TODO(abarth): Remove.

  /// The path to clip in the parent's coordinate system
Hixie's avatar
Hixie committed
333
  Path clipPath;
334 335
  // 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
336

337
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
338 339 340
    Offset childOffset = offset + layerOffset;
    builder.pushClipPath(clipPath.shift(childOffset), bounds.shift(childOffset));
    addChildrenToScene(builder, childOffset);
341
    builder.pop();
Hixie's avatar
Hixie committed
342
  }
343

344 345 346 347 348
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('bounds: $bounds');
    settings.add('clipPath: $clipPath');
  }
349 350
}

351
/// A composited layer that applies a transformation matrix to its children
352
class TransformLayer extends ContainerLayer {
353
  TransformLayer({ Offset offset: Offset.zero, this.transform }) : super(offset: offset);
354

355
  /// The matrix to apply
356
  Matrix4 transform;
357

358
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
359 360 361 362 363
    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();
364
  }
365 366 367 368 369 370

  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('transform:');
    settings.addAll(debugDescribeTransform(transform));
  }
371 372
}

373
/// A composited layer that makes its children partially transparent
374 375
class OpacityLayer extends ContainerLayer {
  OpacityLayer({ Offset offset: Offset.zero, this.bounds, this.alpha }) : super(offset: offset);
376

377
  /// Unused
Hixie's avatar
Hixie committed
378
  Rect bounds;
379 380 381 382 383 384
  // TODO(abarth): Remove.

  /// 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.
385 386
  int alpha;

387
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
388 389 390
    Offset childOffset = offset + layerOffset;
    builder.pushOpacity(alpha, bounds?.shift(childOffset));
    addChildrenToScene(builder, childOffset);
391
    builder.pop();
392
  }
393 394 395 396 397 398

  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('bounds: $bounds');
    settings.add('alpha: $alpha');
  }
399
}