mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
426 lines
13 KiB
Dart
426 lines
13 KiB
Dart
// 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.
|
|
|
|
import 'dart:ui' as ui show Picture, SceneBuilder;
|
|
import 'dart:ui' show Offset;
|
|
|
|
import 'package:flutter/painting.dart';
|
|
import 'package:vector_math/vector_math_64.dart';
|
|
import 'package:mojo_services/mojo/ui/layouts.mojom.dart' as mojom;
|
|
|
|
import 'debug.dart';
|
|
|
|
/// 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.
|
|
abstract class Layer {
|
|
/// This layer's parent in the layer tree
|
|
ContainerLayer get parent => _parent;
|
|
ContainerLayer _parent;
|
|
|
|
/// This layer's next sibling in the parent layer's child list
|
|
Layer get nextSibling => _nextSibling;
|
|
Layer _nextSibling;
|
|
|
|
/// This layer's previous sibling in the parent layer's child list
|
|
Layer get previousSibling => _previousSibling;
|
|
Layer _previousSibling;
|
|
|
|
/// Removes this layer from its parent layer's child list
|
|
void detach() {
|
|
if (_parent != null)
|
|
_parent._remove(this);
|
|
}
|
|
|
|
/// Replaces this layer with the given layer in the parent layer's child list
|
|
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;
|
|
assert(() {
|
|
Layer node = this;
|
|
while (node.parent != null)
|
|
node = node.parent;
|
|
assert(node != newLayer); // indicates we are about to create a cycle
|
|
return true;
|
|
});
|
|
newLayer._parent = _parent;
|
|
if (_parent._firstChild == this)
|
|
_parent._firstChild = newLayer;
|
|
if (_parent._lastChild == this)
|
|
_parent._lastChild = newLayer;
|
|
_nextSibling = null;
|
|
_previousSibling = null;
|
|
_parent = null;
|
|
}
|
|
|
|
/// 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.
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset);
|
|
|
|
String toString() => '$runtimeType';
|
|
|
|
dynamic debugOwner;
|
|
|
|
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
|
|
String result = '$prefixLineOne$this\n';
|
|
final String childrenDescription = debugDescribeChildren(prefixOtherLines);
|
|
final String descriptionPrefix = childrenDescription != '' ? '$prefixOtherLines \u2502 ' : '$prefixOtherLines ';
|
|
List<String> description = <String>[];
|
|
debugFillDescription(description);
|
|
result += description.map((String description) => "$descriptionPrefix$description\n").join();
|
|
if (childrenDescription == '')
|
|
result += '$prefixOtherLines\n';
|
|
result += childrenDescription;
|
|
return result;
|
|
}
|
|
|
|
void debugFillDescription(List<String> description) {
|
|
if (debugOwner != null)
|
|
description.add('owner: $debugOwner');
|
|
}
|
|
|
|
String debugDescribeChildren(String prefix) => '';
|
|
}
|
|
|
|
/// A composited layer containing a [Picture]
|
|
class PictureLayer extends Layer {
|
|
/// The picture recorded for this layer
|
|
///
|
|
/// The picture's coodinate system matches this layer's coodinate system
|
|
ui.Picture picture;
|
|
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
|
builder.addPicture(layerOffset, picture);
|
|
}
|
|
}
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
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}');
|
|
}
|
|
}
|
|
|
|
/// A layer that indicates to the compositor that it should display
|
|
/// certain performance statistics within it.
|
|
class PerformanceOverlayLayer extends Layer {
|
|
PerformanceOverlayLayer({
|
|
this.overlayRect,
|
|
this.optionsMask,
|
|
this.rasterizerThreshold
|
|
});
|
|
|
|
/// The rectangle in this layer's coodinate system that the overlay should occupy.
|
|
Rect overlayRect;
|
|
|
|
/// A mask specifying the statistics to display.
|
|
final int optionsMask;
|
|
|
|
final int rasterizerThreshold;
|
|
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
|
assert(optionsMask != null);
|
|
builder.addPerformanceOverlay(optionsMask, overlayRect.shift(layerOffset));
|
|
builder.setRasterizerTracingThreshold(rasterizerThreshold);
|
|
}
|
|
}
|
|
|
|
|
|
/// A composited layer that has a list of children
|
|
class ContainerLayer extends Layer {
|
|
/// The first composited layer in this layer's child list
|
|
Layer get firstChild => _firstChild;
|
|
Layer _firstChild;
|
|
|
|
/// The last composited layer in this layer's child list
|
|
Layer get lastChild => _lastChild;
|
|
Layer _lastChild;
|
|
|
|
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;
|
|
}
|
|
|
|
/// Adds the given layer to the end of this layer's child list
|
|
void append(Layer child) {
|
|
assert(child != this);
|
|
assert(child != _firstChild);
|
|
assert(child != _lastChild);
|
|
assert(child._parent == null);
|
|
assert(child._nextSibling == null);
|
|
assert(child._previousSibling == null);
|
|
assert(() {
|
|
Layer node = this;
|
|
while (node.parent != null)
|
|
node = node.parent;
|
|
assert(node != child); // indicates we are about to create a cycle
|
|
return true;
|
|
});
|
|
child._parent = this;
|
|
child._previousSibling = _lastChild;
|
|
if (_lastChild != null)
|
|
_lastChild._nextSibling = child;
|
|
_lastChild = child;
|
|
if (_firstChild == null)
|
|
_firstChild = child;
|
|
}
|
|
|
|
void _remove(Layer child) {
|
|
assert(child._parent == this);
|
|
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;
|
|
}
|
|
|
|
/// Removes all of this layer's children from its child list
|
|
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;
|
|
}
|
|
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
|
addChildrenToScene(builder, layerOffset);
|
|
}
|
|
|
|
/// Uploads all of this layer's children to the engine
|
|
void addChildrenToScene(ui.SceneBuilder builder, Offset childOffset) {
|
|
Layer child = _firstChild;
|
|
while (child != null) {
|
|
child.addToScene(builder, childOffset);
|
|
child = child.nextSibling;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
description.add('offset: $offset');
|
|
}
|
|
}
|
|
|
|
|
|
/// A composite layer that clips its children using a rectangle
|
|
class ClipRectLayer extends ContainerLayer {
|
|
ClipRectLayer({ this.clipRect });
|
|
|
|
/// The rectangle to clip in the parent's coordinate system
|
|
Rect clipRect;
|
|
// TODO(abarth): Why is the rectangle in the parent's coordinate system
|
|
// instead of in the coordinate system of this layer?
|
|
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
|
builder.pushClipRect(clipRect.shift(layerOffset));
|
|
addChildrenToScene(builder, layerOffset);
|
|
builder.pop();
|
|
}
|
|
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
description.add('clipRect: $clipRect');
|
|
}
|
|
}
|
|
|
|
/// A composite layer that clips its children using a rounded rectangle
|
|
class ClipRRectLayer extends ContainerLayer {
|
|
ClipRRectLayer({ this.clipRRect });
|
|
|
|
/// The rounded-rect to clip in the parent's coordinate system
|
|
RRect clipRRect;
|
|
// TODO(abarth): Why is the rounded-rect in the parent's coordinate system
|
|
// instead of in the coordinate system of this layer?
|
|
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
|
builder.pushClipRRect(clipRRect.shift(layerOffset));
|
|
addChildrenToScene(builder, layerOffset);
|
|
builder.pop();
|
|
}
|
|
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
description.add('clipRRect: $clipRRect');
|
|
}
|
|
}
|
|
|
|
/// A composite layer that clips its children using a path
|
|
class ClipPathLayer extends ContainerLayer {
|
|
ClipPathLayer({ this.clipPath });
|
|
|
|
/// The path to clip in the parent's coordinate system
|
|
Path clipPath;
|
|
// TODO(abarth): Why is the path in the parent's coordinate system instead of
|
|
// in the coordinate system of this layer?
|
|
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
|
builder.pushClipPath(clipPath.shift(layerOffset));
|
|
addChildrenToScene(builder, layerOffset);
|
|
builder.pop();
|
|
}
|
|
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
description.add('clipPath: $clipPath');
|
|
}
|
|
}
|
|
|
|
/// A composited layer that applies a transformation matrix to its children
|
|
class TransformLayer extends OffsetLayer {
|
|
TransformLayer({ Offset offset: Offset.zero, this.transform }): super(offset: offset);
|
|
|
|
/// The matrix to apply
|
|
Matrix4 transform;
|
|
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
|
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();
|
|
}
|
|
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
description.add('transform:');
|
|
description.addAll(debugDescribeTransform(transform));
|
|
}
|
|
}
|
|
|
|
/// A composited layer that makes its children partially transparent
|
|
class OpacityLayer extends ContainerLayer {
|
|
OpacityLayer({ this.alpha });
|
|
|
|
/// 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.
|
|
int alpha;
|
|
|
|
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
|
builder.pushOpacity(alpha);
|
|
addChildrenToScene(builder, layerOffset);
|
|
builder.pop();
|
|
}
|
|
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
description.add('alpha: $alpha');
|
|
}
|
|
}
|
|
|
|
/// A composited layer that applies a shader to hits children.
|
|
class ShaderMaskLayer extends ContainerLayer {
|
|
ShaderMaskLayer({ this.shader, this.maskRect, this.transferMode });
|
|
|
|
/// The shader to apply to the children.
|
|
Shader shader;
|
|
|
|
/// 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) {
|
|
builder.pushShaderMask(shader, maskRect.shift(layerOffset), transferMode);
|
|
addChildrenToScene(builder, layerOffset);
|
|
builder.pop();
|
|
}
|
|
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
description.add('shader: $shader');
|
|
description.add('maskRect: $maskRect');
|
|
description.add('transferMode: $transferMode');
|
|
}
|
|
}
|