2015-12-01 20:36:52 -08:00

1432 lines
52 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:developer';
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/scheduler.dart';
import 'package:vector_math/vector_math_64.dart';
import 'debug.dart';
import 'hit_test.dart';
import 'layer.dart';
import 'node.dart';
export 'layer.dart';
export 'hit_test.dart';
typedef ui.Shader ShaderCallback(Rect bounds);
/// Base class for data associated with a [RenderObject] by its parent
///
/// Some render objects wish to store data on their children, such as their
/// input parameters to the parent's layout algorithm or their position relative
/// to other children.
class ParentData {
/// Called when the RenderObject is removed from the tree.
void detach() { }
/// Override this function in subclasses to merge in data from other instance
/// into this instance.
void merge(ParentData other) {
assert(other.runtimeType == this.runtimeType);
}
String toString() => '<none>';
}
/// Obsolete class that will be removed eventually
class PaintingCanvas extends Canvas {
PaintingCanvas(ui.PictureRecorder recorder, Rect bounds) : super(recorder, bounds);
// TODO(ianh): Just use ui.Canvas everywhere instead
}
typedef void PaintingContextCallback(PaintingContext context, Offset offset);
/// A place to paint.
///
/// Rather than holding a canvas directly, render objects paint using a painting
/// context. The painting context has a canvas, which receives the
/// individual draw operations, and also has functions for painting child
/// render objects.
///
/// When painting a child render object, the canvas held by the painting context
/// can change because the draw operations issued before and after painting the
/// child might be recorded in separate compositing layers. For this reason, do
/// not hold a reference to the canvas across operations that might paint
/// child render objects.
class PaintingContext {
PaintingContext._(this._containerLayer, this._paintBounds) {
assert(_containerLayer != null);
assert(_paintBounds != null);
}
final ContainerLayer _containerLayer;
final Rect _paintBounds;
/// Repaint the given render object.
///
/// The render object must have a composited layer and must be in need of
/// painting. The render object's layer is re-used, along with any layers in
/// the subtree that don't need to be repainted.
static void repaintCompositedChild(RenderObject child) {
assert(child.hasLayer);
assert(child.needsPaint);
child._layer ??= new ContainerLayer();
child._layer.removeAllChildren();
assert(() {
child._layer.debugOwner = child.debugOwner ?? child.runtimeType;
return true;
});
PaintingContext childContext = new PaintingContext._(child._layer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero);
childContext._stopRecordingIfNeeded();
}
/// Paint a child render object.
///
/// If the child has its own composited layer, the child will be composited
/// into the layer subtree associated with this painting context. Otherwise,
/// the child will be painted into the current PictureLayer for this context.
void paintChild(RenderObject child, Offset offset) {
if (child.hasLayer) {
_stopRecordingIfNeeded();
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
}
void _compositeChild(RenderObject child, Offset offset) {
assert(!_isRecording);
assert(child.hasLayer);
assert(_canvas == null || _canvas.getSaveCount() == 1);
// Create a layer for our child, and paint the child into it.
if (child.needsPaint) {
repaintCompositedChild(child);
} else {
assert(child._layer != null);
child._layer.detach();
assert(() {
child._layer.debugOwner = child.debugOwner ?? child.runtimeType;
return true;
});
}
_appendLayer(child._layer, offset);
}
void _appendLayer(Layer layer, Offset offset) {
assert(!_isRecording);
layer.offset = offset;
_containerLayer.append(layer);
}
bool get _isRecording {
final bool hasCanvas = (_canvas != null);
assert(() {
if (hasCanvas) {
assert(_currentLayer != null);
assert(_recorder != null);
assert(_canvas != null);
} else {
assert(_currentLayer == null);
assert(_recorder == null);
assert(_canvas == null);
}
return true;
});
return hasCanvas;
}
// Recording state
PictureLayer _currentLayer;
ui.PictureRecorder _recorder;
PaintingCanvas _canvas;
/// The canvas on which to paint.
///
/// The current canvas can change whenever you paint a child using this
/// context, which means it's fragile to hold a reference to the canvas
/// returned by this getter.
PaintingCanvas get canvas {
if (_canvas == null)
_startRecording();
return _canvas;
}
void _startRecording() {
assert(!_isRecording);
_currentLayer = new PictureLayer(paintBounds: _paintBounds);
_recorder = new ui.PictureRecorder();
_canvas = new PaintingCanvas(_recorder, _paintBounds);
_containerLayer.append(_currentLayer);
}
void _stopRecordingIfNeeded() {
if (!_isRecording)
return;
assert(() {
if (debugEnableRepaintRainbox)
canvas.drawRect(_paintBounds, new Paint()..color = debugCurrentRepaintColor.toColor());
if (debugPaintLayerBordersEnabled) {
Paint paint = new Paint()
..style = ui.PaintingStyle.stroke
..strokeWidth = 1.0
..color = debugPaintLayerBordersColor;
canvas.drawRect(_paintBounds, paint);
}
return true;
});
_currentLayer.picture = _recorder.endRecording();
_currentLayer = null;
_recorder = null;
_canvas = null;
}
static final Paint _disableAntialias = new Paint()..isAntiAlias = false;
/// Push a statistics overlay.
///
/// Statistics overlays are always composited because they're drawn by the
/// compositor.
void pushStatistics(Offset offset, int optionsMask, int rasterizerThreshold, Size size) {
_stopRecordingIfNeeded();
StatisticsLayer statisticsLayer = new StatisticsLayer(
paintBounds: new Rect.fromLTWH(0.0, 0.0, size.width, size.height),
optionsMask: optionsMask,
rasterizerThreshold: rasterizerThreshold
);
_appendLayer(statisticsLayer, offset);
}
/// Push a rectangular clip rect.
///
/// This function will call painter synchronously with a painting context that
/// is clipped by the given clip. The given clip should not incorporate the
/// painting offset.
void pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter) {
if (needsCompositing) {
_stopRecordingIfNeeded();
ClipRectLayer clipLayer = new ClipRectLayer(clipRect: clipRect);
_appendLayer(clipLayer, offset);
PaintingContext childContext = new PaintingContext._(clipLayer, clipRect);
painter(childContext, Offset.zero);
childContext._stopRecordingIfNeeded();
} else {
canvas.save();
canvas.clipRect(clipRect.shift(offset));
painter(this, offset);
canvas.restore();
}
}
/// Push a rounded-rect clip rect.
///
/// This function will call painter synchronously with a painting context that
/// is clipped by the given clip. The given clip should not incorporate the
/// painting offset.
void pushClipRRect(bool needsCompositing, Offset offset, Rect bounds, ui.RRect clipRRect, PaintingContextCallback painter) {
if (needsCompositing) {
_stopRecordingIfNeeded();
ClipRRectLayer clipLayer = new ClipRRectLayer(bounds: bounds, clipRRect: clipRRect);
_appendLayer(clipLayer, offset);
PaintingContext childContext = new PaintingContext._(clipLayer, bounds);
painter(childContext, Offset.zero);
childContext._stopRecordingIfNeeded();
} else {
canvas.saveLayer(bounds.shift(offset), _disableAntialias);
// TODO(abarth): Remove this translation once RRect.shift works again.
canvas.translate(offset.dx, offset.dy);
canvas.clipRRect(clipRRect);
painter(this, Offset.zero);
canvas.restore();
}
}
/// Push a path clip.
///
/// This function will call painter synchronously with a painting context that
/// is clipped by the given clip. The given clip should not incorporate the
/// painting offset.
void pushClipPath(bool needsCompositing, Offset offset, Rect bounds, Path clipPath, PaintingContextCallback painter) {
if (needsCompositing) {
_stopRecordingIfNeeded();
ClipPathLayer clipLayer = new ClipPathLayer(bounds: bounds, clipPath: clipPath);
_appendLayer(clipLayer, offset);
PaintingContext childContext = new PaintingContext._(clipLayer, bounds);
painter(childContext, Offset.zero);
childContext._stopRecordingIfNeeded();
} else {
canvas.saveLayer(bounds.shift(offset), _disableAntialias);
canvas.clipPath(clipPath.shift(offset));
painter(this, offset);
canvas.restore();
}
}
/// Push a transform.
///
/// This function will call painter synchronously with a painting context that
/// is transformed by the given transform. The given transform should not
/// incorporate the painting offset.
void pushTransform(bool needsCompositing, Offset offset, Matrix4 transform, PaintingContextCallback painter) {
if (needsCompositing) {
_stopRecordingIfNeeded();
TransformLayer transformLayer = new TransformLayer(transform: transform);
_appendLayer(transformLayer, offset);
PaintingContext childContext = new PaintingContext._(transformLayer, _paintBounds);
painter(childContext, Offset.zero);
childContext._stopRecordingIfNeeded();
} else {
Matrix4 offsetMatrix = new Matrix4.translationValues(offset.dx, offset.dy, 0.0);
Matrix4 transformWithOffset = offsetMatrix * transform;
canvas.save();
canvas.concat(transformWithOffset.storage);
painter(this, Offset.zero);
canvas.restore();
}
}
static Paint _getPaintForAlpha(int alpha) {
return new Paint()
..color = new Color.fromARGB(alpha, 0, 0, 0)
..transferMode = TransferMode.srcOver
..isAntiAlias = false;
}
/// Push an opacity layer.
///
/// This function will call painter synchronously with a painting context that
/// will be blended with the given alpha value.
void pushOpacity(bool needsCompositing, Offset offset, Rect bounds, int alpha, PaintingContextCallback painter) {
if (needsCompositing) {
_stopRecordingIfNeeded();
OpacityLayer opacityLayer = new OpacityLayer(bounds: bounds, alpha: alpha);
_appendLayer(opacityLayer, offset);
PaintingContext childContext = new PaintingContext._(opacityLayer, _paintBounds);
painter(childContext, Offset.zero);
childContext._stopRecordingIfNeeded();
} else {
// TODO(abarth): pushOpacity should require bounds.
canvas.saveLayer(bounds?.shift(offset), _getPaintForAlpha(alpha));
painter(this, offset);
canvas.restore();
}
}
static Paint _getPaintForShaderMask(Rect bounds,
ShaderCallback shaderCallback,
TransferMode transferMode) {
return new Paint()
..transferMode = transferMode
..shader = shaderCallback(bounds);
}
/// Push a shader mask.
///
/// This function will call painter synchronously with a painting context that
/// will be masked with the given shader.
///
/// WARNING: This function does not yet support compositing.
void pushShaderMask(bool needsCompositing, Offset offset, Rect bounds, ShaderCallback shaderCallback, TransferMode transferMode, PaintingContextCallback painter) {
assert(!needsCompositing); // TODO(abarth): Implement compositing for shader masks.
canvas.saveLayer(bounds.shift(offset), _disableAntialias);
painter(this, offset);
Paint shaderPaint = _getPaintForShaderMask(bounds, shaderCallback, transferMode);
canvas.drawRect(bounds, shaderPaint);
canvas.restore();
}
}
/// An encapsulation of a renderer and a paint() method.
///
/// A renderer may allow its paint() method to be augmented or redefined by
/// providing a Painter. See for example overlayPainter in BlockViewport.
abstract class Painter {
RenderObject get renderObject => _renderObject;
RenderObject _renderObject;
void attach(RenderObject renderObject) {
assert(_renderObject == null);
_renderObject = renderObject;
}
void detach() {
assert(_renderObject != null);
_renderObject = null;
}
void paint(PaintingContext context, Offset offset);
}
/// An abstract set of layout constraints
///
/// Concrete layout models (such as box) will create concrete subclasses to
/// communicate layout constraints between parents and children.
abstract class Constraints {
const Constraints();
/// Whether there is exactly one size possible given these constraints
bool get isTight;
}
typedef void RenderObjectVisitor(RenderObject child);
typedef void LayoutCallback(Constraints constraints);
typedef double ExtentCallback(Constraints constraints);
typedef void RenderingExceptionHandler(RenderObject source, String method, dynamic exception, StackTrace stack);
/// This callback is invoked whenever an exception is caught by the rendering
/// system. The 'source' argument is the [RenderObject] object that caught the
/// exception. The 'method' argument is the method in which the exception
/// occurred; it will be one of 'performResize', 'performLayout, or 'paint'. The
/// 'exception' argument contains the object that was thrown, and the 'stack'
/// argument contains the stack trace. The callback is invoked after the
/// information is printed to the console, and could be used to print additional
/// information, such as from [debugDumpRenderTree()].
RenderingExceptionHandler debugRenderingExceptionHandler;
/// An object in the render tree
///
/// Render objects have a reference to their parent but do not commit to a model
/// for their children.
abstract class RenderObject extends AbstractNode implements HitTestTarget {
// LAYOUT
/// Data for use by the parent render object
///
/// The parent data is used by the render object that lays out this object
/// (typically this object's parent in the render tree) to store information
/// relevant to itself and to any other nodes who happen to know exactly what
/// the data means. The parent data is opaque to the child.
///
/// - The parent data field must not be directly set, except by calling
/// [setupParentData] on the parent node.
/// - The parent data can be set before the child is added to the parent, by
/// calling [setupParentData] on the future parent node.
/// - The conventions for using the parent data depend on the layout protocol
/// used between the parent and child. For example, in box layout, the
/// parent data is completely opaque but in sector layout the child is
/// permitted to read some fields of the parent data.
ParentData parentData;
/// Override to setup parent data correctly for your children
///
/// You can call this function to set up the parent data for child before the
/// child is added to the parent's child list.
void setupParentData(RenderObject child) {
assert(debugCanPerformMutations);
if (child.parentData is! ParentData)
child.parentData = new ParentData();
}
/// Called by subclasses when they decide a render object is a child
///
/// Only for use by subclasses when changing their child lists. Calling this
/// in other cases will lead to an inconsistent tree and probably cause crashes.
void adoptChild(RenderObject child) {
assert(debugCanPerformMutations);
assert(child != null);
setupParentData(child);
super.adoptChild(child);
markNeedsLayout();
_markNeedsCompositingBitsUpdate();
}
/// Called by subclasses when they decide a render object is no longer a child
///
/// Only for use by subclasses when changing their child lists. Calling this
/// in other cases will lead to an inconsistent tree and probably cause crashes.
void dropChild(RenderObject child) {
assert(debugCanPerformMutations);
assert(child != null);
assert(child.parentData != null);
child._cleanRelayoutSubtreeRoot();
child.parentData.detach();
child.parentData = null;
super.dropChild(child);
markNeedsLayout();
_markNeedsCompositingBitsUpdate();
}
/// Calls visitor for each immediate child of this render object
///
/// Override in subclasses with children and call the visitor for each child
void visitChildren(RenderObjectVisitor visitor) { }
dynamic debugOwner;
void _debugReportException(String method, dynamic exception, StackTrace stack) {
debugPrint('-- EXCEPTION --');
debugPrint('The following exception was raised during $method():');
debugPrint('$exception');
debugPrint('Stack trace:');
debugPrint('$stack');
debugPrint('The following RenderObject was being processed when the exception was fired:\n${this}');
if (debugOwner != null)
debugPrint('That RenderObject had the following owner:\n$debugOwner');
if (debugRenderingExceptionHandler != null)
debugRenderingExceptionHandler(this, method, exception, stack);
}
static bool _debugDoingLayout = false;
static bool get debugDoingLayout => _debugDoingLayout;
bool _debugDoingThisResize = false;
bool get debugDoingThisResize => _debugDoingThisResize;
bool _debugDoingThisLayout = false;
bool get debugDoingThisLayout => _debugDoingThisLayout;
static RenderObject _debugActiveLayout = null;
static RenderObject get debugActiveLayout => _debugActiveLayout;
bool _debugMutationsLocked = false;
bool _debugCanParentUseSize;
bool get debugCanParentUseSize => _debugCanParentUseSize;
bool get debugCanPerformMutations {
RenderObject node = this;
while (true) {
if (node._doingThisLayoutWithCallback)
return true;
if (node._debugMutationsLocked)
return false;
if (node.parent is! RenderObject)
return true;
node = node.parent;
}
}
static List<RenderObject> _nodesNeedingLayout = new List<RenderObject>();
bool _needsLayout = true;
/// Whether this render object's layout information is dirty
bool get needsLayout => _needsLayout;
RenderObject _relayoutSubtreeRoot;
bool _doingThisLayoutWithCallback = false;
Constraints _constraints;
/// The layout constraints most recently supplied by the parent
Constraints get constraints => _constraints;
/// Override this function in a subclass to verify that your state matches the constraints object
bool debugDoesMeetConstraints();
bool debugAncestorsAlreadyMarkedNeedsLayout() {
if (_relayoutSubtreeRoot == null)
return true; // we haven't yet done layout even once, so there's nothing for us to do
RenderObject node = this;
while (node != _relayoutSubtreeRoot) {
assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot);
assert(node.parent != null);
node = node.parent;
if ((!node._needsLayout) && (!node._debugDoingThisLayout))
return false;
}
assert(node._relayoutSubtreeRoot == node);
return true;
}
/// Mark this render object's layout information as dirty
///
/// Rather than eagerly updating layout information in response to writes into
/// this render object, we instead mark the layout information as dirty, which
/// schedules a visual update. As part of the visual update, the rendering
/// pipeline will update this render object's layout information.
///
/// This mechanism batches the layout work so that multiple sequential writes
/// are coalesced, removing redundant computation.
///
/// Causes [needsLayout] to return true for this render object. If the parent
/// render object indicated that it uses the size of this render object in
/// computing its layout information, this function will also mark the parent
/// as needing layout.
void markNeedsLayout() {
assert(debugCanPerformMutations);
if (_needsLayout) {
assert(debugAncestorsAlreadyMarkedNeedsLayout());
return;
}
_needsLayout = true;
assert(_relayoutSubtreeRoot != null);
if (_relayoutSubtreeRoot != this) {
final RenderObject parent = this.parent;
if (!_doingThisLayoutWithCallback) {
parent.markNeedsLayout();
} else {
assert(parent._debugDoingThisLayout);
}
assert(parent == this.parent);
} else {
_nodesNeedingLayout.add(this);
scheduler.ensureVisualUpdate();
}
}
void _cleanRelayoutSubtreeRoot() {
if (_relayoutSubtreeRoot != this) {
_relayoutSubtreeRoot = null;
_needsLayout = true;
visitChildren((RenderObject child) {
child._cleanRelayoutSubtreeRoot();
});
}
}
/// Bootstrap the rendering pipeline by scheduling the very first layout
///
/// Requires this render object to be attached and that this render object
/// is the root of the render tree.
///
/// See [RenderView] for an example of how this function is used.
void scheduleInitialLayout() {
assert(attached);
assert(parent is! RenderObject);
assert(!_debugDoingLayout);
assert(_relayoutSubtreeRoot == null);
_relayoutSubtreeRoot = this;
assert(() {
_debugCanParentUseSize = false;
return true;
});
_nodesNeedingLayout.add(this);
}
/// Update the layout information for all dirty render objects
///
/// This function is one of the core stages of the rendering pipeline. Layout
/// information is cleaned prior to painting so that render objects will
/// appear on screen in their up-to-date locations.
///
/// See [FlutterBinding] for an example of how this function is used.
static void flushLayout() {
Timeline.startSync('Layout');
_debugDoingLayout = true;
try {
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themeselves
while (_nodesNeedingLayout.isNotEmpty) {
List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = new List<RenderObject>();
dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)..forEach((RenderObject node) {
if (node._needsLayout && node.attached)
node._layoutWithoutResize();
});
}
} finally {
_debugDoingLayout = false;
Timeline.finishSync();
}
}
void _layoutWithoutResize() {
assert(_relayoutSubtreeRoot == this);
RenderObject debugPreviousActiveLayout;
assert(!_debugMutationsLocked);
assert(!_doingThisLayoutWithCallback);
assert(_debugCanParentUseSize != null);
assert(() {
_debugMutationsLocked = true;
_debugDoingThisLayout = true;
debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
return true;
});
try {
performLayout();
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugMutationsLocked = false;
return true;
});
_needsLayout = false;
markNeedsPaint();
}
/// Compute the layout for this render object
///
/// This function is the main entry point for parents to ask their children to
/// update their layout information. The parent passes a constraints object,
/// which informs the child as which layouts are permissible. The child is
/// required to obey the given constraints.
///
/// If the parent reads information computed during the child's layout, the
/// parent must pass true for parentUsesSize. In that case, the parent will be
/// marked as needing layout whenever the child is marked as needing layout
/// because the parent's layout information depends on the child's layout
/// information. If the parent uses the default value (false) for
/// parentUsesSize, the child can change its layout information (subject to
/// the given constraints) without informing the parent.
///
/// Subclasses should not override layout directly. Instead, they should
/// override performResize and/or performLayout.
///
/// The parent's performLayout method should call the layout of all its
/// children unconditionally. It is the layout functions's responsibility (as
/// implemented here) to return early if the child does not need to do any
/// work to update its layout information.
void layout(Constraints constraints, { bool parentUsesSize: false }) {
assert(!_debugDoingThisResize);
assert(!_debugDoingThisLayout);
final RenderObject parent = this.parent;
RenderObject relayoutSubtreeRoot;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject)
relayoutSubtreeRoot = this;
else
relayoutSubtreeRoot = parent._relayoutSubtreeRoot;
assert(parent == this.parent);
assert(() {
_debugCanParentUseSize = parentUsesSize;
return true;
});
if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _relayoutSubtreeRoot) {
assert(() {
// in case parentUsesSize changed since the last invocation, set size
// to itself, so it has the right internal debug values.
_debugDoingThisResize = sizedByParent;
_debugDoingThisLayout = !sizedByParent;
RenderObject debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
debugResetSize();
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugDoingThisResize = false;
return true;
});
return;
}
_constraints = constraints;
_relayoutSubtreeRoot = relayoutSubtreeRoot;
assert(!_debugMutationsLocked);
assert(!_doingThisLayoutWithCallback);
assert(() {
_debugMutationsLocked = true;
return true;
});
if (sizedByParent) {
assert(() { _debugDoingThisResize = true; return true; });
try {
performResize();
assert(debugDoesMeetConstraints());
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
assert(() { _debugDoingThisResize = false; return true; });
}
RenderObject debugPreviousActiveLayout;
assert(() {
_debugDoingThisLayout = true;
debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
return true;
});
try {
performLayout();
assert(debugDoesMeetConstraints());
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugMutationsLocked = false;
return true;
});
_needsLayout = false;
markNeedsPaint();
assert(parent == this.parent);
}
/// If a subclass has a "size" (the state controlled by "parentUsesSize",
/// whatever it is in the subclass, e.g. the actual "size" property of
/// RenderBox), and the subclass verifies that in checked mode this "size"
/// property isn't used when debugCanParentUseSize isn't set, then that
/// subclass should override debugResetSize() to reapply the current values of
/// debugCanParentUseSize to that state.
void debugResetSize() { }
/// Whether the constraints are the only input to the sizing algorithm (in
/// particular, child nodes have no impact)
///
/// Returning false is always correct, but returning true can be more
/// efficient when computing the size of this render object because we don't
/// need to recompute the size if the constraints don't change.
bool get sizedByParent => false;
/// Updates the render objects size using only the constraints
///
/// Do not call this function directly: call [layout] instead. This function
/// is called by [layout] when there is actually work to be done by this
/// render object during layout. The layout constraints provided by your
/// parent are available via the [constraints] getter.
///
/// Subclasses that set [sizedByParent] to true should override this function
/// to compute their size.
///
/// Note: This function is called only if [sizedByParent] is true.
void performResize();
/// Do the work of computing the layout for this render object
///
/// Do not call this function directly: call [layout] instead. This function
/// is called by [layout] when there is actually work to be done by this
/// render object during layout. The layout constraints provided by your
/// parent are available via the [constraints] getter.
///
/// If [sizedByParent] is true, then this function should not actually change
/// the dimensions of this render object. Instead, that work should be done by
/// [performResize]. If [sizedByParent] is false, then this function should
/// both change the dimensions of this render object and instruct its children
/// to layout.
///
/// In implementing this function, you must call [layout] on each of your
/// children, passing true for parentUsesSize if your layout information is
/// dependent on your child's layout information. Passing true for
/// parentUsesSize ensures that this render object will undergo layout if the
/// child undergoes layout. Otherwise, the child can changes its layout
/// information without informing this render object.
void performLayout();
/// Allows this render object to mutation its child list during layout and
/// invokes callback
void invokeLayoutCallback(LayoutCallback callback) {
assert(_debugMutationsLocked);
assert(_debugDoingThisLayout);
assert(!_doingThisLayoutWithCallback);
_doingThisLayoutWithCallback = true;
try {
callback(constraints);
} finally {
_doingThisLayoutWithCallback = false;
}
}
/// Rotate this render object (not yet implemented)
void rotate({
int oldAngle, // 0..3
int newAngle, // 0..3
Duration time
}) { }
// when the parent has rotated (e.g. when the screen has been turned
// 90 degrees), immediately prior to layout() being called for the
// new dimensions, rotate() is called with the old and new angles.
// The next time paint() is called, the coordinate space will have
// been rotated N quarter-turns clockwise, where:
// N = newAngle-oldAngle
// ...but the rendering is expected to remain the same, pixel for
// pixel, on the output device. Then, the layout() method or
// equivalent will be invoked.
// PAINTING
static bool _debugDoingPaint = false;
static bool get debugDoingPaint => _debugDoingPaint;
static void set debugDoingPaint(bool value) {
_debugDoingPaint = value;
}
bool _debugDoingThisPaint = false;
bool get debugDoingThisPaint => _debugDoingThisPaint;
static RenderObject _debugActivePaint = null;
static RenderObject get debugActivePaint => _debugActivePaint;
static List<RenderObject> _nodesNeedingPaint = new List<RenderObject>();
/// Whether this render object paints using a composited layer
///
/// Override this in subclasses to indicate that instances of your class need
/// to have their own compositing layer. For example, videos should return
/// true if they use hardware decoders.
///
/// Note: This getter must not change value over the lifetime of this object.
bool get hasLayer => false;
ContainerLayer _layer;
/// The compositing layer that this render object uses to paint
///
/// Call only when [hasLayer] is true.
ContainerLayer get layer {
assert(hasLayer);
assert(!_needsPaint);
return _layer;
}
bool _needsCompositingBitsUpdate = true;
/// Mark the compositing state for this render object as dirty
///
/// When the subtree is mutated, we need to recompute our [needsCompositing]
/// bit, and our ancestors need to do the same (in case ours changed).
/// Therefore, [adoptChild] and [dropChild] call
/// [markNeedsCompositingBitsUpdate].
void _markNeedsCompositingBitsUpdate() {
if (_needsCompositingBitsUpdate)
return;
_needsCompositingBitsUpdate = true;
final AbstractNode parent = this.parent;
if (parent is RenderObject)
parent._markNeedsCompositingBitsUpdate();
assert(parent == this.parent);
}
bool _needsCompositing = false;
/// Whether we or one of our descendants has a compositing layer
///
/// Only legal to call after [flushLayout] and [updateCompositingBits] have
/// been called.
bool get needsCompositing {
assert(!_needsCompositingBitsUpdate); // make sure we don't use this bit when it is dirty
return _needsCompositing;
}
/// Updates the [needsCompositing] bits
///
/// Called as part of the rendering pipeline after [flushLayout] and before
/// [flushPaint].
void updateCompositingBits() {
if (!_needsCompositingBitsUpdate)
return;
bool didHaveCompositedDescendant = _needsCompositing;
visitChildren((RenderObject child) {
child.updateCompositingBits();
if (child.needsCompositing)
_needsCompositing = true;
});
if (hasLayer)
_needsCompositing = true;
if (didHaveCompositedDescendant != _needsCompositing)
markNeedsPaint();
_needsCompositingBitsUpdate = false;
}
bool _needsPaint = true;
/// The visual appearance of this render object has changed since it last painted
bool get needsPaint => _needsPaint;
/// Mark this render object as having changed its visual appearance
///
/// Rather than eagerly updating this render object's display list
/// in response to writes, we instead mark the the render object as needing to
/// paint, which schedules a visual update. As part of the visual update, the
/// rendering pipeline will give this render object an opportunity to update
/// its display list.
///
/// This mechanism batches the painting work so that multiple sequential
/// writes are coalesced, removing redundant computation.
void markNeedsPaint() {
assert(!debugDoingPaint);
if (!attached)
return; // Don't try painting things that aren't in the hierarchy
if (_needsPaint)
return;
_needsPaint = true;
if (hasLayer) {
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layer != null);
_nodesNeedingPaint.add(this);
scheduler.ensureVisualUpdate();
} else if (parent is RenderObject) {
// We don't have our own layer; one of our ancestors will take
// care of updating the layer we're in and when they do that
// we'll get our paint() method called.
assert(_layer == null);
final RenderObject parent = this.parent;
parent.markNeedsPaint();
assert(parent == this.parent);
} else {
// If we're the root of the render tree (probably a RenderView),
// then we have to paint ourselves, since nobody else can paint
// us. We don't add ourselves to _nodesNeedingPaint in this
// case, because the root is always told to paint regardless.
scheduler.ensureVisualUpdate();
}
}
/// Update the display lists for all render objects
///
/// This function is one of the core stages of the rendering pipeline.
/// Painting occurs after layout and before the scene is recomposited so that
/// scene is composited with up-to-date display lists for every render object.
///
/// See [FlutterBinding] for an example of how this function is used.
static void flushPaint() {
Timeline.startSync('Paint');
_debugDoingPaint = true;
try {
List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = new List<RenderObject>();
// Sort the dirty nodes in reverse order (deepest first).
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._needsPaint);
if (node.attached)
PaintingContext.repaintCompositedChild(node);
};
assert(_nodesNeedingPaint.length == 0);
} finally {
_debugDoingPaint = false;
Timeline.finishSync();
}
}
/// Bootstrap the rendering pipeline by scheduling the very first paint
///
/// Requires that this render object is attached, is the root of the render
/// tree, and has a composited layer.
///
/// See [RenderView] for an example of how this function is used.
void scheduleInitialPaint(ContainerLayer rootLayer) {
assert(attached);
assert(parent is! RenderObject);
assert(!_debugDoingPaint);
assert(hasLayer);
assert(_layer == null);
_layer = rootLayer;
assert(_needsPaint);
_nodesNeedingPaint.add(this);
}
void _paintWithContext(PaintingContext context, Offset offset) {
assert(!_debugDoingThisPaint);
assert(!_needsLayout);
assert(!_needsCompositingBitsUpdate);
RenderObject debugLastActivePaint;
assert(() {
_debugDoingThisPaint = true;
debugLastActivePaint = _debugActivePaint;
_debugActivePaint = this;
assert(!hasLayer || _layer != null);
return true;
});
_needsPaint = false;
try {
paint(context, offset);
assert(!_needsLayout); // check that the paint() method didn't mark us dirty again
assert(!_needsPaint); // check that the paint() method didn't mark us dirty again
} catch (e, stack) {
_debugReportException('paint', e, stack);
}
assert(() {
debugPaint(context, offset);
_debugActivePaint = debugLastActivePaint;
_debugDoingThisPaint = false;
return true;
});
}
/// The bounds within which this render object will paint
///
/// A render object is permitted to paint outside the region it occupies
/// during layout but is not permitted to paint outside these paints bounds.
/// These paint bounds are used to construct memory-efficient composited
/// layers, which means attempting to paint outside these bounds can attempt
/// to write to pixels that do not exist in this render object's composited
/// layer.
Rect get paintBounds;
/// Override this function to paint debugging information
void debugPaint(PaintingContext context, Offset offset) { }
/// Paint this render object into the given context at the given offset
///
/// Subclasses should override this function to provide a visual appearance
/// for themselves. The render object's local coordinate system is
/// axis-aligned with the coordinate system of the context's canvas and the
/// render object's local origin (i.e, x=0 and y=0) is placed at the given
/// offset in the context's canvas.
///
/// Do not call this function directly. If you wish to paint yourself, call
/// [markNeedsPaint] instead to schedule a call to this function. If you wish
/// to paint one of your children, call one of the paint child functions on
/// the given context, such as [paintChild] or [paintChildWithClipRect].
///
/// When painting one of your children (via a paint child function on the
/// given context), the current canvas held by the context might change
/// because draw operations before and after painting children might need to
/// be recorded on separate compositing layers.
void paint(PaintingContext context, Offset offset) { }
/// If this render object applies a transform before painting, apply that
/// transform to the given matrix
///
/// Used by coordinate conversion functions to translate coordiantes local to
/// one render object into coordinates local to another render object.
void applyPaintTransform(Matrix4 transform) { }
// EVENTS
/// Override this function to handle events that hit this render object
void handleEvent(InputEvent event, HitTestEntry entry) { }
// HIT TESTING
// RenderObject subclasses are expected to have a method like the
// following (with the signature being whatever passes for coordinates
// for this particular class):
// bool hitTest(HitTestResult result, { Point position }) {
// // If (x,y) is not inside this node, then return false. (You
// // can assume that the given coordinate is inside your
// // dimensions. You only need to check this if you're an
// // irregular shape, e.g. if you have a hole.)
// // Otherwise:
// // For each child that intersects x,y, in z-order starting from the top,
// // call hitTest() for that child, passing it /result/, and the coordinates
// // converted to the child's coordinate origin, and stop at the first child
// // that returns true.
// // Then, add yourself to /result/, and return true.
// }
// You must not add yourself to /result/ if you return false.
/// Returns a human understandable name
String toString() {
String header = '$runtimeType';
if (_relayoutSubtreeRoot != null && _relayoutSubtreeRoot != this) {
int count = 1;
RenderObject target = parent;
while (target != null && target != _relayoutSubtreeRoot) {
target = target.parent;
count += 1;
}
header += ' relayoutSubtreeRoot=up$count';
}
if (_needsLayout)
header += ' NEEDS-LAYOUT';
if (!attached)
header += ' DETACHED';
return header;
}
/// Returns a description of the tree rooted at this node.
/// If the prefix argument is provided, then every line in the output
/// will be prefixed by that string.
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
RenderObject debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = null;
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;
_debugActiveLayout = debugPreviousActiveLayout;
return result;
}
/// Returns a list of strings describing the current node's fields, one field
/// per string. Subclasses should override this to have their information
/// included in toStringDeep().
void debugDescribeSettings(List<String> settings) {
if (debugOwner != null)
settings.add('owner: $debugOwner');
settings.add('parentData: $parentData');
settings.add('constraints: $constraints');
}
/// Returns a string describing the current node's descendants. Each line of
/// the subtree in the output should be indented by the prefix argument.
String debugDescribeChildren(String prefix) => '';
}
/// Obsolete function that will be removed eventually
double clamp({ double min: 0.0, double value: 0.0, double max: double.INFINITY }) {
assert(min != null);
assert(value != null);
assert(max != null);
return math.max(min, math.min(max, value));
}
/// Generic mixin for render objects with one child
///
/// Provides a child model for a render object subclass that has a unique child
abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implements RenderObject {
ChildType _child;
/// The render object's unique child
ChildType get child => _child;
void set child (ChildType value) {
if (_child != null)
dropChild(_child);
_child = value;
if (_child != null)
adoptChild(_child);
}
void attach() {
super.attach();
if (_child != null)
_child.attach();
}
void detach() {
super.detach();
if (_child != null)
_child.detach();
}
void visitChildren(RenderObjectVisitor visitor) {
if (_child != null)
visitor(_child);
}
String debugDescribeChildren(String prefix) {
if (child != null)
return '$prefix \u2502\n${child.toStringDeep('$prefix \u2514\u2500child: ', '$prefix ')}';
return '';
}
}
/// Parent data to support a doubly-linked list of children
abstract class ContainerParentDataMixin<ChildType extends RenderObject> implements ParentData {
/// The previous sibling in the parent's child list
ChildType previousSibling;
/// The next sibling in the parent's child list
ChildType nextSibling;
/// Clear the sibling pointers.
void detach() {
super.detach();
if (previousSibling != null) {
final ContainerParentDataMixin<ChildType> previousSiblingParentData = previousSibling.parentData;
assert(previousSibling != this);
assert(previousSiblingParentData.nextSibling == this);
previousSiblingParentData.nextSibling = nextSibling;
}
if (nextSibling != null) {
final ContainerParentDataMixin<ChildType> nextSiblingParentData = nextSibling.parentData;
assert(nextSibling != this);
assert(nextSiblingParentData.previousSibling == this);
nextSiblingParentData.previousSibling = previousSibling;
}
previousSibling = null;
nextSibling = null;
}
}
/// Generic mixin for render objects with a list of children
///
/// Provides a child model for a render object subclass that has a doubly-linked
/// list of children.
abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> implements RenderObject {
bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) {
ParentDataType childParentData = child.parentData;
while (childParentData.previousSibling != null) {
assert(childParentData.previousSibling != child);
child = childParentData.previousSibling;
childParentData = child.parentData;
}
return child == equals;
}
bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) {
ParentDataType childParentData = child.parentData;
while (childParentData.nextSibling != null) {
assert(childParentData.nextSibling != child);
child = childParentData.nextSibling;
childParentData = child.parentData;
}
return child == equals;
}
int _childCount = 0;
/// The number of children
int get childCount => _childCount;
ChildType _firstChild;
ChildType _lastChild;
void _addToChildList(ChildType child, { ChildType before }) {
final ParentDataType childParentData = child.parentData;
assert(childParentData.nextSibling == null);
assert(childParentData.previousSibling == null);
_childCount += 1;
assert(_childCount > 0);
if (before == null) {
// append at the end (_lastChild)
childParentData.previousSibling = _lastChild;
if (_lastChild != null) {
final ParentDataType _lastChildParentData = _lastChild.parentData;
_lastChildParentData.nextSibling = child;
}
_lastChild = child;
if (_firstChild == null)
_firstChild = child;
} else {
assert(_firstChild != null);
assert(_lastChild != null);
assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild));
assert(_debugUltimateNextSiblingOf(before, equals: _lastChild));
final ParentDataType beforeParentData = before.parentData;
if (beforeParentData.previousSibling == null) {
// insert at the start (_firstChild); we'll end up with two or more children
assert(before == _firstChild);
childParentData.nextSibling = before;
beforeParentData.previousSibling = child;
_firstChild = child;
} else {
// insert in the middle; we'll end up with three or more children
// set up links from child to siblings
childParentData.previousSibling = beforeParentData.previousSibling;
childParentData.nextSibling = before;
// set up links from siblings to child
final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling.parentData;
final ParentDataType childNextSiblingParentData = childParentData.nextSibling.parentData;
childPreviousSiblingParentData.nextSibling = child;
childNextSiblingParentData.previousSibling = child;
assert(beforeParentData.previousSibling == child);
}
}
}
/// Insert child into this render object's child list before the given child
///
/// To insert a child at the end of the child list, omit the before parameter.
void add(ChildType child, { ChildType before }) {
assert(child != this);
assert(before != this);
assert(child != before);
assert(child != _firstChild);
assert(child != _lastChild);
adoptChild(child);
_addToChildList(child, before: before);
}
/// Add all the children to the end of this render object's child list
void addAll(List<ChildType> children) {
if (children != null)
for (ChildType child in children)
add(child);
}
void _removeFromChildList(ChildType child) {
final ParentDataType childParentData = child.parentData;
assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
assert(_childCount >= 0);
if (childParentData.previousSibling == null) {
assert(_firstChild == child);
_firstChild = childParentData.nextSibling;
} else {
final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling.parentData;
childPreviousSiblingParentData.nextSibling = childParentData.nextSibling;
}
if (childParentData.nextSibling == null) {
assert(_lastChild == child);
_lastChild = childParentData.previousSibling;
} else {
final ParentDataType childNextSiblingParentData = childParentData.nextSibling.parentData;
childNextSiblingParentData.previousSibling = childParentData.previousSibling;
}
childParentData.previousSibling = null;
childParentData.nextSibling = null;
_childCount -= 1;
}
/// Remove this child from the child list
///
/// Requires the child to be present in the child list.
void remove(ChildType child) {
_removeFromChildList(child);
dropChild(child);
}
/// Remove all their children from this render object's child list
///
/// More efficient than removing them individually.
void removeAll() {
ChildType child = _firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData;
ChildType next = childParentData.nextSibling;
childParentData.previousSibling = null;
childParentData.nextSibling = null;
dropChild(child);
child = next;
}
_firstChild = null;
_lastChild = null;
_childCount = 0;
}
/// Move this child in the child list to be before the given child
///
/// More efficient than removing and re-adding the child. Requires the child
/// to already be in the child list at some position. Pass null for before to
/// move the child to the end of the child list.
void move(ChildType child, { ChildType before }) {
assert(child != this);
assert(before != this);
assert(child != before);
assert(child.parent == this);
final ParentDataType childParentData = child.parentData;
if (childParentData.nextSibling == before)
return;
_removeFromChildList(child);
_addToChildList(child, before: before);
}
void attach() {
super.attach();
ChildType child = _firstChild;
while (child != null) {
child.attach();
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
void detach() {
super.detach();
ChildType child = _firstChild;
while (child != null) {
child.detach();
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
void redepthChildren() {
ChildType child = _firstChild;
while (child != null) {
redepthChild(child);
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
void visitChildren(RenderObjectVisitor visitor) {
ChildType child = _firstChild;
while (child != null) {
visitor(child);
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
}
/// The first child in the child list
ChildType get firstChild => _firstChild;
/// The last child in the child list
ChildType get lastChild => _lastChild;
/// The next child after the given child in the child list
ChildType childAfter(ChildType child) {
final ParentDataType childParentData = child.parentData;
return childParentData.nextSibling;
}
String debugDescribeChildren(String prefix) {
String result = '$prefix \u2502\n';
if (_firstChild != null) {
ChildType child = _firstChild;
int count = 1;
while (child != _lastChild) {
result += '${child.toStringDeep("$prefix \u251C\u2500child $count: ", "$prefix \u2502")}';
count += 1;
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
if (child != null) {
assert(child == _lastChild);
result += '${child.toStringDeep("$prefix \u2514\u2500child $count: ", "$prefix ")}';
}
}
return result;
}
}