blob: 1f73177c77a99a5165441153f3ecf81e52fb74db [file] [log] [blame]
// Copyright 2014 The Flutter 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:ui' as ui show PictureRecorder;
import 'dart:ui';
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
import 'debug.dart';
import 'layer.dart';
import 'proxy_box.dart';
export 'package:flutter/foundation.dart' show
DiagnosticPropertiesBuilder,
DiagnosticsNode,
DiagnosticsProperty,
DoubleProperty,
EnumProperty,
ErrorDescription,
ErrorHint,
ErrorSummary,
FlagProperty,
FlutterError,
InformationCollector,
IntProperty,
StringProperty;
export 'package:flutter/gestures.dart' show HitTestEntry, HitTestResult;
export 'package:flutter/painting.dart';
/// Base class for data associated with a [RenderObject] by its parent.
///
/// Some render objects wish to store data on their children, such as the
/// children's input parameters to the parent's layout algorithm or the
/// children's position relative to other children.
///
/// See also:
///
/// * [RenderObject.setupParentData], which [RenderObject] subclasses may
/// override to attach specific types of parent data to children.
class ParentData {
/// Called when the RenderObject is removed from the tree.
@protected
@mustCallSuper
void detach() { }
@override
String toString() => '<none>';
}
/// Signature for painting into a [PaintingContext].
///
/// The `offset` argument is the offset from the origin of the coordinate system
/// of the [PaintingContext.canvas] to the coordinate system of the callee.
///
/// Used by many of the methods of [PaintingContext].
typedef PaintingContextCallback = void Function(PaintingContext context, Offset offset);
/// A place to paint.
///
/// Rather than holding a canvas directly, [RenderObject]s 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.
///
/// New [PaintingContext] objects are created automatically when using
/// [PaintingContext.repaintCompositedChild] and [pushLayer].
class PaintingContext extends ClipContext {
/// Creates a painting context.
///
/// Typically only called by [PaintingContext.repaintCompositedChild]
/// and [pushLayer].
@protected
PaintingContext(this._containerLayer, this.estimatedBounds);
final ContainerLayer _containerLayer;
/// An estimate of the bounds within which the painting context's [canvas]
/// will record painting commands. This can be useful for debugging.
///
/// The canvas will allow painting outside these bounds.
///
/// The [estimatedBounds] rectangle is in the [canvas] coordinate system.
final Rect estimatedBounds;
/// Repaint the given render object.
///
/// The render object must be attached to a [PipelineOwner], must have a
/// composited layer, and must be in need of painting. The render object's
/// layer, if any, is re-used, along with any layers in the subtree that don't
/// need to be repainted.
///
/// See also:
///
/// * [RenderObject.isRepaintBoundary], which determines if a [RenderObject]
/// has a composited layer.
static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
assert(child._needsPaint);
_repaintCompositedChild(
child,
debugAlsoPaintedParent: debugAlsoPaintedParent,
);
}
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext? childContext,
}) {
assert(child.isRepaintBoundary);
assert(() {
// register the call for RepaintBoundary metrics
child.debugRegisterRepaintBoundaryPaint(
includedParent: debugAlsoPaintedParent,
includedChild: true,
);
return true;
}());
OffsetLayer? childLayer = child._layerHandle.layer as OffsetLayer?;
if (childLayer == null) {
assert(debugAlsoPaintedParent);
assert(child._layerHandle.layer == null);
// Not using the `layer` setter because the setter asserts that we not
// replace the layer for repaint boundaries. That assertion does not
// apply here because this is exactly the place designed to create a
// layer for repaint boundaries.
final OffsetLayer layer = child.updateCompositedLayer(oldLayer: null);
child._layerHandle.layer = childLayer = layer;
} else {
assert(debugAlsoPaintedParent || childLayer.attached);
Offset? debugOldOffset;
assert(() {
debugOldOffset = childLayer!.offset;
return true;
}());
childLayer.removeAllChildren();
final OffsetLayer updatedLayer = child.updateCompositedLayer(oldLayer: childLayer);
assert(identical(updatedLayer, childLayer),
'$child created a new layer instance $updatedLayer instead of reusing the '
'existing layer $childLayer. See the documentation of RenderObject.updateCompositedLayer '
'for more information on how to correctly implement this method.'
);
assert(debugOldOffset == updatedLayer.offset);
}
child._needsCompositedLayerUpdate = false;
assert(identical(childLayer, child._layerHandle.layer));
assert(child._layerHandle.layer is OffsetLayer);
assert(() {
childLayer!.debugCreator = child.debugCreator ?? child.runtimeType;
return true;
}());
childContext ??= PaintingContext(childLayer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero);
// Double-check that the paint method did not replace the layer (the first
// check is done in the [layer] setter itself).
assert(identical(childLayer, child._layerHandle.layer));
childContext.stopRecordingIfNeeded();
}
/// Update the composited layer of [child] without repainting its children.
///
/// The render object must be attached to a [PipelineOwner], must have a
/// composited layer, and must be in need of a composited layer update but
/// not in need of painting. The render object's layer is re-used, and none
/// of its children are repaint or their layers updated.
///
/// See also:
///
/// * [RenderObject.isRepaintBoundary], which determines if a [RenderObject]
/// has a composited layer.
static void updateLayerProperties(RenderObject child) {
assert(child.isRepaintBoundary && child._wasRepaintBoundary);
assert(!child._needsPaint);
assert(child._layerHandle.layer != null);
final OffsetLayer childLayer = child._layerHandle.layer! as OffsetLayer;
Offset? debugOldOffset;
assert(() {
debugOldOffset = childLayer.offset;
return true;
}());
final OffsetLayer updatedLayer = child.updateCompositedLayer(oldLayer: childLayer);
assert(identical(updatedLayer, childLayer),
'$child created a new layer instance $updatedLayer instead of reusing the '
'existing layer $childLayer. See the documentation of RenderObject.updateCompositedLayer '
'for more information on how to correctly implement this method.'
);
assert(debugOldOffset == updatedLayer.offset);
child._needsCompositedLayerUpdate = false;
}
/// In debug mode, repaint the given render object using a custom painting
/// context that can record the results of the painting operation in addition
/// to performing the regular paint of the child.
///
/// See also:
///
/// * [repaintCompositedChild], for repainting a composited child without
/// instrumentation.
static void debugInstrumentRepaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
required PaintingContext customContext,
}) {
assert(() {
_repaintCompositedChild(
child,
debugAlsoPaintedParent: debugAlsoPaintedParent,
childContext: customContext,
);
return true;
}());
}
/// Paint a child [RenderObject].
///
/// 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) {
assert(() {
debugOnProfilePaint?.call(child);
return true;
}());
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
_compositeChild(child, offset);
// If a render object was a repaint boundary but no longer is one, this
// is where the framework managed layer is automatically disposed.
} else if (child._wasRepaintBoundary) {
assert(child._layerHandle.layer is OffsetLayer);
child._layerHandle.layer = null;
child._paintWithContext(this, offset);
} else {
child._paintWithContext(this, offset);
}
}
void _compositeChild(RenderObject child, Offset offset) {
assert(!_isRecording);
assert(child.isRepaintBoundary);
assert(_canvas == null || _canvas!.getSaveCount() == 1);
// Create a layer for our child, and paint the child into it.
if (child._needsPaint || !child._wasRepaintBoundary) {
repaintCompositedChild(child, debugAlsoPaintedParent: true);
} else {
if (child._needsCompositedLayerUpdate) {
updateLayerProperties(child);
}
assert(() {
// register the call for RepaintBoundary metrics
child.debugRegisterRepaintBoundaryPaint();
child._layerHandle.layer!.debugCreator = child.debugCreator ?? child;
return true;
}());
}
assert(child._layerHandle.layer is OffsetLayer);
final OffsetLayer childOffsetLayer = child._layerHandle.layer! as OffsetLayer;
childOffsetLayer.offset = offset;
appendLayer(childOffsetLayer);
}
/// Adds a layer to the recording requiring that the recording is already
/// stopped.
///
/// Do not call this function directly: call [addLayer] or [pushLayer]
/// instead. This function is called internally when all layers not
/// generated from the [canvas] are added.
///
/// Subclasses that need to customize how layers are added should override
/// this method.
@protected
void appendLayer(Layer layer) {
assert(!_isRecording);
layer.remove();
_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;
Canvas? _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.
@override
Canvas get canvas {
if (_canvas == null) {
_startRecording();
}
assert(_currentLayer != null);
return _canvas!;
}
void _startRecording() {
assert(!_isRecording);
_currentLayer = PictureLayer(estimatedBounds);
_recorder = ui.PictureRecorder();
_canvas = Canvas(_recorder!);
_containerLayer.append(_currentLayer!);
}
/// Adds a [CompositionCallback] for the current [ContainerLayer] used by this
/// context.
///
/// Composition callbacks are called whenever the layer tree containing the
/// current layer of this painting context gets composited, or when it gets
/// detached and will not be rendered again. This happens regardless of
/// whether the layer is added via retained rendering or not.
///
/// {@macro flutter.rendering.Layer.compositionCallbacks}
///
/// See also:
/// * [Layer.addCompositionCallback].
VoidCallback addCompositionCallback(CompositionCallback callback) {
return _containerLayer.addCompositionCallback(callback);
}
/// Stop recording to a canvas if recording has started.
///
/// Do not call this function directly: functions in this class will call
/// this method as needed. This function is called internally to ensure that
/// recording is stopped before adding layers or finalizing the results of a
/// paint.
///
/// Subclasses that need to customize how recording to a canvas is performed
/// should override this method to save the results of the custom canvas
/// recordings.
@protected
@mustCallSuper
void stopRecordingIfNeeded() {
if (!_isRecording) {
return;
}
assert(() {
if (debugRepaintRainbowEnabled) {
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 6.0
..color = debugCurrentRepaintColor.toColor();
canvas.drawRect(estimatedBounds.deflate(3.0), paint);
}
if (debugPaintLayerBordersEnabled) {
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.0
..color = const Color(0xFFFF9800);
canvas.drawRect(estimatedBounds, paint);
}
return true;
}());
_currentLayer!.picture = _recorder!.endRecording();
_currentLayer = null;
_recorder = null;
_canvas = null;
}
/// Hints that the painting in the current layer is complex and would benefit
/// from caching.
///
/// If this hint is not set, the compositor will apply its own heuristics to
/// decide whether the current layer is complex enough to benefit from
/// caching.
void setIsComplexHint() {
_currentLayer?.isComplexHint = true;
}
/// Hints that the painting in the current layer is likely to change next frame.
///
/// This hint tells the compositor not to cache the current layer because the
/// cache will not be used in the future. If this hint is not set, the
/// compositor will apply its own heuristics to decide whether the current
/// layer is likely to be reused in the future.
void setWillChangeHint() {
_currentLayer?.willChangeHint = true;
}
/// Adds a composited leaf layer to the recording.
///
/// After calling this function, the [canvas] property will change to refer to
/// a new [Canvas] that draws on top of the given layer.
///
/// A [RenderObject] that uses this function is very likely to require its
/// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
/// ancestor render objects that this render object will include a composited
/// layer, which, for example, causes them to use composited clips.
///
/// See also:
///
/// * [pushLayer], for adding a layer and painting further contents within
/// it.
void addLayer(Layer layer) {
stopRecordingIfNeeded();
appendLayer(layer);
}
/// Appends the given layer to the recording, and calls the `painter` callback
/// with that layer, providing the `childPaintBounds` as the estimated paint
/// bounds of the child. The `childPaintBounds` can be used for debugging but
/// have no effect on painting.
///
/// The given layer must be an unattached orphan. (Providing a newly created
/// object, rather than reusing an existing layer, satisfies that
/// requirement.)
///
/// {@template flutter.rendering.PaintingContext.pushLayer.offset}
/// The `offset` is the offset to pass to the `painter`. In particular, it is
/// not an offset applied to the layer itself. Layers conceptually by default
/// have no position or size, though they can transform their contents. For
/// example, an [OffsetLayer] applies an offset to its children.
/// {@endtemplate}
///
/// If the `childPaintBounds` are not specified then the current layer's paint
/// bounds are used. This is appropriate if the child layer does not apply any
/// transformation or clipping to its contents. The `childPaintBounds`, if
/// specified, must be in the coordinate system of the new layer (i.e. as seen
/// by its children after it applies whatever transform to its contents), and
/// should not go outside the current layer's paint bounds.
///
/// See also:
///
/// * [addLayer], for pushing a layer without painting further contents
/// within it.
void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect? childPaintBounds }) {
// If a layer is being reused, it may already contain children. We remove
// them so that `painter` can add children that are relevant for this frame.
if (childLayer.hasChildren) {
childLayer.removeAllChildren();
}
stopRecordingIfNeeded();
appendLayer(childLayer);
final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
painter(childContext, offset);
childContext.stopRecordingIfNeeded();
}
/// Creates a painting context configured to paint into [childLayer].
///
/// The `bounds` are estimated paint bounds for debugging purposes.
@protected
PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) {
return PaintingContext(childLayer, bounds);
}
/// Clip further painting using a rectangle.
///
/// {@template flutter.rendering.PaintingContext.pushClipRect.needsCompositing}
/// The `needsCompositing` argument specifies whether the child needs
/// compositing. Typically this matches the value of
/// [RenderObject.needsCompositing] for the caller. If false, this method
/// returns null, indicating that a layer is no longer necessary. If a render
/// object calling this method stores the `oldLayer` in its
/// [RenderObject.layer] field, it should set that field to null.
///
/// When `needsCompositing` is false, this method will use a more efficient
/// way to apply the layer effect than actually creating a layer.
/// {@endtemplate}
///
/// {@template flutter.rendering.PaintingContext.pushClipRect.offset}
/// The `offset` argument is the offset from the origin of the canvas'
/// coordinate system to the origin of the caller's coordinate system.
/// {@endtemplate}
///
/// The `clipRect` is the rectangle (in the caller's coordinate system) to use
/// to clip the painting done by [painter]. It should not include the
/// `offset`.
///
/// The `painter` callback will be called while the `clipRect` is applied. It
/// is called synchronously during the call to [pushClipRect].
///
/// The `clipBehavior` argument controls how the rectangle is clipped.
///
/// {@template flutter.rendering.PaintingContext.pushClipRect.oldLayer}
/// For the `oldLayer` argument, specify the layer created in the previous
/// frame. This gives the engine more information for performance
/// optimizations. Typically this is the value of [RenderObject.layer] that a
/// render object creates once, then reuses for all subsequent frames until a
/// layer is no longer needed (e.g. the render object no longer needs
/// compositing) or until the render object changes the type of the layer
/// (e.g. from opacity layer to a clip rect layer).
/// {@endtemplate}
ClipRectLayer? pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.hardEdge, ClipRectLayer? oldLayer }) {
if (clipBehavior == Clip.none) {
painter(this, offset);
return null;
}
final Rect offsetClipRect = clipRect.shift(offset);
if (needsCompositing) {
final ClipRectLayer layer = oldLayer ?? ClipRectLayer();
layer
..clipRect = offsetClipRect
..clipBehavior = clipBehavior;
pushLayer(layer, painter, offset, childPaintBounds: offsetClipRect);
return layer;
} else {
clipRectAndPaint(offsetClipRect, clipBehavior, offsetClipRect, () => painter(this, offset));
return null;
}
}
/// Clip further painting using a rounded rectangle.
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing}
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.offset}
///
/// The `bounds` argument is used to specify the region of the canvas (in the
/// caller's coordinate system) into which `painter` will paint.
///
/// The `clipRRect` argument specifies the rounded-rectangle (in the caller's
/// coordinate system) to use to clip the painting done by `painter`. It
/// should not include the `offset`.
///
/// The `painter` callback will be called while the `clipRRect` is applied. It
/// is called synchronously during the call to [pushClipRRect].
///
/// The `clipBehavior` argument controls how the rounded rectangle is clipped.
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
ClipRRectLayer? pushClipRRect(bool needsCompositing, Offset offset, Rect bounds, RRect clipRRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias, ClipRRectLayer? oldLayer }) {
if (clipBehavior == Clip.none) {
painter(this, offset);
return null;
}
final Rect offsetBounds = bounds.shift(offset);
final RRect offsetClipRRect = clipRRect.shift(offset);
if (needsCompositing) {
final ClipRRectLayer layer = oldLayer ?? ClipRRectLayer();
layer
..clipRRect = offsetClipRRect
..clipBehavior = clipBehavior;
pushLayer(layer, painter, offset, childPaintBounds: offsetBounds);
return layer;
} else {
clipRRectAndPaint(offsetClipRRect, clipBehavior, offsetBounds, () => painter(this, offset));
return null;
}
}
/// Clip further painting using a path.
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing}
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.offset}
///
/// The `bounds` argument is used to specify the region of the canvas (in the
/// caller's coordinate system) into which `painter` will paint.
///
/// The `clipPath` argument specifies the [Path] (in the caller's coordinate
/// system) to use to clip the painting done by `painter`. It should not
/// include the `offset`.
///
/// The `painter` callback will be called while the `clipPath` is applied. It
/// is called synchronously during the call to [pushClipPath].
///
/// The `clipBehavior` argument controls how the path is clipped.
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
ClipPathLayer? pushClipPath(bool needsCompositing, Offset offset, Rect bounds, Path clipPath, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias, ClipPathLayer? oldLayer }) {
if (clipBehavior == Clip.none) {
painter(this, offset);
return null;
}
final Rect offsetBounds = bounds.shift(offset);
final Path offsetClipPath = clipPath.shift(offset);
if (needsCompositing) {
final ClipPathLayer layer = oldLayer ?? ClipPathLayer();
layer
..clipPath = offsetClipPath
..clipBehavior = clipBehavior;
pushLayer(layer, painter, offset, childPaintBounds: offsetBounds);
return layer;
} else {
clipPathAndPaint(offsetClipPath, clipBehavior, offsetBounds, () => painter(this, offset));
return null;
}
}
/// Blend further painting with a color filter.
///
/// {@macro flutter.rendering.PaintingContext.pushLayer.offset}
///
/// The `colorFilter` argument is the [ColorFilter] value to use when blending
/// the painting done by `painter`.
///
/// The `painter` callback will be called while the `colorFilter` is applied.
/// It is called synchronously during the call to [pushColorFilter].
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
///
/// A [RenderObject] that uses this function is very likely to require its
/// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
/// ancestor render objects that this render object will include a composited
/// layer, which, for example, causes them to use composited clips.
ColorFilterLayer pushColorFilter(Offset offset, ColorFilter colorFilter, PaintingContextCallback painter, { ColorFilterLayer? oldLayer }) {
final ColorFilterLayer layer = oldLayer ?? ColorFilterLayer();
layer.colorFilter = colorFilter;
pushLayer(layer, painter, offset);
return layer;
}
/// Transform further painting using a matrix.
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing}
///
/// The `offset` argument is the offset to pass to `painter` and the offset to
/// the origin used by `transform`.
///
/// The `transform` argument is the [Matrix4] with which to transform the
/// coordinate system while calling `painter`. It should not include `offset`.
/// It is applied effectively after applying `offset`.
///
/// The `painter` callback will be called while the `transform` is applied. It
/// is called synchronously during the call to [pushTransform].
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
TransformLayer? pushTransform(bool needsCompositing, Offset offset, Matrix4 transform, PaintingContextCallback painter, { TransformLayer? oldLayer }) {
final Matrix4 effectiveTransform = Matrix4.translationValues(offset.dx, offset.dy, 0.0)
..multiply(transform)..translate(-offset.dx, -offset.dy);
if (needsCompositing) {
final TransformLayer layer = oldLayer ?? TransformLayer();
layer.transform = effectiveTransform;
pushLayer(
layer,
painter,
offset,
childPaintBounds: MatrixUtils.inverseTransformRect(effectiveTransform, estimatedBounds),
);
return layer;
} else {
canvas
..save()
..transform(effectiveTransform.storage);
painter(this, offset);
canvas.restore();
return null;
}
}
/// Blend further painting with an alpha value.
///
/// The `offset` argument indicates an offset to apply to all the children
/// (the rendering created by `painter`).
///
/// The `alpha` argument is the alpha value to use when blending the painting
/// done by `painter`. An alpha value of 0 means the painting is fully
/// transparent and an alpha value of 255 means the painting is fully opaque.
///
/// The `painter` callback will be called while the `alpha` is applied. It
/// is called synchronously during the call to [pushOpacity].
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
///
/// A [RenderObject] that uses this function is very likely to require its
/// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
/// ancestor render objects that this render object will include a composited
/// layer, which, for example, causes them to use composited clips.
OpacityLayer pushOpacity(Offset offset, int alpha, PaintingContextCallback painter, { OpacityLayer? oldLayer }) {
final OpacityLayer layer = oldLayer ?? OpacityLayer();
layer
..alpha = alpha
..offset = offset;
pushLayer(layer, painter, Offset.zero);
return layer;
}
@override
String toString() => '${objectRuntimeType(this, 'PaintingContext')}#$hashCode(layer: $_containerLayer, canvas bounds: $estimatedBounds)';
}
/// An abstract set of layout constraints.
///
/// Concrete layout models (such as box) will create concrete subclasses to
/// communicate layout constraints between parents and children.
///
/// ## Writing a Constraints subclass
///
/// When creating a new [RenderObject] subclass with a new layout protocol, one
/// will usually need to create a new [Constraints] subclass to express the
/// input to the layout algorithms.
///
/// A [Constraints] subclass should be immutable (all fields final). There are
/// several members to implement, in addition to whatever fields, constructors,
/// and helper methods one may find useful for a particular layout protocol:
///
/// * The [isTight] getter, which should return true if the object represents a
/// case where the [RenderObject] class has no choice for how to lay itself
/// out. For example, [BoxConstraints] returns true for [isTight] when both
/// the minimum and maximum widths and the minimum and maximum heights are
/// equal.
///
/// * The [isNormalized] getter, which should return true if the object
/// represents its data in its canonical form. Sometimes, it is possible for
/// fields to be redundant with each other, such that several different
/// representations have the same implications. For example, a
/// [BoxConstraints] instance with its minimum width greater than its maximum
/// width is equivalent to one where the maximum width is set to that minimum
/// width (`2<w<1` is equivalent to `2<w<2`, since minimum constraints have
/// priority). This getter is used by the default implementation of
/// [debugAssertIsValid].
///
/// * The [debugAssertIsValid] method, which should assert if there's anything
/// wrong with the constraints object. (We use this approach rather than
/// asserting in constructors so that our constructors can be `const` and so
/// that it is possible to create invalid constraints temporarily while
/// building valid ones.) See the implementation of
/// [BoxConstraints.debugAssertIsValid] for an example of the detailed checks
/// that can be made.
///
/// * The [==] operator and the [hashCode] getter, so that constraints can be
/// compared for equality. If a render object is given constraints that are
/// equal, then the rendering library will avoid laying the object out again
/// if it is not dirty.
///
/// * The [toString] method, which should describe the constraints so that they
/// appear in a usefully readable form in the output of [debugDumpRenderTree].
@immutable
abstract class Constraints {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const Constraints();
/// Whether there is exactly one size possible given these constraints.
bool get isTight;
/// Whether the constraint is expressed in a consistent manner.
bool get isNormalized;
/// Asserts that the constraints are valid.
///
/// This might involve checks more detailed than [isNormalized].
///
/// For example, the [BoxConstraints] subclass verifies that the constraints
/// are not [double.nan].
///
/// If the `isAppliedConstraint` argument is true, then even stricter rules
/// are enforced. This argument is set to true when checking constraints that
/// are about to be applied to a [RenderObject] during layout, as opposed to
/// constraints that may be further affected by other constraints. For
/// example, the asserts for verifying the validity of
/// [RenderConstrainedBox.additionalConstraints] do not set this argument, but
/// the asserts for verifying the argument passed to the [RenderObject.layout]
/// method do.
///
/// The `informationCollector` argument takes an optional callback which is
/// called when an exception is to be thrown. The collected information is
/// then included in the message after the error line.
///
/// Returns the same as [isNormalized] if asserts are disabled.
bool debugAssertIsValid({
bool isAppliedConstraint = false,
InformationCollector? informationCollector,
}) {
assert(isNormalized);
return isNormalized;
}
}
/// Signature for a function that is called for each [RenderObject].
///
/// Used by [RenderObject.visitChildren] and [RenderObject.visitChildrenForSemantics].
///
/// The `child` argument must not be null.
typedef RenderObjectVisitor = void Function(RenderObject child);
/// Signature for a function that is called during layout.
///
/// Used by [RenderObject.invokeLayoutCallback].
typedef LayoutCallback<T extends Constraints> = void Function(T constraints);
class _LocalSemanticsHandle implements SemanticsHandle {
_LocalSemanticsHandle._(PipelineOwner owner, this.listener)
: _owner = owner {
if (listener != null) {
_owner.semanticsOwner!.addListener(listener!);
}
}
final PipelineOwner _owner;
/// The callback that will be notified when the semantics tree updates.
final VoidCallback? listener;
@override
void dispose() {
if (listener != null) {
_owner.semanticsOwner!.removeListener(listener!);
}
_owner._didDisposeSemanticsHandle();
}
}
/// The pipeline owner manages the rendering pipeline.
///
/// The pipeline owner provides an interface for driving the rendering pipeline
/// and stores the state about which render objects have requested to be visited
/// in each stage of the pipeline. To flush the pipeline, call the following
/// functions in order:
///
/// 1. [flushLayout] updates any render objects that need to compute their
/// layout. During this phase, the size and position of each render
/// object is calculated. Render objects might dirty their painting or
/// compositing state during this phase.
/// 2. [flushCompositingBits] updates any render objects that have dirty
/// compositing bits. During this phase, each render object learns whether
/// any of its children require compositing. This information is used during
/// the painting phase when selecting how to implement visual effects such as
/// clipping. If a render object has a composited child, it needs to use a
/// [Layer] to create the clip in order for the clip to apply to the
/// composited child (which will be painted into its own [Layer]).
/// 3. [flushPaint] visits any render objects that need to paint. During this
/// phase, render objects get a chance to record painting commands into
/// [PictureLayer]s and construct other composited [Layer]s.
/// 4. Finally, if semantics are enabled, [flushSemantics] will compile the
/// semantics for the render objects. This semantic information is used by
/// assistive technology to improve the accessibility of the render tree.
///
/// The [RendererBinding] holds the pipeline owner for the render objects that
/// are visible on screen. You can create other pipeline owners to manage
/// off-screen objects, which can flush their pipelines independently of the
/// on-screen render objects.
///
/// [PipelineOwner]s can be organized in a tree to manage multiple render trees,
/// where each [PipelineOwner] is responsible for one of the render trees. To
/// build or modify the tree, call [adoptChild] or [dropChild]. During each of
/// the different flush phases described above, a [PipelineOwner] will first
/// perform the phase on the nodes it manages in its own render tree before
/// calling the same flush method on its children. No assumption must be made
/// about the order in which child [PipelineOwner]s are flushed.
///
/// A [PipelineOwner] may also be [attach]ed to a [PipelineManifold], which
/// gives it access to platform functionality usually exposed by the bindings
/// without tying it to a specific binding implementation. All [PipelineOwner]s
/// in a given tree must be attached to the same [PipelineManifold]. This
/// happens automatically during [adoptChild].
class PipelineOwner {
/// Creates a pipeline owner.
///
/// Typically created by the binding (e.g., [RendererBinding]), but can be
/// created separately from the binding to drive off-screen render objects
/// through the rendering pipeline.
PipelineOwner({
this.onNeedVisualUpdate,
this.onSemanticsOwnerCreated,
this.onSemanticsUpdate,
this.onSemanticsOwnerDisposed,
});
/// Called when a render object associated with this pipeline owner wishes to
/// update its visual appearance.
///
/// Typical implementations of this function will schedule a task to flush the
/// various stages of the pipeline. This function might be called multiple
/// times in quick succession. Implementations should take care to discard
/// duplicate calls quickly.
///
/// When the [PipelineOwner] is attached to a [PipelineManifold] and
/// [onNeedVisualUpdate] is provided, the [onNeedVisualUpdate] callback is
/// invoked instead of calling [PipelineManifold.requestVisualUpdate].
final VoidCallback? onNeedVisualUpdate;
/// Called whenever this pipeline owner creates a semantics object.
///
/// Typical implementations will schedule the creation of the initial
/// semantics tree.
final VoidCallback? onSemanticsOwnerCreated;
/// Called whenever this pipeline owner's semantics owner emits a [SemanticsUpdate].
///
/// Typical implementations will delegate the [SemanticsUpdate] to a [FlutterView]
/// that can handle the [SemanticsUpdate].
final SemanticsUpdateCallback? onSemanticsUpdate;
/// Called whenever this pipeline owner disposes its semantics owner.
///
/// Typical implementations will tear down the semantics tree.
final VoidCallback? onSemanticsOwnerDisposed;
/// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
///
/// Used to notify the pipeline owner that an associated render object wishes
/// to update its visual appearance.
void requestVisualUpdate() {
if (onNeedVisualUpdate != null) {
onNeedVisualUpdate!();
} else {
_manifold?.requestVisualUpdate();
}
}
/// The unique object managed by this pipeline that has no parent.
///
/// This object does not have to be a [RenderObject].
AbstractNode? get rootNode => _rootNode;
AbstractNode? _rootNode;
set rootNode(AbstractNode? value) {
if (_rootNode == value) {
return;
}
_rootNode?.detach();
_rootNode = value;
_rootNode?.attach(this);
}
// Whether the current [flushLayout] call should pause to incorporate the
// [RenderObject]s in `_nodesNeedingLayout` into the current dirty list,
// before continuing to process dirty relayout boundaries.
//
// This flag is set to true when a [RenderObject.invokeLayoutCallback]
// returns, to avoid laying out dirty relayout boundaries in an incorrect
// order and causing them to be laid out more than once per frame. See
// layout_builder_mutations_test.dart for an example.
//
// The new dirty nodes are not immediately merged after a
// [RenderObject.invokeLayoutCallback] call because we may encounter multiple
// such calls while processing a single relayout boundary in [flushLayout].
// Batching new dirty nodes can reduce the number of merges [flushLayout]
// has to perform.
bool _shouldMergeDirtyNodes = false;
List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
/// Whether this pipeline is currently in the layout phase.
///
/// Specifically, whether [flushLayout] is currently running.
///
/// Only valid when asserts are enabled; in release builds, this
/// always returns false.
bool get debugDoingLayout => _debugDoingLayout;
bool _debugDoingLayout = false;
bool _debugDoingChildLayout = false;
/// 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 [RendererBinding] for an example of how this function is used.
void flushLayout() {
if (!kReleaseMode) {
Map<String, String>? debugTimelineArguments;
assert(() {
if (debugEnhanceLayoutTimelineArguments) {
debugTimelineArguments = <String, String>{
'dirty count': '${_nodesNeedingLayout.length}',
'dirty list': '$_nodesNeedingLayout',
};
}
return true;
}());
Timeline.startSync(
'LAYOUT',
arguments: debugTimelineArguments,
);
}
assert(() {
_debugDoingLayout = true;
return true;
}());
try {
while (_nodesNeedingLayout.isNotEmpty) {
assert(!_shouldMergeDirtyNodes);
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
dirtyNodes.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (int i = 0; i < dirtyNodes.length; i++) {
if (_shouldMergeDirtyNodes) {
_shouldMergeDirtyNodes = false;
if (_nodesNeedingLayout.isNotEmpty) {
_nodesNeedingLayout.addAll(dirtyNodes.getRange(i, dirtyNodes.length));
break;
}
}
final RenderObject node = dirtyNodes[i];
if (node._needsLayout && node.owner == this) {
node._layoutWithoutResize();
}
}
// No need to merge dirty nodes generated from processing the last
// relayout boundary back.
_shouldMergeDirtyNodes = false;
}
assert(() {
_debugDoingChildLayout = true;
return true;
}());
for (final PipelineOwner child in _children) {
child.flushLayout();
}
assert(_nodesNeedingLayout.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.');
} finally {
_shouldMergeDirtyNodes = false;
assert(() {
_debugDoingLayout = false;
_debugDoingChildLayout = false;
return true;
}());
if (!kReleaseMode) {
Timeline.finishSync();
}
}
}
// This flag is used to allow the kinds of mutations performed by GlobalKey
// reparenting while a LayoutBuilder is being rebuilt and in so doing tries to
// move a node from another LayoutBuilder subtree that hasn't been updated
// yet. To set this, call [_enableMutationsToDirtySubtrees], which is called
// by [RenderObject.invokeLayoutCallback].
bool _debugAllowMutationsToDirtySubtrees = false;
// See [RenderObject.invokeLayoutCallback].
void _enableMutationsToDirtySubtrees(VoidCallback callback) {
assert(_debugDoingLayout);
bool? oldState;
assert(() {
oldState = _debugAllowMutationsToDirtySubtrees;
_debugAllowMutationsToDirtySubtrees = true;
return true;
}());
try {
callback();
} finally {
_shouldMergeDirtyNodes = true;
assert(() {
_debugAllowMutationsToDirtySubtrees = oldState!;
return true;
}());
}
}
final List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
/// Updates the [RenderObject.needsCompositing] bits.
///
/// Called as part of the rendering pipeline after [flushLayout] and before
/// [flushPaint].
void flushCompositingBits() {
if (!kReleaseMode) {
Timeline.startSync('UPDATING COMPOSITING BITS');
}
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this) {
node._updateCompositingBits();
}
}
_nodesNeedingCompositingBitsUpdate.clear();
for (final PipelineOwner child in _children) {
child.flushCompositingBits();
}
assert(_nodesNeedingCompositingBitsUpdate.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.');
if (!kReleaseMode) {
Timeline.finishSync();
}
}
List<RenderObject> _nodesNeedingPaint = <RenderObject>[];
/// Whether this pipeline is currently in the paint phase.
///
/// Specifically, whether [flushPaint] is currently running.
///
/// Only valid when asserts are enabled. In release builds,
/// this always returns false.
bool get debugDoingPaint => _debugDoingPaint;
bool _debugDoingPaint = false;
/// 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 [RendererBinding] for an example of how this function is used.
void flushPaint() {
if (!kReleaseMode) {
Map<String, String>? debugTimelineArguments;
assert(() {
if (debugEnhancePaintTimelineArguments) {
debugTimelineArguments = <String, String>{
'dirty count': '${_nodesNeedingPaint.length}',
'dirty list': '$_nodesNeedingPaint',
};
}
return true;
}());
Timeline.startSync(
'PAINT',
arguments: debugTimelineArguments,
);
}
try {
assert(() {
_debugDoingPaint = true;
return true;
}());
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layerHandle.layer != null);
if ((node._needsPaint || node._needsCompositedLayerUpdate) && node.owner == this) {
if (node._layerHandle.layer!.attached) {
assert(node.isRepaintBoundary);
if (node._needsPaint) {
PaintingContext.repaintCompositedChild(node);
} else {
PaintingContext.updateLayerProperties(node);
}
} else {
node._skippedPaintingOnLayer();
}
}
}
for (final PipelineOwner child in _children) {
child.flushPaint();
}
assert(_nodesNeedingPaint.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.');
} finally {
assert(() {
_debugDoingPaint = false;
return true;
}());
if (!kReleaseMode) {
Timeline.finishSync();
}
}
}
/// The object that is managing semantics for this pipeline owner, if any.
///
/// An owner is created by [ensureSemantics] or when the [PipelineManifold] to
/// which this owner is connected has [PipelineManifold.semanticsEnabled] set
/// to true. The owner is valid for as long as
/// [PipelineManifold.semanticsEnabled] remains true or while there are
/// outstanding [SemanticsHandle]s from calls to [ensureSemantics]. The
/// [semanticsOwner] field will revert to null once both conditions are no
/// longer met.
///
/// When [semanticsOwner] is null, the [PipelineOwner] skips all steps
/// relating to semantics.
SemanticsOwner? get semanticsOwner => _semanticsOwner;
SemanticsOwner? _semanticsOwner;
/// The number of clients registered to listen for semantics.
///
/// The number is increased whenever [ensureSemantics] is called and decreased
/// when [SemanticsHandle.dispose] is called.
int get debugOutstandingSemanticsHandles => _outstandingSemanticsHandles;
int _outstandingSemanticsHandles = 0;
/// Opens a [SemanticsHandle] and calls [listener] whenever the semantics tree
/// generated from the render tree owned by this [PipelineOwner] updates.
///
/// Calling this method only ensures that this particular [PipelineOwner] will
/// generate a semantics tree. Consider calling
/// [SemanticsBinding.ensureSemantics] instead to turn on semantics globally
/// for the entire app.
///
/// The [PipelineOwner] updates the semantics tree only when there are clients
/// that wish to use the semantics tree. These clients express their interest
/// by holding [SemanticsHandle] objects that notify them whenever the
/// semantics tree updates.
///
/// Clients can close their [SemanticsHandle] by calling
/// [SemanticsHandle.dispose]. Once all the outstanding [SemanticsHandle]
/// objects for a given [PipelineOwner] are closed, the [PipelineOwner] stops
/// maintaining the semantics tree.
SemanticsHandle ensureSemantics({ VoidCallback? listener }) {
_outstandingSemanticsHandles += 1;
_updateSemanticsOwner();
return _LocalSemanticsHandle._(this, listener);
}
void _updateSemanticsOwner() {
if ((_manifold?.semanticsEnabled ?? false) || _outstandingSemanticsHandles > 0) {
if (_semanticsOwner == null) {
assert(onSemanticsUpdate != null, 'Attempted to enable semantics without configuring an onSemanticsUpdate callback.');
_semanticsOwner = SemanticsOwner(onSemanticsUpdate: onSemanticsUpdate!);
onSemanticsOwnerCreated?.call();
}
} else if (_semanticsOwner != null) {
_semanticsOwner?.dispose();
_semanticsOwner = null;
onSemanticsOwnerDisposed?.call();
}
}
void _didDisposeSemanticsHandle() {
assert(_semanticsOwner != null);
_outstandingSemanticsHandles -= 1;
_updateSemanticsOwner();
}
bool _debugDoingSemantics = false;
final Set<RenderObject> _nodesNeedingSemantics = <RenderObject>{};
/// Update the semantics for render objects marked as needing a semantics
/// update.
///
/// Initially, only the root node, as scheduled by
/// [RenderObject.scheduleInitialSemantics], needs a semantics update.
///
/// This function is one of the core stages of the rendering pipeline. The
/// semantics are compiled after painting and only after
/// [RenderObject.scheduleInitialSemantics] has been called.
///
/// See [RendererBinding] for an example of how this function is used.
void flushSemantics() {
if (_semanticsOwner == null) {
return;
}
if (!kReleaseMode) {
Timeline.startSync('SEMANTICS');
}
assert(_semanticsOwner != null);
assert(() {
_debugDoingSemantics = true;
return true;
}());
try {
final List<RenderObject> nodesToProcess = _nodesNeedingSemantics.toList()
..sort((RenderObject a, RenderObject b) => a.depth - b.depth);
_nodesNeedingSemantics.clear();
for (final RenderObject node in nodesToProcess) {
if (node._needsSemanticsUpdate && node.owner == this) {
node._updateSemantics();
}
}
_semanticsOwner!.sendSemanticsUpdate();
for (final PipelineOwner child in _children) {
child.flushSemantics();
}
assert(_nodesNeedingSemantics.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.');
} finally {
assert(() {
_debugDoingSemantics = false;
return true;
}());
if (!kReleaseMode) {
Timeline.finishSync();
}
}
}
// TREE MANAGEMENT
final Set<PipelineOwner> _children = <PipelineOwner>{};
PipelineManifold? _manifold;
PipelineOwner? _debugParent;
bool _debugSetParent(PipelineOwner child, PipelineOwner? parent) {
child._debugParent = parent;
return true;
}
/// Mark this [PipelineOwner] as attached to the given [PipelineManifold].
///
/// Typically, this is only called directly on the root [PipelineOwner].
/// Children are automatically attached to their parent's [PipelineManifold]
/// when [adoptChild] is called.
void attach(PipelineManifold manifold) {
assert(_manifold == null);
_manifold = manifold;
_manifold!.addListener(_updateSemanticsOwner);
_updateSemanticsOwner();
for (final PipelineOwner child in _children) {
child.attach(manifold);
}
}
/// Mark this [PipelineOwner] as detached.
///
/// Typically, this is only called directly on the root [PipelineOwner].
/// Children are automatically detached from their parent's [PipelineManifold]
/// when [dropChild] is called.
void detach() {
assert(_manifold != null);
_manifold!.removeListener(_updateSemanticsOwner);
_manifold = null;
_updateSemanticsOwner();
for (final PipelineOwner child in _children) {
child.detach();
}
}
// In theory, child list modifications are also disallowed between
// _debugDoingChildrenLayout and _debugDoingPaint as well as between
// _debugDoingPaint and _debugDoingSemantics. However, since the associated
// flush methods are usually called back to back, this gets us close enough.
bool get _debugAllowChildListModifications => !_debugDoingChildLayout && !_debugDoingPaint && !_debugDoingSemantics;
/// Adds `child` to this [PipelineOwner].
///
/// During the phases of frame production (see [RendererBinding.drawFrame]),
/// the parent [PipelineOwner] will complete a phase for the nodes it owns
/// directly before invoking the flush method corresponding to the current
/// phase on its child [PipelineOwner]s. For example, during layout, the
/// parent [PipelineOwner] will first lay out its own nodes before calling
/// [flushLayout] on its children. During paint, it will first paint its own
/// nodes before calling [flushPaint] on its children. This order also applies
/// for all the other phases.
///
/// No assumptions must be made about the order in which child
/// [PipelineOwner]s are flushed.
///
/// No new children may be added after the [PipelineOwner] has started calling
/// [flushLayout] on any of its children until the end of the current frame.
///
/// To remove a child, call [dropChild].
void adoptChild(PipelineOwner child) {
assert(child._debugParent == null);
assert(!_children.contains(child));
assert(_debugAllowChildListModifications, 'Cannot modify child list after layout.');
_children.add(child);
assert(_debugSetParent(child, this));
if (_manifold != null) {
child.attach(_manifold!);
}
}
/// Removes a child [PipelineOwner] previously added via [adoptChild].
///
/// This node will cease to call the flush methods on the `child` during frame
/// production.
///
/// No children may be removed after the [PipelineOwner] has started calling
/// [flushLayout] on any of its children until the end of the current frame.
void dropChild(PipelineOwner child) {
assert(child._debugParent == this);
assert(_children.contains(child));
assert(_debugAllowChildListModifications, 'Cannot modify child list after layout.');
_children.remove(child);
assert(_debugSetParent(child, null));
if (_manifold != null) {
child.detach();
}
}
/// Calls `visitor` for each immediate child of this [PipelineOwner].
///
/// See also:
///
/// * [adoptChild] to add a child.
/// * [dropChild] to remove a child.
void visitChildren(PipelineOwnerVisitor visitor) {
_children.forEach(visitor);
}
}
/// Signature for the callback to [PipelineOwner.visitChildren].
///
/// The argument is the child being visited.
typedef PipelineOwnerVisitor = void Function(PipelineOwner child);
/// Manages a tree of [PipelineOwner]s.
///
/// All [PipelineOwner]s within a tree are attached to the same
/// [PipelineManifold], which gives them access to shared functionality such
/// as requesting a visual update (by calling [requestVisualUpdate]). As such,
/// the [PipelineManifold] gives the [PipelineOwner]s access to functionality
/// usually provided by the bindings without tying the [PipelineOwner]s to a
/// particular binding implementation.
///
/// The root of the [PipelineOwner] tree is attached to a [PipelineManifold] by
/// passing the manifold to [PipelineOwner.attach]. Children are attached to the
/// same [PipelineManifold] as their parent when they are adopted via
/// [PipelineOwner.adoptChild].
///
/// [PipelineOwner]s can register listeners with the [PipelineManifold] to be
/// informed when certain values provided by the [PipelineManifold] change.
abstract class PipelineManifold implements Listenable {
/// Whether [PipelineOwner]s connected to this [PipelineManifold] should
/// collect semantics information and produce a semantics tree.
///
/// The [PipelineManifold] notifies its listeners (managed with [addListener]
/// and [removeListener]) when this property changes its value.
///
/// See also:
///
/// * [SemanticsBinding.semanticsEnabled], which [PipelineManifold]
/// implementations typically use to back this property.
bool get semanticsEnabled;
/// Called by a [PipelineOwner] connected to this [PipelineManifold] when a
/// [RenderObject] associated with that pipeline owner wishes to update its
/// visual appearance.
///
/// Typical implementations of this function will schedule a task to flush the
/// various stages of the pipeline. This function might be called multiple
/// times in quick succession. Implementations should take care to discard
/// duplicate calls quickly.
///
/// A [PipelineOwner] connected to this [PipelineManifold] will call
/// [PipelineOwner.onNeedVisualUpdate] instead of this method if it has been
/// configured with a non-null [PipelineOwner.onNeedVisualUpdate] callback.
///
/// See also:
///
/// * [SchedulerBinding.ensureVisualUpdate], which [PipelineManifold]
/// implementations typically call to implement this method.
void requestVisualUpdate();
}
const String _flutterRenderingLibrary = 'package:flutter/rendering.dart';
/// An object in the render tree.
///
/// The [RenderObject] class hierarchy is the core of the rendering
/// library's reason for being.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=zmbmrw07qBc}
///
/// [RenderObject]s have a [parent], and have a slot called [parentData] in
/// which the parent [RenderObject] can store child-specific data, for example,
/// the child position. The [RenderObject] class also implements the basic
/// layout and paint protocols.
///
/// The [RenderObject] class, however, does not define a child model (e.g.
/// whether a node has zero, one, or more children). It also doesn't define a
/// coordinate system (e.g. whether children are positioned in Cartesian
/// coordinates, in polar coordinates, etc) or a specific layout protocol (e.g.
/// whether the layout is width-in-height-out, or constraint-in-size-out, or
/// whether the parent sets the size and position of the child before or after
/// the child lays out, etc; or indeed whether the children are allowed to read
/// their parent's [parentData] slot).
///
/// The [RenderBox] subclass introduces the opinion that the layout
/// system uses Cartesian coordinates.
///
/// ## Lifecycle
///
/// A [RenderObject] must [dispose] when it is no longer needed. The creator
/// of the object is responsible for disposing of it. Typically, the creator is
/// a [RenderObjectElement], and that element will dispose the object it creates
/// when it is unmounted.
///
/// [RenderObject]s are responsible for cleaning up any expensive resources
/// they hold when [dispose] is called, such as [Picture] or [Image] objects.
/// This includes any [Layer]s that the render object has directly created. The
/// base implementation of dispose will nullify the [layer] property. Subclasses
/// must also nullify any other layer(s) it directly creates.
///
/// ## Writing a RenderObject subclass
///
/// In most cases, subclassing [RenderObject] itself is overkill, and
/// [RenderBox] would be a better starting point. However, if a render object
/// doesn't want to use a Cartesian coordinate system, then it should indeed
/// inherit from [RenderObject] directly. This allows it to define its own
/// layout protocol by using a new subclass of [Constraints] rather than using
/// [BoxConstraints], and by potentially using an entirely new set of objects
/// and values to represent the result of the output rather than just a [Size].
/// This increased flexibility comes at the cost of not being able to rely on
/// the features of [RenderBox]. For example, [RenderBox] implements an
/// intrinsic sizing protocol that allows you to measure a child without fully
/// laying it out, in such a way that if that child changes size, the parent
/// will be laid out again (to take into account the new dimensions of the
/// child). This is a subtle and bug-prone feature to get right.
///
/// Most aspects of writing a [RenderBox] apply to writing a [RenderObject] as
/// well, and therefore the discussion at [RenderBox] is recommended background
/// reading. The main differences are around layout and hit testing, since those
/// are the aspects that [RenderBox] primarily specializes.
///
/// ### Layout
///
/// A layout protocol begins with a subclass of [Constraints]. See the
/// discussion at [Constraints] for more information on how to write a
/// [Constraints] subclass.
///
/// The [performLayout] method should take the [constraints], and apply them.
/// The output of the layout algorithm is fields set on the object that describe
/// the geometry of the object for the purposes of the parent's layout. For
/// example, with [RenderBox] the output is the [RenderBox.size] field. This
/// output should only be read by the parent if the parent specified
/// `parentUsesSize` as true when calling [layout] on the child.
///
/// Anytime anything changes on a render object that would affect the layout of
/// that object, it should call [markNeedsLayout].
///
/// ### Hit Testing
///
/// Hit testing is even more open-ended than layout. There is no method to
/// override, you are expected to provide one.
///
/// The general behavior of your hit-testing method should be similar to the
/// behavior described for [RenderBox]. The main difference is that the input
/// need not be an [Offset]. You are also allowed to use a different subclass of
/// [HitTestEntry] when adding entries to the [HitTestResult]. When the
/// [handleEvent] method is called, the same object that was added to the
/// [HitTestResult] will be passed in, so it can be used to track information
/// like the precise coordinate of the hit, in whatever coordinate system is
/// used by the new layout protocol.
///
/// ### Adapting from one protocol to another
///
/// In general, the root of a Flutter render object tree is a [RenderView]. This
/// object has a single child, which must be a [RenderBox]. Thus, if you want to
/// have a custom [RenderObject] subclass in the render tree, you have two
/// choices: you either need to replace the [RenderView] itself, or you need to
/// have a [RenderBox] that has your class as its child. (The latter is the much
/// more common case.)
///
/// This [RenderBox] subclass converts from the box protocol to the protocol of
/// your class.
///
/// In particular, this means that for hit testing it overrides
/// [RenderBox.hitTest], and calls whatever method you have in your class for
/// hit testing.
///
/// Similarly, it overrides [performLayout] to create a [Constraints] object
/// appropriate for your class and passes that to the child's [layout] method.
///
/// ### Layout interactions between render objects
///
/// In general, the layout of a render object should only depend on the output of
/// its child's layout, and then only if `parentUsesSize` is set to true in the
/// [layout] call. Furthermore, if it is set to true, the parent must call the
/// child's [layout] if the child is to be rendered, because otherwise the
/// parent will not be notified when the child changes its layout outputs.
///
/// It is possible to set up render object protocols that transfer additional
/// information. For example, in the [RenderBox] protocol you can query your
/// children's intrinsic dimensions and baseline geometry. However, if this is
/// done then it is imperative that the child call [markNeedsLayout] on the
/// parent any time that additional information changes, if the parent used it
/// in the last layout phase. For an example of how to implement this, see the
/// [RenderBox.markNeedsLayout] method. It overrides
/// [RenderObject.markNeedsLayout] so that if a parent has queried the intrinsic
/// or baseline information, it gets marked dirty whenever the child's geometry
/// changes.
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
/// Initializes internal fields for subclasses.
RenderObject() {
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterRenderingLibrary,
className: '$RenderObject',
object: this,
);
}
_needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
_wasRepaintBoundary = isRepaintBoundary;
}
/// Cause the entire subtree rooted at the given [RenderObject] to be marked
/// dirty for layout, paint, etc, so that the effects of a hot reload can be
/// seen, or so that the effect of changing a global debug flag (such as
/// [debugPaintSizeEnabled]) can be applied.
///
/// This is called by the [RendererBinding] in response to the
/// `ext.flutter.reassemble` hook, which is used by development tools when the
/// application code has changed, to cause the widget tree to pick up any
/// changed implementations.
///
/// This is expensive and should not be called except during development.
///
/// See also:
///
/// * [BindingBase.reassembleApplication]
void reassemble() {
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsPaint();
markNeedsSemanticsUpdate();
visitChildren((RenderObject child) {
child.reassemble();
});
}
/// Whether this has been disposed.
///
/// If asserts are disabled, this property is always null.
bool? get debugDisposed {
bool? disposed;
assert(() {
disposed = _debugDisposed;
return true;
}());
return disposed;
}
bool _debugDisposed = false;
/// Release any resources held by this render object.
///
/// The object that creates a RenderObject is in charge of disposing it.
/// If this render object has created any children directly, it must dispose
/// of those children in this method as well. It must not dispose of any
/// children that were created by some other object, such as
/// a [RenderObjectElement]. Those children will be disposed when that
/// element unmounts, which may be delayed if the element is moved to another
/// part of the tree.
///
/// Implementations of this method must end with a call to the inherited
/// method, as in `super.dispose()`.
///
/// The object is no longer usable after calling dispose.
@mustCallSuper
void dispose() {
assert(!_debugDisposed);
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
_layerHandle.layer = null;
assert(() {
// TODO(dnfield): Enable this assert once clients have had a chance to
// migrate.
// visitChildren((RenderObject child) {
// assert(
// child.debugDisposed!,
// '${child.runtimeType} (child of $runtimeType) must be disposed before calling super.dispose().',
// );
// });
_debugDisposed = true;
return true;
}());
}
// 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(covariant RenderObject child) {
assert(_debugCanPerformMutations);
if (child.parentData is! ParentData) {
child.parentData = 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.
@override
void adoptChild(RenderObject child) {
setupParentData(child);
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
super.adoptChild(child);
}
/// 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.
@override
void dropChild(RenderObject child) {
assert(child.parentData != null);
child._cleanRelayoutBoundary();
child.parentData!.detach();
child.parentData = null;
super.dropChild(child);
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
}
/// 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) { }
/// The object responsible for creating this render object.
///
/// Used in debug messages.
///
/// See also:
///
/// * [DebugCreator], which the [widgets] library uses as values for this field.
Object? debugCreator;
void _reportException(String method, Object exception, StackTrace stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'rendering library',
context: ErrorDescription('during $method()'),
informationCollector: () => <DiagnosticsNode>[
// debugCreator should always be null outside of debugMode, but we want
// the tree shaker to notice this.
if (kDebugMode && debugCreator != null)
DiagnosticsDebugCreator(debugCreator!),
describeForError('The following RenderObject was being processed when the exception was fired'),
// TODO(jacobr): this error message has a code smell. Consider whether
// displaying the truncated children is really useful for command line
// users. Inspector users can see the full tree by clicking on the
// render object so this may not be that useful.
describeForError('RenderObject', style: DiagnosticsTreeStyle.truncateChildren),
],
));
}
/// Whether [performResize] for this render object is currently running.
///
/// Only valid when asserts are enabled. In release builds, always returns
/// false.
bool get debugDoingThisResize => _debugDoingThisResize;
bool _debugDoingThisResize = false;
/// Whether [performLayout] for this render object is currently running.
///
/// Only valid when asserts are enabled. In release builds, always returns
/// false.
bool get debugDoingThisLayout => _debugDoingThisLayout;
bool _debugDoingThisLayout = false;
/// The render object that is actively computing layout.
///
/// Only valid when asserts are enabled. In release builds, always returns
/// null.
static RenderObject? get debugActiveLayout => _debugActiveLayout;
static RenderObject? _debugActiveLayout;
/// Set [debugActiveLayout] to null when [inner] callback is called.
/// This is useful when you have to temporarily clear that variable to
/// disable some false-positive checks, such as when computing toStringDeep
/// or using custom trees.
@pragma('vm:prefer-inline')
static T _withDebugActiveLayoutCleared<T>(T Function() inner) {
RenderObject? debugPreviousActiveLayout;
assert(() {
debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = null;
return true;
}());
final T result = inner();
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
return true;
}());
return result;
}
/// Whether the parent render object is permitted to use this render object's
/// size.
///
/// Determined by the `parentUsesSize` parameter to [layout].
///
/// Only valid when asserts are enabled. In release builds, throws.
bool get debugCanParentUseSize => _debugCanParentUseSize!;
bool? _debugCanParentUseSize;
bool _debugMutationsLocked = false;
/// Whether tree mutations are currently permitted.
///
/// This is only useful during layout. One should also not mutate the tree at
/// other times (e.g. during paint or while assembling the semantic tree) but
/// this function does not currently enforce those conventions.
///
/// Only valid when asserts are enabled. This will throw in release builds.
bool get _debugCanPerformMutations {
late bool result;
assert(() {
if (_debugDisposed) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('A disposed RenderObject was mutated.'),
DiagnosticsProperty<RenderObject>(
'The disposed RenderObject was',
this,
style: DiagnosticsTreeStyle.errorProperty,
),
]);
}
final PipelineOwner? owner = this.owner;
// Detached nodes are allowed to mutate and the "can perform mutations"
// check will be performed when they re-attach. This assert is only useful
// during layout.
if (owner == null || !owner.debugDoingLayout) {
result = true;
return true;
}
RenderObject? activeLayoutRoot = this;
while (activeLayoutRoot != null) {
final bool mutationsToDirtySubtreesAllowed = activeLayoutRoot.owner?._debugAllowMutationsToDirtySubtrees ?? false;
final bool doingLayoutWithCallback = activeLayoutRoot._doingThisLayoutWithCallback;
// Mutations on this subtree is allowed when:
// - the subtree is being mutated in a layout callback.
// - a different part of the render tree is doing a layout callback,
// and this subtree is being reparented to that subtree, as a result
// of global key reparenting.
if (doingLayoutWithCallback || mutationsToDirtySubtreesAllowed && activeLayoutRoot._needsLayout) {
result = true;
return true;
}
if (!activeLayoutRoot._debugMutationsLocked) {
final AbstractNode? p = activeLayoutRoot.debugLayoutParent;
activeLayoutRoot = p is RenderObject ? p : null;
} else {
// activeLayoutRoot found.
break;
}
}
final RenderObject debugActiveLayout = RenderObject.debugActiveLayout!;
final String culpritMethodName = debugActiveLayout.debugDoingThisLayout ? 'performLayout' : 'performResize';
final String culpritFullMethodName = '${debugActiveLayout.runtimeType}.$culpritMethodName';
result = false;
if (activeLayoutRoot == null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('A $runtimeType was mutated in $culpritFullMethodName.'),
ErrorDescription(
'The RenderObject was mutated when none of its ancestors is actively performing layout.',
),
DiagnosticsProperty<RenderObject>(
'The RenderObject being mutated was',
this,
style: DiagnosticsTreeStyle.errorProperty,
),
DiagnosticsProperty<RenderObject>(
'The RenderObject that was mutating the said $runtimeType was',
debugActiveLayout,
style: DiagnosticsTreeStyle.errorProperty,
),
]);
}
if (activeLayoutRoot == this) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('A $runtimeType was mutated in its own $culpritMethodName implementation.'),
ErrorDescription('A RenderObject must not re-dirty itself while still being laid out.'),
DiagnosticsProperty<RenderObject>(
'The RenderObject being mutated was',
this,
style: DiagnosticsTreeStyle.errorProperty,
),
ErrorHint('Consider using the LayoutBuilder widget to dynamically change a subtree during layout.'),
]);
}
final ErrorSummary summary = ErrorSummary('A $runtimeType was mutated in $culpritFullMethodName.');
final bool isMutatedByAncestor = activeLayoutRoot == debugActiveLayout;
final String description = isMutatedByAncestor
? 'A RenderObject must not mutate its descendants in its $culpritMethodName method.'
: 'A RenderObject must not mutate another RenderObject from a different render subtree '
'in its $culpritMethodName method.';
throw FlutterError.fromParts(<DiagnosticsNode>[
summary,
ErrorDescription(description),
DiagnosticsProperty<RenderObject>(
'The RenderObject being mutated was',
this,
style: DiagnosticsTreeStyle.errorProperty,
),
DiagnosticsProperty<RenderObject>(
'The ${isMutatedByAncestor ? 'ancestor ' : ''}RenderObject that was mutating the said $runtimeType was',
debugActiveLayout,
style: DiagnosticsTreeStyle.errorProperty,
),
if (!isMutatedByAncestor) DiagnosticsProperty<RenderObject>(
'Their common ancestor was',
activeLayoutRoot,
style: DiagnosticsTreeStyle.errorProperty,
),
ErrorHint(
'Mutating the layout of another RenderObject may cause some RenderObjects in its subtree to be laid out more than once. '
'Consider using the LayoutBuilder widget to dynamically mutate a subtree during layout.'
),
]);
}());
return result;
}
/// The [RenderObject] that's expected to call [layout] on this [RenderObject]
/// in its [performLayout] implementation.
///
/// This method is used to implement an assert that ensures the render subtree
/// actively performing layout can not get accidently mutated. It's only
/// implemented in debug mode and always returns null in release mode.
///
/// The default implementation returns [parent] and overriding is rarely
/// needed. A [RenderObject] subclass that expects its
/// [RenderObject.performLayout] to be called from a different [RenderObject]
/// that's not its [parent] should override this property to return the actual
/// layout parent.
@protected
RenderObject? get debugLayoutParent {
RenderObject? layoutParent;
assert(() {
final AbstractNode? parent = this.parent;
layoutParent = parent is RenderObject? ? parent : null;
return true;
}());
return layoutParent;
}
@override
PipelineOwner? get owner => super.owner as PipelineOwner?;
@override
void attach(PipelineOwner owner) {
assert(!_debugDisposed);
super.attach(owner);
// If the node was dirtied in some way while unattached, make sure to add
// it to the appropriate dirty list now that an owner is available
if (_needsLayout && _relayoutBoundary != null) {
// Don't enter this block if we've never laid out at all;
// scheduleInitialLayout() will handle it
_needsLayout = false;
markNeedsLayout();
}
if (_needsCompositingBitsUpdate) {
_needsCompositingBitsUpdate = false;
markNeedsCompositingBitsUpdate();
}
if (_needsPaint && _layerHandle.layer != null) {
// Don't enter this block if we've never painted at all;
// scheduleInitialPaint() will handle it
_needsPaint = false;
markNeedsPaint();
}
if (_needsSemanticsUpdate && _semanticsConfiguration.isSemanticBoundary) {
// Don't enter this block if we've never updated semantics at all;
// scheduleInitialSemantics() will handle it
_needsSemanticsUpdate = false;
markNeedsSemanticsUpdate();
}
}
/// Whether this render object's layout information is dirty.
///
/// This is only set in debug mode. In general, render objects should not need
/// to condition their runtime behavior on whether they are dirty or not,
/// since they should only be marked dirty immediately prior to being laid
/// out and painted. In release builds, this throws.
///
/// It is intended to be used by tests and asserts.
bool get debugNeedsLayout {
late bool result;
assert(() {
result = _needsLayout;
return true;
}());
return result;
}
bool _needsLayout = true;
RenderObject? _relayoutBoundary;
/// Whether [invokeLayoutCallback] for this render object is currently running.
bool get debugDoingThisLayoutWithCallback => _doingThisLayoutWithCallback;
bool _doingThisLayoutWithCallback = false;
/// The layout constraints most recently supplied by the parent.
///
/// If layout has not yet happened, accessing this getter will
/// throw a [StateError] exception.
@protected
Constraints get constraints {
if (_constraints == null) {
throw StateError('A RenderObject does not have any constraints before it has been laid out.');
}
return _constraints!;
}
Constraints? _constraints;
/// Verify that the object's constraints are being met. Override
/// this function in a subclass to verify that your state matches
/// the constraints object. This function is only called in checked
/// mode and only when needsLayout is false. If the constraints are
/// not met, it should assert or throw an exception.
@protected
void debugAssertDoesMeetConstraints();
/// When true, debugAssertDoesMeetConstraints() is currently
/// executing asserts for verifying the consistent behavior of
/// intrinsic dimensions methods.
///
/// This should only be set by debugAssertDoesMeetConstraints()
/// implementations. It is used by tests to selectively ignore
/// custom layout callbacks. It should not be set outside of
/// debugAssertDoesMeetConstraints(), and should not be checked in
/// release mode (where it will always be false).
static bool debugCheckingIntrinsics = false;
bool _debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout() {
if (_relayoutBoundary == null) {
// We don't know where our relayout boundary is yet.
return true;
}
RenderObject node = this;
while (node != _relayoutBoundary) {
assert(node._relayoutBoundary == _relayoutBoundary);
assert(node.parent != null);
node = node.parent! as RenderObject;
if ((!node._needsLayout) && (!node._debugDoingThisLayout)) {
return false;
}
}
assert(node._relayoutBoundary == node);
return true;
}
/// Mark this render object's layout information as dirty, and either register
/// this object with its [PipelineOwner], or defer to the parent, depending on
/// whether this object is a relayout boundary or not respectively.
///
/// ## Background
///
/// Rather than eagerly updating layout information in response to writes into
/// a render object, we instead mark the layout information as dirty, which
/// schedules a visual update. As part of the visual update, the rendering
/// pipeline updates the render object's layout information.
///
/// This mechanism batches the layout work so that multiple sequential writes
/// are coalesced, removing redundant computation.
///
/// If a render object's parent indicates that it uses the size of one of its
/// render object children when computing its layout information, this
/// function, when called for the child, will also mark the parent as needing
/// layout. In that case, since both the parent and the child need to have
/// their layout recomputed, the pipeline owner is only notified about the
/// parent; when the parent is laid out, it will call the child's [layout]
/// method and thus the child will be laid out as well.
///
/// Once [markNeedsLayout] has been called on a render object,
/// [debugNeedsLayout] returns true for that render object until just after
/// the pipeline owner has called [layout] on the render object.
///
/// ## Special cases
///
/// Some subclasses of [RenderObject], notably [RenderBox], have other
/// situations in which the parent needs to be notified if the child is
/// dirtied (e.g., if the child's intrinsic dimensions or baseline changes).
/// Such subclasses override markNeedsLayout and either call
/// `super.markNeedsLayout()`, in the normal case, or call
/// [markParentNeedsLayout], in the case where the parent needs to be laid out
/// as well as the child.
///
/// If [sizedByParent] has changed, calls
/// [markNeedsLayoutForSizedByParentChange] instead of [markNeedsLayout].
void markNeedsLayout() {
assert(_debugCanPerformMutations);
if (_needsLayout) {
assert(_debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout());
return;
}
if (_relayoutBoundary == null) {
_needsLayout = true;
if (parent != null) {
// _relayoutBoundary is cleaned by an ancestor in RenderObject.layout.
// Conservatively mark everything dirty until it reaches the closest
// known relayout boundary.
markParentNeedsLayout();
}
return;
}
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
assert(() {
if (debugPrintMarkNeedsLayoutStacks) {
debugPrintStack(label: 'markNeedsLayout() called for $this');
}
return true;
}());
owner!._nodesNeedingLayout.add(this);
owner!.requestVisualUpdate();
}
}
}
/// Mark this render object's layout information as dirty, and then defer to
/// the parent.
///
/// This function should only be called from [markNeedsLayout] or
/// [markNeedsLayoutForSizedByParentChange] implementations of subclasses that
/// introduce more reasons for deferring the handling of dirty layout to the
/// parent. See [markNeedsLayout] for details.
///
/// Only call this if [parent] is not null.
@protected
void markParentNeedsLayout() {
assert(_debugCanPerformMutations);
_needsLayout = true;
assert(this.parent != null);
final RenderObject parent = this.parent! as RenderObject;
if (!_doingThisLayoutWithCallback) {
parent.markNeedsLayout();
} else {
assert(parent._debugDoingThisLayout);
}
assert(parent == this.parent);
}
/// Mark this render object's layout information as dirty (like
/// [markNeedsLayout]), and additionally also handle any necessary work to
/// handle the case where [sizedByParent] has changed value.
///
/// This should be called whenever [sizedByParent] might have changed.
///
/// Only call this if [parent] is not null.
void markNeedsLayoutForSizedByParentChange() {
markNeedsLayout();
markParentNeedsLayout();
}
void _cleanRelayoutBoundary() {
if (_relayoutBoundary != this) {
_relayoutBoundary = null;
visitChildren(_cleanChildRelayoutBoundary);
}
}
void _propagateRelayoutBoundary() {
if (_relayoutBoundary == this) {
return;
}
final RenderObject? parentRelayoutBoundary = (parent as RenderObject?)?._relayoutBoundary;
assert(parentRelayoutBoundary != null);
if (parentRelayoutBoundary != _relayoutBoundary) {
_relayoutBoundary = parentRelayoutBoundary;
visitChildren(_propagateRelayoutBoundaryToChild);
}
}
// Reduces closure allocation for visitChildren use cases.
static void _cleanChildRelayoutBoundary(RenderObject child) {
child._cleanRelayoutBoundary();
}
static void _propagateRelayoutBoundaryToChild(RenderObject child) {
child._propagateRelayoutBoundary();
}
/// 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(!_debugDisposed);
assert(attached);
assert(parent is! RenderObject);
assert(!owner!._debugDoingLayout);
assert(_relayoutBoundary == null);
_relayoutBoundary = this;
assert(() {
_debugCanParentUseSize = false;
return true;
}());
owner!._nodesNeedingLayout.add(this);
}
@pragma('vm:notify-debugger-on-exception')
void _layoutWithoutResize() {
assert(_relayoutBoundary == this);
RenderObject? debugPreviousActiveLayout;
assert(!_debugMutationsLocked);
assert(!_doingThisLayoutWithCallback);
assert(_debugCanParentUseSize != null);
assert(() {
_debugMutationsLocked = true;
_debugDoingThisLayout = true;
debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
if (debugPrintLayouts) {
debugPrint('Laying out (without resize) $this');
}
return true;
}());
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {
_reportException('performLayout', e, stack);
}
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugMutationsLocked = false;
return true;
}());
_needsLayout = false;
markNeedsPaint();
}
/// Compute the layout for this render object.
///
/// This method 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 to 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 [layout] method
/// delegates the actual work to [performResize] and [performLayout].
///
/// The parent's [performLayout] method should call the [layout] of all its
/// children unconditionally. It is the [layout] method's responsibility (as
/// implemented here) to return early if the child does not need to do any
/// work to update its layout information.
@pragma('vm:notify-debugger-on-exception')
void layout(Constraints constraints, { bool parentUsesSize = false }) {
assert(!_debugDisposed);
if (!kReleaseMode && debugProfileLayoutsEnabled) {
Map<String, String>? debugTimelineArguments;
assert(() {
if (debugEnhanceLayoutTimelineArguments) {
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
}
return true;
}());
Timeline.startSync(
'$runtimeType',
arguments: debugTimelineArguments,
);
}
assert(constraints.debugAssertIsValid(
isAppliedConstraint: true,
informationCollector: () {
final List<String> stack = StackTrace.current.toString().split('\n');
int? targetFrame;
final Pattern layoutFramePattern = RegExp(r'^#[0-9]+ +Render(?:Object|Box).layout \(');
for (int i = 0; i < stack.length; i += 1) {
if (layoutFramePattern.matchAsPrefix(stack[i]) != null) {
targetFrame = i + 1;
} else if (targetFrame != null) {
break;
}
}
if (targetFrame != null && targetFrame < stack.length) {
final Pattern targetFramePattern = RegExp(r'^#[0-9]+ +(.+)$');
final Match? targetFrameMatch = targetFramePattern.matchAsPrefix(stack[targetFrame]);
final String? problemFunction = (targetFrameMatch != null && targetFrameMatch.groupCount > 0) ? targetFrameMatch.group(1) : stack[targetFrame].trim();
return <DiagnosticsNode>[
ErrorDescription(
"These invalid constraints were provided to $runtimeType's layout() "
'function by the following function, which probably computed the '
'invalid constraints in question:\n'
' $problemFunction',
),
];
}
return <DiagnosticsNode>[];
},
));
assert(!_debugDoingThisResize);
assert(!_debugDoingThisLayout);
final bool isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject;
final RenderObject relayoutBoundary = isRelayoutBoundary ? this : (parent! as RenderObject)._relayoutBoundary!;
assert(() {
_debugCanParentUseSize = parentUsesSize;
return true;
}());
if (!_needsLayout && constraints == _constraints) {
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;
final RenderObject? debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
debugResetSize();
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugDoingThisResize = false;
return true;
}());
if (relayoutBoundary != _relayoutBoundary) {
_relayoutBoundary = relayoutBoundary;
visitChildren(_propagateRelayoutBoundaryToChild);
}
if (!kReleaseMode && debugProfileLayoutsEnabled) {
Timeline.finishSync();
}
return;
}
_constraints = constraints;
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
// The local relayout boundary has changed, must notify children in case
// they also need updating. Otherwise, they will be confused about what
// their actual relayout boundary is later.
visitChildren(_cleanChildRelayoutBoundary);
}
_relayoutBoundary = relayoutBoundary;
assert(!_debugMutationsLocked);
assert(!_doingThisLayoutWithCallback);
assert(() {
_debugMutationsLocked = true;
if (debugPrintLayouts) {
debugPrint('Laying out (${sizedByParent ? "with separate resize" : "with resize allowed"}) $this');
}
return true;
}());
if (sizedByParent) {
assert(() {
_debugDoingThisResize = true;
return true;
}());
try {
performResize();
assert(() {
debugAssertDoesMeetConstraints();
return true;
}());
} catch (e, stack) {
_reportException('performResize', e, stack);
}
assert(() {
_debugDoingThisResize = false;
return true;
}());
}
RenderObject? debugPreviousActiveLayout;
assert(() {
_debugDoingThisLayout = true;
debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
return true;
}());
try {
performLayout();
markNeedsSemanticsUpdate();
assert(() {
debugAssertDoesMeetConstraints();
return true;
}());
} catch (e, stack) {
_reportException('performLayout', e, stack);
}
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugMutationsLocked = false;
return true;
}());
_needsLayout = false;
markNeedsPaint();
if (!kReleaseMode && debugProfileLayoutsEnabled) {
Timeline.finishSync();
}
}
/// 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 debug 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.
@protected
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.
///
/// Typically, subclasses will always return the same value. If the value can
/// change, then, when it does change, the subclass should make sure to call
/// [markNeedsLayoutForSizedByParentChange].
///
/// Subclasses that return true must not change the dimensions of this render
/// object in [performLayout]. Instead, that work should be done by
/// [performResize] or - for subclasses of [RenderBox] - in
/// [RenderBox.computeDryLayout].
@protected
bool get sizedByParent => false;
/// {@template flutter.rendering.RenderObject.performResize}
/// 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.
///
/// This function is called only if [sizedByParent] is true.
/// {@endtemplate}
///
/// Subclasses that set [sizedByParent] to true should override this method to
/// compute their size. Subclasses of [RenderBox] should consider overriding
/// [RenderBox.computeDryLayout] instead.
@protected
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 change its layout
/// information without informing this render object.
@protected
void performLayout();
/// Allows mutations to be made to this object's child list (and any
/// descendants) as well as to any other dirty nodes in the render tree owned
/// by the same [PipelineOwner] as this object. The `callback` argument is
/// invoked synchronously, and the mutations are allowed only during that
/// callback's execution.
///
/// This exists to allow child lists to be built on-demand during layout (e.g.
/// based on the object's size), and to enable nodes to be moved around the
/// tree as this happens (e.g. to handle [GlobalKey] reparenting), while still
/// ensuring that any particular node is only laid out once per frame.
///
/// Calling this function disables a number of assertions that are intended to
/// catch likely bugs. As such, using this function is generally discouraged.
///
/// This function can only be called during layout.
@protected
void invokeLayoutCallback<T extends Constraints>(LayoutCallback<T> callback) {
assert(_debugMutationsLocked);
assert(_debugDoingThisLayout);
assert(!_doingThisLayoutWithCallback);
_doingThisLayoutWithCallback = true;
try {
owner!._enableMutationsToDirtySubtrees(() { callback(constraints as T); });
} finally {
_doingThisLayoutWithCallback = false;
}
}
// PAINTING
/// Whether [paint] for this render object is currently running.
///
/// Only valid when asserts are enabled. In release builds, always returns
/// false.
bool get debugDoingThisPaint => _debugDoingThisPaint;
bool _debugDoingThisPaint = false;
/// The render object that is actively painting.
///
/// Only valid when asserts are enabled. In release builds, always returns
/// null.
static RenderObject? get debugActivePaint => _debugActivePaint;
static RenderObject? _debugActivePaint;
/// Whether this render object repaints separately from its parent.
///
/// Override this in subclasses to indicate that instances of your class ought
/// to repaint independently. For example, render objects that repaint
/// frequently might want to repaint themselves without requiring their parent
/// to repaint.
///
/// If this getter returns true, the [paintBounds] are applied to this object
/// and all descendants. The framework invokes [RenderObject.updateCompositedLayer]
/// to create an [OffsetLayer] and assigns it to the [layer] field.
/// Render objects that declare themselves as repaint boundaries must not replace
/// the layer created by the framework.
///
/// If the value of this getter changes, [markNeedsCompositingBitsUpdate] must
/// be called.
///
/// See [RepaintBoundary] for more information about how repaint boundaries function.
bool get isRepaintBoundary => false;
/// Called, in debug mode, if [isRepaintBoundary] is true, when either the
/// this render object or its parent attempt to paint.
///
/// This can be used to record metrics about whether the node should actually
/// be a repaint boundary.
void debugRegisterRepaintBoundaryPaint({ bool includedParent = true, bool includedChild = false }) { }
/// Whether this render object always needs compositing.
///
/// Override this in subclasses to indicate that your paint function always
/// creates at least one composited layer. For example, videos should return
/// true if they use hardware decoders.
///
/// You must call [markNeedsCompositingBitsUpdate] if the value of this getter
/// changes. (This is implied when [adoptChild] or [dropChild] are called.)
@protected
bool get alwaysNeedsCompositing => false;
late bool _wasRepaintBoundary;
/// Update the composited layer owned by this render object.
///
/// This method is called by the framework when [isRepaintBoundary] is true.
///
/// If [oldLayer] is `null`, this method must return a new [OffsetLayer]
/// (or subtype thereof). If [oldLayer] is not `null`, then this method must
/// reuse the layer instance that is provided - it is an error to create a new
/// layer in this instance. The layer will be disposed by the framework when
/// either the render object is disposed or if it is no longer a repaint
/// boundary.
///
/// The [OffsetLayer.offset] property will be managed by the framework and
/// must not be updated by this method.
///
/// If a property of the composited layer needs to be updated, the render object
/// must call [markNeedsCompositedLayerUpdate] which will schedule this method
/// to be called without repainting children. If this widget was marked as
/// needing to paint and needing a composited layer update, this method is only
/// called once.
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/102102 revisit the
// constraint that the instance/type of layer cannot be changed at runtime.
OffsetLayer updateCompositedLayer({required covariant OffsetLayer? oldLayer}) {
assert(isRepaintBoundary);
return oldLayer ?? OffsetLayer();
}
/// The compositing layer that this render object uses to repaint.
///
/// If this render object is not a repaint boundary, it is the responsibility
/// of the [paint] method to populate this field. If [needsCompositing] is
/// true, this field may be populated with the root-most layer used by the
/// render object implementation. When repainting, instead of creating a new
/// layer the render object may update the layer stored in this field for better
/// performance. It is also OK to leave this field as null and create a new
/// layer on every repaint, but without the performance benefit. If
/// [needsCompositing] is false, this field must be set to null either by
/// never populating this field, or by setting it to null when the value of
/// [needsCompositing] changes from true to false.
///
/// If a new layer is created and stored in some other field on the render
/// object, the render object must use a [LayerHandle] to store it. A layer
/// handle will prevent the layer from being disposed before the render
/// object is finished with it, and it will also make sure that the layer
/// gets appropriately disposed when the render object creates a replacement
/// or nulls it out. The render object must null out the [LayerHandle.layer]
/// in its [dispose] method.
///
/// If this render object is a repaint boundary, the framework automatically
/// creates an [OffsetLayer] and populates this field prior to calling the
/// [paint] method. The [paint] method must not replace the value of this
/// field.
@protected
ContainerLayer? get layer {
assert(!isRepaintBoundary || _layerHandle.layer == null || _layerHandle.layer is OffsetLayer);
return _layerHandle.layer;
}
@protected
set layer(ContainerLayer? newLayer) {
assert(
!isRepaintBoundary,
'Attempted to set a layer to a repaint boundary render object.\n'
'The framework creates and assigns an OffsetLayer to a repaint '
'boundary automatically.',
);
_layerHandle.layer = newLayer;
}
final LayerHandle<ContainerLayer> _layerHandle = LayerHandle<ContainerLayer>();
/// In debug mode, the compositing layer that this render object uses to repaint.
///
/// This getter is intended for debugging purposes only. In release builds, it
/// always returns null. In debug builds, it returns the layer even if the layer
/// is dirty.
///
/// For production code, consider [layer].
ContainerLayer? get debugLayer {
ContainerLayer? result;
assert(() {
result = _layerHandle.layer;
return true;
}());
return result;
}
bool _needsCompositingBitsUpdate = false; // set to true when a child is added
/// Mark the compositing state for this render object as dirty.
///
/// This is called to indicate that the value for [needsCompositing] needs to
/// be recomputed during the next [PipelineOwner.flushCompositingBits] engine
/// phase.
///
/// When the subtree is mutated, we need to recompute our
/// [needsCompositing] bit, and some of our ancestors need to do the
/// same (in case ours changed in a way that will change theirs). To
/// this end, [adoptChild] and [dropChild] call this method, and, as
/// necessary, this method calls the parent's, etc, walking up the
/// tree to mark all the nodes that need updating.
///
/// This method does not schedule a rendering frame, because since
/// it cannot be the case that _only_ the compositing bits changed,
/// something else will have scheduled a frame for us.
void markNeedsCompositingBitsUpdate() {
assert(!_debugDisposed);
if (_needsCompositingBitsUpdate) {
return;
}
_needsCompositingBitsUpdate = true;
if (parent is RenderObject) {
final RenderObject parent = this.parent! as RenderObject;
if (parent._needsCompositingBitsUpdate) {
return;
}
if ((!_wasRepaintBoundary || !isRepaintBoundary) && !parent.isRepaintBoundary) {
parent.markNeedsCompositingBitsUpdate();
return;
}
}
// parent is fine (or there isn't one), but we are dirty
if (owner != null) {
owner!._nodesNeedingCompositingBitsUpdate.add(this);
}
}
late bool _needsCompositing; // initialized in the constructor
/// Whether we or one of our descendants has a compositing layer.
///
/// If this node needs compositing as indicated by this bit, then all ancestor
/// nodes will also need compositing.
///
/// Only legal to call after [PipelineOwner.flushLayout] and
/// [PipelineOwner.flushCompositingBits] have been called.
bool get needsCompositing {
assert(!_needsCompositingBitsUpdate); // make sure we don't use this bit when it is dirty
return _needsCompositing;
}
void _updateCompositingBits() {
if (!_needsCompositingBitsUpdate) {
return;
}
final bool oldNeedsCompositing = _needsCompositing;
_needsCompositing = false;
visitChildren((RenderObject child) {
child._updateCompositingBits();
if (child.needsCompositing) {
_needsCompositing = true;
}
});
if (isRepaintBoundary || alwaysNeedsCompositing) {
_needsCompositing = true;
}
// If a node was previously a repaint boundary, but no longer is one, then
// regardless of its compositing state we need to find a new parent to
// paint from. To do this, we mark it clean again so that the traversal
// in markNeedsPaint is not short-circuited. It is removed from _nodesNeedingPaint
// so that we do not attempt to paint from it after locating a parent.
if (!isRepaintBoundary && _wasRepaintBoundary) {
_needsPaint = false;
_needsCompositedLayerUpdate = false;
owner?._nodesNeedingPaint.remove(this);
_needsCompositingBitsUpdate = false;
markNeedsPaint();
} else if (oldNeedsCompositing != _needsCompositing) {
_needsCompositingBitsUpdate = false;
markNeedsPaint();
} else {
_needsCompositingBitsUpdate = false;
}
}
/// Whether this render object's paint information is dirty.
///
/// This is only set in debug mode. In general, render objects should not need
/// to condition their runtime behavior on whether they are dirty or not,
/// since they should only be marked dirty immediately prior to being laid
/// out and painted. (In release builds, this throws.)
///
/// It is intended to be used by tests and asserts.
///
/// It is possible (and indeed, quite common) for [debugNeedsPaint] to be
/// false and [debugNeedsLayout] to be true. The render object will still be
/// repainted in the next frame when this is the case, because the
/// [markNeedsPaint] method is implicitly called by the framework after a
/// render object is laid out, prior to the paint phase.
bool get debugNeedsPaint {
late bool result;
assert(() {
result = _needsPaint;
return true;
}());
return result;
}
bool _needsPaint = true;
/// Whether this render object's layer information is dirty.
///
/// This is only set in debug mode. In general, render objects should not need
/// to condition their runtime behavior on whether they are dirty or not,
/// since they should only be marked dirty immediately prior to being laid
/// out and painted. (In release builds, this throws.)
///
/// It is intended to be used by tests and asserts.
bool get debugNeedsCompositedLayerUpdate {
late bool result;
assert(() {
result = _needsCompositedLayerUpdate;
return true;
}());
return result;
}
bool _needsCompositedLayerUpdate = false;
/// 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 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.
///
/// Once [markNeedsPaint] has been called on a render object,
/// [debugNeedsPaint] returns true for that render object until just after
/// the pipeline owner has called [paint] on the render object.
///
/// See also:
///
/// * [RepaintBoundary], to scope a subtree of render objects to their own
/// layer, thus limiting the number of nodes that [markNeedsPaint] must mark
/// dirty.
void markNeedsPaint() {
assert(!_debugDisposed);
assert(owner == null || !owner!.debugDoingPaint);
if (_needsPaint) {
return;
}
_needsPaint = true;
// If this was not previously a repaint boundary it will not have
// a layer we can paint from.
if (isRepaintBoundary && _wasRepaintBoundary) {
assert(() {
if (debugPrintMarkNeedsPaintStacks) {
debugPrintStack(label: 'markNeedsPaint() called for $this');
}
return true;
}());
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layerHandle.layer is OffsetLayer);
if (owner != null) {
owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent! as RenderObject;
parent.markNeedsPaint();
assert(parent == this.parent);
} else {
assert(() {
if (debugPrintMarkNeedsPaintStacks) {
debugPrintStack(label: 'markNeedsPaint() called for $this (root of render tree)');
}
return true;
}());
// If we are the root of the render tree and not a repaint boundary
// 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.
//
// Trees rooted at a RenderView do not go through this
// code path because RenderViews are repaint boundaries.
if (owner != null) {
owner!.requestVisualUpdate();
}
}
}
/// Mark this render object as having changed a property on its composited
/// layer.
///
/// Render objects that have a composited layer have [isRepaintBoundary] equal
/// to true may update the properties of that composited layer without repainting
/// their children. If this render object is a repaint boundary but does
/// not yet have a composited layer created for it, this method will instead
/// mark the nearest repaint boundary parent as needing to be painted.
///
/// If this method is called on a render object that is not a repaint boundary
/// or is a repaint boundary but hasn't been composited yet, it is equivalent
/// to calling [markNeedsPaint].
///
/// See also:
///
/// * [RenderOpacity], which uses this method when its opacity is updated to
/// update the layer opacity without repainting children.
void markNeedsCompositedLayerUpdate() {
assert(!_debugDisposed);
assert(owner == null || !owner!.debugDoingPaint);
if (_needsCompositedLayerUpdate || _needsPaint) {
return;
}
_needsCompositedLayerUpdate = true;
// If this was not previously a repaint boundary it will not have
// a layer we can paint from.
if (isRepaintBoundary && _wasRepaintBoundary) {
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layerHandle.layer != null);
if (owner != null) {
owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate();
}
} else {
markNeedsPaint();
}
}
// Called when flushPaint() tries to make us paint but our layer is detached.
// To make sure that our subtree is repainted when it's finally reattached,
// even in the case where some ancestor layer is itself never marked dirty, we
// have to mark our entire detached subtree as dirty and needing to be
// repainted. That way, we'll eventually be repainted.
void _skippedPaintingOnLayer() {
assert(attached);
assert(isRepaintBoundary);
assert(_needsPaint || _needsCompositedLayerUpdate);
assert(_layerHandle.layer != null);
assert(!_layerHandle.layer!.attached);
AbstractNode? node = parent;
while (node is RenderObject) {
if (node.isRepaintBoundary) {
if (node._layerHandle.layer == null) {
// Looks like the subtree here has never been painted. Let it handle itself.
break;
}
if (node._layerHandle.layer!.attached) {
// It's the one that detached us, so it's the one that will decide to repaint us.
break;
}
node._needsPaint = true;
}
node = node.parent;
}
}
/// 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(rootLayer.attached);
assert(attached);
assert(parent is! RenderObject);
assert(!owner!._debugDoingPaint);
assert(isRepaintBoundary);
assert(_layerHandle.layer == null);
_layerHandle.layer = rootLayer;
assert(_needsPaint);
owner!._nodesNeedingPaint.add(this);
}
/// Replace the layer. This is only valid for the root of a render
/// object subtree (whatever object [scheduleInitialPaint] was
/// called on).
///
/// This might be called if, e.g., the device pixel ratio changed.
void replaceRootLayer(OffsetLayer rootLayer) {
assert(!_debugDisposed);
assert(rootLayer.attached);
assert(attached);
assert(parent is! RenderObject);
assert(!owner!._debugDoingPaint);
assert(isRepaintBoundary);
assert(_layerHandle.layer != null); // use scheduleInitialPaint the first time
_layerHandle.layer!.detach();
_layerHandle.layer = rootLayer;
markNeedsPaint();
}
void _paintWithContext(PaintingContext context, Offset offset) {
assert(!_debugDisposed);
assert(() {
if (_debugDoingThisPaint) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Tried to paint a RenderObject reentrantly.'),
describeForError(
'The following RenderObject was already being painted when it was '
'painted again',
),
ErrorDescription(
'Since this typically indicates an infinite recursion, it is '
'disallowed.',
),
]);
}
return true;
}());
// If we still need layout, then that means that we were skipped in the
// layout phase and therefore don't need painting. We might not know that
// yet (that is, our layer might not have been detached yet), because the
// same node that skipped us in layout is above us in the tree (obviously)
// and therefore may not have had a chance to paint yet (since the tree
// paints in reverse order). In particular this will happen if they have
// a different layer, because there's a repaint boundary between us.
if (_needsLayout) {
return;
}
if (!kReleaseMode && debugProfilePaintsEnabled) {
Map<String, String>? debugTimelineArguments;
assert(() {
if (debugEnhancePaintTimelineArguments) {
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
}
return true;
}());
Timeline.startSync(
'$runtimeType',
arguments: debugTimelineArguments,
);
}
assert(() {
if (_needsCompositingBitsUpdate) {
if (parent is RenderObject) {
final RenderObject parent = this.parent! as RenderObject;
bool visitedByParent = false;
parent.visitChildren((RenderObject child) {
if (child == this) {
visitedByParent = true;
}
});
if (!visitedByParent) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
"A RenderObject was not visited by the parent's visitChildren "
'during paint.',
),
parent.describeForError(
'The parent was',
),
describeForError(
'The child that was not visited was',
),
ErrorDescription(
'A RenderObject with children must implement visitChildren and '
'call the visitor exactly once for each child; it also should not '
'paint children that were removed with dropChild.',
),
ErrorHint(
'This usually indicates an error in the Flutter framework itself.',
),
]);
}
}
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
'Tried to paint a RenderObject before its compositing bits were '
'updated.',
),
describeForError(
'The following RenderObject was marked as having dirty compositing '
'bits at the time that it was painted',
),
ErrorDescription(
'A RenderObject that still has dirty compositing bits cannot be '
'painted because this indicates that the tree has not yet been '
'properly configured for creating the layer tree.',
),
ErrorHint(
'This usually indicates an error in the Flutter framework itself.',
),
]);
}
return true;
}());
RenderObject? debugLastActivePaint;
assert(() {
_debugDoingThisPaint = true;
debugLastActivePaint = _debugActivePaint;
_debugActivePaint = this;
assert(!isRepaintBoundary || _layerHandle.layer != null);
return true;
}());
_needsPaint = false;
_needsCompositedLayerUpdate = false;
_wasRepaintBoundary = isRepaintBoundary;
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) {
_reportException('paint', e, stack);
}
assert(() {
debugPaint(context, offset);
_debugActivePaint = debugLastActivePaint;
_debugDoingThisPaint = false;
return true;
}());
if (!kReleaseMode && debugProfilePaintsEnabled) {
Timeline.finishSync();
}
}
/// An estimate of the bounds within which this render object will paint.
/// Useful for debugging flags such as [debugPaintLayerBordersEnabled].
///
/// These are also the bounds used by [showOnScreen] to make a [RenderObject]
/// visible on screen.
Rect get paintBounds;
/// Override this method 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 method 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 [PaintingContext.paintChild] on the
/// given `context`.
///
/// 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) { }
/// Applies the transform that would be applied when painting the given child
/// to the given matrix.
///
/// Used by coordinate conversion functions to translate coordinates local to
/// one render object into coordinates local to another render object.
///
/// Some RenderObjects will provide a zeroed out matrix in this method,
/// indicating that the child should not paint anything or respond to hit
/// tests currently. A parent may supply a non-zero matrix even though it
/// does not paint its child currently, for example if the parent is a
/// [RenderOffstage] with `offstage` set to true. In both of these cases,
/// the parent must return `false` from [paintsChild].
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
assert(child.parent == this);
}
/// Whether the given child would be painted if [paint] were called.
///
/// Some RenderObjects skip painting their children if they are configured to
/// not produce any visible effects. For example, a [RenderOffstage] with
/// its `offstage` property set to true, or a [RenderOpacity] with its opacity
/// value set to zero.
///
/// In these cases, the parent may still supply a non-zero matrix in
/// [applyPaintTransform] to inform callers about where it would paint the
/// child if the child were painted at all. Alternatively, the parent may
/// supply a zeroed out matrix if it would not otherwise be able to determine
/// a valid matrix for the child and thus cannot meaningfully determine where
/// the child would paint.
bool paintsChild(covariant RenderObject child) {
assert(child.parent == this);
return true;
}
/// {@template flutter.rendering.RenderObject.getTransformTo}
/// Applies the paint transform up the tree to `ancestor`.
///
/// Returns a matrix that maps the local paint coordinate system to the
/// coordinate system of `ancestor`.
///
/// If `ancestor` is null, this method returns a matrix that maps from the
/// local paint coordinate system to the coordinate system of the
/// [PipelineOwner.rootNode].
/// {@endtemplate}
///
/// For the render tree owned by the [RendererBinding] (i.e. for the main
/// render tree displayed on the device) this means that this method maps to
/// the global coordinate system in logical pixels. To get physical pixels,
/// use [applyPaintTransform] from the [RenderView] to further transform the
/// coordinate.
Matrix4 getTransformTo(RenderObject? ancestor) {
final bool ancestorSpecified = ancestor != null;
assert(attached);
if (ancestor == null) {
final AbstractNode? rootNode = owner!.rootNode;
if (rootNode is RenderObject) {
ancestor = rootNode;
}
}
final List<RenderObject> renderers = <RenderObject>[];
for (RenderObject renderer = this; renderer != ancestor; renderer = renderer.parent! as RenderObject) {
renderers.add(renderer);
assert(renderer.parent != null); // Failed to find ancestor in parent chain.
}
if (ancestorSpecified) {
renderers.add(ancestor!);
}
final Matrix4 transform = Matrix4.identity();
for (int index = renderers.length - 1; index > 0; index -= 1) {
renderers[index].applyPaintTransform(renderers[index - 1], transform);
}
return transform;
}
/// Returns a rect in this object's coordinate system that describes
/// the approximate bounding box of the clip rect that would be
/// applied to the given child during the paint phase, if any.
///
/// Returns null if the child would not be clipped.
///
/// This is used in the semantics phase to avoid including children
/// that are not physically visible.
///
/// RenderObjects that respect a [Clip] behavior when painting _must_ respect
/// that same behavior when describing this value. For example, if passing
/// [Clip.none] to [PaintingContext.pushClipRect] as the `clipBehavior`, then
/// the implementation of this method must return null.
Rect? describeApproximatePaintClip(covariant RenderObject child) => null;
/// Returns a rect in this object's coordinate system that describes
/// which [SemanticsNode]s produced by the `child` should be included in the
/// semantics tree. [SemanticsNode]s from the `child` that are positioned
/// outside of this rect will be dropped. Child [SemanticsNode]s that are
/// positioned inside this rect, but outside of [describeApproximatePaintClip]
/// will be included in the tree marked as hidden. Child [SemanticsNode]s
/// that are inside of both rect will be included in the tree as regular
/// nodes.
///
/// This method only returns a non-null value if the semantics clip rect
/// is different from the rect returned by [describeApproximatePaintClip].
/// If the semantics clip rect and the paint clip rect are the same, this
/// method returns null.
///
/// A viewport would typically implement this method to include semantic nodes
/// in the semantics tree that are currently hidden just before the leading
/// or just after the trailing edge. These nodes have to be included in the
/// semantics tree to implement implicit accessibility scrolling on iOS where
/// the viewport scrolls implicitly when moving the accessibility focus from
/// the last visible node in the viewport to the first hidden one.
///
/// See also:
///
/// * [RenderViewportBase.cacheExtent], used by viewports to extend their
/// semantics clip beyond their approximate paint clip.
Rect? describeSemanticsClip(covariant RenderObject? child) => null;
// SEMANTICS
/// Bootstrap the semantics reporting mechanism by marking this node
/// as needing a semantics update.
///
/// Requires that this render object is attached, and is the root of
/// the render tree.
///
/// See [RendererBinding] for an example of how this function is used.
void scheduleInitialSemantics() {
assert(!_debugDisposed);
assert(attached);
assert(parent is! RenderObject);
assert(!owner!._debugDoingSemantics);
assert(_semantics == null);
assert(_needsSemanticsUpdate);
assert(owner!._semanticsOwner != null);
owner!._nodesNeedingSemantics.add(this);
owner!.requestVisualUpdate();
}
/// Report the semantics of this node, for example for accessibility purposes.
///
/// This method should be overridden by subclasses that have interesting
/// semantic information.
///
/// The given [SemanticsConfiguration] object is mutable and should be
/// annotated in a manner that describes the current state. No reference
/// should be kept to that object; mutating it outside of the context of the
/// [describeSemanticsConfiguration] call (for example as a result of
/// asynchronous computation) will at best have no useful effect and at worse
/// will cause crashes as the data will be in an inconsistent state.
///
/// {@tool snippet}
///
/// The following snippet will describe the node as a button that responds to
/// tap actions.
///
/// ```dart
/// abstract class SemanticButtonRenderObject extends RenderObject {
/// @override
/// void describeSemanticsConfiguration(SemanticsConfiguration config) {
/// super.describeSemanticsConfiguration(config);
/// config
/// ..onTap = _handleTap
/// ..label = 'I am a button'
/// ..isButton = true;
/// }
///
/// void _handleTap() {
/// // Do something.
/// }
/// }
/// ```
/// {@end-tool}
@protected
void describeSemanticsConfiguration(SemanticsConfiguration config) {
// Nothing to do by default.
}
/// Sends a [SemanticsEvent] associated with this render object's [SemanticsNode].
///
/// If this render object has no semantics information, the first parent
/// render object with a non-null semantic node is used.
///
/// If semantics are disabled, no events are dispatched.
///
/// See [SemanticsNode.sendEvent] for a full description of the behavior.
void sendSemanticsEvent(SemanticsEvent semanticsEvent) {
if