| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | /// @docImport 'dart:ui'; |
| 6 | /// |
| 7 | /// @docImport 'package:flutter/widgets.dart'; |
| 8 | /// |
| 9 | /// @docImport 'box.dart'; |
| 10 | /// @docImport 'paragraph.dart'; |
| 11 | /// @docImport 'proxy_box.dart'; |
| 12 | /// @docImport 'view.dart'; |
| 13 | /// @docImport 'viewport.dart'; |
| 14 | library; |
| 15 | |
| 16 | import 'dart:ui' as ui show PictureRecorder; |
| 17 | |
| 18 | import 'package:flutter/animation.dart'; |
| 19 | import 'package:flutter/foundation.dart'; |
| 20 | import 'package:flutter/gestures.dart'; |
| 21 | import 'package:flutter/painting.dart'; |
| 22 | import 'package:flutter/scheduler.dart'; |
| 23 | import 'package:flutter/semantics.dart'; |
| 24 | |
| 25 | import 'binding.dart'; |
| 26 | import 'debug.dart'; |
| 27 | import 'layer.dart'; |
| 28 | |
| 29 | export 'package:flutter/foundation.dart' |
| 30 | show |
| 31 | DiagnosticPropertiesBuilder, |
| 32 | DiagnosticsNode, |
| 33 | DiagnosticsProperty, |
| 34 | DoubleProperty, |
| 35 | EnumProperty, |
| 36 | ErrorDescription, |
| 37 | ErrorHint, |
| 38 | ErrorSummary, |
| 39 | FlagProperty, |
| 40 | FlutterError, |
| 41 | InformationCollector, |
| 42 | IntProperty, |
| 43 | StringProperty; |
| 44 | export 'package:flutter/gestures.dart' show HitTestEntry, HitTestResult; |
| 45 | export 'package:flutter/painting.dart'; |
| 46 | |
| 47 | /// Base class for data associated with a [RenderObject] by its parent. |
| 48 | /// |
| 49 | /// Some render objects wish to store data on their children, such as the |
| 50 | /// children's input parameters to the parent's layout algorithm or the |
| 51 | /// children's position relative to other children. |
| 52 | /// |
| 53 | /// See also: |
| 54 | /// |
| 55 | /// * [RenderObject.setupParentData], which [RenderObject] subclasses may |
| 56 | /// override to attach specific types of parent data to children. |
| 57 | class ParentData { |
| 58 | /// Called when the RenderObject is removed from the tree. |
| 59 | @protected |
| 60 | @mustCallSuper |
| 61 | void detach() {} |
| 62 | |
| 63 | @override |
| 64 | String toString() => '<none>' ; |
| 65 | } |
| 66 | |
| 67 | /// Signature for painting into a [PaintingContext]. |
| 68 | /// |
| 69 | /// The `offset` argument is the offset from the origin of the coordinate system |
| 70 | /// of the [PaintingContext.canvas] to the coordinate system of the callee. |
| 71 | /// |
| 72 | /// Used by many of the methods of [PaintingContext]. |
| 73 | typedef PaintingContextCallback = void Function(PaintingContext context, Offset offset); |
| 74 | |
| 75 | /// A place to paint. |
| 76 | /// |
| 77 | /// Rather than holding a canvas directly, [RenderObject]s paint using a painting |
| 78 | /// context. The painting context has a [Canvas], which receives the |
| 79 | /// individual draw operations, and also has functions for painting child |
| 80 | /// render objects. |
| 81 | /// |
| 82 | /// When painting a child render object, the canvas held by the painting context |
| 83 | /// can change because the draw operations issued before and after painting the |
| 84 | /// child might be recorded in separate compositing layers. For this reason, do |
| 85 | /// not hold a reference to the canvas across operations that might paint |
| 86 | /// child render objects. |
| 87 | /// |
| 88 | /// New [PaintingContext] objects are created automatically when using |
| 89 | /// [PaintingContext.repaintCompositedChild] and [pushLayer]. |
| 90 | class PaintingContext extends ClipContext { |
| 91 | /// Creates a painting context. |
| 92 | /// |
| 93 | /// Typically only called by [PaintingContext.repaintCompositedChild] |
| 94 | /// and [pushLayer]. |
| 95 | @protected |
| 96 | PaintingContext(this._containerLayer, this.estimatedBounds); |
| 97 | |
| 98 | final ContainerLayer _containerLayer; |
| 99 | |
| 100 | /// An estimate of the bounds within which the painting context's [canvas] |
| 101 | /// will record painting commands. This can be useful for debugging. |
| 102 | /// |
| 103 | /// The canvas will allow painting outside these bounds. |
| 104 | /// |
| 105 | /// The [estimatedBounds] rectangle is in the [canvas] coordinate system. |
| 106 | final Rect estimatedBounds; |
| 107 | |
| 108 | /// Repaint the given render object. |
| 109 | /// |
| 110 | /// The render object must be attached to a [PipelineOwner], must have a |
| 111 | /// composited layer, and must be in need of painting. The render object's |
| 112 | /// layer, if any, is re-used, along with any layers in the subtree that don't |
| 113 | /// need to be repainted. |
| 114 | /// |
| 115 | /// See also: |
| 116 | /// |
| 117 | /// * [RenderObject.isRepaintBoundary], which determines if a [RenderObject] |
| 118 | /// has a composited layer. |
| 119 | static void repaintCompositedChild(RenderObject child, {bool debugAlsoPaintedParent = false}) { |
| 120 | assert(child._needsPaint); |
| 121 | _repaintCompositedChild(child, debugAlsoPaintedParent: debugAlsoPaintedParent); |
| 122 | } |
| 123 | |
| 124 | static void _repaintCompositedChild( |
| 125 | RenderObject child, { |
| 126 | bool debugAlsoPaintedParent = false, |
| 127 | PaintingContext? childContext, |
| 128 | }) { |
| 129 | assert(child.isRepaintBoundary); |
| 130 | assert(() { |
| 131 | // register the call for RepaintBoundary metrics |
| 132 | child.debugRegisterRepaintBoundaryPaint( |
| 133 | includedParent: debugAlsoPaintedParent, |
| 134 | includedChild: true, |
| 135 | ); |
| 136 | return true; |
| 137 | }()); |
| 138 | OffsetLayer? childLayer = child._layerHandle.layer as OffsetLayer?; |
| 139 | if (childLayer == null) { |
| 140 | assert(debugAlsoPaintedParent); |
| 141 | assert(child._layerHandle.layer == null); |
| 142 | |
| 143 | // Not using the `layer` setter because the setter asserts that we not |
| 144 | // replace the layer for repaint boundaries. That assertion does not |
| 145 | // apply here because this is exactly the place designed to create a |
| 146 | // layer for repaint boundaries. |
| 147 | final OffsetLayer layer = child.updateCompositedLayer(oldLayer: null); |
| 148 | child._layerHandle.layer = childLayer = layer; |
| 149 | } else { |
| 150 | assert(debugAlsoPaintedParent || childLayer.attached); |
| 151 | Offset? debugOldOffset; |
| 152 | assert(() { |
| 153 | debugOldOffset = childLayer!.offset; |
| 154 | return true; |
| 155 | }()); |
| 156 | childLayer.removeAllChildren(); |
| 157 | final OffsetLayer updatedLayer = child.updateCompositedLayer(oldLayer: childLayer); |
| 158 | assert( |
| 159 | identical(updatedLayer, childLayer), |
| 160 | ' $child created a new layer instance $updatedLayer instead of reusing the ' |
| 161 | 'existing layer $childLayer. See the documentation of RenderObject.updateCompositedLayer ' |
| 162 | 'for more information on how to correctly implement this method.' , |
| 163 | ); |
| 164 | assert(debugOldOffset == updatedLayer.offset); |
| 165 | } |
| 166 | child._needsCompositedLayerUpdate = false; |
| 167 | |
| 168 | assert(identical(childLayer, child._layerHandle.layer)); |
| 169 | assert(child._layerHandle.layer is OffsetLayer); |
| 170 | assert(() { |
| 171 | childLayer!.debugCreator = child.debugCreator ?? child.runtimeType; |
| 172 | return true; |
| 173 | }()); |
| 174 | |
| 175 | childContext ??= PaintingContext(childLayer, child.paintBounds); |
| 176 | child._paintWithContext(childContext, Offset.zero); |
| 177 | |
| 178 | // Double-check that the paint method did not replace the layer (the first |
| 179 | // check is done in the [layer] setter itself). |
| 180 | assert(identical(childLayer, child._layerHandle.layer)); |
| 181 | childContext.stopRecordingIfNeeded(); |
| 182 | } |
| 183 | |
| 184 | /// Update the composited layer of [child] without repainting its children. |
| 185 | /// |
| 186 | /// The render object must be attached to a [PipelineOwner], must have a |
| 187 | /// composited layer, and must be in need of a composited layer update but |
| 188 | /// not in need of painting. The render object's layer is re-used, and none |
| 189 | /// of its children are repaint or their layers updated. |
| 190 | /// |
| 191 | /// See also: |
| 192 | /// |
| 193 | /// * [RenderObject.isRepaintBoundary], which determines if a [RenderObject] |
| 194 | /// has a composited layer. |
| 195 | static void updateLayerProperties(RenderObject child) { |
| 196 | assert(child.isRepaintBoundary && child._wasRepaintBoundary); |
| 197 | assert(!child._needsPaint); |
| 198 | assert(child._layerHandle.layer != null); |
| 199 | |
| 200 | final OffsetLayer childLayer = child._layerHandle.layer! as OffsetLayer; |
| 201 | Offset? debugOldOffset; |
| 202 | assert(() { |
| 203 | debugOldOffset = childLayer.offset; |
| 204 | return true; |
| 205 | }()); |
| 206 | final OffsetLayer updatedLayer = child.updateCompositedLayer(oldLayer: childLayer); |
| 207 | assert( |
| 208 | identical(updatedLayer, childLayer), |
| 209 | ' $child created a new layer instance $updatedLayer instead of reusing the ' |
| 210 | 'existing layer $childLayer. See the documentation of RenderObject.updateCompositedLayer ' |
| 211 | 'for more information on how to correctly implement this method.' , |
| 212 | ); |
| 213 | assert(debugOldOffset == updatedLayer.offset); |
| 214 | child._needsCompositedLayerUpdate = false; |
| 215 | } |
| 216 | |
| 217 | /// In debug mode, repaint the given render object using a custom painting |
| 218 | /// context that can record the results of the painting operation in addition |
| 219 | /// to performing the regular paint of the child. |
| 220 | /// |
| 221 | /// See also: |
| 222 | /// |
| 223 | /// * [repaintCompositedChild], for repainting a composited child without |
| 224 | /// instrumentation. |
| 225 | static void debugInstrumentRepaintCompositedChild( |
| 226 | RenderObject child, { |
| 227 | bool debugAlsoPaintedParent = false, |
| 228 | required PaintingContext customContext, |
| 229 | }) { |
| 230 | assert(() { |
| 231 | _repaintCompositedChild( |
| 232 | child, |
| 233 | debugAlsoPaintedParent: debugAlsoPaintedParent, |
| 234 | childContext: customContext, |
| 235 | ); |
| 236 | return true; |
| 237 | }()); |
| 238 | } |
| 239 | |
| 240 | /// Paint a child [RenderObject]. |
| 241 | /// |
| 242 | /// If the child has its own composited layer, the child will be composited |
| 243 | /// into the layer subtree associated with this painting context. Otherwise, |
| 244 | /// the child will be painted into the current PictureLayer for this context. |
| 245 | void paintChild(RenderObject child, Offset offset) { |
| 246 | assert(() { |
| 247 | debugOnProfilePaint?.call(child); |
| 248 | return true; |
| 249 | }()); |
| 250 | |
| 251 | if (child.isRepaintBoundary) { |
| 252 | stopRecordingIfNeeded(); |
| 253 | _compositeChild(child, offset); |
| 254 | // If a render object was a repaint boundary but no longer is one, this |
| 255 | // is where the framework managed layer is automatically disposed. |
| 256 | } else if (child._wasRepaintBoundary) { |
| 257 | assert(child._layerHandle.layer is OffsetLayer); |
| 258 | child._layerHandle.layer = null; |
| 259 | child._paintWithContext(this, offset); |
| 260 | } else { |
| 261 | child._paintWithContext(this, offset); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | void _compositeChild(RenderObject child, Offset offset) { |
| 266 | assert(!_isRecording); |
| 267 | assert(child.isRepaintBoundary); |
| 268 | assert(_canvas == null || _canvas!.getSaveCount() == 1); |
| 269 | |
| 270 | // Create a layer for our child, and paint the child into it. |
| 271 | if (child._needsPaint || !child._wasRepaintBoundary) { |
| 272 | repaintCompositedChild(child, debugAlsoPaintedParent: true); |
| 273 | } else { |
| 274 | if (child._needsCompositedLayerUpdate) { |
| 275 | updateLayerProperties(child); |
| 276 | } |
| 277 | assert(() { |
| 278 | // register the call for RepaintBoundary metrics |
| 279 | child.debugRegisterRepaintBoundaryPaint(); |
| 280 | child._layerHandle.layer!.debugCreator = child.debugCreator ?? child; |
| 281 | return true; |
| 282 | }()); |
| 283 | } |
| 284 | assert(child._layerHandle.layer is OffsetLayer); |
| 285 | final OffsetLayer childOffsetLayer = child._layerHandle.layer! as OffsetLayer; |
| 286 | childOffsetLayer.offset = offset; |
| 287 | appendLayer(childOffsetLayer); |
| 288 | } |
| 289 | |
| 290 | /// Adds a layer to the recording requiring that the recording is already |
| 291 | /// stopped. |
| 292 | /// |
| 293 | /// Do not call this function directly: call [addLayer] or [pushLayer] |
| 294 | /// instead. This function is called internally when all layers not |
| 295 | /// generated from the [canvas] are added. |
| 296 | /// |
| 297 | /// Subclasses that need to customize how layers are added should override |
| 298 | /// this method. |
| 299 | @protected |
| 300 | void appendLayer(Layer layer) { |
| 301 | assert(!_isRecording); |
| 302 | layer.remove(); |
| 303 | _containerLayer.append(layer); |
| 304 | } |
| 305 | |
| 306 | bool get _isRecording { |
| 307 | final bool hasCanvas = _canvas != null; |
| 308 | assert(() { |
| 309 | if (hasCanvas) { |
| 310 | assert(_currentLayer != null); |
| 311 | assert(_recorder != null); |
| 312 | assert(_canvas != null); |
| 313 | } else { |
| 314 | assert(_currentLayer == null); |
| 315 | assert(_recorder == null); |
| 316 | assert(_canvas == null); |
| 317 | } |
| 318 | return true; |
| 319 | }()); |
| 320 | return hasCanvas; |
| 321 | } |
| 322 | |
| 323 | // Recording state |
| 324 | PictureLayer? _currentLayer; |
| 325 | ui.PictureRecorder? _recorder; |
| 326 | Canvas? _canvas; |
| 327 | |
| 328 | /// The canvas on which to paint. |
| 329 | /// |
| 330 | /// The current canvas can change whenever you paint a child using this |
| 331 | /// context, which means it's fragile to hold a reference to the canvas |
| 332 | /// returned by this getter. |
| 333 | @override |
| 334 | Canvas get canvas { |
| 335 | if (_canvas == null) { |
| 336 | _startRecording(); |
| 337 | } |
| 338 | assert(_currentLayer != null); |
| 339 | return _canvas!; |
| 340 | } |
| 341 | |
| 342 | void _startRecording() { |
| 343 | assert(!_isRecording); |
| 344 | _currentLayer = PictureLayer(estimatedBounds); |
| 345 | _recorder = RendererBinding.instance.createPictureRecorder(); |
| 346 | _canvas = RendererBinding.instance.createCanvas(_recorder!); |
| 347 | _containerLayer.append(_currentLayer!); |
| 348 | } |
| 349 | |
| 350 | /// Adds a [CompositionCallback] for the current [ContainerLayer] used by this |
| 351 | /// context. |
| 352 | /// |
| 353 | /// Composition callbacks are called whenever the layer tree containing the |
| 354 | /// current layer of this painting context gets composited, or when it gets |
| 355 | /// detached and will not be rendered again. This happens regardless of |
| 356 | /// whether the layer is added via retained rendering or not. |
| 357 | /// |
| 358 | /// {@macro flutter.rendering.Layer.compositionCallbacks} |
| 359 | /// |
| 360 | /// See also: |
| 361 | /// * [Layer.addCompositionCallback]. |
| 362 | VoidCallback addCompositionCallback(CompositionCallback callback) { |
| 363 | return _containerLayer.addCompositionCallback(callback); |
| 364 | } |
| 365 | |
| 366 | /// Stop recording to a canvas if recording has started. |
| 367 | /// |
| 368 | /// Do not call this function directly: functions in this class will call |
| 369 | /// this method as needed. This function is called internally to ensure that |
| 370 | /// recording is stopped before adding layers or finalizing the results of a |
| 371 | /// paint. |
| 372 | /// |
| 373 | /// Subclasses that need to customize how recording to a canvas is performed |
| 374 | /// should override this method to save the results of the custom canvas |
| 375 | /// recordings. |
| 376 | @protected |
| 377 | @mustCallSuper |
| 378 | void stopRecordingIfNeeded() { |
| 379 | if (!_isRecording) { |
| 380 | return; |
| 381 | } |
| 382 | assert(() { |
| 383 | if (debugRepaintRainbowEnabled) { |
| 384 | final Paint paint = Paint() |
| 385 | ..style = PaintingStyle.stroke |
| 386 | ..strokeWidth = 6.0 |
| 387 | ..color = debugCurrentRepaintColor.toColor(); |
| 388 | canvas.drawRect(estimatedBounds.deflate(3.0), paint); |
| 389 | } |
| 390 | if (debugPaintLayerBordersEnabled) { |
| 391 | final Paint paint = Paint() |
| 392 | ..style = PaintingStyle.stroke |
| 393 | ..strokeWidth = 1.0 |
| 394 | ..color = const Color(0xFFFF9800); |
| 395 | canvas.drawRect(estimatedBounds, paint); |
| 396 | } |
| 397 | return true; |
| 398 | }()); |
| 399 | _currentLayer!.picture = _recorder!.endRecording(); |
| 400 | _currentLayer = null; |
| 401 | _recorder = null; |
| 402 | _canvas = null; |
| 403 | } |
| 404 | |
| 405 | /// Hints that the painting in the current layer is complex and would benefit |
| 406 | /// from caching. |
| 407 | /// |
| 408 | /// If this hint is not set, the compositor will apply its own heuristics to |
| 409 | /// decide whether the current layer is complex enough to benefit from |
| 410 | /// caching. |
| 411 | /// |
| 412 | /// Calling this ensures a [Canvas] is available. Only draw calls on the |
| 413 | /// current canvas will be hinted; the hint is not propagated to new canvases |
| 414 | /// created after a new layer is added to the painting context (e.g. with |
| 415 | /// [addLayer] or [pushLayer]). |
| 416 | void setIsComplexHint() { |
| 417 | if (_currentLayer == null) { |
| 418 | _startRecording(); |
| 419 | } |
| 420 | _currentLayer!.isComplexHint = true; |
| 421 | } |
| 422 | |
| 423 | /// Hints that the painting in the current layer is likely to change next frame. |
| 424 | /// |
| 425 | /// This hint tells the compositor not to cache the current layer because the |
| 426 | /// cache will not be used in the future. If this hint is not set, the |
| 427 | /// compositor will apply its own heuristics to decide whether the current |
| 428 | /// layer is likely to be reused in the future. |
| 429 | /// |
| 430 | /// Calling this ensures a [Canvas] is available. Only draw calls on the |
| 431 | /// current canvas will be hinted; the hint is not propagated to new canvases |
| 432 | /// created after a new layer is added to the painting context (e.g. with |
| 433 | /// [addLayer] or [pushLayer]). |
| 434 | void setWillChangeHint() { |
| 435 | if (_currentLayer == null) { |
| 436 | _startRecording(); |
| 437 | } |
| 438 | _currentLayer!.willChangeHint = true; |
| 439 | } |
| 440 | |
| 441 | /// Adds a composited leaf layer to the recording. |
| 442 | /// |
| 443 | /// After calling this function, the [canvas] property will change to refer to |
| 444 | /// a new [Canvas] that draws on top of the given layer. |
| 445 | /// |
| 446 | /// A [RenderObject] that uses this function is very likely to require its |
| 447 | /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs |
| 448 | /// ancestor render objects that this render object will include a composited |
| 449 | /// layer, which, for example, causes them to use composited clips. |
| 450 | /// |
| 451 | /// See also: |
| 452 | /// |
| 453 | /// * [pushLayer], for adding a layer and painting further contents within |
| 454 | /// it. |
| 455 | void addLayer(Layer layer) { |
| 456 | stopRecordingIfNeeded(); |
| 457 | appendLayer(layer); |
| 458 | } |
| 459 | |
| 460 | /// Appends the given layer to the recording, and calls the `painter` callback |
| 461 | /// with that layer, providing the `childPaintBounds` as the estimated paint |
| 462 | /// bounds of the child. The `childPaintBounds` can be used for debugging but |
| 463 | /// have no effect on painting. |
| 464 | /// |
| 465 | /// The given layer must be an unattached orphan. (Providing a newly created |
| 466 | /// object, rather than reusing an existing layer, satisfies that |
| 467 | /// requirement.) |
| 468 | /// |
| 469 | /// {@template flutter.rendering.PaintingContext.pushLayer.offset} |
| 470 | /// The `offset` is the offset to pass to the `painter`. In particular, it is |
| 471 | /// not an offset applied to the layer itself. Layers conceptually by default |
| 472 | /// have no position or size, though they can transform their contents. For |
| 473 | /// example, an [OffsetLayer] applies an offset to its children. |
| 474 | /// {@endtemplate} |
| 475 | /// |
| 476 | /// If the `childPaintBounds` are not specified then the current layer's paint |
| 477 | /// bounds are used. This is appropriate if the child layer does not apply any |
| 478 | /// transformation or clipping to its contents. The `childPaintBounds`, if |
| 479 | /// specified, must be in the coordinate system of the new layer (i.e. as seen |
| 480 | /// by its children after it applies whatever transform to its contents), and |
| 481 | /// should not go outside the current layer's paint bounds. |
| 482 | /// |
| 483 | /// See also: |
| 484 | /// |
| 485 | /// * [addLayer], for pushing a layer without painting further contents |
| 486 | /// within it. |
| 487 | void pushLayer( |
| 488 | ContainerLayer childLayer, |
| 489 | PaintingContextCallback painter, |
| 490 | Offset offset, { |
| 491 | Rect? childPaintBounds, |
| 492 | }) { |
| 493 | // If a layer is being reused, it may already contain children. We remove |
| 494 | // them so that `painter` can add children that are relevant for this frame. |
| 495 | if (childLayer.hasChildren) { |
| 496 | childLayer.removeAllChildren(); |
| 497 | } |
| 498 | stopRecordingIfNeeded(); |
| 499 | appendLayer(childLayer); |
| 500 | final PaintingContext childContext = createChildContext( |
| 501 | childLayer, |
| 502 | childPaintBounds ?? estimatedBounds, |
| 503 | ); |
| 504 | |
| 505 | painter(childContext, offset); |
| 506 | childContext.stopRecordingIfNeeded(); |
| 507 | } |
| 508 | |
| 509 | /// Creates a painting context configured to paint into [childLayer]. |
| 510 | /// |
| 511 | /// The `bounds` are estimated paint bounds for debugging purposes. |
| 512 | @protected |
| 513 | PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) { |
| 514 | return PaintingContext(childLayer, bounds); |
| 515 | } |
| 516 | |
| 517 | /// Clip further painting using a rectangle. |
| 518 | /// |
| 519 | /// {@template flutter.rendering.PaintingContext.pushClipRect.needsCompositing} |
| 520 | /// The `needsCompositing` argument specifies whether the child needs |
| 521 | /// compositing. Typically this matches the value of |
| 522 | /// [RenderObject.needsCompositing] for the caller. If false, this method |
| 523 | /// returns null, indicating that a layer is no longer necessary. If a render |
| 524 | /// object calling this method stores the `oldLayer` in its |
| 525 | /// [RenderObject.layer] field, it should set that field to null. |
| 526 | /// |
| 527 | /// When `needsCompositing` is false, this method will use a more efficient |
| 528 | /// way to apply the layer effect than actually creating a layer. |
| 529 | /// {@endtemplate} |
| 530 | /// |
| 531 | /// {@template flutter.rendering.PaintingContext.pushClipRect.offset} |
| 532 | /// The `offset` argument is the offset from the origin of the canvas' |
| 533 | /// coordinate system to the origin of the caller's coordinate system. |
| 534 | /// {@endtemplate} |
| 535 | /// |
| 536 | /// The `clipRect` is the rectangle (in the caller's coordinate system) to use |
| 537 | /// to clip the painting done by [painter]. It should not include the |
| 538 | /// `offset`. |
| 539 | /// |
| 540 | /// The `painter` callback will be called while the `clipRect` is applied. It |
| 541 | /// is called synchronously during the call to [pushClipRect]. |
| 542 | /// |
| 543 | /// The `clipBehavior` argument controls how the rectangle is clipped. |
| 544 | /// |
| 545 | /// {@template flutter.rendering.PaintingContext.pushClipRect.oldLayer} |
| 546 | /// For the `oldLayer` argument, specify the layer created in the previous |
| 547 | /// frame. This gives the engine more information for performance |
| 548 | /// optimizations. Typically this is the value of [RenderObject.layer] that a |
| 549 | /// render object creates once, then reuses for all subsequent frames until a |
| 550 | /// layer is no longer needed (e.g. the render object no longer needs |
| 551 | /// compositing) or until the render object changes the type of the layer |
| 552 | /// (e.g. from opacity layer to a clip rect layer). |
| 553 | /// {@endtemplate} |
| 554 | ClipRectLayer? pushClipRect( |
| 555 | bool needsCompositing, |
| 556 | Offset offset, |
| 557 | Rect clipRect, |
| 558 | PaintingContextCallback painter, { |
| 559 | Clip clipBehavior = Clip.hardEdge, |
| 560 | ClipRectLayer? oldLayer, |
| 561 | }) { |
| 562 | if (clipBehavior == Clip.none) { |
| 563 | painter(this, offset); |
| 564 | return null; |
| 565 | } |
| 566 | final Rect offsetClipRect = clipRect.shift(offset); |
| 567 | if (needsCompositing) { |
| 568 | final ClipRectLayer layer = oldLayer ?? ClipRectLayer(); |
| 569 | layer |
| 570 | ..clipRect = offsetClipRect |
| 571 | ..clipBehavior = clipBehavior; |
| 572 | pushLayer(layer, painter, offset, childPaintBounds: offsetClipRect); |
| 573 | return layer; |
| 574 | } else { |
| 575 | clipRectAndPaint(offsetClipRect, clipBehavior, offsetClipRect, () => painter(this, offset)); |
| 576 | return null; |
| 577 | } |
| 578 | } |
| 579 | |
| 580 | /// Clip further painting using a rounded rectangle. |
| 581 | /// |
| 582 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing} |
| 583 | /// |
| 584 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.offset} |
| 585 | /// |
| 586 | /// The `bounds` argument is used to specify the region of the canvas (in the |
| 587 | /// caller's coordinate system) into which `painter` will paint. |
| 588 | /// |
| 589 | /// The `clipRRect` argument specifies the rounded-rectangle (in the caller's |
| 590 | /// coordinate system) to use to clip the painting done by `painter`. It |
| 591 | /// should not include the `offset`. |
| 592 | /// |
| 593 | /// The `painter` callback will be called while the `clipRRect` is applied. It |
| 594 | /// is called synchronously during the call to [pushClipRRect]. |
| 595 | /// |
| 596 | /// The `clipBehavior` argument controls how the rounded rectangle is clipped. |
| 597 | /// |
| 598 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer} |
| 599 | ClipRRectLayer? pushClipRRect( |
| 600 | bool needsCompositing, |
| 601 | Offset offset, |
| 602 | Rect bounds, |
| 603 | RRect clipRRect, |
| 604 | PaintingContextCallback painter, { |
| 605 | Clip clipBehavior = Clip.antiAlias, |
| 606 | ClipRRectLayer? oldLayer, |
| 607 | }) { |
| 608 | if (clipBehavior == Clip.none) { |
| 609 | painter(this, offset); |
| 610 | return null; |
| 611 | } |
| 612 | final Rect offsetBounds = bounds.shift(offset); |
| 613 | final RRect offsetClipRRect = clipRRect.shift(offset); |
| 614 | if (needsCompositing) { |
| 615 | final ClipRRectLayer layer = oldLayer ?? ClipRRectLayer(); |
| 616 | layer |
| 617 | ..clipRRect = offsetClipRRect |
| 618 | ..clipBehavior = clipBehavior; |
| 619 | pushLayer(layer, painter, offset, childPaintBounds: offsetBounds); |
| 620 | return layer; |
| 621 | } else { |
| 622 | clipRRectAndPaint(offsetClipRRect, clipBehavior, offsetBounds, () => painter(this, offset)); |
| 623 | return null; |
| 624 | } |
| 625 | } |
| 626 | |
| 627 | /// Clip further painting using a rounded superellipse. |
| 628 | /// |
| 629 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing} |
| 630 | /// |
| 631 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.offset} |
| 632 | /// |
| 633 | /// The `bounds` argument is used to specify the region of the canvas (in the |
| 634 | /// caller's coordinate system) into which `painter` will paint. |
| 635 | /// |
| 636 | /// The `clipRSuperellipse` argument specifies the rounded-superellipse (in the caller's |
| 637 | /// coordinate system) to use to clip the painting done by `painter`. It |
| 638 | /// should not include the `offset`. |
| 639 | /// |
| 640 | /// The `painter` callback will be called while the `clipRSuperellipse` is applied. It |
| 641 | /// is called synchronously during the call to [pushClipRSuperellipse]. |
| 642 | /// |
| 643 | /// The `clipBehavior` argument controls how the rounded rectangle is clipped. |
| 644 | /// |
| 645 | /// Hit tests are performed based on the bounding box of the [RSuperellipse]. |
| 646 | /// |
| 647 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer} |
| 648 | ClipRSuperellipseLayer? pushClipRSuperellipse( |
| 649 | bool needsCompositing, |
| 650 | Offset offset, |
| 651 | Rect bounds, |
| 652 | RSuperellipse clipRSuperellipse, |
| 653 | PaintingContextCallback painter, { |
| 654 | Clip clipBehavior = Clip.antiAlias, |
| 655 | ClipRSuperellipseLayer? oldLayer, |
| 656 | }) { |
| 657 | if (clipBehavior == Clip.none) { |
| 658 | painter(this, offset); |
| 659 | return null; |
| 660 | } |
| 661 | final Rect offsetBounds = bounds.shift(offset); |
| 662 | final RSuperellipse offsetShape = clipRSuperellipse.shift(offset); |
| 663 | if (needsCompositing) { |
| 664 | final ClipRSuperellipseLayer layer = oldLayer ?? ClipRSuperellipseLayer(); |
| 665 | layer |
| 666 | ..clipRSuperellipse = offsetShape |
| 667 | ..clipBehavior = clipBehavior; |
| 668 | pushLayer(layer, painter, offset, childPaintBounds: offsetBounds); |
| 669 | return layer; |
| 670 | } else { |
| 671 | clipRSuperellipseAndPaint( |
| 672 | offsetShape, |
| 673 | clipBehavior, |
| 674 | offsetBounds, |
| 675 | () => painter(this, offset), |
| 676 | ); |
| 677 | return null; |
| 678 | } |
| 679 | } |
| 680 | |
| 681 | /// Clip further painting using a path. |
| 682 | /// |
| 683 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing} |
| 684 | /// |
| 685 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.offset} |
| 686 | /// |
| 687 | /// The `bounds` argument is used to specify the region of the canvas (in the |
| 688 | /// caller's coordinate system) into which `painter` will paint. |
| 689 | /// |
| 690 | /// The `clipPath` argument specifies the [Path] (in the caller's coordinate |
| 691 | /// system) to use to clip the painting done by `painter`. It should not |
| 692 | /// include the `offset`. |
| 693 | /// |
| 694 | /// The `painter` callback will be called while the `clipPath` is applied. It |
| 695 | /// is called synchronously during the call to [pushClipPath]. |
| 696 | /// |
| 697 | /// The `clipBehavior` argument controls how the path is clipped. |
| 698 | /// |
| 699 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer} |
| 700 | ClipPathLayer? pushClipPath( |
| 701 | bool needsCompositing, |
| 702 | Offset offset, |
| 703 | Rect bounds, |
| 704 | Path clipPath, |
| 705 | PaintingContextCallback painter, { |
| 706 | Clip clipBehavior = Clip.antiAlias, |
| 707 | ClipPathLayer? oldLayer, |
| 708 | }) { |
| 709 | if (clipBehavior == Clip.none) { |
| 710 | painter(this, offset); |
| 711 | return null; |
| 712 | } |
| 713 | final Rect offsetBounds = bounds.shift(offset); |
| 714 | final Path offsetClipPath = clipPath.shift(offset); |
| 715 | if (needsCompositing) { |
| 716 | final ClipPathLayer layer = oldLayer ?? ClipPathLayer(); |
| 717 | layer |
| 718 | ..clipPath = offsetClipPath |
| 719 | ..clipBehavior = clipBehavior; |
| 720 | pushLayer(layer, painter, offset, childPaintBounds: offsetBounds); |
| 721 | return layer; |
| 722 | } else { |
| 723 | clipPathAndPaint(offsetClipPath, clipBehavior, offsetBounds, () => painter(this, offset)); |
| 724 | return null; |
| 725 | } |
| 726 | } |
| 727 | |
| 728 | /// Blend further painting with a color filter. |
| 729 | /// |
| 730 | /// {@macro flutter.rendering.PaintingContext.pushLayer.offset} |
| 731 | /// |
| 732 | /// The `colorFilter` argument is the [ColorFilter] value to use when blending |
| 733 | /// the painting done by `painter`. |
| 734 | /// |
| 735 | /// The `painter` callback will be called while the `colorFilter` is applied. |
| 736 | /// It is called synchronously during the call to [pushColorFilter]. |
| 737 | /// |
| 738 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer} |
| 739 | /// |
| 740 | /// A [RenderObject] that uses this function is very likely to require its |
| 741 | /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs |
| 742 | /// ancestor render objects that this render object will include a composited |
| 743 | /// layer, which, for example, causes them to use composited clips. |
| 744 | ColorFilterLayer pushColorFilter( |
| 745 | Offset offset, |
| 746 | ColorFilter colorFilter, |
| 747 | PaintingContextCallback painter, { |
| 748 | ColorFilterLayer? oldLayer, |
| 749 | }) { |
| 750 | final ColorFilterLayer layer = oldLayer ?? ColorFilterLayer(); |
| 751 | layer.colorFilter = colorFilter; |
| 752 | pushLayer(layer, painter, offset); |
| 753 | return layer; |
| 754 | } |
| 755 | |
| 756 | /// Transform further painting using a matrix. |
| 757 | /// |
| 758 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing} |
| 759 | /// |
| 760 | /// The `offset` argument is the offset to pass to `painter` and the offset to |
| 761 | /// the origin used by `transform`. |
| 762 | /// |
| 763 | /// The `transform` argument is the [Matrix4] with which to transform the |
| 764 | /// coordinate system while calling `painter`. It should not include `offset`. |
| 765 | /// It is applied effectively after applying `offset`. |
| 766 | /// |
| 767 | /// The `painter` callback will be called while the `transform` is applied. It |
| 768 | /// is called synchronously during the call to [pushTransform]. |
| 769 | /// |
| 770 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer} |
| 771 | TransformLayer? pushTransform( |
| 772 | bool needsCompositing, |
| 773 | Offset offset, |
| 774 | Matrix4 transform, |
| 775 | PaintingContextCallback painter, { |
| 776 | TransformLayer? oldLayer, |
| 777 | }) { |
| 778 | final Matrix4 effectiveTransform = Matrix4.translationValues(offset.dx, offset.dy, 0.0) |
| 779 | ..multiply(transform) |
| 780 | ..translateByDouble(-offset.dx, -offset.dy, 0, 1); |
| 781 | if (needsCompositing) { |
| 782 | final TransformLayer layer = oldLayer ?? TransformLayer(); |
| 783 | layer.transform = effectiveTransform; |
| 784 | pushLayer( |
| 785 | layer, |
| 786 | painter, |
| 787 | offset, |
| 788 | childPaintBounds: MatrixUtils.inverseTransformRect(effectiveTransform, estimatedBounds), |
| 789 | ); |
| 790 | return layer; |
| 791 | } else { |
| 792 | canvas |
| 793 | ..save() |
| 794 | ..transform(effectiveTransform.storage); |
| 795 | painter(this, offset); |
| 796 | canvas.restore(); |
| 797 | return null; |
| 798 | } |
| 799 | } |
| 800 | |
| 801 | /// Blend further painting with an alpha value. |
| 802 | /// |
| 803 | /// The `offset` argument indicates an offset to apply to all the children |
| 804 | /// (the rendering created by `painter`). |
| 805 | /// |
| 806 | /// The `alpha` argument is the alpha value to use when blending the painting |
| 807 | /// done by `painter`. An alpha value of 0 means the painting is fully |
| 808 | /// transparent and an alpha value of 255 means the painting is fully opaque. |
| 809 | /// |
| 810 | /// The `painter` callback will be called while the `alpha` is applied. It |
| 811 | /// is called synchronously during the call to [pushOpacity]. |
| 812 | /// |
| 813 | /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer} |
| 814 | /// |
| 815 | /// A [RenderObject] that uses this function is very likely to require its |
| 816 | /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs |
| 817 | /// ancestor render objects that this render object will include a composited |
| 818 | /// layer, which, for example, causes them to use composited clips. |
| 819 | OpacityLayer pushOpacity( |
| 820 | Offset offset, |
| 821 | int alpha, |
| 822 | PaintingContextCallback painter, { |
| 823 | OpacityLayer? oldLayer, |
| 824 | }) { |
| 825 | final OpacityLayer layer = oldLayer ?? OpacityLayer(); |
| 826 | layer |
| 827 | ..alpha = alpha |
| 828 | ..offset = offset; |
| 829 | pushLayer(layer, painter, Offset.zero); |
| 830 | return layer; |
| 831 | } |
| 832 | |
| 833 | @override |
| 834 | String toString() => |
| 835 | ' ${objectRuntimeType(this, 'PaintingContext' )}# $hashCode(layer: $_containerLayer, canvas bounds: $estimatedBounds)' ; |
| 836 | } |
| 837 | |
| 838 | /// An abstract set of layout constraints. |
| 839 | /// |
| 840 | /// Concrete layout models (such as box) will create concrete subclasses to |
| 841 | /// communicate layout constraints between parents and children. |
| 842 | /// |
| 843 | /// ## Writing a Constraints subclass |
| 844 | /// |
| 845 | /// When creating a new [RenderObject] subclass with a new layout protocol, one |
| 846 | /// will usually need to create a new [Constraints] subclass to express the |
| 847 | /// input to the layout algorithms. |
| 848 | /// |
| 849 | /// A [Constraints] subclass should be immutable (all fields final). There are |
| 850 | /// several members to implement, in addition to whatever fields, constructors, |
| 851 | /// and helper methods one may find useful for a particular layout protocol: |
| 852 | /// |
| 853 | /// * The [isTight] getter, which should return true if the object represents a |
| 854 | /// case where the [RenderObject] class has no choice for how to lay itself |
| 855 | /// out. For example, [BoxConstraints] returns true for [isTight] when both |
| 856 | /// the minimum and maximum widths and the minimum and maximum heights are |
| 857 | /// equal. |
| 858 | /// |
| 859 | /// * The [isNormalized] getter, which should return true if the object |
| 860 | /// represents its data in its canonical form. Sometimes, it is possible for |
| 861 | /// fields to be redundant with each other, such that several different |
| 862 | /// representations have the same implications. For example, a |
| 863 | /// [BoxConstraints] instance with its minimum width greater than its maximum |
| 864 | /// width is equivalent to one where the maximum width is set to that minimum |
| 865 | /// width (`2<w<1` is equivalent to `2<w<2`, since minimum constraints have |
| 866 | /// priority). This getter is used by the default implementation of |
| 867 | /// [debugAssertIsValid]. |
| 868 | /// |
| 869 | /// * The [debugAssertIsValid] method, which should assert if there's anything |
| 870 | /// wrong with the constraints object. (We use this approach rather than |
| 871 | /// asserting in constructors so that our constructors can be `const` and so |
| 872 | /// that it is possible to create invalid constraints temporarily while |
| 873 | /// building valid ones.) See the implementation of |
| 874 | /// [BoxConstraints.debugAssertIsValid] for an example of the detailed checks |
| 875 | /// that can be made. |
| 876 | /// |
| 877 | /// * The [==] operator and the [hashCode] getter, so that constraints can be |
| 878 | /// compared for equality. If a render object is given constraints that are |
| 879 | /// equal, then the rendering library will avoid laying the object out again |
| 880 | /// if it is not dirty. |
| 881 | /// |
| 882 | /// * The [toString] method, which should describe the constraints so that they |
| 883 | /// appear in a usefully readable form in the output of [debugDumpRenderTree]. |
| 884 | @immutable |
| 885 | abstract class Constraints { |
| 886 | /// Abstract const constructor. This constructor enables subclasses to provide |
| 887 | /// const constructors so that they can be used in const expressions. |
| 888 | const Constraints(); |
| 889 | |
| 890 | /// Whether there is exactly one size possible given these constraints. |
| 891 | bool get isTight; |
| 892 | |
| 893 | /// Whether the constraint is expressed in a consistent manner. |
| 894 | bool get isNormalized; |
| 895 | |
| 896 | /// Asserts that the constraints are valid. |
| 897 | /// |
| 898 | /// This might involve checks more detailed than [isNormalized]. |
| 899 | /// |
| 900 | /// For example, the [BoxConstraints] subclass verifies that the constraints |
| 901 | /// are not [double.nan]. |
| 902 | /// |
| 903 | /// If the `isAppliedConstraint` argument is true, then even stricter rules |
| 904 | /// are enforced. This argument is set to true when checking constraints that |
| 905 | /// are about to be applied to a [RenderObject] during layout, as opposed to |
| 906 | /// constraints that may be further affected by other constraints. For |
| 907 | /// example, the asserts for verifying the validity of |
| 908 | /// [RenderConstrainedBox.additionalConstraints] do not set this argument, but |
| 909 | /// the asserts for verifying the argument passed to the [RenderObject.layout] |
| 910 | /// method do. |
| 911 | /// |
| 912 | /// The `informationCollector` argument takes an optional callback which is |
| 913 | /// called when an exception is to be thrown. The collected information is |
| 914 | /// then included in the message after the error line. |
| 915 | /// |
| 916 | /// Returns the same as [isNormalized] if asserts are disabled. |
| 917 | bool debugAssertIsValid({ |
| 918 | bool isAppliedConstraint = false, |
| 919 | InformationCollector? informationCollector, |
| 920 | }) { |
| 921 | assert(isNormalized); |
| 922 | return isNormalized; |
| 923 | } |
| 924 | } |
| 925 | |
| 926 | /// Signature for a function that is called for each [RenderObject]. |
| 927 | /// |
| 928 | /// Used by [RenderObject.visitChildren] and [RenderObject.visitChildrenForSemantics]. |
| 929 | typedef RenderObjectVisitor = void Function(RenderObject child); |
| 930 | |
| 931 | /// Signature for a function that is called during layout. |
| 932 | /// |
| 933 | /// Used by [RenderObject.invokeLayoutCallback]. |
| 934 | typedef LayoutCallback<T extends Constraints> = void Function(T constraints); |
| 935 | |
| 936 | class _LocalSemanticsHandle implements SemanticsHandle { |
| 937 | _LocalSemanticsHandle._(PipelineOwner owner, this.listener) : _owner = owner { |
| 938 | assert(debugMaybeDispatchCreated('rendering' , '_LocalSemanticsHandle' , this)); |
| 939 | if (listener != null) { |
| 940 | _owner.semanticsOwner!.addListener(listener!); |
| 941 | } |
| 942 | } |
| 943 | |
| 944 | final PipelineOwner _owner; |
| 945 | |
| 946 | /// The callback that will be notified when the semantics tree updates. |
| 947 | final VoidCallback? listener; |
| 948 | |
| 949 | @override |
| 950 | void dispose() { |
| 951 | assert(debugMaybeDispatchDisposed(this)); |
| 952 | if (listener != null) { |
| 953 | _owner.semanticsOwner!.removeListener(listener!); |
| 954 | } |
| 955 | _owner._didDisposeSemanticsHandle(); |
| 956 | } |
| 957 | } |
| 958 | |
| 959 | /// The pipeline owner manages the rendering pipeline. |
| 960 | /// |
| 961 | /// The pipeline owner provides an interface for driving the rendering pipeline |
| 962 | /// and stores the state about which render objects have requested to be visited |
| 963 | /// in each stage of the pipeline. To flush the pipeline, call the following |
| 964 | /// functions in order: |
| 965 | /// |
| 966 | /// 1. [flushLayout] updates any render objects that need to compute their |
| 967 | /// layout. During this phase, the size and position of each render |
| 968 | /// object is calculated. Render objects might dirty their painting or |
| 969 | /// compositing state during this phase. |
| 970 | /// 2. [flushCompositingBits] updates any render objects that have dirty |
| 971 | /// compositing bits. During this phase, each render object learns whether |
| 972 | /// any of its children require compositing. This information is used during |
| 973 | /// the painting phase when selecting how to implement visual effects such as |
| 974 | /// clipping. If a render object has a composited child, it needs to use a |
| 975 | /// [Layer] to create the clip in order for the clip to apply to the |
| 976 | /// composited child (which will be painted into its own [Layer]). |
| 977 | /// 3. [flushPaint] visits any render objects that need to paint. During this |
| 978 | /// phase, render objects get a chance to record painting commands into |
| 979 | /// [PictureLayer]s and construct other composited [Layer]s. |
| 980 | /// 4. Finally, if semantics are enabled, [flushSemantics] will compile the |
| 981 | /// semantics for the render objects. This semantic information is used by |
| 982 | /// assistive technology to improve the accessibility of the render tree. |
| 983 | /// |
| 984 | /// The [RendererBinding] holds the pipeline owner for the render objects that |
| 985 | /// are visible on screen. You can create other pipeline owners to manage |
| 986 | /// off-screen objects, which can flush their pipelines independently of the |
| 987 | /// on-screen render objects. |
| 988 | /// |
| 989 | /// [PipelineOwner]s can be organized in a tree to manage multiple render trees, |
| 990 | /// where each [PipelineOwner] is responsible for one of the render trees. To |
| 991 | /// build or modify the tree, call [adoptChild] or [dropChild]. During each of |
| 992 | /// the different flush phases described above, a [PipelineOwner] will first |
| 993 | /// perform the phase on the nodes it manages in its own render tree before |
| 994 | /// calling the same flush method on its children. No assumption must be made |
| 995 | /// about the order in which child [PipelineOwner]s are flushed. |
| 996 | /// |
| 997 | /// A [PipelineOwner] may also be [attach]ed to a [PipelineManifold], which |
| 998 | /// gives it access to platform functionality usually exposed by the bindings |
| 999 | /// without tying it to a specific binding implementation. All [PipelineOwner]s |
| 1000 | /// in a given tree must be attached to the same [PipelineManifold]. This |
| 1001 | /// happens automatically during [adoptChild]. |
| 1002 | base class PipelineOwner with DiagnosticableTreeMixin { |
| 1003 | /// Creates a pipeline owner. |
| 1004 | /// |
| 1005 | /// Typically created by the binding (e.g., [RendererBinding]), but can be |
| 1006 | /// created separately from the binding to drive off-screen render objects |
| 1007 | /// through the rendering pipeline. |
| 1008 | PipelineOwner({ |
| 1009 | this.onNeedVisualUpdate, |
| 1010 | this.onSemanticsOwnerCreated, |
| 1011 | this.onSemanticsUpdate, |
| 1012 | this.onSemanticsOwnerDisposed, |
| 1013 | }) { |
| 1014 | assert(debugMaybeDispatchCreated('rendering' , 'PipelineOwner' , this)); |
| 1015 | } |
| 1016 | |
| 1017 | /// Called when a render object associated with this pipeline owner wishes to |
| 1018 | /// update its visual appearance. |
| 1019 | /// |
| 1020 | /// Typical implementations of this function will schedule a task to flush the |
| 1021 | /// various stages of the pipeline. This function might be called multiple |
| 1022 | /// times in quick succession. Implementations should take care to discard |
| 1023 | /// duplicate calls quickly. |
| 1024 | /// |
| 1025 | /// When the [PipelineOwner] is attached to a [PipelineManifold] and |
| 1026 | /// [onNeedVisualUpdate] is provided, the [onNeedVisualUpdate] callback is |
| 1027 | /// invoked instead of calling [PipelineManifold.requestVisualUpdate]. |
| 1028 | final VoidCallback? onNeedVisualUpdate; |
| 1029 | |
| 1030 | /// Called whenever this pipeline owner creates a semantics object. |
| 1031 | /// |
| 1032 | /// Typical implementations will schedule the creation of the initial |
| 1033 | /// semantics tree. |
| 1034 | final VoidCallback? onSemanticsOwnerCreated; |
| 1035 | |
| 1036 | /// Called whenever this pipeline owner's semantics owner emits a [SemanticsUpdate]. |
| 1037 | /// |
| 1038 | /// Typical implementations will delegate the [SemanticsUpdate] to a [FlutterView] |
| 1039 | /// that can handle the [SemanticsUpdate]. |
| 1040 | final SemanticsUpdateCallback? onSemanticsUpdate; |
| 1041 | |
| 1042 | /// Called whenever this pipeline owner disposes its semantics owner. |
| 1043 | /// |
| 1044 | /// Typical implementations will tear down the semantics tree. |
| 1045 | final VoidCallback? onSemanticsOwnerDisposed; |
| 1046 | |
| 1047 | /// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null. |
| 1048 | /// |
| 1049 | /// Used to notify the pipeline owner that an associated render object wishes |
| 1050 | /// to update its visual appearance. |
| 1051 | void requestVisualUpdate() { |
| 1052 | if (onNeedVisualUpdate != null) { |
| 1053 | onNeedVisualUpdate!(); |
| 1054 | } else { |
| 1055 | _manifold?.requestVisualUpdate(); |
| 1056 | } |
| 1057 | } |
| 1058 | |
| 1059 | /// The unique object managed by this pipeline that has no parent. |
| 1060 | RenderObject? get rootNode => _rootNode; |
| 1061 | RenderObject? _rootNode; |
| 1062 | set rootNode(RenderObject? value) { |
| 1063 | if (_rootNode == value) { |
| 1064 | return; |
| 1065 | } |
| 1066 | _rootNode?.detach(); |
| 1067 | _rootNode = value; |
| 1068 | _rootNode?.attach(this); |
| 1069 | } |
| 1070 | |
| 1071 | // Whether the current [flushLayout] call should pause to incorporate the |
| 1072 | // [RenderObject]s in `_nodesNeedingLayout` into the current dirty list, |
| 1073 | // before continuing to process dirty relayout boundaries. |
| 1074 | // |
| 1075 | // This flag is set to true when a [RenderObject.invokeLayoutCallback] |
| 1076 | // returns, to avoid laying out dirty relayout boundaries in an incorrect |
| 1077 | // order and causing them to be laid out more than once per frame. See |
| 1078 | // layout_builder_mutations_test.dart for an example. |
| 1079 | // |
| 1080 | // The new dirty nodes are not immediately merged after a |
| 1081 | // [RenderObject.invokeLayoutCallback] call because we may encounter multiple |
| 1082 | // such calls while processing a single relayout boundary in [flushLayout]. |
| 1083 | // Batching new dirty nodes can reduce the number of merges [flushLayout] |
| 1084 | // has to perform. |
| 1085 | bool _shouldMergeDirtyNodes = false; |
| 1086 | List<RenderObject> _nodesNeedingLayout = <RenderObject>[]; |
| 1087 | |
| 1088 | /// The [RenderObject]s representing relayout boundaries which need to be laid out |
| 1089 | /// in the next [flushLayout] pass. |
| 1090 | /// |
| 1091 | /// Relayout boundaries are added when they are marked for layout. |
| 1092 | /// Subclasses of [PipelineOwner] may use them to invalidate caches or |
| 1093 | /// otherwise make performance optimizations. Since nodes may be marked for |
| 1094 | /// layout at any time, they are best checked during [flushLayout]. |
| 1095 | /// |
| 1096 | /// Relayout boundaries owned by child [PipelineOwner]s are not included here. |
| 1097 | /// |
| 1098 | /// Boundaries appear in an arbitrary order, and may appear multiple times. |
| 1099 | @protected |
| 1100 | @nonVirtual |
| 1101 | Iterable<RenderObject> get nodesNeedingLayout => _nodesNeedingLayout; |
| 1102 | |
| 1103 | /// Whether this pipeline is currently in the layout phase. |
| 1104 | /// |
| 1105 | /// Specifically, whether [flushLayout] is currently running. |
| 1106 | /// |
| 1107 | /// Only valid when asserts are enabled; in release builds, this |
| 1108 | /// always returns false. |
| 1109 | bool get debugDoingLayout => _debugDoingLayout; |
| 1110 | bool _debugDoingLayout = false; |
| 1111 | bool _debugDoingChildLayout = false; |
| 1112 | |
| 1113 | /// Update the layout information for all dirty render objects. |
| 1114 | /// |
| 1115 | /// This function is one of the core stages of the rendering pipeline. Layout |
| 1116 | /// information is cleaned prior to painting so that render objects will |
| 1117 | /// appear on screen in their up-to-date locations. |
| 1118 | /// |
| 1119 | /// See [RendererBinding] for an example of how this function is used. |
| 1120 | void flushLayout() { |
| 1121 | if (!kReleaseMode) { |
| 1122 | Map<String, String>? debugTimelineArguments; |
| 1123 | assert(() { |
| 1124 | if (debugEnhanceLayoutTimelineArguments) { |
| 1125 | debugTimelineArguments = <String, String>{ |
| 1126 | 'dirty count' : ' ${_nodesNeedingLayout.length}' , |
| 1127 | 'dirty list' : ' $_nodesNeedingLayout' , |
| 1128 | }; |
| 1129 | } |
| 1130 | return true; |
| 1131 | }()); |
| 1132 | FlutterTimeline.startSync( |
| 1133 | 'LAYOUT $_debugRootSuffixForTimelineEventNames' , |
| 1134 | arguments: debugTimelineArguments, |
| 1135 | ); |
| 1136 | } |
| 1137 | assert(() { |
| 1138 | _debugDoingLayout = true; |
| 1139 | return true; |
| 1140 | }()); |
| 1141 | try { |
| 1142 | while (_nodesNeedingLayout.isNotEmpty) { |
| 1143 | assert(!_shouldMergeDirtyNodes); |
| 1144 | final List<RenderObject> dirtyNodes = _nodesNeedingLayout; |
| 1145 | _nodesNeedingLayout = <RenderObject>[]; |
| 1146 | dirtyNodes.sort((RenderObject a, RenderObject b) => a.depth - b.depth); |
| 1147 | for (int i = 0; i < dirtyNodes.length; i++) { |
| 1148 | if (_shouldMergeDirtyNodes) { |
| 1149 | _shouldMergeDirtyNodes = false; |
| 1150 | if (_nodesNeedingLayout.isNotEmpty) { |
| 1151 | _nodesNeedingLayout.addAll(dirtyNodes.getRange(i, dirtyNodes.length)); |
| 1152 | break; |
| 1153 | } |
| 1154 | } |
| 1155 | final RenderObject node = dirtyNodes[i]; |
| 1156 | if (node._needsLayout && node.owner == this) { |
| 1157 | node._layoutWithoutResize(); |
| 1158 | } |
| 1159 | } |
| 1160 | // No need to merge dirty nodes generated from processing the last |
| 1161 | // relayout boundary back. |
| 1162 | _shouldMergeDirtyNodes = false; |
| 1163 | } |
| 1164 | |
| 1165 | assert(() { |
| 1166 | _debugDoingChildLayout = true; |
| 1167 | return true; |
| 1168 | }()); |
| 1169 | for (final PipelineOwner child in _children) { |
| 1170 | child.flushLayout(); |
| 1171 | } |
| 1172 | assert( |
| 1173 | _nodesNeedingLayout.isEmpty, |
| 1174 | 'Child PipelineOwners must not dirty nodes in their parent.' , |
| 1175 | ); |
| 1176 | } finally { |
| 1177 | _shouldMergeDirtyNodes = false; |
| 1178 | assert(() { |
| 1179 | _debugDoingLayout = false; |
| 1180 | _debugDoingChildLayout = false; |
| 1181 | return true; |
| 1182 | }()); |
| 1183 | if (!kReleaseMode) { |
| 1184 | FlutterTimeline.finishSync(); |
| 1185 | } |
| 1186 | } |
| 1187 | } |
| 1188 | |
| 1189 | // This flag is used to allow the kinds of mutations performed by GlobalKey |
| 1190 | // reparenting while a LayoutBuilder is being rebuilt and in so doing tries to |
| 1191 | // move a node from another LayoutBuilder subtree that hasn't been updated |
| 1192 | // yet. To set this, call [_enableMutationsToDirtySubtrees], which is called |
| 1193 | // by [RenderObject.invokeLayoutCallback]. |
| 1194 | bool _debugAllowMutationsToDirtySubtrees = false; |
| 1195 | |
| 1196 | // See [RenderObject.invokeLayoutCallback]. |
| 1197 | void _enableMutationsToDirtySubtrees(VoidCallback callback) { |
| 1198 | assert(_debugDoingLayout); |
| 1199 | bool? oldState; |
| 1200 | assert(() { |
| 1201 | oldState = _debugAllowMutationsToDirtySubtrees; |
| 1202 | _debugAllowMutationsToDirtySubtrees = true; |
| 1203 | return true; |
| 1204 | }()); |
| 1205 | try { |
| 1206 | callback(); |
| 1207 | } finally { |
| 1208 | _shouldMergeDirtyNodes = true; |
| 1209 | assert(() { |
| 1210 | _debugAllowMutationsToDirtySubtrees = oldState!; |
| 1211 | return true; |
| 1212 | }()); |
| 1213 | } |
| 1214 | } |
| 1215 | |
| 1216 | final List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[]; |
| 1217 | |
| 1218 | /// Updates the [RenderObject.needsCompositing] bits. |
| 1219 | /// |
| 1220 | /// Called as part of the rendering pipeline after [flushLayout] and before |
| 1221 | /// [flushPaint]. |
| 1222 | void flushCompositingBits() { |
| 1223 | if (!kReleaseMode) { |
| 1224 | FlutterTimeline.startSync('UPDATING COMPOSITING BITS $_debugRootSuffixForTimelineEventNames' ); |
| 1225 | } |
| 1226 | _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth); |
| 1227 | for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) { |
| 1228 | if (node._needsCompositingBitsUpdate && node.owner == this) { |
| 1229 | node._updateCompositingBits(); |
| 1230 | } |
| 1231 | } |
| 1232 | _nodesNeedingCompositingBitsUpdate.clear(); |
| 1233 | for (final PipelineOwner child in _children) { |
| 1234 | child.flushCompositingBits(); |
| 1235 | } |
| 1236 | assert( |
| 1237 | _nodesNeedingCompositingBitsUpdate.isEmpty, |
| 1238 | 'Child PipelineOwners must not dirty nodes in their parent.' , |
| 1239 | ); |
| 1240 | if (!kReleaseMode) { |
| 1241 | FlutterTimeline.finishSync(); |
| 1242 | } |
| 1243 | } |
| 1244 | |
| 1245 | List<RenderObject> _nodesNeedingPaint = <RenderObject>[]; |
| 1246 | |
| 1247 | /// The [RenderObject]s which need to be painted in the next [flushPaint] pass. |
| 1248 | /// |
| 1249 | /// [RenderObject]s marked with [RenderObject.isRepaintBoundary] are added |
| 1250 | /// when they are marked needing paint. Subclasses of [PipelineOwner] may use them |
| 1251 | /// to invalidate caches or otherwise make performance optimizations. |
| 1252 | /// Since nodes may be marked for layout at any time, they are best checked during |
| 1253 | /// [flushPaint]. |
| 1254 | /// |
| 1255 | /// Marked children of child [PipelineOwner]s are not included here. |
| 1256 | @protected |
| 1257 | @nonVirtual |
| 1258 | Iterable<RenderObject> get nodesNeedingPaint => _nodesNeedingPaint; |
| 1259 | |
| 1260 | /// Whether this pipeline is currently in the paint phase. |
| 1261 | /// |
| 1262 | /// Specifically, whether [flushPaint] is currently running. |
| 1263 | /// |
| 1264 | /// Only valid when asserts are enabled. In release builds, |
| 1265 | /// this always returns false. |
| 1266 | bool get debugDoingPaint => _debugDoingPaint; |
| 1267 | bool _debugDoingPaint = false; |
| 1268 | |
| 1269 | /// Update the display lists for all render objects. |
| 1270 | /// |
| 1271 | /// This function is one of the core stages of the rendering pipeline. |
| 1272 | /// Painting occurs after layout and before the scene is recomposited so that |
| 1273 | /// scene is composited with up-to-date display lists for every render object. |
| 1274 | /// |
| 1275 | /// See [RendererBinding] for an example of how this function is used. |
| 1276 | void flushPaint() { |
| 1277 | if (!kReleaseMode) { |
| 1278 | Map<String, String>? debugTimelineArguments; |
| 1279 | assert(() { |
| 1280 | if (debugEnhancePaintTimelineArguments) { |
| 1281 | debugTimelineArguments = <String, String>{ |
| 1282 | 'dirty count' : ' ${_nodesNeedingPaint.length}' , |
| 1283 | 'dirty list' : ' $_nodesNeedingPaint' , |
| 1284 | }; |
| 1285 | } |
| 1286 | return true; |
| 1287 | }()); |
| 1288 | FlutterTimeline.startSync( |
| 1289 | 'PAINT $_debugRootSuffixForTimelineEventNames' , |
| 1290 | arguments: debugTimelineArguments, |
| 1291 | ); |
| 1292 | } |
| 1293 | try { |
| 1294 | assert(() { |
| 1295 | _debugDoingPaint = true; |
| 1296 | return true; |
| 1297 | }()); |
| 1298 | final List<RenderObject> dirtyNodes = _nodesNeedingPaint; |
| 1299 | _nodesNeedingPaint = <RenderObject>[]; |
| 1300 | |
| 1301 | // Sort the dirty nodes in reverse order (deepest first). |
| 1302 | for (final RenderObject node |
| 1303 | in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { |
| 1304 | assert(node._layerHandle.layer != null); |
| 1305 | if ((node._needsPaint || node._needsCompositedLayerUpdate) && node.owner == this) { |
| 1306 | if (node._layerHandle.layer!.attached) { |
| 1307 | assert(node.isRepaintBoundary); |
| 1308 | if (node._needsPaint) { |
| 1309 | PaintingContext.repaintCompositedChild(node); |
| 1310 | } else { |
| 1311 | PaintingContext.updateLayerProperties(node); |
| 1312 | } |
| 1313 | } else { |
| 1314 | node._skippedPaintingOnLayer(); |
| 1315 | } |
| 1316 | } |
| 1317 | } |
| 1318 | for (final PipelineOwner child in _children) { |
| 1319 | child.flushPaint(); |
| 1320 | } |
| 1321 | assert( |
| 1322 | _nodesNeedingPaint.isEmpty, |
| 1323 | 'Child PipelineOwners must not dirty nodes in their parent.' , |
| 1324 | ); |
| 1325 | } finally { |
| 1326 | assert(() { |
| 1327 | _debugDoingPaint = false; |
| 1328 | return true; |
| 1329 | }()); |
| 1330 | if (!kReleaseMode) { |
| 1331 | FlutterTimeline.finishSync(); |
| 1332 | } |
| 1333 | } |
| 1334 | } |
| 1335 | |
| 1336 | /// The object that is managing semantics for this pipeline owner, if any. |
| 1337 | /// |
| 1338 | /// An owner is created by [ensureSemantics] or when the [PipelineManifold] to |
| 1339 | /// which this owner is connected has [PipelineManifold.semanticsEnabled] set |
| 1340 | /// to true. The owner is valid for as long as |
| 1341 | /// [PipelineManifold.semanticsEnabled] remains true or while there are |
| 1342 | /// outstanding [SemanticsHandle]s from calls to [ensureSemantics]. The |
| 1343 | /// [semanticsOwner] field will revert to null once both conditions are no |
| 1344 | /// longer met. |
| 1345 | /// |
| 1346 | /// When [semanticsOwner] is null, the [PipelineOwner] skips all steps |
| 1347 | /// relating to semantics. |
| 1348 | SemanticsOwner? get semanticsOwner => _semanticsOwner; |
| 1349 | SemanticsOwner? _semanticsOwner; |
| 1350 | |
| 1351 | /// Deprecated. |
| 1352 | /// |
| 1353 | /// Use [SemanticsBinding.debugOutstandingSemanticsHandles] instead. This |
| 1354 | /// API is broken because an outstanding semantics handle on a given pipeline |
| 1355 | /// owner doesn't mean that semantics are actually produced. |
| 1356 | @Deprecated( |
| 1357 | 'Use SemanticsBinding.debugOutstandingSemanticsHandles instead. ' |
| 1358 | 'This API is broken (see ensureSemantics). ' |
| 1359 | 'This feature was deprecated after v3.22.0-23.0.pre.' , |
| 1360 | ) |
| 1361 | int get debugOutstandingSemanticsHandles => _outstandingSemanticsHandles; |
| 1362 | int _outstandingSemanticsHandles = 0; |
| 1363 | |
| 1364 | /// Deprecated. |
| 1365 | /// |
| 1366 | /// Call [SemanticsBinding.ensureSemantics] instead and optionally add a |
| 1367 | /// listener to [PipelineOwner.semanticsOwner]. This API is broken as calling |
| 1368 | /// it does not guarantee that semantics are produced. |
| 1369 | @Deprecated( |
| 1370 | 'Call SemanticsBinding.ensureSemantics instead and optionally add a listener to PipelineOwner.semanticsOwner. ' |
| 1371 | 'This API is broken; it does not guarantee that semantics are actually produced. ' |
| 1372 | 'This feature was deprecated after v3.22.0-23.0.pre.' , |
| 1373 | ) |
| 1374 | SemanticsHandle ensureSemantics({VoidCallback? listener}) { |
| 1375 | _outstandingSemanticsHandles += 1; |
| 1376 | _updateSemanticsOwner(); |
| 1377 | return _LocalSemanticsHandle._(this, listener); |
| 1378 | } |
| 1379 | |
| 1380 | void _updateSemanticsOwner() { |
| 1381 | if ((_manifold?.semanticsEnabled ?? false) || _outstandingSemanticsHandles > 0) { |
| 1382 | if (_semanticsOwner == null) { |
| 1383 | assert( |
| 1384 | onSemanticsUpdate != null, |
| 1385 | 'Attempted to enable semantics without configuring an onSemanticsUpdate callback.' , |
| 1386 | ); |
| 1387 | _semanticsOwner = SemanticsOwner(onSemanticsUpdate: onSemanticsUpdate!); |
| 1388 | onSemanticsOwnerCreated?.call(); |
| 1389 | } |
| 1390 | } else if (_semanticsOwner != null) { |
| 1391 | _semanticsOwner?.dispose(); |
| 1392 | _semanticsOwner = null; |
| 1393 | onSemanticsOwnerDisposed?.call(); |
| 1394 | } |
| 1395 | } |
| 1396 | |
| 1397 | void _didDisposeSemanticsHandle() { |
| 1398 | assert(_semanticsOwner != null); |
| 1399 | _outstandingSemanticsHandles -= 1; |
| 1400 | _updateSemanticsOwner(); |
| 1401 | } |
| 1402 | |
| 1403 | bool _debugDoingSemantics = false; |
| 1404 | final Set<RenderObject> _nodesNeedingSemantics = <RenderObject>{}; |
| 1405 | |
| 1406 | /// Update the semantics for render objects marked as needing a semantics |
| 1407 | /// update. |
| 1408 | /// |
| 1409 | /// Initially, only the root node, as scheduled by |
| 1410 | /// [RenderObject.scheduleInitialSemantics], needs a semantics update. |
| 1411 | /// |
| 1412 | /// This function is one of the core stages of the rendering pipeline. The |
| 1413 | /// semantics are compiled after painting and only after |
| 1414 | /// [RenderObject.scheduleInitialSemantics] has been called. |
| 1415 | /// |
| 1416 | /// See [RendererBinding] for an example of how this function is used. |
| 1417 | // See [_RenderObjectSemantics]'s documentation for detailed explanations on |
| 1418 | // what this method does |
| 1419 | void flushSemantics() { |
| 1420 | if (_semanticsOwner == null) { |
| 1421 | return; |
| 1422 | } |
| 1423 | if (!kReleaseMode) { |
| 1424 | FlutterTimeline.startSync('SEMANTICS $_debugRootSuffixForTimelineEventNames' ); |
| 1425 | } |
| 1426 | assert(_semanticsOwner != null); |
| 1427 | assert(() { |
| 1428 | _debugDoingSemantics = true; |
| 1429 | return true; |
| 1430 | }()); |
| 1431 | try { |
| 1432 | // This has to be top-to-down order since the geometries of a child and its |
| 1433 | // subtree depends on ancestors' transforms and clips. If it updates child |
| 1434 | // first, it may use dirty geometry in parent's semantics node to |
| 1435 | // calculate the geometries in the subtree. |
| 1436 | final List<RenderObject> nodesToProcess = |
| 1437 | _nodesNeedingSemantics |
| 1438 | .where((RenderObject object) => !object._needsLayout && object.owner == this) |
| 1439 | .toList() |
| 1440 | ..sort((RenderObject a, RenderObject b) => a.depth - b.depth); |
| 1441 | _nodesNeedingSemantics.clear(); |
| 1442 | if (!kReleaseMode) { |
| 1443 | FlutterTimeline.startSync('Semantics.updateChildren' ); |
| 1444 | } |
| 1445 | for (final RenderObject node in nodesToProcess) { |
| 1446 | if (node._semantics.parentDataDirty) { |
| 1447 | // This node is either blocked by a sibling |
| 1448 | // (via SemanticsConfiguration.isBlockingSemanticsOfPreviouslyPaintedNodes) |
| 1449 | // or is hidden by parent through visitChildrenForSemantics. Otherwise, |
| 1450 | // the parent node would have updated this node's parent data and it |
| 1451 | // the not be dirty. |
| 1452 | // |
| 1453 | // Updating the parent data now may create a gap of render object with |
| 1454 | // dirty parent data when this branch later rejoin the rendering tree. |
| 1455 | continue; |
| 1456 | } |
| 1457 | node._semantics.updateChildren(); |
| 1458 | } |
| 1459 | if (!kReleaseMode) { |
| 1460 | FlutterTimeline.finishSync(); |
| 1461 | } |
| 1462 | |
| 1463 | assert(() { |
| 1464 | assert(nodesToProcess.isEmpty || rootNode != null); |
| 1465 | if (rootNode != null) { |
| 1466 | _RenderObjectSemantics.debugCheckForParentData(rootNode!); |
| 1467 | } |
| 1468 | return true; |
| 1469 | }()); |
| 1470 | |
| 1471 | if (!kReleaseMode) { |
| 1472 | FlutterTimeline.startSync('Semantics.ensureGeometry' ); |
| 1473 | } |
| 1474 | for (final RenderObject node in nodesToProcess) { |
| 1475 | if (node._semantics.parentDataDirty) { |
| 1476 | // same as above. |
| 1477 | continue; |
| 1478 | } |
| 1479 | node._semantics.ensureGeometry(); |
| 1480 | } |
| 1481 | if (!kReleaseMode) { |
| 1482 | FlutterTimeline.finishSync(); |
| 1483 | } |
| 1484 | |
| 1485 | if (!kReleaseMode) { |
| 1486 | FlutterTimeline.startSync('Semantics.ensureSemanticsNode' ); |
| 1487 | } |
| 1488 | for (final RenderObject node in nodesToProcess.reversed) { |
| 1489 | if (node._semantics.parentDataDirty) { |
| 1490 | // same as above. |
| 1491 | continue; |
| 1492 | } |
| 1493 | node._semantics.ensureSemanticsNode(); |
| 1494 | } |
| 1495 | if (!kReleaseMode) { |
| 1496 | FlutterTimeline.finishSync(); |
| 1497 | } |
| 1498 | |
| 1499 | _semanticsOwner!.sendSemanticsUpdate(); |
| 1500 | for (final PipelineOwner child in _children) { |
| 1501 | child.flushSemantics(); |
| 1502 | } |
| 1503 | assert( |
| 1504 | _nodesNeedingSemantics.isEmpty, |
| 1505 | 'Child PipelineOwners must not dirty nodes in their parent.' , |
| 1506 | ); |
| 1507 | } finally { |
| 1508 | assert(() { |
| 1509 | _debugDoingSemantics = false; |
| 1510 | return true; |
| 1511 | }()); |
| 1512 | if (!kReleaseMode) { |
| 1513 | FlutterTimeline.finishSync(); |
| 1514 | } |
| 1515 | } |
| 1516 | } |
| 1517 | |
| 1518 | @override |
| 1519 | List<DiagnosticsNode> debugDescribeChildren() { |
| 1520 | return <DiagnosticsNode>[ |
| 1521 | for (final PipelineOwner child in _children) child.toDiagnosticsNode(), |
| 1522 | ]; |
| 1523 | } |
| 1524 | |
| 1525 | @override |
| 1526 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 1527 | super.debugFillProperties(properties); |
| 1528 | properties.add(DiagnosticsProperty<RenderObject>('rootNode' , rootNode, defaultValue: null)); |
| 1529 | } |
| 1530 | |
| 1531 | // TREE MANAGEMENT |
| 1532 | |
| 1533 | final Set<PipelineOwner> _children = <PipelineOwner>{}; |
| 1534 | PipelineManifold? _manifold; |
| 1535 | |
| 1536 | PipelineOwner? _debugParent; |
| 1537 | bool _debugSetParent(PipelineOwner child, PipelineOwner? parent) { |
| 1538 | child._debugParent = parent; |
| 1539 | return true; |
| 1540 | } |
| 1541 | |
| 1542 | String get _debugRootSuffixForTimelineEventNames => _debugParent == null ? ' (root)' : '' ; |
| 1543 | |
| 1544 | /// Mark this [PipelineOwner] as attached to the given [PipelineManifold]. |
| 1545 | /// |
| 1546 | /// Typically, this is only called directly on the root [PipelineOwner]. |
| 1547 | /// Children are automatically attached to their parent's [PipelineManifold] |
| 1548 | /// when [adoptChild] is called. |
| 1549 | void attach(PipelineManifold manifold) { |
| 1550 | assert(_manifold == null); |
| 1551 | _manifold = manifold; |
| 1552 | _manifold!.addListener(_updateSemanticsOwner); |
| 1553 | _updateSemanticsOwner(); |
| 1554 | |
| 1555 | for (final PipelineOwner child in _children) { |
| 1556 | child.attach(manifold); |
| 1557 | } |
| 1558 | } |
| 1559 | |
| 1560 | /// Mark this [PipelineOwner] as detached. |
| 1561 | /// |
| 1562 | /// Typically, this is only called directly on the root [PipelineOwner]. |
| 1563 | /// Children are automatically detached from their parent's [PipelineManifold] |
| 1564 | /// when [dropChild] is called. |
| 1565 | void detach() { |
| 1566 | assert(_manifold != null); |
| 1567 | _manifold!.removeListener(_updateSemanticsOwner); |
| 1568 | _manifold = null; |
| 1569 | // Not updating the semantics owner here to not disrupt any of its clients |
| 1570 | // in case we get re-attached. If necessary, semantics owner will be updated |
| 1571 | // in "attach", or disposed in "dispose", if not reattached. |
| 1572 | |
| 1573 | for (final PipelineOwner child in _children) { |
| 1574 | child.detach(); |
| 1575 | } |
| 1576 | } |
| 1577 | |
| 1578 | // In theory, child list modifications are also disallowed between |
| 1579 | // _debugDoingChildrenLayout and _debugDoingPaint as well as between |
| 1580 | // _debugDoingPaint and _debugDoingSemantics. However, since the associated |
| 1581 | // flush methods are usually called back to back, this gets us close enough. |
| 1582 | bool get _debugAllowChildListModifications => |
| 1583 | !_debugDoingChildLayout && !_debugDoingPaint && !_debugDoingSemantics; |
| 1584 | |
| 1585 | /// Adds `child` to this [PipelineOwner]. |
| 1586 | /// |
| 1587 | /// During the phases of frame production (see [RendererBinding.drawFrame]), |
| 1588 | /// the parent [PipelineOwner] will complete a phase for the nodes it owns |
| 1589 | /// directly before invoking the flush method corresponding to the current |
| 1590 | /// phase on its child [PipelineOwner]s. For example, during layout, the |
| 1591 | /// parent [PipelineOwner] will first lay out its own nodes before calling |
| 1592 | /// [flushLayout] on its children. During paint, it will first paint its own |
| 1593 | /// nodes before calling [flushPaint] on its children. This order also applies |
| 1594 | /// for all the other phases. |
| 1595 | /// |
| 1596 | /// No assumptions must be made about the order in which child |
| 1597 | /// [PipelineOwner]s are flushed. |
| 1598 | /// |
| 1599 | /// No new children may be added after the [PipelineOwner] has started calling |
| 1600 | /// [flushLayout] on any of its children until the end of the current frame. |
| 1601 | /// |
| 1602 | /// To remove a child, call [dropChild]. |
| 1603 | void adoptChild(PipelineOwner child) { |
| 1604 | assert(child._debugParent == null); |
| 1605 | assert(!_children.contains(child)); |
| 1606 | assert(_debugAllowChildListModifications, 'Cannot modify child list after layout.' ); |
| 1607 | _children.add(child); |
| 1608 | if (!kReleaseMode) { |
| 1609 | _debugSetParent(child, this); |
| 1610 | } |
| 1611 | if (_manifold != null) { |
| 1612 | child.attach(_manifold!); |
| 1613 | } |
| 1614 | } |
| 1615 | |
| 1616 | /// Removes a child [PipelineOwner] previously added via [adoptChild]. |
| 1617 | /// |
| 1618 | /// This node will cease to call the flush methods on the `child` during frame |
| 1619 | /// production. |
| 1620 | /// |
| 1621 | /// No children may be removed after the [PipelineOwner] has started calling |
| 1622 | /// [flushLayout] on any of its children until the end of the current frame. |
| 1623 | void dropChild(PipelineOwner child) { |
| 1624 | assert(child._debugParent == this); |
| 1625 | assert(_children.contains(child)); |
| 1626 | assert(_debugAllowChildListModifications, 'Cannot modify child list after layout.' ); |
| 1627 | _children.remove(child); |
| 1628 | if (!kReleaseMode) { |
| 1629 | _debugSetParent(child, null); |
| 1630 | } |
| 1631 | if (_manifold != null) { |
| 1632 | child.detach(); |
| 1633 | } |
| 1634 | } |
| 1635 | |
| 1636 | /// Calls `visitor` for each immediate child of this [PipelineOwner]. |
| 1637 | /// |
| 1638 | /// See also: |
| 1639 | /// |
| 1640 | /// * [adoptChild] to add a child. |
| 1641 | /// * [dropChild] to remove a child. |
| 1642 | void visitChildren(PipelineOwnerVisitor visitor) { |
| 1643 | _children.forEach(visitor); |
| 1644 | } |
| 1645 | |
| 1646 | /// Release any resources held by this pipeline owner. |
| 1647 | /// |
| 1648 | /// Prior to calling this method the pipeline owner must be removed from the |
| 1649 | /// pipeline owner tree, i.e. it must have neither a parent nor any children |
| 1650 | /// (see [dropChild]). It also must be [detach]ed from any [PipelineManifold]. |
| 1651 | /// |
| 1652 | /// The object is no longer usable after calling dispose. |
| 1653 | void dispose() { |
| 1654 | assert(_children.isEmpty); |
| 1655 | assert(rootNode == null); |
| 1656 | assert(_manifold == null); |
| 1657 | assert(_debugParent == null); |
| 1658 | assert(debugMaybeDispatchDisposed(this)); |
| 1659 | _semanticsOwner?.dispose(); |
| 1660 | _semanticsOwner = null; |
| 1661 | _nodesNeedingLayout.clear(); |
| 1662 | _nodesNeedingCompositingBitsUpdate.clear(); |
| 1663 | _nodesNeedingPaint.clear(); |
| 1664 | _nodesNeedingSemantics.clear(); |
| 1665 | } |
| 1666 | } |
| 1667 | |
| 1668 | /// Signature for the callback to [PipelineOwner.visitChildren]. |
| 1669 | /// |
| 1670 | /// The argument is the child being visited. |
| 1671 | typedef PipelineOwnerVisitor = void Function(PipelineOwner child); |
| 1672 | |
| 1673 | /// Manages a tree of [PipelineOwner]s. |
| 1674 | /// |
| 1675 | /// All [PipelineOwner]s within a tree are attached to the same |
| 1676 | /// [PipelineManifold], which gives them access to shared functionality such |
| 1677 | /// as requesting a visual update (by calling [requestVisualUpdate]). As such, |
| 1678 | /// the [PipelineManifold] gives the [PipelineOwner]s access to functionality |
| 1679 | /// usually provided by the bindings without tying the [PipelineOwner]s to a |
| 1680 | /// particular binding implementation. |
| 1681 | /// |
| 1682 | /// The root of the [PipelineOwner] tree is attached to a [PipelineManifold] by |
| 1683 | /// passing the manifold to [PipelineOwner.attach]. Children are attached to the |
| 1684 | /// same [PipelineManifold] as their parent when they are adopted via |
| 1685 | /// [PipelineOwner.adoptChild]. |
| 1686 | /// |
| 1687 | /// [PipelineOwner]s can register listeners with the [PipelineManifold] to be |
| 1688 | /// informed when certain values provided by the [PipelineManifold] change. |
| 1689 | abstract class PipelineManifold implements Listenable { |
| 1690 | /// Whether [PipelineOwner]s connected to this [PipelineManifold] should |
| 1691 | /// collect semantics information and produce a semantics tree. |
| 1692 | /// |
| 1693 | /// The [PipelineManifold] notifies its listeners (managed with [addListener] |
| 1694 | /// and [removeListener]) when this property changes its value. |
| 1695 | /// |
| 1696 | /// See also: |
| 1697 | /// |
| 1698 | /// * [SemanticsBinding.semanticsEnabled], which [PipelineManifold] |
| 1699 | /// implementations typically use to back this property. |
| 1700 | bool get semanticsEnabled; |
| 1701 | |
| 1702 | /// Called by a [PipelineOwner] connected to this [PipelineManifold] when a |
| 1703 | /// [RenderObject] associated with that pipeline owner wishes to update its |
| 1704 | /// visual appearance. |
| 1705 | /// |
| 1706 | /// Typical implementations of this function will schedule a task to flush the |
| 1707 | /// various stages of the pipeline. This function might be called multiple |
| 1708 | /// times in quick succession. Implementations should take care to discard |
| 1709 | /// duplicate calls quickly. |
| 1710 | /// |
| 1711 | /// A [PipelineOwner] connected to this [PipelineManifold] will call |
| 1712 | /// [PipelineOwner.onNeedVisualUpdate] instead of this method if it has been |
| 1713 | /// configured with a non-null [PipelineOwner.onNeedVisualUpdate] callback. |
| 1714 | /// |
| 1715 | /// See also: |
| 1716 | /// |
| 1717 | /// * [SchedulerBinding.ensureVisualUpdate], which [PipelineManifold] |
| 1718 | /// implementations typically call to implement this method. |
| 1719 | void requestVisualUpdate(); |
| 1720 | } |
| 1721 | |
| 1722 | /// An object in the render tree. |
| 1723 | /// |
| 1724 | /// The [RenderObject] class hierarchy is the core of the rendering |
| 1725 | /// library's reason for being. |
| 1726 | /// |
| 1727 | /// {@youtube 560 315 https://www.youtube.com/watch?v=zmbmrw07qBc} |
| 1728 | /// |
| 1729 | /// [RenderObject]s have a [parent], and have a slot called [parentData] in |
| 1730 | /// which the parent [RenderObject] can store child-specific data, for example, |
| 1731 | /// the child position. The [RenderObject] class also implements the basic |
| 1732 | /// layout and paint protocols. |
| 1733 | /// |
| 1734 | /// The [RenderObject] class, however, does not define a child model (e.g. |
| 1735 | /// whether a node has zero, one, or more children). It also doesn't define a |
| 1736 | /// coordinate system (e.g. whether children are positioned in Cartesian |
| 1737 | /// coordinates, in polar coordinates, etc) or a specific layout protocol (e.g. |
| 1738 | /// whether the layout is width-in-height-out, or constraint-in-size-out, or |
| 1739 | /// whether the parent sets the size and position of the child before or after |
| 1740 | /// the child lays out, etc; or indeed whether the children are allowed to read |
| 1741 | /// their parent's [parentData] slot). |
| 1742 | /// |
| 1743 | /// The [RenderBox] subclass introduces the opinion that the layout |
| 1744 | /// system uses Cartesian coordinates. |
| 1745 | /// |
| 1746 | /// ## Lifecycle |
| 1747 | /// |
| 1748 | /// A [RenderObject] must [dispose] when it is no longer needed. The creator |
| 1749 | /// of the object is responsible for disposing of it. Typically, the creator is |
| 1750 | /// a [RenderObjectElement], and that element will dispose the object it creates |
| 1751 | /// when it is unmounted. |
| 1752 | /// |
| 1753 | /// [RenderObject]s are responsible for cleaning up any expensive resources |
| 1754 | /// they hold when [dispose] is called, such as [Picture] or [Image] objects. |
| 1755 | /// This includes any [Layer]s that the render object has directly created. The |
| 1756 | /// base implementation of dispose will nullify the [layer] property. Subclasses |
| 1757 | /// must also nullify any other layer(s) it directly creates. |
| 1758 | /// |
| 1759 | /// ## Writing a RenderObject subclass |
| 1760 | /// |
| 1761 | /// In most cases, subclassing [RenderObject] itself is overkill, and |
| 1762 | /// [RenderBox] would be a better starting point. However, if a render object |
| 1763 | /// doesn't want to use a Cartesian coordinate system, then it should indeed |
| 1764 | /// inherit from [RenderObject] directly. This allows it to define its own |
| 1765 | /// layout protocol by using a new subclass of [Constraints] rather than using |
| 1766 | /// [BoxConstraints], and by potentially using an entirely new set of objects |
| 1767 | /// and values to represent the result of the output rather than just a [Size]. |
| 1768 | /// This increased flexibility comes at the cost of not being able to rely on |
| 1769 | /// the features of [RenderBox]. For example, [RenderBox] implements an |
| 1770 | /// intrinsic sizing protocol that allows you to measure a child without fully |
| 1771 | /// laying it out, in such a way that if that child changes size, the parent |
| 1772 | /// will be laid out again (to take into account the new dimensions of the |
| 1773 | /// child). This is a subtle and bug-prone feature to get right. |
| 1774 | /// |
| 1775 | /// Most aspects of writing a [RenderBox] apply to writing a [RenderObject] as |
| 1776 | /// well, and therefore the discussion at [RenderBox] is recommended background |
| 1777 | /// reading. The main differences are around layout and hit testing, since those |
| 1778 | /// are the aspects that [RenderBox] primarily specializes. |
| 1779 | /// |
| 1780 | /// ### Layout |
| 1781 | /// |
| 1782 | /// A layout protocol begins with a subclass of [Constraints]. See the |
| 1783 | /// discussion at [Constraints] for more information on how to write a |
| 1784 | /// [Constraints] subclass. |
| 1785 | /// |
| 1786 | /// The [performLayout] method should take the [constraints], and apply them. |
| 1787 | /// The output of the layout algorithm is fields set on the object that describe |
| 1788 | /// the geometry of the object for the purposes of the parent's layout. For |
| 1789 | /// example, with [RenderBox] the output is the [RenderBox.size] field. This |
| 1790 | /// output should only be read by the parent if the parent specified |
| 1791 | /// `parentUsesSize` as true when calling [layout] on the child. |
| 1792 | /// |
| 1793 | /// Anytime anything changes on a render object that would affect the layout of |
| 1794 | /// that object, it should call [markNeedsLayout]. |
| 1795 | /// |
| 1796 | /// ### Hit Testing |
| 1797 | /// |
| 1798 | /// Hit testing is even more open-ended than layout. There is no method to |
| 1799 | /// override, you are expected to provide one. |
| 1800 | /// |
| 1801 | /// The general behavior of your hit-testing method should be similar to the |
| 1802 | /// behavior described for [RenderBox]. The main difference is that the input |
| 1803 | /// need not be an [Offset]. You are also allowed to use a different subclass of |
| 1804 | /// [HitTestEntry] when adding entries to the [HitTestResult]. When the |
| 1805 | /// [handleEvent] method is called, the same object that was added to the |
| 1806 | /// [HitTestResult] will be passed in, so it can be used to track information |
| 1807 | /// like the precise coordinate of the hit, in whatever coordinate system is |
| 1808 | /// used by the new layout protocol. |
| 1809 | /// |
| 1810 | /// ### Adapting from one protocol to another |
| 1811 | /// |
| 1812 | /// In general, the root of a Flutter render object tree is a [RenderView]. This |
| 1813 | /// object has a single child, which must be a [RenderBox]. Thus, if you want to |
| 1814 | /// have a custom [RenderObject] subclass in the render tree, you have two |
| 1815 | /// choices: you either need to replace the [RenderView] itself, or you need to |
| 1816 | /// have a [RenderBox] that has your class as its child. (The latter is the much |
| 1817 | /// more common case.) |
| 1818 | /// |
| 1819 | /// This [RenderBox] subclass converts from the box protocol to the protocol of |
| 1820 | /// your class. |
| 1821 | /// |
| 1822 | /// In particular, this means that for hit testing it overrides |
| 1823 | /// [RenderBox.hitTest], and calls whatever method you have in your class for |
| 1824 | /// hit testing. |
| 1825 | /// |
| 1826 | /// Similarly, it overrides [performLayout] to create a [Constraints] object |
| 1827 | /// appropriate for your class and passes that to the child's [layout] method. |
| 1828 | /// |
| 1829 | /// ### Layout interactions between render objects |
| 1830 | /// |
| 1831 | /// In general, the layout of a render object should only depend on the output of |
| 1832 | /// its child's layout, and then only if `parentUsesSize` is set to true in the |
| 1833 | /// [layout] call. Furthermore, if it is set to true, the parent must call the |
| 1834 | /// child's [layout] if the child is to be rendered, because otherwise the |
| 1835 | /// parent will not be notified when the child changes its layout outputs. |
| 1836 | /// |
| 1837 | /// It is possible to set up render object protocols that transfer additional |
| 1838 | /// information. For example, in the [RenderBox] protocol you can query your |
| 1839 | /// children's intrinsic dimensions and baseline geometry. However, if this is |
| 1840 | /// done then it is imperative that the child call [markNeedsLayout] on the |
| 1841 | /// parent any time that additional information changes, if the parent used it |
| 1842 | /// in the last layout phase. For an example of how to implement this, see the |
| 1843 | /// [RenderBox.markNeedsLayout] method. It overrides |
| 1844 | /// [RenderObject.markNeedsLayout] so that if a parent has queried the intrinsic |
| 1845 | /// or baseline information, it gets marked dirty whenever the child's geometry |
| 1846 | /// changes. |
| 1847 | abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarget { |
| 1848 | /// Initializes internal fields for subclasses. |
| 1849 | RenderObject() { |
| 1850 | assert(debugMaybeDispatchCreated('rendering' , 'RenderObject' , this)); |
| 1851 | _needsCompositing = isRepaintBoundary || alwaysNeedsCompositing; |
| 1852 | _wasRepaintBoundary = isRepaintBoundary; |
| 1853 | } |
| 1854 | |
| 1855 | /// Cause the entire subtree rooted at the given [RenderObject] to be marked |
| 1856 | /// dirty for layout, paint, etc, so that the effects of a hot reload can be |
| 1857 | /// seen, or so that the effect of changing a global debug flag (such as |
| 1858 | /// [debugPaintSizeEnabled]) can be applied. |
| 1859 | /// |
| 1860 | /// This is called by the [RendererBinding] in response to the |
| 1861 | /// `ext.flutter.reassemble` hook, which is used by development tools when the |
| 1862 | /// application code has changed, to cause the widget tree to pick up any |
| 1863 | /// changed implementations. |
| 1864 | /// |
| 1865 | /// This is expensive and should not be called except during development. |
| 1866 | /// |
| 1867 | /// See also: |
| 1868 | /// |
| 1869 | /// * [BindingBase.reassembleApplication] |
| 1870 | void reassemble() { |
| 1871 | markNeedsLayout(); |
| 1872 | markNeedsCompositingBitsUpdate(); |
| 1873 | markNeedsPaint(); |
| 1874 | markNeedsSemanticsUpdate(); |
| 1875 | visitChildren((RenderObject child) { |
| 1876 | child.reassemble(); |
| 1877 | }); |
| 1878 | } |
| 1879 | |
| 1880 | /// Whether this has been disposed. |
| 1881 | /// |
| 1882 | /// If asserts are disabled, this property is always null. |
| 1883 | bool? get debugDisposed { |
| 1884 | bool? disposed; |
| 1885 | assert(() { |
| 1886 | disposed = _debugDisposed; |
| 1887 | return true; |
| 1888 | }()); |
| 1889 | return disposed; |
| 1890 | } |
| 1891 | |
| 1892 | bool _debugDisposed = false; |
| 1893 | |
| 1894 | /// Release any resources held by this render object. |
| 1895 | /// |
| 1896 | /// The object that creates a RenderObject is in charge of disposing it. |
| 1897 | /// If this render object has created any children directly, it must dispose |
| 1898 | /// of those children in this method as well. It must not dispose of any |
| 1899 | /// children that were created by some other object, such as |
| 1900 | /// a [RenderObjectElement]. Those children will be disposed when that |
| 1901 | /// element unmounts, which may be delayed if the element is moved to another |
| 1902 | /// part of the tree. |
| 1903 | /// |
| 1904 | /// Implementations of this method must end with a call to the inherited |
| 1905 | /// method, as in `super.dispose()`. |
| 1906 | /// |
| 1907 | /// The object is no longer usable after calling dispose. |
| 1908 | @mustCallSuper |
| 1909 | void dispose() { |
| 1910 | assert(!_debugDisposed); |
| 1911 | assert(debugMaybeDispatchDisposed(this)); |
| 1912 | _layerHandle.layer = null; |
| 1913 | assert(() { |
| 1914 | // TODO(dnfield): Enable this assert once clients have had a chance to |
| 1915 | // migrate. |
| 1916 | // visitChildren((RenderObject child) { |
| 1917 | // assert( |
| 1918 | // child.debugDisposed!, |
| 1919 | // '${child.runtimeType} (child of $runtimeType) must be disposed before calling super.dispose().', |
| 1920 | // ); |
| 1921 | // }); |
| 1922 | _debugDisposed = true; |
| 1923 | return true; |
| 1924 | }()); |
| 1925 | } |
| 1926 | |
| 1927 | // LAYOUT |
| 1928 | |
| 1929 | /// Data for use by the parent render object. |
| 1930 | /// |
| 1931 | /// The parent data is used by the render object that lays out this object |
| 1932 | /// (typically this object's parent in the render tree) to store information |
| 1933 | /// relevant to itself and to any other nodes who happen to know exactly what |
| 1934 | /// the data means. The parent data is opaque to the child. |
| 1935 | /// |
| 1936 | /// * The parent data field must not be directly set, except by calling |
| 1937 | /// [setupParentData] on the parent node. |
| 1938 | /// * The parent data can be set before the child is added to the parent, by |
| 1939 | /// calling [setupParentData] on the future parent node. |
| 1940 | /// * The conventions for using the parent data depend on the layout protocol |
| 1941 | /// used between the parent and child. For example, in box layout, the |
| 1942 | /// parent data is completely opaque but in sector layout the child is |
| 1943 | /// permitted to read some fields of the parent data. |
| 1944 | ParentData? parentData; |
| 1945 | |
| 1946 | /// Override to setup parent data correctly for your children. |
| 1947 | /// |
| 1948 | /// You can call this function to set up the parent data for child before the |
| 1949 | /// child is added to the parent's child list. |
| 1950 | void setupParentData(covariant RenderObject child) { |
| 1951 | assert(_debugCanPerformMutations); |
| 1952 | if (child.parentData is! ParentData) { |
| 1953 | child.parentData = ParentData(); |
| 1954 | } |
| 1955 | } |
| 1956 | |
| 1957 | /// The depth of this render object in the render tree. |
| 1958 | /// |
| 1959 | /// The depth of nodes in a tree monotonically increases as you traverse down |
| 1960 | /// the tree: a node always has a [depth] greater than its ancestors. |
| 1961 | /// There's no guarantee regarding depth between siblings. |
| 1962 | /// |
| 1963 | /// The [depth] of a child can be more than one greater than the [depth] of |
| 1964 | /// the parent, because the [depth] values are never decreased: all that |
| 1965 | /// matters is that it's greater than the parent. Consider a tree with a root |
| 1966 | /// node A, a child B, and a grandchild C. Initially, A will have [depth] 0, |
| 1967 | /// B [depth] 1, and C [depth] 2. If C is moved to be a child of A, |
| 1968 | /// sibling of B, then the numbers won't change. C's [depth] will still be 2. |
| 1969 | /// |
| 1970 | /// The depth of a node is used to ensure that nodes are processed in |
| 1971 | /// depth order. The [depth] is automatically maintained by the [adoptChild] |
| 1972 | /// and [dropChild] methods. |
| 1973 | int get depth => _depth; |
| 1974 | int _depth = 0; |
| 1975 | |
| 1976 | /// Adjust the [depth] of the given [child] to be greater than this node's own |
| 1977 | /// [depth]. |
| 1978 | /// |
| 1979 | /// Only call this method from overrides of [redepthChildren]. |
| 1980 | @protected |
| 1981 | void redepthChild(RenderObject child) { |
| 1982 | assert(child.owner == owner); |
| 1983 | if (child._depth <= _depth) { |
| 1984 | child._depth = _depth + 1; |
| 1985 | child.redepthChildren(); |
| 1986 | } |
| 1987 | } |
| 1988 | |
| 1989 | /// Adjust the [depth] of this node's children, if any. |
| 1990 | /// |
| 1991 | /// Override this method in subclasses with child nodes to call [redepthChild] |
| 1992 | /// for each child. Do not call this method directly. |
| 1993 | @protected |
| 1994 | void redepthChildren() {} |
| 1995 | |
| 1996 | /// The parent of this render object in the render tree. |
| 1997 | /// |
| 1998 | /// The [parent] of the root node in the render tree is null. |
| 1999 | RenderObject? get parent => _parent; |
| 2000 | RenderObject? _parent; |
| 2001 | |
| 2002 | /// The semantics parent of this render object in the semantics tree. |
| 2003 | /// |
| 2004 | /// This is typically the same as [parent]. |
| 2005 | /// |
| 2006 | /// [OverlayPortal] overrides this field to change how it forms its |
| 2007 | /// semantics sub-tree. |
| 2008 | @visibleForOverriding |
| 2009 | RenderObject? get semanticsParent => _parent; |
| 2010 | |
| 2011 | /// Called by subclasses when they decide a render object is a child. |
| 2012 | /// |
| 2013 | /// Only for use by subclasses when changing their child lists. Calling this |
| 2014 | /// in other cases will lead to an inconsistent tree and probably cause crashes. |
| 2015 | @mustCallSuper |
| 2016 | @protected |
| 2017 | void adoptChild(RenderObject child) { |
| 2018 | assert(child._parent == null); |
| 2019 | assert(() { |
| 2020 | RenderObject node = this; |
| 2021 | while (node.parent != null) { |
| 2022 | node = node.parent!; |
| 2023 | } |
| 2024 | assert(node != child); // indicates we are about to create a cycle |
| 2025 | return true; |
| 2026 | }()); |
| 2027 | |
| 2028 | setupParentData(child); |
| 2029 | markNeedsLayout(); |
| 2030 | markNeedsCompositingBitsUpdate(); |
| 2031 | markNeedsSemanticsUpdate(); |
| 2032 | child._parent = this; |
| 2033 | if (attached) { |
| 2034 | child.attach(_owner!); |
| 2035 | } |
| 2036 | redepthChild(child); |
| 2037 | } |
| 2038 | |
| 2039 | /// Called by subclasses when they decide a render object is no longer a child. |
| 2040 | /// |
| 2041 | /// Only for use by subclasses when changing their child lists. Calling this |
| 2042 | /// in other cases will lead to an inconsistent tree and probably cause crashes. |
| 2043 | @mustCallSuper |
| 2044 | @protected |
| 2045 | void dropChild(RenderObject child) { |
| 2046 | assert(child._parent == this); |
| 2047 | assert(child.attached == attached); |
| 2048 | assert(child.parentData != null); |
| 2049 | if (!(_isRelayoutBoundary ?? true)) { |
| 2050 | child._isRelayoutBoundary = null; |
| 2051 | } |
| 2052 | child.parentData!.detach(); |
| 2053 | child.parentData = null; |
| 2054 | child._parent = null; |
| 2055 | if (attached) { |
| 2056 | child.detach(); |
| 2057 | } |
| 2058 | markNeedsLayout(); |
| 2059 | markNeedsCompositingBitsUpdate(); |
| 2060 | markNeedsSemanticsUpdate(); |
| 2061 | } |
| 2062 | |
| 2063 | /// Calls visitor for each immediate child of this render object. |
| 2064 | /// |
| 2065 | /// Override in subclasses with children and call the visitor for each child. |
| 2066 | void visitChildren(RenderObjectVisitor visitor) {} |
| 2067 | |
| 2068 | /// The object responsible for creating this render object. |
| 2069 | /// |
| 2070 | /// Used in debug messages. |
| 2071 | /// |
| 2072 | /// See also: |
| 2073 | /// |
| 2074 | /// * [DebugCreator], which the [widgets] library uses as values for this field. |
| 2075 | Object? debugCreator; |
| 2076 | |
| 2077 | void _reportException(String method, Object exception, StackTrace stack) { |
| 2078 | FlutterError.reportError( |
| 2079 | FlutterErrorDetails( |
| 2080 | exception: exception, |
| 2081 | stack: stack, |
| 2082 | library: 'rendering library' , |
| 2083 | context: ErrorDescription('during $method()' ), |
| 2084 | informationCollector: () => <DiagnosticsNode>[ |
| 2085 | // debugCreator should always be null outside of debugMode, but we want |
| 2086 | // the tree shaker to notice this. |
| 2087 | if (kDebugMode && debugCreator != null) DiagnosticsDebugCreator(debugCreator!), |
| 2088 | describeForError( |
| 2089 | 'The following RenderObject was being processed when the exception was fired' , |
| 2090 | ), |
| 2091 | // TODO(jacobr): this error message has a code smell. Consider whether |
| 2092 | // displaying the truncated children is really useful for command line |
| 2093 | // users. Inspector users can see the full tree by clicking on the |
| 2094 | // render object so this may not be that useful. |
| 2095 | describeForError('RenderObject' , style: DiagnosticsTreeStyle.truncateChildren), |
| 2096 | ], |
| 2097 | ), |
| 2098 | ); |
| 2099 | } |
| 2100 | |
| 2101 | /// Whether [performResize] for this render object is currently running. |
| 2102 | /// |
| 2103 | /// Only valid when asserts are enabled. In release builds, always returns |
| 2104 | /// false. |
| 2105 | bool get debugDoingThisResize => _debugDoingThisResize; |
| 2106 | bool _debugDoingThisResize = false; |
| 2107 | |
| 2108 | /// Whether [performLayout] for this render object is currently running. |
| 2109 | /// |
| 2110 | /// Only valid when asserts are enabled. In release builds, always returns |
| 2111 | /// false. |
| 2112 | bool get debugDoingThisLayout => _debugDoingThisLayout; |
| 2113 | bool _debugDoingThisLayout = false; |
| 2114 | |
| 2115 | /// The render object that is actively computing layout. |
| 2116 | /// |
| 2117 | /// Only valid when asserts are enabled. In release builds, always returns |
| 2118 | /// null. |
| 2119 | static RenderObject? get debugActiveLayout => _debugActiveLayout; |
| 2120 | static RenderObject? _debugActiveLayout; |
| 2121 | |
| 2122 | /// Set [debugActiveLayout] to null when [inner] callback is called. |
| 2123 | /// This is useful when you have to temporarily clear that variable to |
| 2124 | /// disable some false-positive checks, such as when computing toStringDeep |
| 2125 | /// or using custom trees. |
| 2126 | @pragma('dart2js:tryInline' ) |
| 2127 | @pragma('vm:prefer-inline' ) |
| 2128 | @pragma('wasm:prefer-inline' ) |
| 2129 | static T _withDebugActiveLayoutCleared<T>(T Function() inner) { |
| 2130 | RenderObject? debugPreviousActiveLayout; |
| 2131 | assert(() { |
| 2132 | debugPreviousActiveLayout = _debugActiveLayout; |
| 2133 | _debugActiveLayout = null; |
| 2134 | return true; |
| 2135 | }()); |
| 2136 | final T result = inner(); |
| 2137 | assert(() { |
| 2138 | _debugActiveLayout = debugPreviousActiveLayout; |
| 2139 | return true; |
| 2140 | }()); |
| 2141 | return result; |
| 2142 | } |
| 2143 | |
| 2144 | /// Whether the parent render object is permitted to use this render object's |
| 2145 | /// size. |
| 2146 | /// |
| 2147 | /// Determined by the `parentUsesSize` parameter to [layout]. |
| 2148 | /// |
| 2149 | /// Only valid when asserts are enabled. In release builds, throws. |
| 2150 | bool get debugCanParentUseSize => _debugCanParentUseSize!; |
| 2151 | bool? _debugCanParentUseSize; |
| 2152 | |
| 2153 | bool _debugMutationsLocked = false; |
| 2154 | |
| 2155 | /// Whether tree mutations are currently permitted. |
| 2156 | /// |
| 2157 | /// This is only useful during layout. One should also not mutate the tree at |
| 2158 | /// other times (e.g. during paint or while assembling the semantic tree) but |
| 2159 | /// this function does not currently enforce those conventions. |
| 2160 | /// |
| 2161 | /// Only valid when asserts are enabled. This will throw in release builds. |
| 2162 | bool get _debugCanPerformMutations { |
| 2163 | late bool result; |
| 2164 | assert(() { |
| 2165 | if (_debugDisposed) { |
| 2166 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 2167 | ErrorSummary('A disposed RenderObject was mutated.' ), |
| 2168 | DiagnosticsProperty<RenderObject>( |
| 2169 | 'The disposed RenderObject was' , |
| 2170 | this, |
| 2171 | style: DiagnosticsTreeStyle.errorProperty, |
| 2172 | ), |
| 2173 | ]); |
| 2174 | } |
| 2175 | |
| 2176 | final PipelineOwner? owner = this.owner; |
| 2177 | // Detached nodes are allowed to mutate and the "can perform mutations" |
| 2178 | // check will be performed when they re-attach. This assert is only useful |
| 2179 | // during layout. |
| 2180 | if (owner == null || !owner.debugDoingLayout) { |
| 2181 | result = true; |
| 2182 | return true; |
| 2183 | } |
| 2184 | |
| 2185 | RenderObject? activeLayoutRoot = this; |
| 2186 | while (activeLayoutRoot != null) { |
| 2187 | final bool mutationsToDirtySubtreesAllowed = |
| 2188 | activeLayoutRoot.owner?._debugAllowMutationsToDirtySubtrees ?? false; |
| 2189 | final bool doingLayoutWithCallback = activeLayoutRoot._doingThisLayoutWithCallback; |
| 2190 | // Mutations on this subtree is allowed when: |
| 2191 | // - the "activeLayoutRoot" subtree is being mutated in a layout callback. |
| 2192 | // - a different part of the render tree is doing a layout callback, |
| 2193 | // and this subtree is being reparented to that subtree, as a result |
| 2194 | // of global key reparenting. |
| 2195 | if (doingLayoutWithCallback || |
| 2196 | mutationsToDirtySubtreesAllowed && activeLayoutRoot._needsLayout) { |
| 2197 | result = true; |
| 2198 | return true; |
| 2199 | } |
| 2200 | |
| 2201 | if (!activeLayoutRoot._debugMutationsLocked) { |
| 2202 | activeLayoutRoot = activeLayoutRoot.debugLayoutParent; |
| 2203 | } else { |
| 2204 | // activeLayoutRoot found. |
| 2205 | break; |
| 2206 | } |
| 2207 | } |
| 2208 | |
| 2209 | final RenderObject debugActiveLayout = RenderObject.debugActiveLayout!; |
| 2210 | final String culpritMethodName = debugActiveLayout.debugDoingThisLayout |
| 2211 | ? 'performLayout' |
| 2212 | : 'performResize' ; |
| 2213 | final String culpritFullMethodName = ' ${debugActiveLayout.runtimeType}. $culpritMethodName' ; |
| 2214 | result = false; |
| 2215 | |
| 2216 | if (activeLayoutRoot == null) { |
| 2217 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 2218 | ErrorSummary('A $runtimeType was mutated in $culpritFullMethodName.' ), |
| 2219 | ErrorDescription( |
| 2220 | 'The RenderObject was mutated when none of its ancestors is actively performing layout.' , |
| 2221 | ), |
| 2222 | DiagnosticsProperty<RenderObject>( |
| 2223 | 'The RenderObject being mutated was' , |
| 2224 | this, |
| 2225 | style: DiagnosticsTreeStyle.errorProperty, |
| 2226 | ), |
| 2227 | DiagnosticsProperty<RenderObject>( |
| 2228 | 'The RenderObject that was mutating the said $runtimeType was' , |
| 2229 | debugActiveLayout, |
| 2230 | style: DiagnosticsTreeStyle.errorProperty, |
| 2231 | ), |
| 2232 | ]); |
| 2233 | } |
| 2234 | |
| 2235 | if (activeLayoutRoot == this) { |
| 2236 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 2237 | ErrorSummary('A $runtimeType was mutated in its own $culpritMethodName implementation.' ), |
| 2238 | ErrorDescription('A RenderObject must not re-dirty itself while still being laid out.' ), |
| 2239 | DiagnosticsProperty<RenderObject>( |
| 2240 | 'The RenderObject being mutated was' , |
| 2241 | this, |
| 2242 | style: DiagnosticsTreeStyle.errorProperty, |
| 2243 | ), |
| 2244 | ErrorHint( |
| 2245 | 'Consider using the LayoutBuilder widget to dynamically change a subtree during layout.' , |
| 2246 | ), |
| 2247 | ]); |
| 2248 | } |
| 2249 | |
| 2250 | final ErrorSummary summary = ErrorSummary( |
| 2251 | 'A $runtimeType was mutated in $culpritFullMethodName.' , |
| 2252 | ); |
| 2253 | final bool isMutatedByAncestor = activeLayoutRoot == debugActiveLayout; |
| 2254 | final String description = isMutatedByAncestor |
| 2255 | ? 'A RenderObject must not mutate its descendants in its $culpritMethodName method.' |
| 2256 | : 'A RenderObject must not mutate another RenderObject from a different render subtree ' |
| 2257 | 'in its $culpritMethodName method.' ; |
| 2258 | |
| 2259 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 2260 | summary, |
| 2261 | ErrorDescription(description), |
| 2262 | DiagnosticsProperty<RenderObject>( |
| 2263 | 'The RenderObject being mutated was' , |
| 2264 | this, |
| 2265 | style: DiagnosticsTreeStyle.errorProperty, |
| 2266 | ), |
| 2267 | DiagnosticsProperty<RenderObject>( |
| 2268 | 'The ${isMutatedByAncestor ? 'ancestor ' : '' }RenderObject that was mutating the said $runtimeType was' , |
| 2269 | debugActiveLayout, |
| 2270 | style: DiagnosticsTreeStyle.errorProperty, |
| 2271 | ), |
| 2272 | if (!isMutatedByAncestor) |
| 2273 | DiagnosticsProperty<RenderObject>( |
| 2274 | 'Their common ancestor was' , |
| 2275 | activeLayoutRoot, |
| 2276 | style: DiagnosticsTreeStyle.errorProperty, |
| 2277 | ), |
| 2278 | ErrorHint( |
| 2279 | 'Mutating the layout of another RenderObject may cause some RenderObjects in its subtree to be laid out more than once. ' |
| 2280 | 'Consider using the LayoutBuilder widget to dynamically mutate a subtree during layout.' , |
| 2281 | ), |
| 2282 | ]); |
| 2283 | }()); |
| 2284 | return result; |
| 2285 | } |
| 2286 | |
| 2287 | /// The [RenderObject] that's expected to call [layout] on this [RenderObject] |
| 2288 | /// in its [performLayout] implementation. |
| 2289 | /// |
| 2290 | /// This method is used to implement an assert that ensures the render subtree |
| 2291 | /// actively performing layout can not get accidentally mutated. It's only |
| 2292 | /// implemented in debug mode and always returns null in release mode. |
| 2293 | /// |
| 2294 | /// The default implementation returns [parent] and overriding is rarely |
| 2295 | /// needed. A [RenderObject] subclass that expects its |
| 2296 | /// [RenderObject.performLayout] to be called from a different [RenderObject] |
| 2297 | /// that's not its [parent] should override this property to return the actual |
| 2298 | /// layout parent. |
| 2299 | @protected |
| 2300 | RenderObject? get debugLayoutParent { |
| 2301 | RenderObject? layoutParent; |
| 2302 | assert(() { |
| 2303 | layoutParent = parent; |
| 2304 | return true; |
| 2305 | }()); |
| 2306 | return layoutParent; |
| 2307 | } |
| 2308 | |
| 2309 | /// The owner for this render object (null if unattached). |
| 2310 | /// |
| 2311 | /// The entire render tree that this render object belongs to |
| 2312 | /// will have the same owner. |
| 2313 | PipelineOwner? get owner => _owner; |
| 2314 | PipelineOwner? _owner; |
| 2315 | |
| 2316 | /// Whether the render tree this render object belongs to is attached to a [PipelineOwner]. |
| 2317 | /// |
| 2318 | /// This becomes true during the call to [attach]. |
| 2319 | /// |
| 2320 | /// This becomes false during the call to [detach]. |
| 2321 | bool get attached => _owner != null; |
| 2322 | |
| 2323 | /// Mark this render object as attached to the given owner. |
| 2324 | /// |
| 2325 | /// Typically called only from the [parent]'s [attach] method, and by the |
| 2326 | /// [owner] to mark the root of a tree as attached. |
| 2327 | /// |
| 2328 | /// Subclasses with children should override this method to |
| 2329 | /// [attach] all their children to the same [owner] |
| 2330 | /// after calling the inherited method, as in `super.attach(owner)`. |
| 2331 | @mustCallSuper |
| 2332 | void attach(PipelineOwner owner) { |
| 2333 | assert(!_debugDisposed); |
| 2334 | assert(_owner == null); |
| 2335 | _owner = owner; |
| 2336 | // If the node was dirtied in some way while unattached, make sure to add |
| 2337 | // it to the appropriate dirty list now that an owner is available |
| 2338 | if (_needsLayout && _isRelayoutBoundary != null) { |
| 2339 | // Don't enter this block if we've never laid out at all; |
| 2340 | // scheduleInitialLayout() will handle it |
| 2341 | _needsLayout = false; |
| 2342 | markNeedsLayout(); |
| 2343 | } |
| 2344 | if (_needsCompositingBitsUpdate) { |
| 2345 | _needsCompositingBitsUpdate = false; |
| 2346 | markNeedsCompositingBitsUpdate(); |
| 2347 | } |
| 2348 | if (_needsPaint && _layerHandle.layer != null) { |
| 2349 | // Don't enter this block if we've never painted at all; |
| 2350 | // scheduleInitialPaint() will handle it |
| 2351 | _needsPaint = false; |
| 2352 | markNeedsPaint(); |
| 2353 | } |
| 2354 | if (_semantics.configProvider.effective.isSemanticBoundary && |
| 2355 | (_semantics.parentDataDirty || !_semantics.built)) { |
| 2356 | markNeedsSemanticsUpdate(); |
| 2357 | } |
| 2358 | } |
| 2359 | |
| 2360 | /// Mark this render object as detached from its [PipelineOwner]. |
| 2361 | /// |
| 2362 | /// Typically called only from the [parent]'s [detach], and by the [owner] to |
| 2363 | /// mark the root of a tree as detached. |
| 2364 | /// |
| 2365 | /// Subclasses with children should override this method to |
| 2366 | /// [detach] all their children after calling the inherited method, |
| 2367 | /// as in `super.detach()`. |
| 2368 | @mustCallSuper |
| 2369 | void detach() { |
| 2370 | assert(_owner != null); |
| 2371 | _owner = null; |
| 2372 | assert(parent == null || attached == parent!.attached); |
| 2373 | } |
| 2374 | |
| 2375 | /// Whether this render object's layout information is dirty. |
| 2376 | /// |
| 2377 | /// This is only set in debug mode. In general, render objects should not need |
| 2378 | /// to condition their runtime behavior on whether they are dirty or not, |
| 2379 | /// since they should only be marked dirty immediately prior to being laid |
| 2380 | /// out and painted. In release builds, this throws. |
| 2381 | /// |
| 2382 | /// It is intended to be used by tests and asserts. |
| 2383 | bool get debugNeedsLayout { |
| 2384 | late bool result; |
| 2385 | assert(() { |
| 2386 | result = _needsLayout; |
| 2387 | return true; |
| 2388 | }()); |
| 2389 | return result; |
| 2390 | } |
| 2391 | |
| 2392 | bool _needsLayout = true; |
| 2393 | |
| 2394 | /// Whether this [RenderObject] is a known relayout boundary. |
| 2395 | /// |
| 2396 | /// A relayout boundary is a [RenderObject] whose parent does not rely on the |
| 2397 | /// child [RenderObject]'s size in its own layout algorithm. In other words, |
| 2398 | /// if a [RenderObject]'s [performLayout] implementation does not ask the child |
| 2399 | /// for its size at all, **the child** is a relayout boundary. |
| 2400 | /// |
| 2401 | /// The type of "size" is typically defined by the coordinate system a |
| 2402 | /// [RenderObject] subclass uses. For instance, [RenderSliver]s produce |
| 2403 | /// [SliverGeometry] and [RenderBox]es produce [Size]. A parent [RenderObject] |
| 2404 | /// may not read the child's size but still depend on the child's layout (using |
| 2405 | /// a [RenderBox] child's baseline location, for example), this flag does not |
| 2406 | /// reflect such dependencies and the [RenderObject] subclass must handle those |
| 2407 | /// cases in its own implementation. See [RenderBox.markNeedsLayout] for an |
| 2408 | /// example. |
| 2409 | /// |
| 2410 | /// Relayout boundaries enable an important layout optimization: the parent not |
| 2411 | /// depending on the size of a child means the child changing size does not |
| 2412 | /// affect the layout of the parent. When a relayout boundary is marked as |
| 2413 | /// needing layout, its parent does not have to be marked as dirty, hence the |
| 2414 | /// name. For details, see [markNeedsLayout]. |
| 2415 | /// |
| 2416 | /// This flag is typically set in [RenderObject.layout], and consulted by |
| 2417 | /// [markNeedsLayout] in deciding whether to recursively mark the parent as |
| 2418 | /// also needing layout. |
| 2419 | /// |
| 2420 | /// The flag is initially set to `null` when [layout] has yet been called, and |
| 2421 | /// reset to `null` when the parent drops this child via [dropChild]. |
| 2422 | bool? _isRelayoutBoundary; |
| 2423 | |
| 2424 | /// Whether [invokeLayoutCallback] for this render object is currently running. |
| 2425 | bool get debugDoingThisLayoutWithCallback => _doingThisLayoutWithCallback; |
| 2426 | bool _doingThisLayoutWithCallback = false; |
| 2427 | |
| 2428 | /// The layout constraints most recently supplied by the parent. |
| 2429 | /// |
| 2430 | /// If layout has not yet happened, accessing this getter will |
| 2431 | /// throw a [StateError] exception. |
| 2432 | @protected |
| 2433 | Constraints get constraints { |
| 2434 | if (_constraints == null) { |
| 2435 | throw StateError('A RenderObject does not have any constraints before it has been laid out.' ); |
| 2436 | } |
| 2437 | return _constraints!; |
| 2438 | } |
| 2439 | |
| 2440 | Constraints? _constraints; |
| 2441 | |
| 2442 | /// Verify that the object's constraints are being met. Override this function |
| 2443 | /// in a subclass to verify that your state matches the constraints object. |
| 2444 | /// This function is only called when asserts are enabled (i.e. in debug mode) |
| 2445 | /// and only when needsLayout is false. If the constraints are not met, it |
| 2446 | /// should assert or throw an exception. |
| 2447 | @protected |
| 2448 | void debugAssertDoesMeetConstraints(); |
| 2449 | |
| 2450 | /// When true, a debug method ([debugAssertDoesMeetConstraints], for instance) |
| 2451 | /// is currently executing asserts for verifying the consistent behavior of |
| 2452 | /// intrinsic dimensions methods. |
| 2453 | /// |
| 2454 | /// This is typically set by framework debug methods. It is read by tests to |
| 2455 | /// selectively ignore custom layout callbacks. It should not be set outside of |
| 2456 | /// intrinsic-checking debug methods, and should not be checked in release mode |
| 2457 | /// (where it will always be false). |
| 2458 | static bool debugCheckingIntrinsics = false; |
| 2459 | |
| 2460 | bool _debugRelayoutBoundaryAlreadyMarkedNeedsLayout() { |
| 2461 | for ( |
| 2462 | RenderObject? node = this; |
| 2463 | node != null && node._isRelayoutBoundary != null; |
| 2464 | node = node.parent |
| 2465 | ) { |
| 2466 | final bool alreadyMarkedNeedsLayout = node._needsLayout || node._debugDoingThisLayout; |
| 2467 | if (!alreadyMarkedNeedsLayout) { |
| 2468 | return false; |
| 2469 | } |
| 2470 | if (node._isRelayoutBoundary!) { |
| 2471 | return true; |
| 2472 | } |
| 2473 | } |
| 2474 | return true; |
| 2475 | } |
| 2476 | |
| 2477 | /// Mark this render object's layout information as dirty, and either register |
| 2478 | /// this object with its [PipelineOwner], or defer to the parent, depending on |
| 2479 | /// whether this object is a relayout boundary or not respectively. |
| 2480 | /// |
| 2481 | /// ## Background |
| 2482 | /// |
| 2483 | /// Rather than eagerly updating layout information in response to writes into |
| 2484 | /// a render object, we instead mark the layout information as dirty, which |
| 2485 | /// schedules a visual update. As part of the visual update, the rendering |
| 2486 | /// pipeline updates the render object's layout information. |
| 2487 | /// |
| 2488 | /// This mechanism batches the layout work so that multiple sequential writes |
| 2489 | /// are coalesced, removing redundant computation. |
| 2490 | /// |
| 2491 | /// If a render object's parent indicates that it uses the size of one of its |
| 2492 | /// render object children when computing its layout information, this |
| 2493 | /// function, when called for the child, will also mark the parent as needing |
| 2494 | /// layout. In that case, since both the parent and the child need to have |
| 2495 | /// their layout recomputed, the pipeline owner is only notified about the |
| 2496 | /// parent; when the parent is laid out, it will call the child's [layout] |
| 2497 | /// method and thus the child will be laid out as well. |
| 2498 | /// |
| 2499 | /// Once [markNeedsLayout] has been called on a render object, |
| 2500 | /// [debugNeedsLayout] returns true for that render object until just after |
| 2501 | /// the pipeline owner has called [layout] on the render object. |
| 2502 | /// |
| 2503 | /// ## Special cases |
| 2504 | /// |
| 2505 | /// Some subclasses of [RenderObject], notably [RenderBox], have other |
| 2506 | /// situations in which the parent needs to be notified if the child is |
| 2507 | /// dirtied (e.g., if the child's intrinsic dimensions or baseline changes). |
| 2508 | /// Such subclasses override markNeedsLayout and either call |
| 2509 | /// `super.markNeedsLayout()`, in the normal case, or call |
| 2510 | /// [markParentNeedsLayout], in the case where the parent needs to be laid out |
| 2511 | /// as well as the child. |
| 2512 | /// |
| 2513 | /// If [sizedByParent] has changed, calls |
| 2514 | /// [markNeedsLayoutForSizedByParentChange] instead of [markNeedsLayout]. |
| 2515 | void markNeedsLayout() { |
| 2516 | assert(_debugCanPerformMutations); |
| 2517 | if (_needsLayout) { |
| 2518 | assert(_debugRelayoutBoundaryAlreadyMarkedNeedsLayout()); |
| 2519 | return; |
| 2520 | } |
| 2521 | _needsLayout = true; |
| 2522 | if (owner case final PipelineOwner owner? when (_isRelayoutBoundary ?? false)) { |
| 2523 | assert(() { |
| 2524 | if (debugPrintMarkNeedsLayoutStacks) { |
| 2525 | debugPrintStack(label: 'markNeedsLayout() called for $this' ); |
| 2526 | } |
| 2527 | return true; |
| 2528 | }()); |
| 2529 | owner._nodesNeedingLayout.add(this); |
| 2530 | owner.requestVisualUpdate(); |
| 2531 | } else if (parent != null) { |
| 2532 | markParentNeedsLayout(); |
| 2533 | } |
| 2534 | } |
| 2535 | |
| 2536 | /// Mark this render object's layout information as dirty, and then defer to |
| 2537 | /// the parent. |
| 2538 | /// |
| 2539 | /// This function should only be called from [markNeedsLayout] or |
| 2540 | /// [markNeedsLayoutForSizedByParentChange] implementations of subclasses that |
| 2541 | /// introduce more reasons for deferring the handling of dirty layout to the |
| 2542 | /// parent. See [markNeedsLayout] for details. |
| 2543 | /// |
| 2544 | /// Only call this if [parent] is not null. |
| 2545 | @protected |
| 2546 | void markParentNeedsLayout() { |
| 2547 | assert(_debugCanPerformMutations); |
| 2548 | _needsLayout = true; |
| 2549 | assert(this.parent != null); |
| 2550 | final RenderObject parent = this.parent!; |
| 2551 | if (!_doingThisLayoutWithCallback) { |
| 2552 | parent.markNeedsLayout(); |
| 2553 | } else { |
| 2554 | assert(parent._debugDoingThisLayout); |
| 2555 | } |
| 2556 | assert(parent == this.parent); |
| 2557 | } |
| 2558 | |
| 2559 | /// Mark this render object's layout information as dirty (like |
| 2560 | /// [markNeedsLayout]), and additionally also handle any necessary work to |
| 2561 | /// handle the case where [sizedByParent] has changed value. |
| 2562 | /// |
| 2563 | /// This should be called whenever [sizedByParent] might have changed. |
| 2564 | /// |
| 2565 | /// Only call this if [parent] is not null. |
| 2566 | void markNeedsLayoutForSizedByParentChange() { |
| 2567 | markNeedsLayout(); |
| 2568 | markParentNeedsLayout(); |
| 2569 | } |
| 2570 | |
| 2571 | /// Bootstrap the rendering pipeline by scheduling the very first layout. |
| 2572 | /// |
| 2573 | /// Requires this render object to be attached and that this render object |
| 2574 | /// is the root of the render tree. |
| 2575 | /// |
| 2576 | /// See [RenderView] for an example of how this function is used. |
| 2577 | void scheduleInitialLayout() { |
| 2578 | assert(!_debugDisposed); |
| 2579 | assert(attached); |
| 2580 | assert(parent == null); |
| 2581 | assert(!owner!._debugDoingLayout); |
| 2582 | assert(_isRelayoutBoundary == null); |
| 2583 | _isRelayoutBoundary = true; |
| 2584 | assert(() { |
| 2585 | _debugCanParentUseSize = false; |
| 2586 | return true; |
| 2587 | }()); |
| 2588 | owner!._nodesNeedingLayout.add(this); |
| 2589 | } |
| 2590 | |
| 2591 | @pragma('vm:notify-debugger-on-exception' ) |
| 2592 | void _layoutWithoutResize() { |
| 2593 | assert(_needsLayout); |
| 2594 | assert((_isRelayoutBoundary ?? false) || this is RenderObjectWithLayoutCallbackMixin); |
| 2595 | RenderObject? debugPreviousActiveLayout; |
| 2596 | assert(!_debugMutationsLocked); |
| 2597 | assert(!_doingThisLayoutWithCallback); |
| 2598 | assert(_debugCanParentUseSize != null); |
| 2599 | assert(() { |
| 2600 | _debugMutationsLocked = true; |
| 2601 | _debugDoingThisLayout = true; |
| 2602 | debugPreviousActiveLayout = _debugActiveLayout; |
| 2603 | _debugActiveLayout = this; |
| 2604 | if (debugPrintLayouts) { |
| 2605 | debugPrint('Laying out (without resize) $this' ); |
| 2606 | } |
| 2607 | return true; |
| 2608 | }()); |
| 2609 | try { |
| 2610 | performLayout(); |
| 2611 | markNeedsSemanticsUpdate(); |
| 2612 | } catch (e, stack) { |
| 2613 | _reportException('performLayout' , e, stack); |
| 2614 | } |
| 2615 | assert(() { |
| 2616 | _debugActiveLayout = debugPreviousActiveLayout; |
| 2617 | _debugDoingThisLayout = false; |
| 2618 | _debugMutationsLocked = false; |
| 2619 | return true; |
| 2620 | }()); |
| 2621 | _needsLayout = false; |
| 2622 | markNeedsPaint(); |
| 2623 | } |
| 2624 | |
| 2625 | /// Compute the layout for this render object. |
| 2626 | /// |
| 2627 | /// This method is the main entry point for parents to ask their children to |
| 2628 | /// update their layout information. The parent passes a constraints object, |
| 2629 | /// which informs the child as to which layouts are permissible. The child is |
| 2630 | /// required to obey the given constraints. |
| 2631 | /// |
| 2632 | /// If the parent reads information computed during the child's layout, the |
| 2633 | /// parent must pass true for `parentUsesSize`. In that case, the parent will |
| 2634 | /// be marked as needing layout whenever the child is marked as needing layout |
| 2635 | /// because the parent's layout information depends on the child's layout |
| 2636 | /// information. If the parent uses the default value (false) for |
| 2637 | /// `parentUsesSize`, the child can change its layout information (subject to |
| 2638 | /// the given constraints) without informing the parent. |
| 2639 | /// |
| 2640 | /// Subclasses should not override [layout] directly. Instead, they should |
| 2641 | /// override [performResize] and/or [performLayout]. The [layout] method |
| 2642 | /// delegates the actual work to [performResize] and [performLayout]. |
| 2643 | /// |
| 2644 | /// The parent's [performLayout] method should call the [layout] of all its |
| 2645 | /// children unconditionally. It is the [layout] method's responsibility (as |
| 2646 | /// implemented here) to return early if the child does not need to do any |
| 2647 | /// work to update its layout information. |
| 2648 | @pragma('vm:notify-debugger-on-exception' ) |
| 2649 | void layout(Constraints constraints, {bool parentUsesSize = false}) { |
| 2650 | assert(!_debugDisposed); |
| 2651 | if (!kReleaseMode && debugProfileLayoutsEnabled) { |
| 2652 | Map<String, String>? debugTimelineArguments; |
| 2653 | assert(() { |
| 2654 | if (debugEnhanceLayoutTimelineArguments) { |
| 2655 | debugTimelineArguments = toDiagnosticsNode().toTimelineArguments(); |
| 2656 | } |
| 2657 | return true; |
| 2658 | }()); |
| 2659 | FlutterTimeline.startSync(' $runtimeType' , arguments: debugTimelineArguments); |
| 2660 | } |
| 2661 | assert( |
| 2662 | constraints.debugAssertIsValid( |
| 2663 | isAppliedConstraint: true, |
| 2664 | informationCollector: () { |
| 2665 | final List<String> stack = StackTrace.current.toString().split('\n' ); |
| 2666 | int? targetFrame; |
| 2667 | final Pattern layoutFramePattern = RegExp(r'^#[0-9]+ +Render(?:Object|Box).layout \(' ); |
| 2668 | for (int i = 0; i < stack.length; i += 1) { |
| 2669 | if (layoutFramePattern.matchAsPrefix(stack[i]) != null) { |
| 2670 | targetFrame = i + 1; |
| 2671 | } else if (targetFrame != null) { |
| 2672 | break; |
| 2673 | } |
| 2674 | } |
| 2675 | if (targetFrame != null && targetFrame < stack.length) { |
| 2676 | final Pattern targetFramePattern = RegExp(r'^#[0-9]+ +(.+)$' ); |
| 2677 | final Match? targetFrameMatch = targetFramePattern.matchAsPrefix(stack[targetFrame]); |
| 2678 | final String? problemFunction = |
| 2679 | (targetFrameMatch != null && targetFrameMatch.groupCount > 0) |
| 2680 | ? targetFrameMatch.group(1) |
| 2681 | : stack[targetFrame].trim(); |
| 2682 | return <DiagnosticsNode>[ |
| 2683 | ErrorDescription( |
| 2684 | "These invalid constraints were provided to $runtimeType's layout() " |
| 2685 | 'function by the following function, which probably computed the ' |
| 2686 | 'invalid constraints in question:\n' |
| 2687 | ' $problemFunction' , |
| 2688 | ), |
| 2689 | ]; |
| 2690 | } |
| 2691 | return <DiagnosticsNode>[]; |
| 2692 | }, |
| 2693 | ), |
| 2694 | ); |
| 2695 | assert(!_debugDoingThisResize); |
| 2696 | assert(!_debugDoingThisLayout); |
| 2697 | assert(() { |
| 2698 | _debugCanParentUseSize = parentUsesSize; |
| 2699 | return true; |
| 2700 | }()); |
| 2701 | |
| 2702 | _isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent == null; |
| 2703 | if (!_needsLayout && constraints == _constraints) { |
| 2704 | assert(() { |
| 2705 | // in case parentUsesSize changed since the last invocation, set size |
| 2706 | // to itself, so it has the right internal debug values. |
| 2707 | _debugDoingThisResize = sizedByParent; |
| 2708 | _debugDoingThisLayout = !sizedByParent; |
| 2709 | final RenderObject? debugPreviousActiveLayout = _debugActiveLayout; |
| 2710 | _debugActiveLayout = this; |
| 2711 | debugResetSize(); |
| 2712 | _debugActiveLayout = debugPreviousActiveLayout; |
| 2713 | _debugDoingThisLayout = false; |
| 2714 | _debugDoingThisResize = false; |
| 2715 | return true; |
| 2716 | }()); |
| 2717 | if (!kReleaseMode && debugProfileLayoutsEnabled) { |
| 2718 | FlutterTimeline.finishSync(); |
| 2719 | } |
| 2720 | return; |
| 2721 | } |
| 2722 | _constraints = constraints; |
| 2723 | |
| 2724 | assert(!_debugMutationsLocked); |
| 2725 | assert(!_doingThisLayoutWithCallback); |
| 2726 | assert(() { |
| 2727 | _debugMutationsLocked = true; |
| 2728 | if (debugPrintLayouts) { |
| 2729 | debugPrint( |
| 2730 | 'Laying out ( ${sizedByParent ? "with separate resize" : "with resize allowed" }) $this' , |
| 2731 | ); |
| 2732 | } |
| 2733 | return true; |
| 2734 | }()); |
| 2735 | if (sizedByParent) { |
| 2736 | assert(() { |
| 2737 | _debugDoingThisResize = true; |
| 2738 | return true; |
| 2739 | }()); |
| 2740 | try { |
| 2741 | performResize(); |
| 2742 | assert(() { |
| 2743 | debugAssertDoesMeetConstraints(); |
| 2744 | return true; |
| 2745 | }()); |
| 2746 | } catch (e, stack) { |
| 2747 | _reportException('performResize' , e, stack); |
| 2748 | } |
| 2749 | assert(() { |
| 2750 | _debugDoingThisResize = false; |
| 2751 | return true; |
| 2752 | }()); |
| 2753 | } |
| 2754 | RenderObject? debugPreviousActiveLayout; |
| 2755 | assert(() { |
| 2756 | _debugDoingThisLayout = true; |
| 2757 | debugPreviousActiveLayout = _debugActiveLayout; |
| 2758 | _debugActiveLayout = this; |
| 2759 | return true; |
| 2760 | }()); |
| 2761 | try { |
| 2762 | performLayout(); |
| 2763 | markNeedsSemanticsUpdate(); |
| 2764 | assert(() { |
| 2765 | debugAssertDoesMeetConstraints(); |
| 2766 | return true; |
| 2767 | }()); |
| 2768 | } catch (e, stack) { |
| 2769 | _reportException('performLayout' , e, stack); |
| 2770 | } |
| 2771 | assert(() { |
| 2772 | _debugActiveLayout = debugPreviousActiveLayout; |
| 2773 | _debugDoingThisLayout = false; |
| 2774 | _debugMutationsLocked = false; |
| 2775 | return true; |
| 2776 | }()); |
| 2777 | _needsLayout = false; |
| 2778 | markNeedsPaint(); |
| 2779 | |
| 2780 | if (!kReleaseMode && debugProfileLayoutsEnabled) { |
| 2781 | FlutterTimeline.finishSync(); |
| 2782 | } |
| 2783 | } |
| 2784 | |
| 2785 | /// If a subclass has a "size" (the state controlled by `parentUsesSize`, |
| 2786 | /// whatever it is in the subclass, e.g. the actual `size` property of |
| 2787 | /// [RenderBox]), and the subclass verifies that in debug mode this "size" |
| 2788 | /// property isn't used when [debugCanParentUseSize] isn't set, then that |
| 2789 | /// subclass should override [debugResetSize] to reapply the current values of |
| 2790 | /// [debugCanParentUseSize] to that state. |
| 2791 | @protected |
| 2792 | void debugResetSize() {} |
| 2793 | |
| 2794 | /// Whether the constraints are the only input to the sizing algorithm (in |
| 2795 | /// particular, child nodes have no impact). |
| 2796 | /// |
| 2797 | /// Returning false is always correct, but returning true can be more |
| 2798 | /// efficient when computing the size of this render object because we don't |
| 2799 | /// need to recompute the size if the constraints don't change. |
| 2800 | /// |
| 2801 | /// Typically, subclasses will always return the same value. If the value can |
| 2802 | /// change, then, when it does change, the subclass should make sure to call |
| 2803 | /// [markNeedsLayoutForSizedByParentChange]. |
| 2804 | /// |
| 2805 | /// Subclasses that return true must not change the dimensions of this render |
| 2806 | /// object in [performLayout]. Instead, that work should be done by |
| 2807 | /// [performResize] or - for subclasses of [RenderBox] - in |
| 2808 | /// [RenderBox.computeDryLayout]. |
| 2809 | @protected |
| 2810 | bool get sizedByParent => false; |
| 2811 | |
| 2812 | /// {@template flutter.rendering.RenderObject.performResize} |
| 2813 | /// Updates the render objects size using only the constraints. |
| 2814 | /// |
| 2815 | /// Do not call this function directly: call [layout] instead. This function |
| 2816 | /// is called by [layout] when there is actually work to be done by this |
| 2817 | /// render object during layout. The layout constraints provided by your |
| 2818 | /// parent are available via the [constraints] getter. |
| 2819 | /// |
| 2820 | /// This function is called only if [sizedByParent] is true. |
| 2821 | /// {@endtemplate} |
| 2822 | /// |
| 2823 | /// Subclasses that set [sizedByParent] to true should override this method to |
| 2824 | /// compute their size. Subclasses of [RenderBox] should consider overriding |
| 2825 | /// [RenderBox.computeDryLayout] instead. |
| 2826 | @protected |
| 2827 | void performResize(); |
| 2828 | |
| 2829 | /// Do the work of computing the layout for this render object. |
| 2830 | /// |
| 2831 | /// Do not call this function directly: call [layout] instead. This function |
| 2832 | /// is called by [layout] when there is actually work to be done by this |
| 2833 | /// render object during layout. The layout constraints provided by your |
| 2834 | /// parent are available via the [constraints] getter. |
| 2835 | /// |
| 2836 | /// If [sizedByParent] is true, then this function should not actually change |
| 2837 | /// the dimensions of this render object. Instead, that work should be done by |
| 2838 | /// [performResize]. If [sizedByParent] is false, then this function should |
| 2839 | /// both change the dimensions of this render object and instruct its children |
| 2840 | /// to layout. |
| 2841 | /// |
| 2842 | /// In implementing this function, you must call [layout] on each of your |
| 2843 | /// children, passing true for parentUsesSize if your layout information is |
| 2844 | /// dependent on your child's layout information. Passing true for |
| 2845 | /// parentUsesSize ensures that this render object will undergo layout if the |
| 2846 | /// child undergoes layout. Otherwise, the child can change its layout |
| 2847 | /// information without informing this render object. |
| 2848 | /// |
| 2849 | /// Some special [RenderObject] subclasses (such as the one used by |
| 2850 | /// [OverlayPortal.overlayChildLayoutBuilder]) call [applyPaintTransform] in |
| 2851 | /// their [performLayout] implementation. To ensure such [RenderObject]s get |
| 2852 | /// the up-to-date paint transform, [RenderObject] subclasses should typically |
| 2853 | /// update the paint transform (as reported by [applyPaintTransform]) in this |
| 2854 | /// method instead of [paint]. |
| 2855 | @protected |
| 2856 | void performLayout(); |
| 2857 | |
| 2858 | /// Allows mutations to be made to this object's child list (and any |
| 2859 | /// descendants) as well as to any other dirty nodes in the render tree owned |
| 2860 | /// by the same [PipelineOwner] as this object. The `callback` argument is |
| 2861 | /// invoked synchronously, and the mutations are allowed only during that |
| 2862 | /// callback's execution. |
| 2863 | /// |
| 2864 | /// This exists to allow child lists to be built on-demand during layout (e.g. |
| 2865 | /// based on the object's size), and to enable nodes to be moved around the |
| 2866 | /// tree as this happens (e.g. to handle [GlobalKey] reparenting), while still |
| 2867 | /// ensuring that any particular node is only laid out once per frame. |
| 2868 | /// |
| 2869 | /// Calling this function disables a number of assertions that are intended to |
| 2870 | /// catch likely bugs. As such, using this function is generally discouraged. |
| 2871 | /// |
| 2872 | /// This function can only be called during layout. |
| 2873 | @protected |
| 2874 | void invokeLayoutCallback<T extends Constraints>(LayoutCallback<T> callback) { |
| 2875 | assert(_debugMutationsLocked); |
| 2876 | assert(_debugDoingThisLayout); |
| 2877 | assert(!_doingThisLayoutWithCallback); |
| 2878 | _doingThisLayoutWithCallback = true; |
| 2879 | try { |
| 2880 | owner!._enableMutationsToDirtySubtrees(() { |
| 2881 | callback(constraints as T); |
| 2882 | }); |
| 2883 | } finally { |
| 2884 | _doingThisLayoutWithCallback = false; |
| 2885 | } |
| 2886 | } |
| 2887 | |
| 2888 | // PAINTING |
| 2889 | |
| 2890 | /// Whether [paint] for this render object is currently running. |
| 2891 | /// |
| 2892 | /// Only valid when asserts are enabled. In release builds, always returns |
| 2893 | /// false. |
| 2894 | bool get debugDoingThisPaint => _debugDoingThisPaint; |
| 2895 | bool _debugDoingThisPaint = false; |
| 2896 | |
| 2897 | /// The render object that is actively painting. |
| 2898 | /// |
| 2899 | /// Only valid when asserts are enabled. In release builds, always returns |
| 2900 | /// null. |
| 2901 | static RenderObject? get debugActivePaint => _debugActivePaint; |
| 2902 | static RenderObject? _debugActivePaint; |
| 2903 | |
| 2904 | /// Whether this render object repaints separately from its parent. |
| 2905 | /// |
| 2906 | /// Override this in subclasses to indicate that instances of your class ought |
| 2907 | /// to repaint independently. For example, render objects that repaint |
| 2908 | /// frequently might want to repaint themselves without requiring their parent |
| 2909 | /// to repaint. |
| 2910 | /// |
| 2911 | /// If this getter returns true, the [paintBounds] are applied to this object |
| 2912 | /// and all descendants. The framework invokes [RenderObject.updateCompositedLayer] |
| 2913 | /// to create an [OffsetLayer] and assigns it to the [layer] field. |
| 2914 | /// Render objects that declare themselves as repaint boundaries must not replace |
| 2915 | /// the layer created by the framework. |
| 2916 | /// |
| 2917 | /// If the value of this getter changes, [markNeedsCompositingBitsUpdate] must |
| 2918 | /// be called. |
| 2919 | /// |
| 2920 | /// See [RepaintBoundary] for more information about how repaint boundaries function. |
| 2921 | bool get isRepaintBoundary => false; |
| 2922 | |
| 2923 | /// Called, in debug mode, if [isRepaintBoundary] is true, when either the |
| 2924 | /// this render object or its parent attempt to paint. |
| 2925 | /// |
| 2926 | /// This can be used to record metrics about whether the node should actually |
| 2927 | /// be a repaint boundary. |
| 2928 | void debugRegisterRepaintBoundaryPaint({ |
| 2929 | bool includedParent = true, |
| 2930 | bool includedChild = false, |
| 2931 | }) {} |
| 2932 | |
| 2933 | /// Whether this render object always needs compositing. |
| 2934 | /// |
| 2935 | /// Override this in subclasses to indicate that your paint function always |
| 2936 | /// creates at least one composited layer. For example, videos should return |
| 2937 | /// true if they use hardware decoders. |
| 2938 | /// |
| 2939 | /// You must call [markNeedsCompositingBitsUpdate] if the value of this getter |
| 2940 | /// changes. (This is implied when [adoptChild] or [dropChild] are called.) |
| 2941 | @protected |
| 2942 | bool get alwaysNeedsCompositing => false; |
| 2943 | |
| 2944 | late bool _wasRepaintBoundary; |
| 2945 | |
| 2946 | /// Update the composited layer owned by this render object. |
| 2947 | /// |
| 2948 | /// This method is called by the framework when [isRepaintBoundary] is true. |
| 2949 | /// |
| 2950 | /// If [oldLayer] is `null`, this method must return a new [OffsetLayer] |
| 2951 | /// (or subtype thereof). If [oldLayer] is not `null`, then this method must |
| 2952 | /// reuse the layer instance that is provided - it is an error to create a new |
| 2953 | /// layer in this instance. The layer will be disposed by the framework when |
| 2954 | /// either the render object is disposed or if it is no longer a repaint |
| 2955 | /// boundary. |
| 2956 | /// |
| 2957 | /// The [OffsetLayer.offset] property will be managed by the framework and |
| 2958 | /// must not be updated by this method. |
| 2959 | /// |
| 2960 | /// If a property of the composited layer needs to be updated, the render object |
| 2961 | /// must call [markNeedsCompositedLayerUpdate] which will schedule this method |
| 2962 | /// to be called without repainting children. If this widget was marked as |
| 2963 | /// needing to paint and needing a composited layer update, this method is only |
| 2964 | /// called once. |
| 2965 | // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/102102 revisit the |
| 2966 | // constraint that the instance/type of layer cannot be changed at runtime. |
| 2967 | OffsetLayer updateCompositedLayer({required covariant OffsetLayer? oldLayer}) { |
| 2968 | assert(isRepaintBoundary); |
| 2969 | return oldLayer ?? OffsetLayer(); |
| 2970 | } |
| 2971 | |
| 2972 | /// The compositing layer that this render object uses to repaint. |
| 2973 | /// |
| 2974 | /// If this render object is not a repaint boundary, it is the responsibility |
| 2975 | /// of the [paint] method to populate this field. If [needsCompositing] is |
| 2976 | /// true, this field may be populated with the root-most layer used by the |
| 2977 | /// render object implementation. When repainting, instead of creating a new |
| 2978 | /// layer the render object may update the layer stored in this field for better |
| 2979 | /// performance. It is also OK to leave this field as null and create a new |
| 2980 | /// layer on every repaint, but without the performance benefit. If |
| 2981 | /// [needsCompositing] is false, this field must be set to null either by |
| 2982 | /// never populating this field, or by setting it to null when the value of |
| 2983 | /// [needsCompositing] changes from true to false. |
| 2984 | /// |
| 2985 | /// If a new layer is created and stored in some other field on the render |
| 2986 | /// object, the render object must use a [LayerHandle] to store it. A layer |
| 2987 | /// handle will prevent the layer from being disposed before the render |
| 2988 | /// object is finished with it, and it will also make sure that the layer |
| 2989 | /// gets appropriately disposed when the render object creates a replacement |
| 2990 | /// or nulls it out. The render object must null out the [LayerHandle.layer] |
| 2991 | /// in its [dispose] method. |
| 2992 | /// |
| 2993 | /// If this render object is a repaint boundary, the framework automatically |
| 2994 | /// creates an [OffsetLayer] and populates this field prior to calling the |
| 2995 | /// [paint] method. The [paint] method must not replace the value of this |
| 2996 | /// field. |
| 2997 | @protected |
| 2998 | ContainerLayer? get layer { |
| 2999 | assert(!isRepaintBoundary || _layerHandle.layer == null || _layerHandle.layer is OffsetLayer); |
| 3000 | return _layerHandle.layer; |
| 3001 | } |
| 3002 | |
| 3003 | @protected |
| 3004 | set layer(ContainerLayer? newLayer) { |
| 3005 | assert( |
| 3006 | !isRepaintBoundary, |
| 3007 | 'Attempted to set a layer to a repaint boundary render object.\n' |
| 3008 | 'The framework creates and assigns an OffsetLayer to a repaint ' |
| 3009 | 'boundary automatically.' , |
| 3010 | ); |
| 3011 | _layerHandle.layer = newLayer; |
| 3012 | } |
| 3013 | |
| 3014 | final LayerHandle<ContainerLayer> _layerHandle = LayerHandle<ContainerLayer>(); |
| 3015 | |
| 3016 | /// In debug mode, the compositing layer that this render object uses to repaint. |
| 3017 | /// |
| 3018 | /// This getter is intended for debugging purposes only. In release builds, it |
| 3019 | /// always returns null. In debug builds, it returns the layer even if the layer |
| 3020 | /// is dirty. |
| 3021 | /// |
| 3022 | /// For production code, consider [layer]. |
| 3023 | ContainerLayer? get debugLayer { |
| 3024 | ContainerLayer? result; |
| 3025 | assert(() { |
| 3026 | result = _layerHandle.layer; |
| 3027 | return true; |
| 3028 | }()); |
| 3029 | return result; |
| 3030 | } |
| 3031 | |
| 3032 | bool _needsCompositingBitsUpdate = false; // set to true when a child is added |
| 3033 | /// Mark the compositing state for this render object as dirty. |
| 3034 | /// |
| 3035 | /// This is called to indicate that the value for [needsCompositing] needs to |
| 3036 | /// be recomputed during the next [PipelineOwner.flushCompositingBits] engine |
| 3037 | /// phase. |
| 3038 | /// |
| 3039 | /// When the subtree is mutated, we need to recompute our |
| 3040 | /// [needsCompositing] bit, and some of our ancestors need to do the |
| 3041 | /// same (in case ours changed in a way that will change theirs). To |
| 3042 | /// this end, [adoptChild] and [dropChild] call this method, and, as |
| 3043 | /// necessary, this method calls the parent's, etc, walking up the |
| 3044 | /// tree to mark all the nodes that need updating. |
| 3045 | /// |
| 3046 | /// This method does not schedule a rendering frame, because since |
| 3047 | /// it cannot be the case that _only_ the compositing bits changed, |
| 3048 | /// something else will have scheduled a frame for us. |
| 3049 | void markNeedsCompositingBitsUpdate() { |
| 3050 | assert(!_debugDisposed); |
| 3051 | if (_needsCompositingBitsUpdate) { |
| 3052 | return; |
| 3053 | } |
| 3054 | _needsCompositingBitsUpdate = true; |
| 3055 | final RenderObject? parent = this.parent; |
| 3056 | if (parent != null) { |
| 3057 | if (parent._needsCompositingBitsUpdate) { |
| 3058 | return; |
| 3059 | } |
| 3060 | |
| 3061 | if ((!_wasRepaintBoundary || !isRepaintBoundary) && !parent.isRepaintBoundary) { |
| 3062 | parent.markNeedsCompositingBitsUpdate(); |
| 3063 | return; |
| 3064 | } |
| 3065 | } |
| 3066 | // parent is fine (or there isn't one), but we are dirty |
| 3067 | owner?._nodesNeedingCompositingBitsUpdate.add(this); |
| 3068 | } |
| 3069 | |
| 3070 | late bool _needsCompositing; // initialized in the constructor |
| 3071 | /// Whether we or one of our descendants has a compositing layer. |
| 3072 | /// |
| 3073 | /// If this node needs compositing as indicated by this bit, then all ancestor |
| 3074 | /// nodes will also need compositing. |
| 3075 | /// |
| 3076 | /// Only legal to call after [PipelineOwner.flushLayout] and |
| 3077 | /// [PipelineOwner.flushCompositingBits] have been called. |
| 3078 | bool get needsCompositing { |
| 3079 | assert(!_needsCompositingBitsUpdate); // make sure we don't use this bit when it is dirty |
| 3080 | return _needsCompositing; |
| 3081 | } |
| 3082 | |
| 3083 | void _updateCompositingBits() { |
| 3084 | if (!_needsCompositingBitsUpdate) { |
| 3085 | return; |
| 3086 | } |
| 3087 | final bool oldNeedsCompositing = _needsCompositing; |
| 3088 | _needsCompositing = false; |
| 3089 | visitChildren((RenderObject child) { |
| 3090 | child._updateCompositingBits(); |
| 3091 | if (child.needsCompositing) { |
| 3092 | _needsCompositing = true; |
| 3093 | } |
| 3094 | }); |
| 3095 | if (isRepaintBoundary || alwaysNeedsCompositing) { |
| 3096 | _needsCompositing = true; |
| 3097 | } |
| 3098 | // If a node was previously a repaint boundary, but no longer is one, then |
| 3099 | // regardless of its compositing state we need to find a new parent to |
| 3100 | // paint from. To do this, we mark it clean again so that the traversal |
| 3101 | // in markNeedsPaint is not short-circuited. It is removed from _nodesNeedingPaint |
| 3102 | // so that we do not attempt to paint from it after locating a parent. |
| 3103 | if (!isRepaintBoundary && _wasRepaintBoundary) { |
| 3104 | _needsPaint = false; |
| 3105 | _needsCompositedLayerUpdate = false; |
| 3106 | owner?._nodesNeedingPaint.removeWhere((RenderObject t) => identical(t, this)); |
| 3107 | _needsCompositingBitsUpdate = false; |
| 3108 | markNeedsPaint(); |
| 3109 | } else if (oldNeedsCompositing != _needsCompositing) { |
| 3110 | _needsCompositingBitsUpdate = false; |
| 3111 | markNeedsPaint(); |
| 3112 | } else { |
| 3113 | _needsCompositingBitsUpdate = false; |
| 3114 | } |
| 3115 | } |
| 3116 | |
| 3117 | /// Whether this render object's paint information is dirty. |
| 3118 | /// |
| 3119 | /// This is only set in debug mode. In general, render objects should not need |
| 3120 | /// to condition their runtime behavior on whether they are dirty or not, |
| 3121 | /// since they should only be marked dirty immediately prior to being laid |
| 3122 | /// out and painted. (In release builds, this throws.) |
| 3123 | /// |
| 3124 | /// It is intended to be used by tests and asserts. |
| 3125 | /// |
| 3126 | /// It is possible (and indeed, quite common) for [debugNeedsPaint] to be |
| 3127 | /// false and [debugNeedsLayout] to be true. The render object will still be |
| 3128 | /// repainted in the next frame when this is the case, because the |
| 3129 | /// [markNeedsPaint] method is implicitly called by the framework after a |
| 3130 | /// render object is laid out, prior to the paint phase. |
| 3131 | bool get debugNeedsPaint { |
| 3132 | late bool result; |
| 3133 | assert(() { |
| 3134 | result = _needsPaint; |
| 3135 | return true; |
| 3136 | }()); |
| 3137 | return result; |
| 3138 | } |
| 3139 | |
| 3140 | bool _needsPaint = true; |
| 3141 | |
| 3142 | /// Whether this render object's layer information is dirty. |
| 3143 | /// |
| 3144 | /// This is only set in debug mode. In general, render objects should not need |
| 3145 | /// to condition their runtime behavior on whether they are dirty or not, |
| 3146 | /// since they should only be marked dirty immediately prior to being laid |
| 3147 | /// out and painted. (In release builds, this throws.) |
| 3148 | /// |
| 3149 | /// It is intended to be used by tests and asserts. |
| 3150 | bool get debugNeedsCompositedLayerUpdate { |
| 3151 | late bool result; |
| 3152 | assert(() { |
| 3153 | result = _needsCompositedLayerUpdate; |
| 3154 | return true; |
| 3155 | }()); |
| 3156 | return result; |
| 3157 | } |
| 3158 | |
| 3159 | bool _needsCompositedLayerUpdate = false; |
| 3160 | |
| 3161 | /// Mark this render object as having changed its visual appearance. |
| 3162 | /// |
| 3163 | /// Rather than eagerly updating this render object's display list |
| 3164 | /// in response to writes, we instead mark the render object as needing to |
| 3165 | /// paint, which schedules a visual update. As part of the visual update, the |
| 3166 | /// rendering pipeline will give this render object an opportunity to update |
| 3167 | /// its display list. |
| 3168 | /// |
| 3169 | /// This mechanism batches the painting work so that multiple sequential |
| 3170 | /// writes are coalesced, removing redundant computation. |
| 3171 | /// |
| 3172 | /// Once [markNeedsPaint] has been called on a render object, |
| 3173 | /// [debugNeedsPaint] returns true for that render object until just after |
| 3174 | /// the pipeline owner has called [paint] on the render object. |
| 3175 | /// |
| 3176 | /// See also: |
| 3177 | /// |
| 3178 | /// * [RepaintBoundary], to scope a subtree of render objects to their own |
| 3179 | /// layer, thus limiting the number of nodes that [markNeedsPaint] must mark |
| 3180 | /// dirty. |
| 3181 | void markNeedsPaint() { |
| 3182 | assert(!_debugDisposed); |
| 3183 | assert(owner == null || !owner!.debugDoingPaint); |
| 3184 | if (_needsPaint) { |
| 3185 | return; |
| 3186 | } |
| 3187 | _needsPaint = true; |
| 3188 | // If this was not previously a repaint boundary it will not have |
| 3189 | // a layer we can paint from. |
| 3190 | if (isRepaintBoundary && _wasRepaintBoundary) { |
| 3191 | assert(() { |
| 3192 | if (debugPrintMarkNeedsPaintStacks) { |
| 3193 | debugPrintStack(label: 'markNeedsPaint() called for $this' ); |
| 3194 | } |
| 3195 | return true; |
| 3196 | }()); |
| 3197 | // If we always have our own layer, then we can just repaint |
| 3198 | // ourselves without involving any other nodes. |
| 3199 | assert(_layerHandle.layer is OffsetLayer); |
| 3200 | if (owner != null) { |
| 3201 | owner!._nodesNeedingPaint.add(this); |
| 3202 | owner!.requestVisualUpdate(); |
| 3203 | } |
| 3204 | } else if (parent != null) { |
| 3205 | parent!.markNeedsPaint(); |
| 3206 | } else { |
| 3207 | assert(() { |
| 3208 | if (debugPrintMarkNeedsPaintStacks) { |
| 3209 | debugPrintStack(label: 'markNeedsPaint() called for $this (root of render tree)' ); |
| 3210 | } |
| 3211 | return true; |
| 3212 | }()); |
| 3213 | // If we are the root of the render tree and not a repaint boundary |
| 3214 | // then we have to paint ourselves, since nobody else can paint us. |
| 3215 | // We don't add ourselves to _nodesNeedingPaint in this case, |
| 3216 | // because the root is always told to paint regardless. |
| 3217 | // |
| 3218 | // Trees rooted at a RenderView do not go through this |
| 3219 | // code path because RenderViews are repaint boundaries. |
| 3220 | owner?.requestVisualUpdate(); |
| 3221 | } |
| 3222 | } |
| 3223 | |
| 3224 | /// Mark this render object as having changed a property on its composited |
| 3225 | /// layer. |
| 3226 | /// |
| 3227 | /// Render objects that have a composited layer have [isRepaintBoundary] equal |
| 3228 | /// to true may update the properties of that composited layer without repainting |
| 3229 | /// their children. If this render object is a repaint boundary but does |
| 3230 | /// not yet have a composited layer created for it, this method will instead |
| 3231 | /// mark the nearest repaint boundary parent as needing to be painted. |
| 3232 | /// |
| 3233 | /// If this method is called on a render object that is not a repaint boundary |
| 3234 | /// or is a repaint boundary but hasn't been composited yet, it is equivalent |
| 3235 | /// to calling [markNeedsPaint]. |
| 3236 | /// |
| 3237 | /// See also: |
| 3238 | /// |
| 3239 | /// * [RenderOpacity], which uses this method when its opacity is updated to |
| 3240 | /// update the layer opacity without repainting children. |
| 3241 | void markNeedsCompositedLayerUpdate() { |
| 3242 | assert(!_debugDisposed); |
| 3243 | assert(owner == null || !owner!.debugDoingPaint); |
| 3244 | if (_needsCompositedLayerUpdate || _needsPaint) { |
| 3245 | return; |
| 3246 | } |
| 3247 | _needsCompositedLayerUpdate = true; |
| 3248 | // If this was not previously a repaint boundary it will not have |
| 3249 | // a layer we can paint from. |
| 3250 | if (isRepaintBoundary && _wasRepaintBoundary) { |
| 3251 | // If we always have our own layer, then we can just repaint |
| 3252 | // ourselves without involving any other nodes. |
| 3253 | assert(_layerHandle.layer != null); |
| 3254 | if (owner != null) { |
| 3255 | owner!._nodesNeedingPaint.add(this); |
| 3256 | owner!.requestVisualUpdate(); |
| 3257 | } |
| 3258 | } else { |
| 3259 | markNeedsPaint(); |
| 3260 | } |
| 3261 | } |
| 3262 | |
| 3263 | // Called when flushPaint() tries to make us paint but our layer is detached. |
| 3264 | // To make sure that our subtree is repainted when it's finally reattached, |
| 3265 | // even in the case where some ancestor layer is itself never marked dirty, we |
| 3266 | // have to mark our entire detached subtree as dirty and needing to be |
| 3267 | // repainted. That way, we'll eventually be repainted. |
| 3268 | void _skippedPaintingOnLayer() { |
| 3269 | assert(attached); |
| 3270 | assert(isRepaintBoundary); |
| 3271 | assert(_needsPaint || _needsCompositedLayerUpdate); |
| 3272 | assert(_layerHandle.layer != null); |
| 3273 | assert(!_layerHandle.layer!.attached); |
| 3274 | RenderObject? node = parent; |
| 3275 | while (node != null) { |
| 3276 | if (node.isRepaintBoundary) { |
| 3277 | if (node._layerHandle.layer == null) { |
| 3278 | // Looks like the subtree here has never been painted. Let it handle itself. |
| 3279 | break; |
| 3280 | } |
| 3281 | if (node._layerHandle.layer!.attached) { |
| 3282 | // It's the one that detached us, so it's the one that will decide to repaint us. |
| 3283 | break; |
| 3284 | } |
| 3285 | node._needsPaint = true; |
| 3286 | } |
| 3287 | node = node.parent; |
| 3288 | } |
| 3289 | } |
| 3290 | |
| 3291 | /// Bootstrap the rendering pipeline by scheduling the very first paint. |
| 3292 | /// |
| 3293 | /// Requires that this render object is attached, is the root of the render |
| 3294 | /// tree, and has a composited layer. |
| 3295 | /// |
| 3296 | /// See [RenderView] for an example of how this function is used. |
| 3297 | void scheduleInitialPaint(ContainerLayer rootLayer) { |
| 3298 | assert(rootLayer.attached); |
| 3299 | assert(attached); |
| 3300 | assert(parent == null); |
| 3301 | assert(!owner!._debugDoingPaint); |
| 3302 | assert(isRepaintBoundary); |
| 3303 | assert(_layerHandle.layer == null); |
| 3304 | _layerHandle.layer = rootLayer; |
| 3305 | assert(_needsPaint); |
| 3306 | owner!._nodesNeedingPaint.add(this); |
| 3307 | } |
| 3308 | |
| 3309 | /// Replace the layer. This is only valid for the root of a render |
| 3310 | /// object subtree (whatever object [scheduleInitialPaint] was |
| 3311 | /// called on). |
| 3312 | /// |
| 3313 | /// This might be called if, e.g., the device pixel ratio changed. |
| 3314 | void replaceRootLayer(OffsetLayer rootLayer) { |
| 3315 | assert(!_debugDisposed); |
| 3316 | assert(rootLayer.attached); |
| 3317 | assert(attached); |
| 3318 | assert(parent == null); |
| 3319 | assert(!owner!._debugDoingPaint); |
| 3320 | assert(isRepaintBoundary); |
| 3321 | assert(_layerHandle.layer != null); // use scheduleInitialPaint the first time |
| 3322 | _layerHandle.layer!.detach(); |
| 3323 | _layerHandle.layer = rootLayer; |
| 3324 | markNeedsPaint(); |
| 3325 | } |
| 3326 | |
| 3327 | void _paintWithContext(PaintingContext context, Offset offset) { |
| 3328 | assert(!_debugDisposed); |
| 3329 | assert(() { |
| 3330 | if (_debugDoingThisPaint) { |
| 3331 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 3332 | ErrorSummary('Tried to paint a RenderObject reentrantly.' ), |
| 3333 | describeForError( |
| 3334 | 'The following RenderObject was already being painted when it was ' |
| 3335 | 'painted again' , |
| 3336 | ), |
| 3337 | ErrorDescription( |
| 3338 | 'Since this typically indicates an infinite recursion, it is ' |
| 3339 | 'disallowed.' , |
| 3340 | ), |
| 3341 | ]); |
| 3342 | } |
| 3343 | return true; |
| 3344 | }()); |
| 3345 | // If we still need layout, then that means that we were skipped in the |
| 3346 | // layout phase and therefore don't need painting. We might not know that |
| 3347 | // yet (that is, our layer might not have been detached yet), because the |
| 3348 | // same node that skipped us in layout is above us in the tree (obviously) |
| 3349 | // and therefore may not have had a chance to paint yet (since the tree |
| 3350 | // paints in reverse order). In particular this will happen if they have |
| 3351 | // a different layer, because there's a repaint boundary between us. |
| 3352 | if (_needsLayout) { |
| 3353 | return; |
| 3354 | } |
| 3355 | if (!kReleaseMode && debugProfilePaintsEnabled) { |
| 3356 | Map<String, String>? debugTimelineArguments; |
| 3357 | assert(() { |
| 3358 | if (debugEnhancePaintTimelineArguments) { |
| 3359 | debugTimelineArguments = toDiagnosticsNode().toTimelineArguments(); |
| 3360 | } |
| 3361 | return true; |
| 3362 | }()); |
| 3363 | FlutterTimeline.startSync(' $runtimeType' , arguments: debugTimelineArguments); |
| 3364 | } |
| 3365 | assert(() { |
| 3366 | if (_needsCompositingBitsUpdate) { |
| 3367 | final RenderObject? parent = this.parent; |
| 3368 | if (parent != null) { |
| 3369 | bool visitedByParent = false; |
| 3370 | parent.visitChildren((RenderObject child) { |
| 3371 | if (child == this) { |
| 3372 | visitedByParent = true; |
| 3373 | } |
| 3374 | }); |
| 3375 | if (!visitedByParent) { |
| 3376 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 3377 | ErrorSummary( |
| 3378 | "A RenderObject was not visited by the parent's visitChildren " |
| 3379 | 'during paint.' , |
| 3380 | ), |
| 3381 | parent.describeForError('The parent was' ), |
| 3382 | describeForError('The child that was not visited was' ), |
| 3383 | ErrorDescription( |
| 3384 | 'A RenderObject with children must implement visitChildren and ' |
| 3385 | 'call the visitor exactly once for each child; it also should not ' |
| 3386 | 'paint children that were removed with dropChild.' , |
| 3387 | ), |
| 3388 | ErrorHint('This usually indicates an error in the Flutter framework itself.' ), |
| 3389 | ]); |
| 3390 | } |
| 3391 | } |
| 3392 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 3393 | ErrorSummary( |
| 3394 | 'Tried to paint a RenderObject before its compositing bits were ' |
| 3395 | 'updated.' , |
| 3396 | ), |
| 3397 | describeForError( |
| 3398 | 'The following RenderObject was marked as having dirty compositing ' |
| 3399 | 'bits at the time that it was painted' , |
| 3400 | ), |
| 3401 | ErrorDescription( |
| 3402 | 'A RenderObject that still has dirty compositing bits cannot be ' |
| 3403 | 'painted because this indicates that the tree has not yet been ' |
| 3404 | 'properly configured for creating the layer tree.' , |
| 3405 | ), |
| 3406 | ErrorHint('This usually indicates an error in the Flutter framework itself.' ), |
| 3407 | ]); |
| 3408 | } |
| 3409 | return true; |
| 3410 | }()); |
| 3411 | RenderObject? debugLastActivePaint; |
| 3412 | assert(() { |
| 3413 | _debugDoingThisPaint = true; |
| 3414 | debugLastActivePaint = _debugActivePaint; |
| 3415 | _debugActivePaint = this; |
| 3416 | assert(!isRepaintBoundary || _layerHandle.layer != null); |
| 3417 | return true; |
| 3418 | }()); |
| 3419 | _needsPaint = false; |
| 3420 | _needsCompositedLayerUpdate = false; |
| 3421 | _wasRepaintBoundary = isRepaintBoundary; |
| 3422 | try { |
| 3423 | paint(context, offset); |
| 3424 | assert(!_needsLayout); // check that the paint() method didn't mark us dirty again |
| 3425 | assert(!_needsPaint); // check that the paint() method didn't mark us dirty again |
| 3426 | } catch (e, stack) { |
| 3427 | _reportException('paint' , e, stack); |
| 3428 | } |
| 3429 | assert(() { |
| 3430 | debugPaint(context, offset); |
| 3431 | _debugActivePaint = debugLastActivePaint; |
| 3432 | _debugDoingThisPaint = false; |
| 3433 | return true; |
| 3434 | }()); |
| 3435 | if (!kReleaseMode && debugProfilePaintsEnabled) { |
| 3436 | FlutterTimeline.finishSync(); |
| 3437 | } |
| 3438 | } |
| 3439 | |
| 3440 | /// An estimate of the bounds within which this render object will paint. |
| 3441 | /// Useful for debugging flags such as [debugPaintLayerBordersEnabled]. |
| 3442 | /// |
| 3443 | /// These are also the bounds used by [showOnScreen] to make a [RenderObject] |
| 3444 | /// visible on screen. |
| 3445 | Rect get paintBounds; |
| 3446 | |
| 3447 | /// Override this method to paint debugging information. |
| 3448 | void debugPaint(PaintingContext context, Offset offset) {} |
| 3449 | |
| 3450 | /// Paint this render object into the given context at the given offset. |
| 3451 | /// |
| 3452 | /// Subclasses should override this method to provide a visual appearance |
| 3453 | /// for themselves. The render object's local coordinate system is |
| 3454 | /// axis-aligned with the coordinate system of the context's canvas and the |
| 3455 | /// render object's local origin (i.e, x=0 and y=0) is placed at the given |
| 3456 | /// offset in the context's canvas. |
| 3457 | /// |
| 3458 | /// Do not call this function directly. If you wish to paint yourself, call |
| 3459 | /// [markNeedsPaint] instead to schedule a call to this function. If you wish |
| 3460 | /// to paint one of your children, call [PaintingContext.paintChild] on the |
| 3461 | /// given `context`. |
| 3462 | /// |
| 3463 | /// When painting one of your children (via a paint child function on the |
| 3464 | /// given context), the current canvas held by the context might change |
| 3465 | /// because draw operations before and after painting children might need to |
| 3466 | /// be recorded on separate compositing layers. |
| 3467 | void paint(PaintingContext context, Offset offset) {} |
| 3468 | |
| 3469 | /// Applies the transform that would be applied when painting the given child |
| 3470 | /// to the given matrix. |
| 3471 | /// |
| 3472 | /// Used by coordinate conversion functions ([getTransformTo], for example) to |
| 3473 | /// translate coordinates local to one render object into coordinates local to |
| 3474 | /// another render object. |
| 3475 | /// |
| 3476 | /// Some RenderObjects will provide a zeroed out matrix in this method, |
| 3477 | /// indicating that the child should not paint anything or respond to hit |
| 3478 | /// tests currently. A parent may supply a non-zero matrix even though it |
| 3479 | /// does not paint its child currently, for example if the parent is a |
| 3480 | /// [RenderOffstage] with `offstage` set to true. In both of these cases, |
| 3481 | /// the parent must return `false` from [paintsChild]. |
| 3482 | void applyPaintTransform(covariant RenderObject child, Matrix4 transform) { |
| 3483 | assert(child.parent == this); |
| 3484 | } |
| 3485 | |
| 3486 | /// Whether the given child would be painted if [paint] were called. |
| 3487 | /// |
| 3488 | /// Some RenderObjects skip painting their children if they are configured to |
| 3489 | /// not produce any visible effects. For example, a [RenderOffstage] with |
| 3490 | /// its `offstage` property set to true, or a [RenderOpacity] with its opacity |
| 3491 | /// value set to zero. |
| 3492 | /// |
| 3493 | /// In these cases, the parent may still supply a non-zero matrix in |
| 3494 | /// [applyPaintTransform] to inform callers about where it would paint the |
| 3495 | /// child if the child were painted at all. Alternatively, the parent may |
| 3496 | /// supply a zeroed out matrix if it would not otherwise be able to determine |
| 3497 | /// a valid matrix for the child and thus cannot meaningfully determine where |
| 3498 | /// the child would paint. |
| 3499 | bool paintsChild(covariant RenderObject child) { |
| 3500 | assert(child.parent == this); |
| 3501 | return true; |
| 3502 | } |
| 3503 | |
| 3504 | /// {@template flutter.rendering.RenderObject.getTransformTo} |
| 3505 | /// Applies the paint transform from this [RenderObject] to the `target` |
| 3506 | /// [RenderObject]. |
| 3507 | /// |
| 3508 | /// Returns a matrix that maps the local paint coordinate system to the |
| 3509 | /// coordinate system of `target`, or a [Matrix4.zero] if the paint transform |
| 3510 | /// can not be computed. |
| 3511 | /// |
| 3512 | /// This method throws an exception when the `target` is not in the same render |
| 3513 | /// tree as this [RenderObject], as the behavior is undefined. |
| 3514 | /// |
| 3515 | /// This method ignores [RenderObject.paintsChild]. This means it will still |
| 3516 | /// try to compute the paint transform even if this [RenderObject] or |
| 3517 | /// `target` is currently not visible. |
| 3518 | /// |
| 3519 | /// If `target` is null, this method returns a matrix that maps from the |
| 3520 | /// local paint coordinate system to the coordinate system of the |
| 3521 | /// [PipelineOwner.rootNode]. |
| 3522 | /// {@endtemplate} |
| 3523 | /// |
| 3524 | /// For the render tree owned by the [RendererBinding] (i.e. for the main |
| 3525 | /// render tree displayed on the device) this means that this method maps to |
| 3526 | /// the global coordinate system in logical pixels. To get physical pixels, |
| 3527 | /// use [applyPaintTransform] from the [RenderView] to further transform the |
| 3528 | /// coordinate. |
| 3529 | Matrix4 getTransformTo(RenderObject? target) { |
| 3530 | assert(attached); |
| 3531 | // The paths from to fromRenderObject and toRenderObject's common ancestor. |
| 3532 | // Each list's length is greater than 1 if not null. |
| 3533 | // |
| 3534 | // [this, ...., commonAncestorRenderObject], or null if `this` is the common |
| 3535 | // ancestor. |
| 3536 | List<RenderObject>? fromPath; |
| 3537 | // [target, ...., commonAncestorRenderObject], or null if `target` is the |
| 3538 | // common ancestor. |
| 3539 | List<RenderObject>? toPath; |
| 3540 | |
| 3541 | RenderObject from = this; |
| 3542 | RenderObject to = target ?? owner!.rootNode!; |
| 3543 | |
| 3544 | while (!identical(from, to)) { |
| 3545 | final int fromDepth = from.depth; |
| 3546 | final int toDepth = to.depth; |
| 3547 | |
| 3548 | if (fromDepth >= toDepth) { |
| 3549 | final RenderObject fromParent = |
| 3550 | from.parent ?? |
| 3551 | (throw FlutterError(' $target and $this are not in the same render tree.' )); |
| 3552 | (fromPath ??= <RenderObject>[this]).add(fromParent); |
| 3553 | from = fromParent; |
| 3554 | } |
| 3555 | if (fromDepth <= toDepth) { |
| 3556 | final RenderObject toParent = |
| 3557 | to.parent ?? (throw FlutterError(' $target and $this are not in the same render tree.' )); |
| 3558 | assert( |
| 3559 | target != null, |
| 3560 | ' $this has a depth that is less than or equal to ${owner?.rootNode}' , |
| 3561 | ); |
| 3562 | (toPath ??= <RenderObject>[target!]).add(toParent); |
| 3563 | to = toParent; |
| 3564 | } |
| 3565 | } |
| 3566 | |
| 3567 | Matrix4? fromTransform; |
| 3568 | if (fromPath != null) { |
| 3569 | assert(fromPath.length > 1); |
| 3570 | fromTransform = Matrix4.identity(); |
| 3571 | final int lastIndex = target == null ? fromPath.length - 2 : fromPath.length - 1; |
| 3572 | for (int index = lastIndex; index > 0; index -= 1) { |
| 3573 | fromPath[index].applyPaintTransform(fromPath[index - 1], fromTransform); |
| 3574 | } |
| 3575 | } |
| 3576 | if (toPath == null) { |
| 3577 | return fromTransform ?? Matrix4.identity(); |
| 3578 | } |
| 3579 | |
| 3580 | assert(toPath.length > 1); |
| 3581 | final Matrix4 toTransform = Matrix4.identity(); |
| 3582 | for (int index = toPath.length - 1; index > 0; index -= 1) { |
| 3583 | toPath[index].applyPaintTransform(toPath[index - 1], toTransform); |
| 3584 | } |
| 3585 | if (toTransform.invert() == 0) { |
| 3586 | // If the matrix is singular then `invert()` doesn't do anything. |
| 3587 | return Matrix4.zero(); |
| 3588 | } |
| 3589 | return (fromTransform?..multiply(toTransform)) ?? toTransform; |
| 3590 | } |
| 3591 | |
| 3592 | /// Returns a rect in this object's coordinate system that describes |
| 3593 | /// the approximate bounding box of the clip rect that would be |
| 3594 | /// applied to the given child during the paint phase, if any. |
| 3595 | /// |
| 3596 | /// Returns null if the child would not be clipped. |
| 3597 | /// |
| 3598 | /// This is used in the semantics phase to avoid including children |
| 3599 | /// that are not physically visible. |
| 3600 | /// |
| 3601 | /// RenderObjects that respect a [Clip] behavior when painting _must_ respect |
| 3602 | /// that same behavior when describing this value. For example, if passing |
| 3603 | /// [Clip.none] to [PaintingContext.pushClipRect] as the `clipBehavior`, then |
| 3604 | /// the implementation of this method must return null. |
| 3605 | Rect? describeApproximatePaintClip(covariant RenderObject child) => null; |
| 3606 | |
| 3607 | /// Returns a rect in this object's coordinate system that describes |
| 3608 | /// which [SemanticsNode]s produced by the `child` should be included in the |
| 3609 | /// semantics tree. [SemanticsNode]s from the `child` that are positioned |
| 3610 | /// outside of this rect will be dropped. Child [SemanticsNode]s that are |
| 3611 | /// positioned inside this rect, but outside of [describeApproximatePaintClip] |
| 3612 | /// will be included in the tree marked as hidden. Child [SemanticsNode]s |
| 3613 | /// that are inside of both rect will be included in the tree as regular |
| 3614 | /// nodes. |
| 3615 | /// |
| 3616 | /// This method only returns a non-null value if the semantics clip rect |
| 3617 | /// is different from the rect returned by [describeApproximatePaintClip]. |
| 3618 | /// If the semantics clip rect and the paint clip rect are the same, this |
| 3619 | /// method returns null. |
| 3620 | /// |
| 3621 | /// A viewport would typically implement this method to include semantic nodes |
| 3622 | /// in the semantics tree that are currently hidden just before the leading |
| 3623 | /// or just after the trailing edge. These nodes have to be included in the |
| 3624 | /// semantics tree to implement implicit accessibility scrolling on iOS where |
| 3625 | /// the viewport scrolls implicitly when moving the accessibility focus from |
| 3626 | /// the last visible node in the viewport to the first hidden one. |
| 3627 | /// |
| 3628 | /// See also: |
| 3629 | /// |
| 3630 | /// * [RenderViewportBase.cacheExtent], used by viewports to extend their |
| 3631 | /// semantics clip beyond their approximate paint clip. |
| 3632 | Rect? describeSemanticsClip(covariant RenderObject? child) => null; |
| 3633 | |
| 3634 | // SEMANTICS |
| 3635 | |
| 3636 | /// Bootstrap the semantics reporting mechanism by marking this node |
| 3637 | /// as needing a semantics update. |
| 3638 | /// |
| 3639 | /// Requires that this render object is attached, and is the root of |
| 3640 | /// the render tree. |
| 3641 | /// |
| 3642 | /// See [RendererBinding] for an example of how this function is used. |
| 3643 | void scheduleInitialSemantics() { |
| 3644 | assert(!_debugDisposed); |
| 3645 | assert(attached); |
| 3646 | assert(parent == null); |
| 3647 | assert(!owner!._debugDoingSemantics); |
| 3648 | assert(_semantics.parentDataDirty || !_semantics.built); |
| 3649 | assert(owner!._semanticsOwner != null); |
| 3650 | owner!._nodesNeedingSemantics.add(this); |
| 3651 | owner!.requestVisualUpdate(); |
| 3652 | } |
| 3653 | |
| 3654 | /// Report the semantics of this node, for example for accessibility purposes. |
| 3655 | /// |
| 3656 | /// This method should be overridden by subclasses that have interesting |
| 3657 | /// semantic information. |
| 3658 | /// |
| 3659 | /// The given [SemanticsConfiguration] object is mutable and should be |
| 3660 | /// annotated in a manner that describes the current state. No reference |
| 3661 | /// should be kept to that object; mutating it outside of the context of the |
| 3662 | /// [describeSemanticsConfiguration] call (for example as a result of |
| 3663 | /// asynchronous computation) will at best have no useful effect and at worse |
| 3664 | /// will cause crashes as the data will be in an inconsistent state. |
| 3665 | /// |
| 3666 | /// {@tool snippet} |
| 3667 | /// |
| 3668 | /// The following snippet will describe the node as a button that responds to |
| 3669 | /// tap actions. |
| 3670 | /// |
| 3671 | /// ```dart |
| 3672 | /// abstract class SemanticButtonRenderObject extends RenderObject { |
| 3673 | /// @override |
| 3674 | /// void describeSemanticsConfiguration(SemanticsConfiguration config) { |
| 3675 | /// super.describeSemanticsConfiguration(config); |
| 3676 | /// config |
| 3677 | /// ..onTap = _handleTap |
| 3678 | /// ..label = 'I am a button' |
| 3679 | /// ..isButton = true; |
| 3680 | /// } |
| 3681 | /// |
| 3682 | /// void _handleTap() { |
| 3683 | /// // Do something. |
| 3684 | /// } |
| 3685 | /// } |
| 3686 | /// ``` |
| 3687 | /// {@end-tool} |
| 3688 | @protected |
| 3689 | void describeSemanticsConfiguration(SemanticsConfiguration config) { |
| 3690 | // Nothing to do by default. |
| 3691 | } |
| 3692 | |
| 3693 | /// Sends a [SemanticsEvent] associated with this render object's [SemanticsNode]. |
| 3694 | /// |
| 3695 | /// If this render object has no semantics information, the first parent |
| 3696 | /// render object with a non-null semantic node is used. |
| 3697 | /// |
| 3698 | /// If semantics are disabled, no events are dispatched. |
| 3699 | /// |
| 3700 | /// See [SemanticsNode.sendEvent] for a full description of the behavior. |
| 3701 | void sendSemanticsEvent(SemanticsEvent semanticsEvent) { |
| 3702 | if (owner!.semanticsOwner == null) { |
| 3703 | return; |
| 3704 | } |
| 3705 | final SemanticsNode? node = _semantics.cachedSemanticsNode; |
| 3706 | if (node != null && !node.isMergedIntoParent) { |
| 3707 | node.sendEvent(semanticsEvent); |
| 3708 | } else if (parent != null) { |
| 3709 | parent!.sendSemanticsEvent(semanticsEvent); |
| 3710 | } |
| 3711 | } |
| 3712 | |
| 3713 | /// The bounding box, in the local coordinate system, of this |
| 3714 | /// object, for accessibility purposes. |
| 3715 | Rect get semanticBounds; |
| 3716 | |
| 3717 | /// Whether the semantics of this render object is dirty and await the update. |
| 3718 | /// |
| 3719 | /// Always returns false in release mode. |
| 3720 | bool get debugNeedsSemanticsUpdate { |
| 3721 | if (kReleaseMode) { |
| 3722 | return false; |
| 3723 | } |
| 3724 | return _semantics.parentDataDirty; |
| 3725 | } |
| 3726 | |
| 3727 | /// The semantics of this render object. |
| 3728 | /// |
| 3729 | /// Exposed only for testing and debugging. To learn about the semantics of |
| 3730 | /// render objects in production, obtain a [SemanticsHandle] from |
| 3731 | /// [PipelineOwner.ensureSemantics]. |
| 3732 | /// |
| 3733 | /// Only valid in debug and profile mode. In release builds, always returns |
| 3734 | /// null. |
| 3735 | SemanticsNode? get debugSemantics { |
| 3736 | // If _semantics.built is not true, the semantics node is an old cache and |
| 3737 | // is not on the semantics tree. |
| 3738 | if (!kReleaseMode && _semantics.built) { |
| 3739 | return _semantics.cachedSemanticsNode; |
| 3740 | } |
| 3741 | return null; |
| 3742 | } |
| 3743 | |
| 3744 | /// Removes all semantics from this render object and its descendants. |
| 3745 | /// |
| 3746 | /// Should only be called on objects whose [parent] is not a [RenderObject]. |
| 3747 | /// |
| 3748 | /// Override this method if you instantiate new [SemanticsNode]s in an |
| 3749 | /// overridden [assembleSemanticsNode] method, to dispose of those nodes. |
| 3750 | @mustCallSuper |
| 3751 | void clearSemantics() { |
| 3752 | _semantics.clear(); |
| 3753 | visitChildren((RenderObject child) { |
| 3754 | child.clearSemantics(); |
| 3755 | }); |
| 3756 | } |
| 3757 | |
| 3758 | /// Mark this node as needing an update to its semantics description. |
| 3759 | /// |
| 3760 | /// This must be called whenever the semantics configuration of this |
| 3761 | /// [RenderObject] as annotated by [describeSemanticsConfiguration] changes in |
| 3762 | /// any way to update the semantics tree. |
| 3763 | void markNeedsSemanticsUpdate() { |
| 3764 | assert(!_debugDisposed); |
| 3765 | assert(!attached || !owner!._debugDoingSemantics); |
| 3766 | if (!attached || owner!._semanticsOwner == null) { |
| 3767 | return; |
| 3768 | } |
| 3769 | _semantics.markNeedsUpdate(); |
| 3770 | } |
| 3771 | |
| 3772 | late final _RenderObjectSemantics _semantics = _RenderObjectSemantics(this); |
| 3773 | |
| 3774 | /// Called when collecting the semantics of this node. |
| 3775 | /// |
| 3776 | /// The implementation has to return the children in paint order skipping all |
| 3777 | /// children that are not semantically relevant (e.g. because they are |
| 3778 | /// invisible). |
| 3779 | /// |
| 3780 | /// The default implementation mirrors the behavior of |
| 3781 | /// [visitChildren] (which is supposed to walk all the children). |
| 3782 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
| 3783 | visitChildren(visitor); |
| 3784 | } |
| 3785 | |
| 3786 | /// Assemble the [SemanticsNode] for this [RenderObject]. |
| 3787 | /// |
| 3788 | /// If [describeSemanticsConfiguration] sets |
| 3789 | /// [SemanticsConfiguration.isSemanticBoundary] to true, this method is called |
| 3790 | /// with the `node` created for this [RenderObject], the `config` to be |
| 3791 | /// applied to that node and the `children` [SemanticsNode]s that descendants |
| 3792 | /// of this RenderObject have generated. |
| 3793 | /// |
| 3794 | /// By default, the method will annotate `node` with `config` and add the |
| 3795 | /// `children` to it. |
| 3796 | /// |
| 3797 | /// Subclasses can override this method to add additional [SemanticsNode]s |
| 3798 | /// to the tree. If new [SemanticsNode]s are instantiated in this method |
| 3799 | /// they must be disposed in [clearSemantics]. |
| 3800 | void assembleSemanticsNode( |
| 3801 | SemanticsNode node, |
| 3802 | SemanticsConfiguration config, |
| 3803 | Iterable<SemanticsNode> children, |
| 3804 | ) { |
| 3805 | assert(node == _semantics.cachedSemanticsNode); |
| 3806 | // TODO(a14n): remove the following cast by updating type of parameter in either updateWith or assembleSemanticsNode |
| 3807 | node.updateWith(config: config, childrenInInversePaintOrder: children as List<SemanticsNode>); |
| 3808 | } |
| 3809 | |
| 3810 | // EVENTS |
| 3811 | |
| 3812 | /// Override this method to handle pointer events that hit this render object. |
| 3813 | @override |
| 3814 | void handleEvent(PointerEvent event, covariant HitTestEntry entry) {} |
| 3815 | |
| 3816 | // HIT TESTING |
| 3817 | |
| 3818 | // RenderObject subclasses are expected to have a method like the following |
| 3819 | // (with the signature being whatever passes for coordinates for this |
| 3820 | // particular class): |
| 3821 | // |
| 3822 | // bool hitTest(HitTestResult result, { required Offset position }) { |
| 3823 | // // If the given position is not inside this node, then return false. |
| 3824 | // // Otherwise: |
| 3825 | // // For each child that intersects the position, in z-order starting from |
| 3826 | // // the top, call hitTest() for that child, passing it /result/, and the |
| 3827 | // // coordinates converted to the child's coordinate origin, and stop at |
| 3828 | // // the first child that returns true. |
| 3829 | // // Then, add yourself to /result/, and return true. |
| 3830 | // } |
| 3831 | // |
| 3832 | // If you add yourself to /result/ and still return false, then that means you |
| 3833 | // will see events but so will objects below you. |
| 3834 | |
| 3835 | /// Returns a human understandable name. |
| 3836 | @override |
| 3837 | String toStringShort() { |
| 3838 | String header = describeIdentity(this); |
| 3839 | if (!kReleaseMode) { |
| 3840 | if (_debugDisposed) { |
| 3841 | header += ' DISPOSED' ; |
| 3842 | return header; |
| 3843 | } |
| 3844 | |
| 3845 | int count = 0; |
| 3846 | for ( |
| 3847 | RenderObject? node = this; |
| 3848 | node != null && !(node._isRelayoutBoundary ?? false); |
| 3849 | node = node.parent |
| 3850 | ) { |
| 3851 | if (node._isRelayoutBoundary == null) { |
| 3852 | count = -1; |
| 3853 | break; |
| 3854 | } |
| 3855 | count += 1; |
| 3856 | } |
| 3857 | if (count > 0) { |
| 3858 | header += ' relayoutBoundary=up $count' ; |
| 3859 | } |
| 3860 | if (_needsLayout) { |
| 3861 | header += ' NEEDS-LAYOUT' ; |
| 3862 | } |
| 3863 | if (_needsPaint) { |
| 3864 | header += ' NEEDS-PAINT' ; |
| 3865 | } |
| 3866 | if (_needsCompositingBitsUpdate) { |
| 3867 | header += ' NEEDS-COMPOSITING-BITS-UPDATE' ; |
| 3868 | } |
| 3869 | if (!attached) { |
| 3870 | header += ' DETACHED' ; |
| 3871 | } |
| 3872 | } |
| 3873 | return header; |
| 3874 | } |
| 3875 | |
| 3876 | @override |
| 3877 | String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) => toStringShort(); |
| 3878 | |
| 3879 | /// Returns a description of the tree rooted at this node. |
| 3880 | /// If the prefix argument is provided, then every line in the output |
| 3881 | /// will be prefixed by that string. |
| 3882 | @override |
| 3883 | String toStringDeep({ |
| 3884 | String prefixLineOne = '' , |
| 3885 | String? prefixOtherLines = '' , |
| 3886 | DiagnosticLevel minLevel = DiagnosticLevel.debug, |
| 3887 | int wrapWidth = 65, |
| 3888 | }) { |
| 3889 | return _withDebugActiveLayoutCleared( |
| 3890 | () => super.toStringDeep( |
| 3891 | prefixLineOne: prefixLineOne, |
| 3892 | prefixOtherLines: prefixOtherLines, |
| 3893 | minLevel: minLevel, |
| 3894 | wrapWidth: wrapWidth, |
| 3895 | ), |
| 3896 | ); |
| 3897 | } |
| 3898 | |
| 3899 | /// Returns a one-line detailed description of the render object. |
| 3900 | /// This description is often somewhat long. |
| 3901 | /// |
| 3902 | /// This includes the same information for this RenderObject as given by |
| 3903 | /// [toStringDeep], but does not recurse to any children. |
| 3904 | @override |
| 3905 | String toStringShallow({String joiner = ', ' , DiagnosticLevel minLevel = DiagnosticLevel.debug}) { |
| 3906 | return _withDebugActiveLayoutCleared( |
| 3907 | () => super.toStringShallow(joiner: joiner, minLevel: minLevel), |
| 3908 | ); |
| 3909 | } |
| 3910 | |
| 3911 | @protected |
| 3912 | @override |
| 3913 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 3914 | super.debugFillProperties(properties); |
| 3915 | properties.add( |
| 3916 | FlagProperty('needsCompositing' , value: _needsCompositing, ifTrue: 'needs compositing' ), |
| 3917 | ); |
| 3918 | properties.add( |
| 3919 | DiagnosticsProperty<Object?>( |
| 3920 | 'creator' , |
| 3921 | debugCreator, |
| 3922 | defaultValue: null, |
| 3923 | level: DiagnosticLevel.debug, |
| 3924 | ), |
| 3925 | ); |
| 3926 | properties.add( |
| 3927 | DiagnosticsProperty<ParentData>( |
| 3928 | 'parentData' , |
| 3929 | parentData, |
| 3930 | tooltip: (_debugCanParentUseSize ?? false) ? 'can use size' : null, |
| 3931 | missingIfNull: true, |
| 3932 | ), |
| 3933 | ); |
| 3934 | properties.add( |
| 3935 | DiagnosticsProperty<Constraints>('constraints' , _constraints, missingIfNull: true), |
| 3936 | ); |
| 3937 | // don't access it via the "layer" getter since that's only valid when we don't need paint |
| 3938 | properties.add( |
| 3939 | DiagnosticsProperty<ContainerLayer>('layer' , _layerHandle.layer, defaultValue: null), |
| 3940 | ); |
| 3941 | properties.add( |
| 3942 | DiagnosticsProperty<SemanticsNode>('semantics node' , debugSemantics, defaultValue: null), |
| 3943 | ); |
| 3944 | properties.add( |
| 3945 | FlagProperty( |
| 3946 | 'isBlockingSemanticsOfPreviouslyPaintedNodes' , |
| 3947 | value: _semantics.configProvider.effective.isBlockingSemanticsOfPreviouslyPaintedNodes, |
| 3948 | ifTrue: 'blocks semantics of earlier render objects below the common boundary' , |
| 3949 | ), |
| 3950 | ); |
| 3951 | properties.add( |
| 3952 | FlagProperty( |
| 3953 | 'isSemanticBoundary' , |
| 3954 | value: _semantics.configProvider.effective.isSemanticBoundary, |
| 3955 | ifTrue: 'semantic boundary' , |
| 3956 | ), |
| 3957 | ); |
| 3958 | } |
| 3959 | |
| 3960 | @override |
| 3961 | List<DiagnosticsNode> debugDescribeChildren() => <DiagnosticsNode>[]; |
| 3962 | |
| 3963 | /// Attempt to make (a portion of) this or a descendant [RenderObject] visible |
| 3964 | /// on screen. |
| 3965 | /// |
| 3966 | /// If `descendant` is provided, that [RenderObject] is made visible. If |
| 3967 | /// `descendant` is omitted, this [RenderObject] is made visible. |
| 3968 | /// |
| 3969 | /// The optional `rect` parameter describes which area of that [RenderObject] |
| 3970 | /// should be shown on screen. If `rect` is null, the entire |
| 3971 | /// [RenderObject] (as defined by its [paintBounds]) will be revealed. The |
| 3972 | /// `rect` parameter is interpreted relative to the coordinate system of |
| 3973 | /// `descendant` if that argument is provided and relative to this |
| 3974 | /// [RenderObject] otherwise. |
| 3975 | /// |
| 3976 | /// The `duration` parameter can be set to a non-zero value to bring the |
| 3977 | /// target object on screen in an animation defined by `curve`. |
| 3978 | /// |
| 3979 | /// See also: |
| 3980 | /// |
| 3981 | /// * [RenderViewportBase.showInViewport], which [RenderViewportBase] and |
| 3982 | /// [SingleChildScrollView] delegate this method to. |
| 3983 | void showOnScreen({ |
| 3984 | RenderObject? descendant, |
| 3985 | Rect? rect, |
| 3986 | Duration duration = Duration.zero, |
| 3987 | Curve curve = Curves.ease, |
| 3988 | }) { |
| 3989 | parent?.showOnScreen( |
| 3990 | descendant: descendant ?? this, |
| 3991 | rect: rect, |
| 3992 | duration: duration, |
| 3993 | curve: curve, |
| 3994 | ); |
| 3995 | } |
| 3996 | |
| 3997 | /// Adds a debug representation of a [RenderObject] optimized for including in |
| 3998 | /// error messages. |
| 3999 | /// |
| 4000 | /// The default [style] of [DiagnosticsTreeStyle.shallow] ensures that all of |
| 4001 | /// the properties of the render object are included in the error output but |
| 4002 | /// none of the children of the object are. |
| 4003 | /// |
| 4004 | /// You should always include a RenderObject in an error message if it is the |
| 4005 | /// [RenderObject] causing the failure or contract violation of the error. |
| 4006 | DiagnosticsNode describeForError( |
| 4007 | String name, { |
| 4008 | DiagnosticsTreeStyle style = DiagnosticsTreeStyle.shallow, |
| 4009 | }) { |
| 4010 | return toDiagnosticsNode(name: name, style: style); |
| 4011 | } |
| 4012 | } |
| 4013 | |
| 4014 | /// Generic mixin for render objects with one child. |
| 4015 | /// |
| 4016 | /// Provides a child model for a render object subclass that has |
| 4017 | /// a unique child, which is accessible via the [child] getter. |
| 4018 | /// |
| 4019 | /// This mixin is typically used to implement render objects created |
| 4020 | /// in a [SingleChildRenderObjectWidget]. |
| 4021 | mixin RenderObjectWithChildMixin<ChildType extends RenderObject> on RenderObject { |
| 4022 | /// Checks whether the given render object has the correct [runtimeType] to be |
| 4023 | /// a child of this render object. |
| 4024 | /// |
| 4025 | /// Does nothing if assertions are disabled. |
| 4026 | /// |
| 4027 | /// Always returns true. |
| 4028 | bool debugValidateChild(RenderObject child) { |
| 4029 | assert(() { |
| 4030 | if (child is! ChildType) { |
| 4031 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 4032 | ErrorSummary( |
| 4033 | 'A $runtimeType expected a child of type $ChildType but received a ' |
| 4034 | 'child of type ${child.runtimeType}.' , |
| 4035 | ), |
| 4036 | ErrorDescription( |
| 4037 | 'RenderObjects expect specific types of children because they ' |
| 4038 | 'coordinate with their children during layout and paint. For ' |
| 4039 | 'example, a RenderSliver cannot be the child of a RenderBox because ' |
| 4040 | 'a RenderSliver does not understand the RenderBox layout protocol.' , |
| 4041 | ), |
| 4042 | ErrorSpacer(), |
| 4043 | DiagnosticsProperty<Object?>( |
| 4044 | 'The $runtimeType that expected a $ChildType child was created by' , |
| 4045 | debugCreator, |
| 4046 | style: DiagnosticsTreeStyle.errorProperty, |
| 4047 | ), |
| 4048 | ErrorSpacer(), |
| 4049 | DiagnosticsProperty<Object?>( |
| 4050 | 'The ${child.runtimeType} that did not match the expected child type ' |
| 4051 | 'was created by' , |
| 4052 | child.debugCreator, |
| 4053 | style: DiagnosticsTreeStyle.errorProperty, |
| 4054 | ), |
| 4055 | ]); |
| 4056 | } |
| 4057 | return true; |
| 4058 | }()); |
| 4059 | return true; |
| 4060 | } |
| 4061 | |
| 4062 | ChildType? _child; |
| 4063 | |
| 4064 | /// The render object's unique child. |
| 4065 | ChildType? get child => _child; |
| 4066 | set child(ChildType? value) { |
| 4067 | if (_child != null) { |
| 4068 | dropChild(_child!); |
| 4069 | } |
| 4070 | _child = value; |
| 4071 | if (_child != null) { |
| 4072 | adoptChild(_child!); |
| 4073 | } |
| 4074 | } |
| 4075 | |
| 4076 | @override |
| 4077 | void attach(PipelineOwner owner) { |
| 4078 | super.attach(owner); |
| 4079 | _child?.attach(owner); |
| 4080 | } |
| 4081 | |
| 4082 | @override |
| 4083 | void detach() { |
| 4084 | super.detach(); |
| 4085 | _child?.detach(); |
| 4086 | } |
| 4087 | |
| 4088 | @override |
| 4089 | void redepthChildren() { |
| 4090 | if (_child != null) { |
| 4091 | redepthChild(_child!); |
| 4092 | } |
| 4093 | } |
| 4094 | |
| 4095 | @override |
| 4096 | void visitChildren(RenderObjectVisitor visitor) { |
| 4097 | if (_child != null) { |
| 4098 | visitor(_child!); |
| 4099 | } |
| 4100 | } |
| 4101 | |
| 4102 | @override |
| 4103 | List<DiagnosticsNode> debugDescribeChildren() { |
| 4104 | return child != null |
| 4105 | ? <DiagnosticsNode>[child!.toDiagnosticsNode(name: 'child' )] |
| 4106 | : <DiagnosticsNode>[]; |
| 4107 | } |
| 4108 | } |
| 4109 | |
| 4110 | /// A mixin for managing [RenderObject] with a [layoutCallback], which will be |
| 4111 | /// invoked during this [RenderObject]'s layout process if scheduled using |
| 4112 | /// [scheduleLayoutCallback]. |
| 4113 | /// |
| 4114 | /// A layout callback is typically a callback that mutates the [RenderObject]'s |
| 4115 | /// render subtree during the [RenderObject]'s layout process. When an ancestor |
| 4116 | /// [RenderObject] chooses to skip laying out this [RenderObject] in its |
| 4117 | /// [performLayout] implementation (for example, for performance reasons, an |
| 4118 | /// [Overlay] may skip laying out an offstage [OverlayEntry] while keeping it in |
| 4119 | /// the tree), normally the [layoutCallback] will not be invoked because the |
| 4120 | /// [layout] method will not be called. This can be undesirable when the |
| 4121 | /// [layoutCallback] involves rebuilding dirty widgets (most notably, the |
| 4122 | /// [LayoutBuilder] widget). Unlike render subtrees, typically all dirty widgets |
| 4123 | /// (even off-screen ones) in a widget tree must be rebuilt. This mixin makes |
| 4124 | /// sure once scheduled, the [layoutCallback] method will be invoked even if it's |
| 4125 | /// skipped by an ancestor [RenderObject], unless this [RenderObject] has never |
| 4126 | /// been laid out. |
| 4127 | /// |
| 4128 | /// Subclasses must not invoke the layout callback directly. Instead, call |
| 4129 | /// [runLayoutCallback] in the [performLayout] implementation. |
| 4130 | /// |
| 4131 | /// See also: |
| 4132 | /// |
| 4133 | /// * [LayoutBuilder] and [SliverLayoutBuilder], which use the mixin. |
| 4134 | mixin RenderObjectWithLayoutCallbackMixin on RenderObject { |
| 4135 | // The initial value of this flag must be set to true to prevent the layout |
| 4136 | // callback from being scheduled when the subtree has never been laid out (in |
| 4137 | // which case the `constraints` or any other layout information is unknown). |
| 4138 | bool _needsRebuild = true; |
| 4139 | |
| 4140 | /// The layout callback to be invoked during [performLayout]. |
| 4141 | /// |
| 4142 | /// This method should not be invoked directly. Instead, call |
| 4143 | /// [runLayoutCallback] in the [performLayout] implementation. This callback |
| 4144 | /// will be invoked using [invokeLayoutCallback]. |
| 4145 | @visibleForOverriding |
| 4146 | void layoutCallback(); |
| 4147 | |
| 4148 | /// Invokes [layoutCallback] with [invokeLayoutCallback]. |
| 4149 | /// |
| 4150 | /// This method must be called in [performLayout], typically as early as |
| 4151 | /// possible before any layout work is done, to avoid re-dirtying any child |
| 4152 | /// [RenderObject]s. |
| 4153 | @mustCallSuper |
| 4154 | void runLayoutCallback() { |
| 4155 | assert(debugDoingThisLayout); |
| 4156 | invokeLayoutCallback((_) => layoutCallback()); |
| 4157 | _needsRebuild = false; |
| 4158 | } |
| 4159 | |
| 4160 | /// Informs the framework that the layout callback has been updated and must be |
| 4161 | /// invoked again when this [RenderObject] is ready for layout, even when an |
| 4162 | /// ancestor [RenderObject] chooses to skip laying out this render subtree. |
| 4163 | @mustCallSuper |
| 4164 | void scheduleLayoutCallback() { |
| 4165 | if (_needsRebuild) { |
| 4166 | assert(debugNeedsLayout); |
| 4167 | return; |
| 4168 | } |
| 4169 | _needsRebuild = true; |
| 4170 | // This ensures that the layout callback will be run even if an ancestor |
| 4171 | // chooses to not lay out this subtree (for example, obstructed OverlayEntries |
| 4172 | // with `maintainState` set to true), to maintain the widget tree integrity |
| 4173 | // (making sure global keys are unique, for example). |
| 4174 | owner?._nodesNeedingLayout.add(this); |
| 4175 | // In an active tree, markNeedsLayout is needed to inform the layout boundary |
| 4176 | // that its child size may change. |
| 4177 | super.markNeedsLayout(); |
| 4178 | } |
| 4179 | } |
| 4180 | |
| 4181 | /// Parent data to support a doubly-linked list of children. |
| 4182 | /// |
| 4183 | /// The children can be traversed using [nextSibling] or [previousSibling], |
| 4184 | /// which can be called on the parent data of the render objects |
| 4185 | /// obtained via [ContainerRenderObjectMixin.firstChild] or |
| 4186 | /// [ContainerRenderObjectMixin.lastChild]. |
| 4187 | mixin ContainerParentDataMixin<ChildType extends RenderObject> on ParentData { |
| 4188 | /// The previous sibling in the parent's child list. |
| 4189 | ChildType? previousSibling; |
| 4190 | |
| 4191 | /// The next sibling in the parent's child list. |
| 4192 | ChildType? nextSibling; |
| 4193 | |
| 4194 | /// Clear the sibling pointers. |
| 4195 | @override |
| 4196 | void detach() { |
| 4197 | assert( |
| 4198 | previousSibling == null, |
| 4199 | 'Pointers to siblings must be nulled before detaching ParentData.' , |
| 4200 | ); |
| 4201 | assert(nextSibling == null, 'Pointers to siblings must be nulled before detaching ParentData.' ); |
| 4202 | super.detach(); |
| 4203 | } |
| 4204 | } |
| 4205 | |
| 4206 | /// Generic mixin for render objects with a list of children. |
| 4207 | /// |
| 4208 | /// Provides a child model for a render object subclass that has a doubly-linked |
| 4209 | /// list of children. |
| 4210 | /// |
| 4211 | /// The [ChildType] specifies the type of the children (extending [RenderObject]), |
| 4212 | /// e.g. [RenderBox]. |
| 4213 | /// |
| 4214 | /// [ParentDataType] stores parent container data on its child render objects. |
| 4215 | /// It must extend [ContainerParentDataMixin], which provides the interface |
| 4216 | /// for visiting children. This data is populated by |
| 4217 | /// [RenderObject.setupParentData] implemented by the class using this mixin. |
| 4218 | /// |
| 4219 | /// When using [RenderBox] as the child type, you will usually want to make use of |
| 4220 | /// [RenderBoxContainerDefaultsMixin] and extend [ContainerBoxParentData] for the |
| 4221 | /// parent data. |
| 4222 | /// |
| 4223 | /// Moreover, this is a required mixin for render objects returned to [MultiChildRenderObjectWidget]. |
| 4224 | /// |
| 4225 | /// See also: |
| 4226 | /// |
| 4227 | /// * [SlottedContainerRenderObjectMixin], which organizes its children |
| 4228 | /// in different named slots. |
| 4229 | mixin ContainerRenderObjectMixin< |
| 4230 | ChildType extends RenderObject, |
| 4231 | ParentDataType extends ContainerParentDataMixin<ChildType> |
| 4232 | > |
| 4233 | on RenderObject { |
| 4234 | bool _debugUltimatePreviousSiblingOf(ChildType child, {ChildType? equals}) { |
| 4235 | ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4236 | while (childParentData.previousSibling != null) { |
| 4237 | assert(childParentData.previousSibling != child); |
| 4238 | child = childParentData.previousSibling!; |
| 4239 | childParentData = child.parentData! as ParentDataType; |
| 4240 | } |
| 4241 | return child == equals; |
| 4242 | } |
| 4243 | |
| 4244 | bool _debugUltimateNextSiblingOf(ChildType child, {ChildType? equals}) { |
| 4245 | ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4246 | while (childParentData.nextSibling != null) { |
| 4247 | assert(childParentData.nextSibling != child); |
| 4248 | child = childParentData.nextSibling!; |
| 4249 | childParentData = child.parentData! as ParentDataType; |
| 4250 | } |
| 4251 | return child == equals; |
| 4252 | } |
| 4253 | |
| 4254 | int _childCount = 0; |
| 4255 | |
| 4256 | /// The number of children. |
| 4257 | int get childCount => _childCount; |
| 4258 | |
| 4259 | /// Checks whether the given render object has the correct [runtimeType] to be |
| 4260 | /// a child of this render object. |
| 4261 | /// |
| 4262 | /// Does nothing if assertions are disabled. |
| 4263 | /// |
| 4264 | /// Always returns true. |
| 4265 | bool debugValidateChild(RenderObject child) { |
| 4266 | assert(() { |
| 4267 | if (child is! ChildType) { |
| 4268 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 4269 | ErrorSummary( |
| 4270 | 'A $runtimeType expected a child of type $ChildType but received a ' |
| 4271 | 'child of type ${child.runtimeType}.' , |
| 4272 | ), |
| 4273 | ErrorDescription( |
| 4274 | 'RenderObjects expect specific types of children because they ' |
| 4275 | 'coordinate with their children during layout and paint. For ' |
| 4276 | 'example, a RenderSliver cannot be the child of a RenderBox because ' |
| 4277 | 'a RenderSliver does not understand the RenderBox layout protocol.' , |
| 4278 | ), |
| 4279 | ErrorSpacer(), |
| 4280 | DiagnosticsProperty<Object?>( |
| 4281 | 'The $runtimeType that expected a $ChildType child was created by' , |
| 4282 | debugCreator, |
| 4283 | style: DiagnosticsTreeStyle.errorProperty, |
| 4284 | ), |
| 4285 | ErrorSpacer(), |
| 4286 | DiagnosticsProperty<Object?>( |
| 4287 | 'The ${child.runtimeType} that did not match the expected child type ' |
| 4288 | 'was created by' , |
| 4289 | child.debugCreator, |
| 4290 | style: DiagnosticsTreeStyle.errorProperty, |
| 4291 | ), |
| 4292 | ]); |
| 4293 | } |
| 4294 | return true; |
| 4295 | }()); |
| 4296 | return true; |
| 4297 | } |
| 4298 | |
| 4299 | ChildType? _firstChild; |
| 4300 | ChildType? _lastChild; |
| 4301 | void _insertIntoChildList(ChildType child, {ChildType? after}) { |
| 4302 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4303 | assert(childParentData.nextSibling == null); |
| 4304 | assert(childParentData.previousSibling == null); |
| 4305 | _childCount += 1; |
| 4306 | assert(_childCount > 0); |
| 4307 | if (after == null) { |
| 4308 | // insert at the start (_firstChild) |
| 4309 | childParentData.nextSibling = _firstChild; |
| 4310 | if (_firstChild != null) { |
| 4311 | final ParentDataType firstChildParentData = _firstChild!.parentData! as ParentDataType; |
| 4312 | firstChildParentData.previousSibling = child; |
| 4313 | } |
| 4314 | _firstChild = child; |
| 4315 | _lastChild ??= child; |
| 4316 | } else { |
| 4317 | assert(_firstChild != null); |
| 4318 | assert(_lastChild != null); |
| 4319 | assert(_debugUltimatePreviousSiblingOf(after, equals: _firstChild)); |
| 4320 | assert(_debugUltimateNextSiblingOf(after, equals: _lastChild)); |
| 4321 | final ParentDataType afterParentData = after.parentData! as ParentDataType; |
| 4322 | if (afterParentData.nextSibling == null) { |
| 4323 | // insert at the end (_lastChild); we'll end up with two or more children |
| 4324 | assert(after == _lastChild); |
| 4325 | childParentData.previousSibling = after; |
| 4326 | afterParentData.nextSibling = child; |
| 4327 | _lastChild = child; |
| 4328 | } else { |
| 4329 | // insert in the middle; we'll end up with three or more children |
| 4330 | // set up links from child to siblings |
| 4331 | childParentData.nextSibling = afterParentData.nextSibling; |
| 4332 | childParentData.previousSibling = after; |
| 4333 | // set up links from siblings to child |
| 4334 | final ParentDataType childPreviousSiblingParentData = |
| 4335 | childParentData.previousSibling!.parentData! as ParentDataType; |
| 4336 | final ParentDataType childNextSiblingParentData = |
| 4337 | childParentData.nextSibling!.parentData! as ParentDataType; |
| 4338 | childPreviousSiblingParentData.nextSibling = child; |
| 4339 | childNextSiblingParentData.previousSibling = child; |
| 4340 | assert(afterParentData.nextSibling == child); |
| 4341 | } |
| 4342 | } |
| 4343 | } |
| 4344 | |
| 4345 | /// Insert child into this render object's child list after the given child. |
| 4346 | /// |
| 4347 | /// If `after` is null, then this inserts the child at the start of the list, |
| 4348 | /// and the child becomes the new [firstChild]. |
| 4349 | void insert(ChildType child, {ChildType? after}) { |
| 4350 | assert(child != this, 'A RenderObject cannot be inserted into itself.' ); |
| 4351 | assert( |
| 4352 | after != this, |
| 4353 | 'A RenderObject cannot simultaneously be both the parent and the sibling of another RenderObject.' , |
| 4354 | ); |
| 4355 | assert(child != after, 'A RenderObject cannot be inserted after itself.' ); |
| 4356 | assert(child != _firstChild); |
| 4357 | assert(child != _lastChild); |
| 4358 | adoptChild(child); |
| 4359 | assert( |
| 4360 | child.parentData is ParentDataType, |
| 4361 | 'A child of $runtimeType has parentData of type ${child.parentData.runtimeType}, ' |
| 4362 | 'which does not conform to $ParentDataType. Class using ContainerRenderObjectMixin ' |
| 4363 | 'should override setupParentData() to set parentData to type $ParentDataType.' , |
| 4364 | ); |
| 4365 | _insertIntoChildList(child, after: after); |
| 4366 | } |
| 4367 | |
| 4368 | /// Append child to the end of this render object's child list. |
| 4369 | void add(ChildType child) { |
| 4370 | insert(child, after: _lastChild); |
| 4371 | } |
| 4372 | |
| 4373 | /// Add all the children to the end of this render object's child list. |
| 4374 | void addAll(List<ChildType>? children) { |
| 4375 | children?.forEach(add); |
| 4376 | } |
| 4377 | |
| 4378 | void _removeFromChildList(ChildType child) { |
| 4379 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4380 | assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); |
| 4381 | assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); |
| 4382 | assert(_childCount >= 0); |
| 4383 | if (childParentData.previousSibling == null) { |
| 4384 | assert(_firstChild == child); |
| 4385 | _firstChild = childParentData.nextSibling; |
| 4386 | } else { |
| 4387 | final ParentDataType childPreviousSiblingParentData = |
| 4388 | childParentData.previousSibling!.parentData! as ParentDataType; |
| 4389 | childPreviousSiblingParentData.nextSibling = childParentData.nextSibling; |
| 4390 | } |
| 4391 | if (childParentData.nextSibling == null) { |
| 4392 | assert(_lastChild == child); |
| 4393 | _lastChild = childParentData.previousSibling; |
| 4394 | } else { |
| 4395 | final ParentDataType childNextSiblingParentData = |
| 4396 | childParentData.nextSibling!.parentData! as ParentDataType; |
| 4397 | childNextSiblingParentData.previousSibling = childParentData.previousSibling; |
| 4398 | } |
| 4399 | childParentData.previousSibling = null; |
| 4400 | childParentData.nextSibling = null; |
| 4401 | _childCount -= 1; |
| 4402 | } |
| 4403 | |
| 4404 | /// Remove this child from the child list. |
| 4405 | /// |
| 4406 | /// Requires the child to be present in the child list. |
| 4407 | void remove(ChildType child) { |
| 4408 | _removeFromChildList(child); |
| 4409 | dropChild(child); |
| 4410 | } |
| 4411 | |
| 4412 | /// Remove all their children from this render object's child list. |
| 4413 | /// |
| 4414 | /// More efficient than removing them individually. |
| 4415 | void removeAll() { |
| 4416 | ChildType? child = _firstChild; |
| 4417 | while (child != null) { |
| 4418 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4419 | final ChildType? next = childParentData.nextSibling; |
| 4420 | childParentData.previousSibling = null; |
| 4421 | childParentData.nextSibling = null; |
| 4422 | dropChild(child); |
| 4423 | child = next; |
| 4424 | } |
| 4425 | _firstChild = null; |
| 4426 | _lastChild = null; |
| 4427 | _childCount = 0; |
| 4428 | } |
| 4429 | |
| 4430 | /// Move the given `child` in the child list to be after another child. |
| 4431 | /// |
| 4432 | /// More efficient than removing and re-adding the child. Requires the child |
| 4433 | /// to already be in the child list at some position. Pass null for `after` to |
| 4434 | /// move the child to the start of the child list. |
| 4435 | void move(ChildType child, {ChildType? after}) { |
| 4436 | assert(child != this); |
| 4437 | assert(after != this); |
| 4438 | assert(child != after); |
| 4439 | assert(child.parent == this); |
| 4440 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4441 | if (childParentData.previousSibling == after) { |
| 4442 | return; |
| 4443 | } |
| 4444 | _removeFromChildList(child); |
| 4445 | _insertIntoChildList(child, after: after); |
| 4446 | markNeedsLayout(); |
| 4447 | } |
| 4448 | |
| 4449 | @override |
| 4450 | void attach(PipelineOwner owner) { |
| 4451 | super.attach(owner); |
| 4452 | ChildType? child = _firstChild; |
| 4453 | while (child != null) { |
| 4454 | child.attach(owner); |
| 4455 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4456 | child = childParentData.nextSibling; |
| 4457 | } |
| 4458 | } |
| 4459 | |
| 4460 | @override |
| 4461 | void detach() { |
| 4462 | super.detach(); |
| 4463 | ChildType? child = _firstChild; |
| 4464 | while (child != null) { |
| 4465 | child.detach(); |
| 4466 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4467 | child = childParentData.nextSibling; |
| 4468 | } |
| 4469 | } |
| 4470 | |
| 4471 | @override |
| 4472 | void redepthChildren() { |
| 4473 | ChildType? child = _firstChild; |
| 4474 | while (child != null) { |
| 4475 | redepthChild(child); |
| 4476 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4477 | child = childParentData.nextSibling; |
| 4478 | } |
| 4479 | } |
| 4480 | |
| 4481 | @override |
| 4482 | void visitChildren(RenderObjectVisitor visitor) { |
| 4483 | ChildType? child = _firstChild; |
| 4484 | while (child != null) { |
| 4485 | visitor(child); |
| 4486 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4487 | child = childParentData.nextSibling; |
| 4488 | } |
| 4489 | } |
| 4490 | |
| 4491 | /// The first child in the child list. |
| 4492 | ChildType? get firstChild => _firstChild; |
| 4493 | |
| 4494 | /// The last child in the child list. |
| 4495 | ChildType? get lastChild => _lastChild; |
| 4496 | |
| 4497 | /// The previous child before the given child in the child list. |
| 4498 | ChildType? childBefore(ChildType child) { |
| 4499 | assert(child.parent == this); |
| 4500 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4501 | return childParentData.previousSibling; |
| 4502 | } |
| 4503 | |
| 4504 | /// The next child after the given child in the child list. |
| 4505 | ChildType? childAfter(ChildType child) { |
| 4506 | assert(child.parent == this); |
| 4507 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4508 | return childParentData.nextSibling; |
| 4509 | } |
| 4510 | |
| 4511 | @override |
| 4512 | List<DiagnosticsNode> debugDescribeChildren() { |
| 4513 | final List<DiagnosticsNode> children = <DiagnosticsNode>[]; |
| 4514 | if (firstChild != null) { |
| 4515 | ChildType child = firstChild!; |
| 4516 | int count = 1; |
| 4517 | while (true) { |
| 4518 | children.add(child.toDiagnosticsNode(name: 'child $count' )); |
| 4519 | if (child == lastChild) { |
| 4520 | break; |
| 4521 | } |
| 4522 | count += 1; |
| 4523 | final ParentDataType childParentData = child.parentData! as ParentDataType; |
| 4524 | child = childParentData.nextSibling!; |
| 4525 | } |
| 4526 | } |
| 4527 | return children; |
| 4528 | } |
| 4529 | } |
| 4530 | |
| 4531 | /// Mixin for [RenderObject] that will call [systemFontsDidChange] whenever the |
| 4532 | /// system fonts change. |
| 4533 | /// |
| 4534 | /// System fonts can change when the OS installs or removes a font. Use this |
| 4535 | /// mixin if the [RenderObject] uses [TextPainter] or [Paragraph] to correctly |
| 4536 | /// update the text when it happens. |
| 4537 | mixin RelayoutWhenSystemFontsChangeMixin on RenderObject { |
| 4538 | /// A callback that is called when system fonts have changed. |
| 4539 | /// |
| 4540 | /// The framework defers the invocation of the callback to the |
| 4541 | /// [SchedulerPhase.transientCallbacks] phase to ensure that the |
| 4542 | /// [RenderObject]'s text layout is still valid when user interactions are in |
| 4543 | /// progress (which usually take place during the [SchedulerPhase.idle] phase). |
| 4544 | /// |
| 4545 | /// By default, [markNeedsLayout] is called on the [RenderObject] |
| 4546 | /// implementing this mixin. |
| 4547 | /// |
| 4548 | /// Subclass should override this method to clear any extra cache that depend |
| 4549 | /// on font-related metrics. |
| 4550 | @protected |
| 4551 | @mustCallSuper |
| 4552 | void systemFontsDidChange() { |
| 4553 | markNeedsLayout(); |
| 4554 | } |
| 4555 | |
| 4556 | bool _hasPendingSystemFontsDidChangeCallBack = false; |
| 4557 | void _scheduleSystemFontsUpdate() { |
| 4558 | assert( |
| 4559 | SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle, |
| 4560 | ' ${objectRuntimeType(this, "RelayoutWhenSystemFontsChangeMixin" )}._scheduleSystemFontsUpdate() ' |
| 4561 | 'called during ${SchedulerBinding.instance.schedulerPhase}.' , |
| 4562 | ); |
| 4563 | if (_hasPendingSystemFontsDidChangeCallBack) { |
| 4564 | return; |
| 4565 | } |
| 4566 | _hasPendingSystemFontsDidChangeCallBack = true; |
| 4567 | SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) { |
| 4568 | assert(_hasPendingSystemFontsDidChangeCallBack); |
| 4569 | _hasPendingSystemFontsDidChangeCallBack = false; |
| 4570 | assert( |
| 4571 | attached || (debugDisposed ?? true), |
| 4572 | ' $this is detached during ${SchedulerBinding.instance.schedulerPhase} but is not disposed.' , |
| 4573 | ); |
| 4574 | if (attached) { |
| 4575 | systemFontsDidChange(); |
| 4576 | } |
| 4577 | }); |
| 4578 | } |
| 4579 | |
| 4580 | @override |
| 4581 | void attach(PipelineOwner owner) { |
| 4582 | super.attach(owner); |
| 4583 | // If there's a pending callback that would imply this node was detached |
| 4584 | // between the idle phase and the next transientCallbacks phase. The tree |
| 4585 | // can not be mutated between those two phases so that should never happen. |
| 4586 | assert(!_hasPendingSystemFontsDidChangeCallBack); |
| 4587 | PaintingBinding.instance.systemFonts.addListener(_scheduleSystemFontsUpdate); |
| 4588 | } |
| 4589 | |
| 4590 | @override |
| 4591 | void detach() { |
| 4592 | assert(!_hasPendingSystemFontsDidChangeCallBack); |
| 4593 | PaintingBinding.instance.systemFonts.removeListener(_scheduleSystemFontsUpdate); |
| 4594 | super.detach(); |
| 4595 | } |
| 4596 | } |
| 4597 | |
| 4598 | /// A mixin for [RenderObject]s that want to annotate the [SemanticsNode] |
| 4599 | /// for their subtree. |
| 4600 | mixin SemanticsAnnotationsMixin on RenderObject { |
| 4601 | /// Initializes the semantics annotations for this mixin. |
| 4602 | // Parameters added to this method should be marked as required to ensure |
| 4603 | // callers of the method provide a value. |
| 4604 | void initSemanticsAnnotations({ |
| 4605 | required SemanticsProperties properties, |
| 4606 | required bool container, |
| 4607 | required bool explicitChildNodes, |
| 4608 | required bool excludeSemantics, |
| 4609 | required bool blockUserActions, |
| 4610 | required Locale? localeForSubtree, |
| 4611 | required TextDirection? textDirection, |
| 4612 | }) { |
| 4613 | _properties = properties; |
| 4614 | _container = container; |
| 4615 | _explicitChildNodes = explicitChildNodes; |
| 4616 | _excludeSemantics = excludeSemantics; |
| 4617 | _blockUserActions = blockUserActions; |
| 4618 | _localeForSubtree = localeForSubtree; |
| 4619 | _textDirection = textDirection; |
| 4620 | _updateAttributedFields(_properties); |
| 4621 | } |
| 4622 | |
| 4623 | /// All of the [SemanticsProperties] for this [SemanticsAnnotationsMixin]. |
| 4624 | SemanticsProperties get properties => _properties; |
| 4625 | late SemanticsProperties _properties; |
| 4626 | set properties(SemanticsProperties value) { |
| 4627 | if (_properties == value) { |
| 4628 | return; |
| 4629 | } |
| 4630 | _properties = value; |
| 4631 | _updateAttributedFields(_properties); |
| 4632 | markNeedsSemanticsUpdate(); |
| 4633 | } |
| 4634 | |
| 4635 | /// If 'container' is true, this [RenderObject] will introduce a new |
| 4636 | /// node in the semantics tree. Otherwise, the semantics will be |
| 4637 | /// merged with the semantics of any ancestors. |
| 4638 | /// |
| 4639 | /// Whether descendants of this [RenderObject] can add their semantic information |
| 4640 | /// to the [SemanticsNode] introduced by this configuration is controlled by |
| 4641 | /// [explicitChildNodes]. |
| 4642 | bool get container => _container; |
| 4643 | late bool _container; |
| 4644 | set container(bool value) { |
| 4645 | if (container == value) { |
| 4646 | return; |
| 4647 | } |
| 4648 | _container = value; |
| 4649 | markNeedsSemanticsUpdate(); |
| 4650 | } |
| 4651 | |
| 4652 | /// Whether descendants of this [RenderObject] are allowed to add semantic |
| 4653 | /// information to the [SemanticsNode] annotated by this widget. |
| 4654 | /// |
| 4655 | /// When set to false descendants are allowed to annotate [SemanticsNode]s of |
| 4656 | /// their parent with the semantic information they want to contribute to the |
| 4657 | /// semantic tree. |
| 4658 | /// When set to true the only way for descendants to contribute semantic |
| 4659 | /// information to the semantic tree is to introduce new explicit |
| 4660 | /// [SemanticsNode]s to the tree. |
| 4661 | /// |
| 4662 | /// This setting is often used in combination with |
| 4663 | /// [SemanticsConfiguration.isSemanticBoundary] to create semantic boundaries |
| 4664 | /// that are either writable or not for children. |
| 4665 | bool get explicitChildNodes => _explicitChildNodes; |
| 4666 | late bool _explicitChildNodes; |
| 4667 | set explicitChildNodes(bool value) { |
| 4668 | if (_explicitChildNodes == value) { |
| 4669 | return; |
| 4670 | } |
| 4671 | _explicitChildNodes = value; |
| 4672 | markNeedsSemanticsUpdate(); |
| 4673 | } |
| 4674 | |
| 4675 | /// Whether descendants of this [RenderObject] should have their semantic |
| 4676 | /// information ignored. |
| 4677 | /// |
| 4678 | /// When this flag is set to true, all child semantics nodes are ignored. |
| 4679 | /// This can be used as a convenience for cases where a child is wrapped in |
| 4680 | /// an [ExcludeSemantics] widget and then another [Semantics] widget. |
| 4681 | bool get excludeSemantics => _excludeSemantics; |
| 4682 | late bool _excludeSemantics; |
| 4683 | set excludeSemantics(bool value) { |
| 4684 | if (_excludeSemantics == value) { |
| 4685 | return; |
| 4686 | } |
| 4687 | _excludeSemantics = value; |
| 4688 | markNeedsSemanticsUpdate(); |
| 4689 | } |
| 4690 | |
| 4691 | /// Whether to block user interactions for the semantics subtree. |
| 4692 | /// |
| 4693 | /// Setting this true prevents user from activating pointer related |
| 4694 | /// [SemanticsAction]s, such as [SemanticsAction.tap] or |
| 4695 | /// [SemanticsAction.longPress]. |
| 4696 | bool get blockUserActions => _blockUserActions; |
| 4697 | late bool _blockUserActions; |
| 4698 | set blockUserActions(bool value) { |
| 4699 | if (_blockUserActions == value) { |
| 4700 | return; |
| 4701 | } |
| 4702 | _blockUserActions = value; |
| 4703 | markNeedsSemanticsUpdate(); |
| 4704 | } |
| 4705 | |
| 4706 | /// The [Locale] for the semantics subtree. |
| 4707 | /// |
| 4708 | /// Setting this to null will inherit locale from ancestor semantics node. |
| 4709 | Locale? get localeForSubtree => _localeForSubtree; |
| 4710 | Locale? _localeForSubtree; |
| 4711 | set localeForSubtree(Locale? value) { |
| 4712 | if (_localeForSubtree == value) { |
| 4713 | return; |
| 4714 | } |
| 4715 | _localeForSubtree = value; |
| 4716 | markNeedsSemanticsUpdate(); |
| 4717 | } |
| 4718 | |
| 4719 | void _updateAttributedFields(SemanticsProperties value) { |
| 4720 | _attributedLabel = _effectiveAttributedLabel(value); |
| 4721 | _attributedValue = _effectiveAttributedValue(value); |
| 4722 | _attributedIncreasedValue = _effectiveAttributedIncreasedValue(value); |
| 4723 | _attributedDecreasedValue = _effectiveAttributedDecreasedValue(value); |
| 4724 | _attributedHint = _effectiveAttributedHint(value); |
| 4725 | } |
| 4726 | |
| 4727 | AttributedString? _effectiveAttributedLabel(SemanticsProperties value) { |
| 4728 | return value.attributedLabel ?? (value.label == null ? null : AttributedString(value.label!)); |
| 4729 | } |
| 4730 | |
| 4731 | AttributedString? _effectiveAttributedValue(SemanticsProperties value) { |
| 4732 | return value.attributedValue ?? (value.value == null ? null : AttributedString(value.value!)); |
| 4733 | } |
| 4734 | |
| 4735 | AttributedString? _effectiveAttributedIncreasedValue(SemanticsProperties value) { |
| 4736 | return value.attributedIncreasedValue ?? |
| 4737 | (value.increasedValue == null ? null : AttributedString(value.increasedValue!)); |
| 4738 | } |
| 4739 | |
| 4740 | AttributedString? _effectiveAttributedDecreasedValue(SemanticsProperties value) { |
| 4741 | return properties.attributedDecreasedValue ?? |
| 4742 | (value.decreasedValue == null ? null : AttributedString(value.decreasedValue!)); |
| 4743 | } |
| 4744 | |
| 4745 | AttributedString? _effectiveAttributedHint(SemanticsProperties value) { |
| 4746 | return value.attributedHint ?? (value.hint == null ? null : AttributedString(value.hint!)); |
| 4747 | } |
| 4748 | |
| 4749 | AttributedString? _attributedLabel; |
| 4750 | AttributedString? _attributedValue; |
| 4751 | AttributedString? _attributedIncreasedValue; |
| 4752 | AttributedString? _attributedDecreasedValue; |
| 4753 | AttributedString? _attributedHint; |
| 4754 | |
| 4755 | /// If non-null, sets the [SemanticsNode.textDirection] semantic to the given |
| 4756 | /// value. |
| 4757 | /// |
| 4758 | /// This must not be null if [SemanticsProperties.attributedLabel], |
| 4759 | /// [SemanticsProperties.attributedHint], |
| 4760 | /// [SemanticsProperties.attributedValue], |
| 4761 | /// [SemanticsProperties.attributedIncreasedValue], or |
| 4762 | /// [SemanticsProperties.attributedDecreasedValue] are not null. |
| 4763 | TextDirection? get textDirection => _textDirection; |
| 4764 | TextDirection? _textDirection; |
| 4765 | set textDirection(TextDirection? value) { |
| 4766 | if (textDirection == value) { |
| 4767 | return; |
| 4768 | } |
| 4769 | _textDirection = value; |
| 4770 | markNeedsSemanticsUpdate(); |
| 4771 | } |
| 4772 | |
| 4773 | @override |
| 4774 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
| 4775 | if (excludeSemantics) { |
| 4776 | return; |
| 4777 | } |
| 4778 | super.visitChildrenForSemantics(visitor); |
| 4779 | } |
| 4780 | |
| 4781 | @override |
| 4782 | void describeSemanticsConfiguration(SemanticsConfiguration config) { |
| 4783 | super.describeSemanticsConfiguration(config); |
| 4784 | config.isSemanticBoundary = container; |
| 4785 | config.explicitChildNodes = explicitChildNodes; |
| 4786 | config.isBlockingUserActions = blockUserActions; |
| 4787 | config.localeForSubtree = localeForSubtree; |
| 4788 | assert( |
| 4789 | ((_properties.scopesRoute ?? false) && explicitChildNodes) || |
| 4790 | !(_properties.scopesRoute ?? false), |
| 4791 | 'explicitChildNodes must be set to true if scopes route is true' , |
| 4792 | ); |
| 4793 | assert( |
| 4794 | !((_properties.toggled ?? false) && (_properties.checked ?? false)), |
| 4795 | 'A semantics node cannot be toggled and checked at the same time' , |
| 4796 | ); |
| 4797 | |
| 4798 | if (_properties.enabled != null) { |
| 4799 | config.isEnabled = _properties.enabled; |
| 4800 | } |
| 4801 | if (_properties.checked != null) { |
| 4802 | config.isChecked = _properties.checked; |
| 4803 | } |
| 4804 | if (_properties.mixed != null) { |
| 4805 | config.isCheckStateMixed = _properties.mixed; |
| 4806 | } |
| 4807 | if (_properties.toggled != null) { |
| 4808 | config.isToggled = _properties.toggled; |
| 4809 | } |
| 4810 | if (_properties.selected != null) { |
| 4811 | config.isSelected = _properties.selected!; |
| 4812 | } |
| 4813 | if (_properties.button != null) { |
| 4814 | config.isButton = _properties.button!; |
| 4815 | } |
| 4816 | if (_properties.expanded != null) { |
| 4817 | config.isExpanded = _properties.expanded; |
| 4818 | } |
| 4819 | if (_properties.link != null) { |
| 4820 | config.isLink = _properties.link!; |
| 4821 | } |
| 4822 | if (_properties.linkUrl != null) { |
| 4823 | config.linkUrl = _properties.linkUrl; |
| 4824 | } |
| 4825 | if (_properties.slider != null) { |
| 4826 | config.isSlider = _properties.slider!; |
| 4827 | } |
| 4828 | if (_properties.keyboardKey != null) { |
| 4829 | config.isKeyboardKey = _properties.keyboardKey!; |
| 4830 | } |
| 4831 | if (_properties.header != null) { |
| 4832 | config.isHeader = _properties.header!; |
| 4833 | } |
| 4834 | if (_properties.headingLevel != null) { |
| 4835 | config.headingLevel = _properties.headingLevel!; |
| 4836 | } |
| 4837 | if (_properties.textField != null) { |
| 4838 | config.isTextField = _properties.textField!; |
| 4839 | } |
| 4840 | if (_properties.readOnly != null) { |
| 4841 | config.isReadOnly = _properties.readOnly!; |
| 4842 | } |
| 4843 | if (_properties.focusable != null) { |
| 4844 | config.isFocusable = _properties.focusable!; |
| 4845 | } |
| 4846 | if (_properties.focused != null) { |
| 4847 | config.isFocused = _properties.focused!; |
| 4848 | } |
| 4849 | if (_properties.inMutuallyExclusiveGroup != null) { |
| 4850 | config.isInMutuallyExclusiveGroup = _properties.inMutuallyExclusiveGroup!; |
| 4851 | } |
| 4852 | if (_properties.obscured != null) { |
| 4853 | config.isObscured = _properties.obscured!; |
| 4854 | } |
| 4855 | if (_properties.multiline != null) { |
| 4856 | config.isMultiline = _properties.multiline!; |
| 4857 | } |
| 4858 | if (_properties.hidden != null) { |
| 4859 | config.isHidden = _properties.hidden!; |
| 4860 | } |
| 4861 | if (_properties.image != null) { |
| 4862 | config.isImage = _properties.image!; |
| 4863 | } |
| 4864 | if (_properties.isRequired != null) { |
| 4865 | config.isRequired = _properties.isRequired; |
| 4866 | } |
| 4867 | if (_properties.identifier != null) { |
| 4868 | config.identifier = _properties.identifier!; |
| 4869 | } |
| 4870 | if (_attributedLabel != null) { |
| 4871 | config.attributedLabel = _attributedLabel!; |
| 4872 | } |
| 4873 | if (_attributedValue != null) { |
| 4874 | config.attributedValue = _attributedValue!; |
| 4875 | } |
| 4876 | if (_attributedIncreasedValue != null) { |
| 4877 | config.attributedIncreasedValue = _attributedIncreasedValue!; |
| 4878 | } |
| 4879 | if (_attributedDecreasedValue != null) { |
| 4880 | config.attributedDecreasedValue = _attributedDecreasedValue!; |
| 4881 | } |
| 4882 | if (_attributedHint != null) { |
| 4883 | config.attributedHint = _attributedHint!; |
| 4884 | } |
| 4885 | if (_properties.tooltip != null) { |
| 4886 | config.tooltip = _properties.tooltip!; |
| 4887 | } |
| 4888 | if (_properties.hintOverrides != null && _properties.hintOverrides!.isNotEmpty) { |
| 4889 | config.hintOverrides = _properties.hintOverrides; |
| 4890 | } |
| 4891 | if (_properties.scopesRoute != null) { |
| 4892 | config.scopesRoute = _properties.scopesRoute!; |
| 4893 | } |
| 4894 | if (_properties.namesRoute != null) { |
| 4895 | config.namesRoute = _properties.namesRoute!; |
| 4896 | } |
| 4897 | if (_properties.liveRegion != null) { |
| 4898 | config.liveRegion = _properties.liveRegion!; |
| 4899 | } |
| 4900 | if (_properties.maxValueLength != null) { |
| 4901 | config.maxValueLength = _properties.maxValueLength; |
| 4902 | } |
| 4903 | if (_properties.currentValueLength != null) { |
| 4904 | config.currentValueLength = _properties.currentValueLength; |
| 4905 | } |
| 4906 | if (textDirection != null) { |
| 4907 | config.textDirection = textDirection; |
| 4908 | } |
| 4909 | if (_properties.sortKey != null) { |
| 4910 | config.sortKey = _properties.sortKey; |
| 4911 | } |
| 4912 | if (_properties.tagForChildren != null) { |
| 4913 | config.addTagForChildren(_properties.tagForChildren!); |
| 4914 | } |
| 4915 | if (properties.role != null) { |
| 4916 | config.role = _properties.role!; |
| 4917 | } |
| 4918 | if (_properties.controlsNodes != null) { |
| 4919 | config.controlsNodes = _properties.controlsNodes; |
| 4920 | } |
| 4921 | if (config.validationResult != _properties.validationResult) { |
| 4922 | config.validationResult = _properties.validationResult; |
| 4923 | } |
| 4924 | |
| 4925 | if (_properties.inputType != null) { |
| 4926 | config.inputType = _properties.inputType!; |
| 4927 | } |
| 4928 | |
| 4929 | // Registering _perform* as action handlers instead of the user provided |
| 4930 | // ones to ensure that changing a user provided handler from a non-null to |
| 4931 | // another non-null value doesn't require a semantics update. |
| 4932 | if (_properties.onTap != null) { |
| 4933 | config.onTap = _performTap; |
| 4934 | } |
| 4935 | if (_properties.onLongPress != null) { |
| 4936 | config.onLongPress = _performLongPress; |
| 4937 | } |
| 4938 | if (_properties.onDismiss != null) { |
| 4939 | config.onDismiss = _performDismiss; |
| 4940 | } |
| 4941 | if (_properties.onScrollLeft != null) { |
| 4942 | config.onScrollLeft = _performScrollLeft; |
| 4943 | } |
| 4944 | if (_properties.onScrollRight != null) { |
| 4945 | config.onScrollRight = _performScrollRight; |
| 4946 | } |
| 4947 | if (_properties.onScrollUp != null) { |
| 4948 | config.onScrollUp = _performScrollUp; |
| 4949 | } |
| 4950 | if (_properties.onScrollDown != null) { |
| 4951 | config.onScrollDown = _performScrollDown; |
| 4952 | } |
| 4953 | if (_properties.onIncrease != null) { |
| 4954 | config.onIncrease = _performIncrease; |
| 4955 | } |
| 4956 | if (_properties.onDecrease != null) { |
| 4957 | config.onDecrease = _performDecrease; |
| 4958 | } |
| 4959 | if (_properties.onCopy != null) { |
| 4960 | config.onCopy = _performCopy; |
| 4961 | } |
| 4962 | if (_properties.onCut != null) { |
| 4963 | config.onCut = _performCut; |
| 4964 | } |
| 4965 | if (_properties.onPaste != null) { |
| 4966 | config.onPaste = _performPaste; |
| 4967 | } |
| 4968 | if (_properties.onMoveCursorForwardByCharacter != null) { |
| 4969 | config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter; |
| 4970 | } |
| 4971 | if (_properties.onMoveCursorBackwardByCharacter != null) { |
| 4972 | config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter; |
| 4973 | } |
| 4974 | if (_properties.onMoveCursorForwardByWord != null) { |
| 4975 | config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord; |
| 4976 | } |
| 4977 | if (_properties.onMoveCursorBackwardByWord != null) { |
| 4978 | config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord; |
| 4979 | } |
| 4980 | if (_properties.onSetSelection != null) { |
| 4981 | config.onSetSelection = _performSetSelection; |
| 4982 | } |
| 4983 | if (_properties.onSetText != null) { |
| 4984 | config.onSetText = _performSetText; |
| 4985 | } |
| 4986 | if (_properties.onDidGainAccessibilityFocus != null) { |
| 4987 | config.onDidGainAccessibilityFocus = _performDidGainAccessibilityFocus; |
| 4988 | } |
| 4989 | if (_properties.onDidLoseAccessibilityFocus != null) { |
| 4990 | config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus; |
| 4991 | } |
| 4992 | if (_properties.onFocus != null) { |
| 4993 | config.onFocus = _performFocus; |
| 4994 | } |
| 4995 | if (_properties.customSemanticsActions != null) { |
| 4996 | config.customSemanticsActions = _properties.customSemanticsActions!; |
| 4997 | } |
| 4998 | } |
| 4999 | |
| 5000 | void _performTap() { |
| 5001 | _properties.onTap?.call(); |
| 5002 | } |
| 5003 | |
| 5004 | void _performLongPress() { |
| 5005 | _properties.onLongPress?.call(); |
| 5006 | } |
| 5007 | |
| 5008 | void _performDismiss() { |
| 5009 | _properties.onDismiss?.call(); |
| 5010 | } |
| 5011 | |
| 5012 | void _performScrollLeft() { |
| 5013 | _properties.onScrollLeft?.call(); |
| 5014 | } |
| 5015 | |
| 5016 | void _performScrollRight() { |
| 5017 | _properties.onScrollRight?.call(); |
| 5018 | } |
| 5019 | |
| 5020 | void _performScrollUp() { |
| 5021 | _properties.onScrollUp?.call(); |
| 5022 | } |
| 5023 | |
| 5024 | void _performScrollDown() { |
| 5025 | _properties.onScrollDown?.call(); |
| 5026 | } |
| 5027 | |
| 5028 | void _performIncrease() { |
| 5029 | _properties.onIncrease?.call(); |
| 5030 | } |
| 5031 | |
| 5032 | void _performDecrease() { |
| 5033 | _properties.onDecrease?.call(); |
| 5034 | } |
| 5035 | |
| 5036 | void _performCopy() { |
| 5037 | _properties.onCopy?.call(); |
| 5038 | } |
| 5039 | |
| 5040 | void _performCut() { |
| 5041 | _properties.onCut?.call(); |
| 5042 | } |
| 5043 | |
| 5044 | void _performPaste() { |
| 5045 | _properties.onPaste?.call(); |
| 5046 | } |
| 5047 | |
| 5048 | void _performMoveCursorForwardByCharacter(bool extendSelection) { |
| 5049 | _properties.onMoveCursorForwardByCharacter?.call(extendSelection); |
| 5050 | } |
| 5051 | |
| 5052 | void _performMoveCursorBackwardByCharacter(bool extendSelection) { |
| 5053 | _properties.onMoveCursorBackwardByCharacter?.call(extendSelection); |
| 5054 | } |
| 5055 | |
| 5056 | void _performMoveCursorForwardByWord(bool extendSelection) { |
| 5057 | _properties.onMoveCursorForwardByWord?.call(extendSelection); |
| 5058 | } |
| 5059 | |
| 5060 | void _performMoveCursorBackwardByWord(bool extendSelection) { |
| 5061 | _properties.onMoveCursorBackwardByWord?.call(extendSelection); |
| 5062 | } |
| 5063 | |
| 5064 | void _performSetSelection(TextSelection selection) { |
| 5065 | _properties.onSetSelection?.call(selection); |
| 5066 | } |
| 5067 | |
| 5068 | void _performSetText(String text) { |
| 5069 | _properties.onSetText?.call(text); |
| 5070 | } |
| 5071 | |
| 5072 | void _performDidGainAccessibilityFocus() { |
| 5073 | _properties.onDidGainAccessibilityFocus?.call(); |
| 5074 | } |
| 5075 | |
| 5076 | void _performDidLoseAccessibilityFocus() { |
| 5077 | _properties.onDidLoseAccessibilityFocus?.call(); |
| 5078 | } |
| 5079 | |
| 5080 | void _performFocus() { |
| 5081 | _properties.onFocus?.call(); |
| 5082 | } |
| 5083 | } |
| 5084 | |
| 5085 | /// Properties of _RenderObjectSemantics that are imposed from parent. |
| 5086 | @immutable |
| 5087 | final class _SemanticsParentData { |
| 5088 | const _SemanticsParentData({ |
| 5089 | required this.mergeIntoParent, |
| 5090 | required this.blocksUserActions, |
| 5091 | required this.explicitChildNodes, |
| 5092 | required this.tagsForChildren, |
| 5093 | required this.localeForChildren, |
| 5094 | }); |
| 5095 | |
| 5096 | /// Whether [SemanticsNode]s created from this render object semantics subtree |
| 5097 | /// will be merged into parent. |
| 5098 | /// |
| 5099 | /// This is imposed by render objects of parent [MergeSemantics]s. |
| 5100 | final bool mergeIntoParent; |
| 5101 | |
| 5102 | /// Whether [SemanticsNode]s created from this render object semantics subtree |
| 5103 | /// ignores user action such as [SemanticsAction.tap] and its friends. |
| 5104 | /// |
| 5105 | /// This is imposed by render objects of parent [IgnorePointer]s or |
| 5106 | /// [AbsorbPointer]s. |
| 5107 | final bool blocksUserActions; |
| 5108 | |
| 5109 | /// Any immediate render object semantics that |
| 5110 | /// [_RenderObjectSemantics.contributesToSemanticsTree] should forms a node |
| 5111 | /// |
| 5112 | /// This is imposed by parent render objects that set |
| 5113 | /// [SemanticsConfiguration.explicitChildNodes] to true. |
| 5114 | final bool explicitChildNodes; |
| 5115 | |
| 5116 | /// Tags for immediate render object semantics that |
| 5117 | /// [_RenderObjectSemantics.shouldFormSemanticsNode] is true. |
| 5118 | final Set<SemanticsTag>? tagsForChildren; |
| 5119 | |
| 5120 | final Locale? localeForChildren; |
| 5121 | |
| 5122 | @override |
| 5123 | bool operator ==(Object other) { |
| 5124 | return other is _SemanticsParentData && |
| 5125 | other.mergeIntoParent == mergeIntoParent && |
| 5126 | other.blocksUserActions == blocksUserActions && |
| 5127 | other.explicitChildNodes == explicitChildNodes && |
| 5128 | other.localeForChildren == localeForChildren && |
| 5129 | setEquals<SemanticsTag>(other.tagsForChildren, tagsForChildren); |
| 5130 | } |
| 5131 | |
| 5132 | @override |
| 5133 | int get hashCode { |
| 5134 | return Object.hash( |
| 5135 | mergeIntoParent, |
| 5136 | blocksUserActions, |
| 5137 | explicitChildNodes, |
| 5138 | localeForChildren, |
| 5139 | Object.hashAllUnordered(tagsForChildren ?? const <SemanticsTag>{}), |
| 5140 | ); |
| 5141 | } |
| 5142 | } |
| 5143 | |
| 5144 | /// A wrapper class that handles the life cycle of the [SemanticsConfiguration] |
| 5145 | /// of a [RenderObject]. |
| 5146 | /// |
| 5147 | /// Typically, this class calls [RenderObject.describeSemanticsConfiguration] to |
| 5148 | /// update the config and owner of this object does not directly mutate the |
| 5149 | /// the config. |
| 5150 | /// |
| 5151 | /// In some cases during [PipelineOwner.flushSemantics], the config has to be |
| 5152 | /// mutated due to [_SemanticsParentData] update to propagate updated property |
| 5153 | /// to semantics node. One should use [updateConfig] to update the config in this |
| 5154 | /// case. |
| 5155 | /// |
| 5156 | /// To access the config stored in this wrapper, uses |
| 5157 | /// [_SemanticsConfigurationProvider.effective] to access the latest config. |
| 5158 | /// Uses [_SemanticsConfigurationProvider.original] if one wants to access the |
| 5159 | /// raw config without post mutations. |
| 5160 | class _SemanticsConfigurationProvider { |
| 5161 | _SemanticsConfigurationProvider(this._renderObject); |
| 5162 | |
| 5163 | /// The owning rendering object for this object. |
| 5164 | final RenderObject _renderObject; |
| 5165 | |
| 5166 | bool _isEffectiveConfigWritable = false; |
| 5167 | SemanticsConfiguration? _originalConfiguration; |
| 5168 | SemanticsConfiguration? _effectiveConfiguration; |
| 5169 | |
| 5170 | bool get wasSemanticsBoundary => _originalConfiguration?.isSemanticBoundary ?? false; |
| 5171 | |
| 5172 | /// The latest config that reflect any change done through [updateConfig]. |
| 5173 | SemanticsConfiguration get effective { |
| 5174 | return _effectiveConfiguration ?? original; |
| 5175 | } |
| 5176 | |
| 5177 | /// The original config without any change through [updateConfig]. |
| 5178 | /// |
| 5179 | /// This is typically use to recalculate certain properties when mutating |
| 5180 | /// [effective] since [effective] may contain stale data from previous update. |
| 5181 | /// An example is [SemanticsConfiguration.isBlockingUserActions]. Otherwise, |
| 5182 | /// use [effective] instead. |
| 5183 | SemanticsConfiguration get original { |
| 5184 | if (_originalConfiguration == null) { |
| 5185 | _effectiveConfiguration = _originalConfiguration = SemanticsConfiguration(); |
| 5186 | _renderObject.describeSemanticsConfiguration(_originalConfiguration!); |
| 5187 | assert( |
| 5188 | !_originalConfiguration!.explicitChildNodes || |
| 5189 | _originalConfiguration!.childConfigurationsDelegate == null, |
| 5190 | 'A SemanticsConfiguration with explicitChildNode set to true cannot have a non-null childConfigsDelegate.' , |
| 5191 | ); |
| 5192 | } |
| 5193 | return _originalConfiguration!; |
| 5194 | } |
| 5195 | |
| 5196 | /// Mutates the config |
| 5197 | /// |
| 5198 | /// This does not change the [original], and the change reflects in |
| 5199 | /// [effective]. |
| 5200 | void updateConfig(ValueSetter<SemanticsConfiguration> callback) { |
| 5201 | if (!_isEffectiveConfigWritable) { |
| 5202 | _effectiveConfiguration = original.copy(); |
| 5203 | _isEffectiveConfigWritable = true; |
| 5204 | } |
| 5205 | callback(_effectiveConfiguration!); |
| 5206 | } |
| 5207 | |
| 5208 | /// Absorb a list of config into [effective]. |
| 5209 | void absorbAll(Iterable<SemanticsConfiguration> configs) { |
| 5210 | updateConfig((SemanticsConfiguration config) { |
| 5211 | configs.forEach(config.absorb); |
| 5212 | }); |
| 5213 | } |
| 5214 | |
| 5215 | /// Reset any post mutation to [effective]. |
| 5216 | void reset() { |
| 5217 | _effectiveConfiguration = original; |
| 5218 | _isEffectiveConfigWritable = false; |
| 5219 | } |
| 5220 | |
| 5221 | /// Remove every cache in this wrapper. |
| 5222 | /// |
| 5223 | /// This cause the [RenderObject.describeSemanticsConfiguration] to be |
| 5224 | /// re-evaluated next time [effective] or [original] is called. |
| 5225 | void clear() { |
| 5226 | _isEffectiveConfigWritable = false; |
| 5227 | _effectiveConfiguration = null; |
| 5228 | _originalConfiguration = null; |
| 5229 | } |
| 5230 | } |
| 5231 | |
| 5232 | /// A convenient abstract interface used for constructing the |
| 5233 | /// [_RenderObjectSemantics] tree. |
| 5234 | /// |
| 5235 | /// The _SemanticsFragment can be an [_IncompleteSemanticsFragment] or a |
| 5236 | /// [_RenderObjectSemantics]. This interface is used so that |
| 5237 | /// [_RenderObjectSemantics] can handle semantics configuration merging without |
| 5238 | /// knowing whether the child fragment is backed by a render object or an |
| 5239 | /// incomplete semantics fragment. |
| 5240 | abstract class _SemanticsFragment { |
| 5241 | SemanticsConfiguration? get configToMergeUp; |
| 5242 | |
| 5243 | _RenderObjectSemantics get owner; |
| 5244 | |
| 5245 | bool mergesToSibling = false; |
| 5246 | |
| 5247 | void markSiblingConfigurationConflict(bool conflict); |
| 5248 | } |
| 5249 | |
| 5250 | /// A fragment that is generated from |
| 5251 | /// [SemanticsConfiguration.childConfigurationsDelegate] |
| 5252 | /// |
| 5253 | /// A render object can choose to add additional semantics config to be merged |
| 5254 | /// upward besides itself. These configs can be added through |
| 5255 | /// [SemanticsConfiguration.childConfigurationsDelegate] and will form |
| 5256 | /// `_IncompleteSemanticsFragment`s |
| 5257 | /// |
| 5258 | /// See [RenderParagraph] for an example usage. |
| 5259 | class _IncompleteSemanticsFragment extends _SemanticsFragment { |
| 5260 | _IncompleteSemanticsFragment(this.configToMergeUp, this.owner); |
| 5261 | |
| 5262 | @override |
| 5263 | final SemanticsConfiguration configToMergeUp; |
| 5264 | |
| 5265 | @override |
| 5266 | final _RenderObjectSemantics owner; |
| 5267 | |
| 5268 | @override |
| 5269 | void markSiblingConfigurationConflict(bool conflict) { |
| 5270 | assert(!conflict); |
| 5271 | } |
| 5272 | } |
| 5273 | |
| 5274 | typedef _MergeUpAndSiblingMergeGroups = ( |
| 5275 | List<_SemanticsFragment> mergeUp, |
| 5276 | List<List<_SemanticsFragment>> siblingMergeGroups, |
| 5277 | ); |
| 5278 | |
| 5279 | /// A wrapper class for a [RenderObject] that provides semantics related |
| 5280 | /// properties and compilations. |
| 5281 | /// |
| 5282 | /// ## A high level summary |
| 5283 | /// |
| 5284 | /// The [PipelineOwner.flushSemantics] calls the [updateChildren] to |
| 5285 | /// build/update a tree of [_RenderObjectSemantics] by querying dirty |
| 5286 | /// RenderObjects about their [SemanticsConfiguration] and updating the |
| 5287 | /// _RenderObjectSemantics of the render objects according to these information. |
| 5288 | /// While doing that, [updateChildren] also decide what |
| 5289 | /// _RenderObjectSemantics will have their own SemanticsNode later on. |
| 5290 | /// After that, [PipelineOwner.flushSemantics] calls [ensureGeometry] to |
| 5291 | /// calculate the geometries for these _RenderObjectSemantics. Finally, |
| 5292 | /// [ensureSemanticsNode] compiles these _RenderObjectSemantics into the actual |
| 5293 | /// SemanticsNodes that form the semantics tree. |
| 5294 | /// |
| 5295 | /// ## Steps Breakdown |
| 5296 | /// |
| 5297 | /// The _RenderObjectSemantics tree is compiled in four phases. Phase 1 and 2 |
| 5298 | /// are done in [updateChildren], Phase 3 is done in [ensureGeometry], and phase |
| 5299 | /// 4 is done in [ensureSemanticsNode]. |
| 5300 | /// |
| 5301 | /// ### Phase 1 |
| 5302 | /// |
| 5303 | /// Gather all the merge up _RenderObjectSemantics(s) by walking the rendering |
| 5304 | /// object tree. |
| 5305 | /// |
| 5306 | /// They are stored in [mergeUp] and [siblingMergeGroups] and should mimic |
| 5307 | /// rendering object tree closely but only contain [_RenderObjectSemantics] that |
| 5308 | /// contributes to semantics tree. i.e. where [contributesToSemanticsTree] is |
| 5309 | /// true. |
| 5310 | /// |
| 5311 | /// ### Phase 2 |
| 5312 | /// |
| 5313 | /// Merge all fragments from [mergeUp] and decide which [_RenderObjectSemantics] |
| 5314 | /// should form a node, i.e. [shouldFormSemanticsNode] is true. Stores the |
| 5315 | /// [_RenderObjectSemantics] that should form a node into [_children]. |
| 5316 | /// |
| 5317 | /// At this point, walking the [_children] forms a tree |
| 5318 | /// that exactly resemble the resulting semantics node tree. |
| 5319 | /// |
| 5320 | /// ### Phase 3 |
| 5321 | /// |
| 5322 | /// Walks the [_children] and calculate their |
| 5323 | /// [_SemanticsGeometry] based on renderObject relationship. |
| 5324 | /// |
| 5325 | /// ### Phase 4 |
| 5326 | /// |
| 5327 | /// Walks the [_children] and produce semantics node for |
| 5328 | /// each [_RenderObjectSemantics] plus the sibling nodes. |
| 5329 | /// |
| 5330 | /// Phase 2, 3, 4 each depends on previous step to finished updating the the |
| 5331 | /// entire _RenderObjectSemantics tree. All three of them require separate tree |
| 5332 | /// walk. |
| 5333 | class _RenderObjectSemantics extends _SemanticsFragment with DiagnosticableTreeMixin { |
| 5334 | _RenderObjectSemantics(this.renderObject) |
| 5335 | : configProvider = _SemanticsConfigurationProvider(renderObject); |
| 5336 | |
| 5337 | /// The owning rendering object for this wrapper. |
| 5338 | final RenderObject renderObject; |
| 5339 | |
| 5340 | bool _hasSiblingConflict = false; |
| 5341 | bool? _blocksPreviousSibling; |
| 5342 | |
| 5343 | // TODO(chunhtai): Figure out what to do when incomplete fragments are asked |
| 5344 | // to form a semantics node. |
| 5345 | // |
| 5346 | // If this is true, the [contributesToSemanticsTree] will also return true. |
| 5347 | // This is a workaround so that the incomplete fragments will not be forced to |
| 5348 | // form nodes if the parent has explicitChildNode = true. |
| 5349 | bool _containsIncompleteFragment = false; |
| 5350 | |
| 5351 | bool built = false; |
| 5352 | |
| 5353 | /// The cached node created directly by this Object. |
| 5354 | /// |
| 5355 | /// This cache is filled after the this object is compiled (usually by |
| 5356 | /// calling [ensureSemanticsNode] on this object or ancestors) and forms a |
| 5357 | /// semantics node. |
| 5358 | /// |
| 5359 | /// Caching the semantics node ensures the id is consistent in the life time |
| 5360 | /// of this object. |
| 5361 | /// |
| 5362 | /// `_RenderSemanticsObject` only forms semantics node if |
| 5363 | /// `shouldFormSemanticsNode` is true. |
| 5364 | SemanticsNode? cachedSemanticsNode; |
| 5365 | |
| 5366 | /// The semantics nodes produced by this render object. |
| 5367 | /// |
| 5368 | /// This is filled after [ensureSemanticsNode] is called on this object or |
| 5369 | /// ancestors when [shouldFormSemanticsNode] is true. In most cases, this only |
| 5370 | /// contains one semantics node equals to [cachedSemanticsNode]. |
| 5371 | /// |
| 5372 | /// If there are [siblingMergeGroups], the nodes produced from the sibling |
| 5373 | /// merge groups are also stored in this list. |
| 5374 | final List<SemanticsNode> semanticsNodes = <SemanticsNode>[]; |
| 5375 | |
| 5376 | /// Fragments that will merge up to parent rendering object semantics. |
| 5377 | final List<_SemanticsFragment> mergeUp = <_SemanticsFragment>[]; |
| 5378 | |
| 5379 | /// A list to store immediate child [_RenderObjectSemantics]s that will form |
| 5380 | /// semantics nodes. |
| 5381 | final List<_RenderObjectSemantics> _children = <_RenderObjectSemantics>[]; |
| 5382 | |
| 5383 | /// Merge groups that will form additional sibling nodes. |
| 5384 | final List<List<_SemanticsFragment>> siblingMergeGroups = <List<_SemanticsFragment>>[]; |
| 5385 | final Map<SemanticsNode, List<_SemanticsFragment>> _producedSiblingNodesAndOwners = |
| 5386 | <SemanticsNode, List<_SemanticsFragment>>{}; |
| 5387 | |
| 5388 | _SemanticsParentData? parentData; |
| 5389 | _SemanticsGeometry? geometry; |
| 5390 | |
| 5391 | final _SemanticsConfigurationProvider configProvider; |
| 5392 | |
| 5393 | @override |
| 5394 | _RenderObjectSemantics get owner => this; |
| 5395 | |
| 5396 | bool get parentDataDirty { |
| 5397 | if (isRoot) { |
| 5398 | return false; |
| 5399 | } |
| 5400 | return parentData == null; |
| 5401 | } |
| 5402 | |
| 5403 | /// If this forms a semantics node, all of the properties in config are |
| 5404 | /// used in creating the node. There is nothing to be merged up. |
| 5405 | @override |
| 5406 | SemanticsConfiguration? get configToMergeUp => |
| 5407 | shouldFormSemanticsNode ? null : configProvider.effective; |
| 5408 | |
| 5409 | bool get contributesToSemanticsTree { |
| 5410 | return configProvider.effective.hasBeenAnnotated || |
| 5411 | _containsIncompleteFragment || |
| 5412 | configProvider.effective.isSemanticBoundary || |
| 5413 | isRoot; |
| 5414 | } |
| 5415 | |
| 5416 | bool get isRoot => renderObject.semanticsParent == null; |
| 5417 | |
| 5418 | bool get shouldFormSemanticsNode { |
| 5419 | if (configProvider.effective.isSemanticBoundary) { |
| 5420 | return true; |
| 5421 | } |
| 5422 | if (isRoot) { |
| 5423 | return true; |
| 5424 | } |
| 5425 | if (!contributesToSemanticsTree) { |
| 5426 | return false; |
| 5427 | } |
| 5428 | |
| 5429 | assert( |
| 5430 | parentData != null, |
| 5431 | 'If there is no explicit flag that enforce semantics node, parent data ' |
| 5432 | 'must be updated before determining whether this object will form a node' , |
| 5433 | ); |
| 5434 | return parentData!.explicitChildNodes || _hasSiblingConflict; |
| 5435 | } |
| 5436 | |
| 5437 | static void debugCheckForParentData(RenderObject root) { |
| 5438 | void debugCheckParentDataNotDirty(_RenderObjectSemantics semantics) { |
| 5439 | assert(!semantics.parentDataDirty); |
| 5440 | semantics._getNonBlockedChildren().forEach(debugCheckParentDataNotDirty); |
| 5441 | } |
| 5442 | |
| 5443 | debugCheckParentDataNotDirty(root._semantics); |
| 5444 | } |
| 5445 | |
| 5446 | /// Whether this render object semantics will block other render object |
| 5447 | /// semantics behind it in render object order from parent. |
| 5448 | /// |
| 5449 | /// The [BlockSemantics] widget will cause this property to be true and hide |
| 5450 | /// any sibling widget behind it from semantics. |
| 5451 | bool get isBlockingPreviousSibling { |
| 5452 | if (_blocksPreviousSibling != null) { |
| 5453 | return _blocksPreviousSibling!; |
| 5454 | } |
| 5455 | |
| 5456 | _blocksPreviousSibling = configProvider.effective.isBlockingSemanticsOfPreviouslyPaintedNodes; |
| 5457 | |
| 5458 | if (_blocksPreviousSibling!) { |
| 5459 | return true; |
| 5460 | } |
| 5461 | |
| 5462 | if (configProvider.effective.isSemanticBoundary) { |
| 5463 | return false; |
| 5464 | } |
| 5465 | |
| 5466 | renderObject.visitChildrenForSemantics((RenderObject child) { |
| 5467 | final _RenderObjectSemantics childSemantics = child._semantics; |
| 5468 | if (childSemantics.isBlockingPreviousSibling) { |
| 5469 | _blocksPreviousSibling = true; |
| 5470 | } |
| 5471 | }); |
| 5472 | return _blocksPreviousSibling!; |
| 5473 | } |
| 5474 | |
| 5475 | bool shouldDrop(SemanticsNode node) => node.isInvisible; |
| 5476 | |
| 5477 | void markNeedsBuild() { |
| 5478 | built = false; |
| 5479 | if (!parentDataDirty && !shouldFormSemanticsNode) { |
| 5480 | return; |
| 5481 | } |
| 5482 | for (final List<_SemanticsFragment> group in siblingMergeGroups) { |
| 5483 | for (final _RenderObjectSemantics semantics in group.whereType<_RenderObjectSemantics>()) { |
| 5484 | if (semantics.parentDataDirty) { |
| 5485 | continue; |
| 5486 | } |
| 5487 | if (!semantics.shouldFormSemanticsNode) { |
| 5488 | // This render object semantics will need to be merged into a sibling |
| 5489 | // node. |
| 5490 | semantics.markNeedsBuild(); |
| 5491 | } |
| 5492 | } |
| 5493 | } |
| 5494 | } |
| 5495 | |
| 5496 | /// Updates the [parentData] for the [_RenderObjectSemantics]s in the |
| 5497 | /// rendering subtree and forms a [_RenderObjectSemantics] tree where children |
| 5498 | /// are stored in [_children]. |
| 5499 | /// |
| 5500 | /// This method does the the phase 1 and 2 of the four phases documented on |
| 5501 | /// [_RenderObjectSemantics]. |
| 5502 | /// |
| 5503 | /// Gather all the merge up _RenderObjectSemantics(s) by walking the rendering |
| 5504 | /// object tree. |
| 5505 | /// |
| 5506 | /// They are stored in [mergeUp] and [siblingMergeGroups] and should mimic |
| 5507 | /// rendering object tree closely but only contain [_RenderObjectSemantics] that |
| 5508 | /// contributes to semantics tree. i.e. |
| 5509 | /// [contributesToSemanticsTree] is true. |
| 5510 | /// |
| 5511 | /// Merge all fragments from [mergeUp] and decide which [_RenderObjectSemantics] |
| 5512 | /// should form a node. i.e. [shouldFormSemanticsNode] is true. Stores the |
| 5513 | /// [_RenderObjectSemantics] that should form a node with elevation adjustments |
| 5514 | /// into [_children]. |
| 5515 | void updateChildren() { |
| 5516 | assert(parentData != null || isRoot, 'parent data can only be null for root rendering object' ); |
| 5517 | configProvider.reset(); |
| 5518 | final Set<SemanticsTag>? tagsForChildren = _getTagsForChildren(); |
| 5519 | final bool explicitChildNodesForChildren = |
| 5520 | isRoot || |
| 5521 | configProvider.effective.explicitChildNodes || |
| 5522 | // ParentData's explicitChildNode only |
| 5523 | // propagate to children if this node doesn't |
| 5524 | // contribute to semantics tree |
| 5525 | (!contributesToSemanticsTree && (parentData?.explicitChildNodes ?? true)); |
| 5526 | |
| 5527 | final bool blocksUserAction = |
| 5528 | (parentData?.blocksUserActions ?? false) || configProvider.effective.isBlockingUserActions; |
| 5529 | |
| 5530 | // localeForSubtree from the config overrides parentData's inherited locale. |
| 5531 | final Locale? localeForChildren = |
| 5532 | configProvider.effective.localeForSubtree ?? parentData?.localeForChildren; |
| 5533 | |
| 5534 | siblingMergeGroups.clear(); |
| 5535 | mergeUp.clear(); |
| 5536 | final _SemanticsParentData childParentData = _SemanticsParentData( |
| 5537 | mergeIntoParent: |
| 5538 | (parentData?.mergeIntoParent ?? false) || |
| 5539 | configProvider.effective.isMergingSemanticsOfDescendants, |
| 5540 | blocksUserActions: blocksUserAction, |
| 5541 | localeForChildren: localeForChildren, |
| 5542 | explicitChildNodes: explicitChildNodesForChildren, |
| 5543 | tagsForChildren: tagsForChildren, |
| 5544 | ); |
| 5545 | |
| 5546 | final _MergeUpAndSiblingMergeGroups result = _collectChildMergeUpAndSiblingGroup( |
| 5547 | childParentData, |
| 5548 | ); |
| 5549 | mergeUp.addAll(result.$1); |
| 5550 | siblingMergeGroups.addAll(result.$2); |
| 5551 | |
| 5552 | // Construct tree for nodes that will form semantics nodes. |
| 5553 | _children.clear(); |
| 5554 | if (contributesToSemanticsTree) { |
| 5555 | _marksConflictsInMergeGroup(mergeUp, isMergeUp: true); |
| 5556 | siblingMergeGroups.forEach(_marksConflictsInMergeGroup); |
| 5557 | |
| 5558 | final Iterable<SemanticsConfiguration> mergeUpConfigs = mergeUp |
| 5559 | .map<SemanticsConfiguration?>((_SemanticsFragment fragment) => fragment.configToMergeUp) |
| 5560 | .whereType<SemanticsConfiguration>(); |
| 5561 | configProvider.absorbAll(mergeUpConfigs); |
| 5562 | // merge up fragments below this object will not be visible to parent |
| 5563 | // because they are either absorbed or will form a semantics node. |
| 5564 | mergeUp.clear(); |
| 5565 | mergeUp.add(this); |
| 5566 | for (final _RenderObjectSemantics childSemantics |
| 5567 | in result.$1.whereType<_RenderObjectSemantics>()) { |
| 5568 | assert(childSemantics.contributesToSemanticsTree); |
| 5569 | if (childSemantics.shouldFormSemanticsNode) { |
| 5570 | _children.add(childSemantics); |
| 5571 | } else { |
| 5572 | _children.addAll(childSemantics._children); |
| 5573 | siblingMergeGroups.addAll(childSemantics.siblingMergeGroups); |
| 5574 | } |
| 5575 | } |
| 5576 | |
| 5577 | final Set<SemanticsTag>? tags = parentData?.tagsForChildren; |
| 5578 | if (tags != null) { |
| 5579 | assert(tags.isNotEmpty); |
| 5580 | configProvider.updateConfig((SemanticsConfiguration config) { |
| 5581 | tags.forEach(config.addTagForChildren); |
| 5582 | }); |
| 5583 | } |
| 5584 | |
| 5585 | if (blocksUserAction != configProvider.effective.isBlockingUserActions) { |
| 5586 | configProvider.updateConfig((SemanticsConfiguration config) { |
| 5587 | config.isBlockingUserActions = blocksUserAction; |
| 5588 | }); |
| 5589 | } |
| 5590 | if (localeForChildren != configProvider.effective.locale) { |
| 5591 | configProvider.updateConfig((SemanticsConfiguration config) { |
| 5592 | config.locale = localeForChildren; |
| 5593 | }); |
| 5594 | } |
| 5595 | } |
| 5596 | } |
| 5597 | |
| 5598 | List<_RenderObjectSemantics> _getNonBlockedChildren() { |
| 5599 | final List<_RenderObjectSemantics> result = <_RenderObjectSemantics>[]; |
| 5600 | renderObject.visitChildrenForSemantics((RenderObject renderChild) { |
| 5601 | if (renderChild._semantics.isBlockingPreviousSibling) { |
| 5602 | result.clear(); |
| 5603 | } |
| 5604 | result.add(renderChild._semantics); |
| 5605 | }); |
| 5606 | return result; |
| 5607 | } |
| 5608 | |
| 5609 | Set<SemanticsTag>? _getTagsForChildren() { |
| 5610 | if (contributesToSemanticsTree) { |
| 5611 | return configProvider.original.tagsForChildren?.toSet(); |
| 5612 | } |
| 5613 | Set<SemanticsTag>? result; |
| 5614 | if (configProvider.original.tagsForChildren != null) { |
| 5615 | result = configProvider.original.tagsForChildren!.toSet(); |
| 5616 | } |
| 5617 | if (parentData?.tagsForChildren != null) { |
| 5618 | if (result == null) { |
| 5619 | result = parentData!.tagsForChildren; |
| 5620 | } else { |
| 5621 | result.addAll(parentData!.tagsForChildren!); |
| 5622 | } |
| 5623 | } |
| 5624 | return result; |
| 5625 | } |
| 5626 | |
| 5627 | _MergeUpAndSiblingMergeGroups _collectChildMergeUpAndSiblingGroup( |
| 5628 | _SemanticsParentData childParentData, |
| 5629 | ) { |
| 5630 | final List<_SemanticsFragment> mergeUp = <_SemanticsFragment>[]; |
| 5631 | final List<List<_SemanticsFragment>> siblingMergeGroups = <List<_SemanticsFragment>>[]; |
| 5632 | |
| 5633 | final List<SemanticsConfiguration> childConfigurations = <SemanticsConfiguration>[]; |
| 5634 | final ChildSemanticsConfigurationsDelegate? childConfigurationsDelegate = |
| 5635 | configProvider.effective.childConfigurationsDelegate; |
| 5636 | final bool hasChildConfigurationsDelegate = childConfigurationsDelegate != null; |
| 5637 | final Map<SemanticsConfiguration, _SemanticsFragment> configToFragment = |
| 5638 | <SemanticsConfiguration, _SemanticsFragment>{}; |
| 5639 | |
| 5640 | // It is possible the childConfigurationsDelegate may produce incomplete |
| 5641 | // fragments. In this case, this render object semantics need to absorb all |
| 5642 | // the mergeUp from children before present itself to the parent to avoid |
| 5643 | // the parent forcing incomplete fragments to form a node. This is done by |
| 5644 | // _containsIncompleteFragment which in turns flips the |
| 5645 | // contributesToSemanticsTree. |
| 5646 | // |
| 5647 | // The problem is we won't know whether it will generate incomplete |
| 5648 | // fragments until it runs, but we have to decide whether to propagate the |
| 5649 | // parent's explicitChildNodes before we collect child fragments. |
| 5650 | // |
| 5651 | // Therefore, we have to make an assumption now to assume it will generate |
| 5652 | // incomplete fragment and not propagate explicitChildNodes. |
| 5653 | final bool needsToMakeIncompleteFragmentAssumption = |
| 5654 | hasChildConfigurationsDelegate && childParentData.explicitChildNodes; |
| 5655 | |
| 5656 | final _SemanticsParentData effectiveChildParentData; |
| 5657 | if (needsToMakeIncompleteFragmentAssumption) { |
| 5658 | effectiveChildParentData = _SemanticsParentData( |
| 5659 | mergeIntoParent: childParentData.mergeIntoParent, |
| 5660 | blocksUserActions: childParentData.blocksUserActions, |
| 5661 | explicitChildNodes: false, |
| 5662 | tagsForChildren: childParentData.tagsForChildren, |
| 5663 | localeForChildren: childParentData.localeForChildren, |
| 5664 | ); |
| 5665 | } else { |
| 5666 | effectiveChildParentData = childParentData; |
| 5667 | } |
| 5668 | for (final _RenderObjectSemantics childSemantics in _getNonBlockedChildren()) { |
| 5669 | assert(!childSemantics.renderObject._needsLayout); |
| 5670 | childSemantics._didUpdateParentData(effectiveChildParentData); |
| 5671 | for (final _SemanticsFragment fragment in childSemantics.mergeUp) { |
| 5672 | if (hasChildConfigurationsDelegate && fragment.configToMergeUp != null) { |
| 5673 | // This fragment need to go through delegate to determine whether it |
| 5674 | // merge up or not. |
| 5675 | childConfigurations.add(fragment.configToMergeUp!); |
| 5676 | configToFragment[fragment.configToMergeUp!] = fragment; |
| 5677 | } else { |
| 5678 | mergeUp.add(fragment); |
| 5679 | } |
| 5680 | } |
| 5681 | |
| 5682 | if (!childSemantics.contributesToSemanticsTree) { |
| 5683 | // This child semantics needs to propagate sibling merge group to be |
| 5684 | // compiled by parent that contributes to semantics tree. |
| 5685 | siblingMergeGroups.addAll(childSemantics.siblingMergeGroups); |
| 5686 | } |
| 5687 | } |
| 5688 | _containsIncompleteFragment = false; |
| 5689 | assert(childConfigurationsDelegate != null || configToFragment.isEmpty); |
| 5690 | if (hasChildConfigurationsDelegate) { |
| 5691 | final ChildSemanticsConfigurationsResult result = childConfigurationsDelegate( |
| 5692 | childConfigurations, |
| 5693 | ); |
| 5694 | mergeUp.addAll( |
| 5695 | result.mergeUp.map<_SemanticsFragment>((SemanticsConfiguration config) { |
| 5696 | final _SemanticsFragment? fragment = configToFragment[config]; |
| 5697 | if (fragment != null) { |
| 5698 | return fragment; |
| 5699 | } |
| 5700 | _containsIncompleteFragment = true; |
| 5701 | return _IncompleteSemanticsFragment(config, this); |
| 5702 | }), |
| 5703 | ); |
| 5704 | for (final Iterable<SemanticsConfiguration> group in result.siblingMergeGroups) { |
| 5705 | siblingMergeGroups.add( |
| 5706 | group.map<_SemanticsFragment>((SemanticsConfiguration config) { |
| 5707 | final _SemanticsFragment? fragment = configToFragment[config]; |
| 5708 | if (fragment != null) { |
| 5709 | return fragment; |
| 5710 | } |
| 5711 | _containsIncompleteFragment = true; |
| 5712 | return _IncompleteSemanticsFragment(config, this); |
| 5713 | }).toList(), |
| 5714 | ); |
| 5715 | } |
| 5716 | } |
| 5717 | |
| 5718 | if (!_containsIncompleteFragment && needsToMakeIncompleteFragmentAssumption) { |
| 5719 | // Assumption was wrong, we have to re-update the child. |
| 5720 | mergeUp.clear(); |
| 5721 | siblingMergeGroups.clear(); |
| 5722 | |
| 5723 | for (final _RenderObjectSemantics childSemantics in _getNonBlockedChildren()) { |
| 5724 | assert(childParentData.explicitChildNodes); |
| 5725 | childSemantics._didUpdateParentData(childParentData); |
| 5726 | mergeUp.addAll(childSemantics.mergeUp); |
| 5727 | |
| 5728 | if (!childSemantics.contributesToSemanticsTree) { |
| 5729 | // This child semantics needs to propagate sibling merge group to be |
| 5730 | // compiled by parent that contributes to semantics tree. |
| 5731 | siblingMergeGroups.addAll(childSemantics.siblingMergeGroups); |
| 5732 | } |
| 5733 | } |
| 5734 | } |
| 5735 | |
| 5736 | return (mergeUp, siblingMergeGroups); |
| 5737 | } |
| 5738 | |
| 5739 | void _didUpdateParentData(_SemanticsParentData newParentData) { |
| 5740 | if (parentData == newParentData) { |
| 5741 | return; |
| 5742 | } |
| 5743 | // Parent data changes may result in node formation changes. |
| 5744 | geometry = null; |
| 5745 | markNeedsBuild(); |
| 5746 | parentData = newParentData; |
| 5747 | updateChildren(); |
| 5748 | } |
| 5749 | |
| 5750 | /// Makes whether this fragment has a sibling fragment with conflicting |
| 5751 | /// [SemanticsConfiguration]. |
| 5752 | @override |
| 5753 | void markSiblingConfigurationConflict(bool conflict) { |
| 5754 | _hasSiblingConflict = conflict; |
| 5755 | } |
| 5756 | |
| 5757 | /// Updates the [geometry] for this [_RenderObjectSemantics]s and its subtree |
| 5758 | /// in [_children]. |
| 5759 | /// |
| 5760 | /// This method does the the phase 3 of the four phases documented on |
| 5761 | /// [_RenderObjectSemantics]. |
| 5762 | /// |
| 5763 | /// This method is short-circuited if the subtree geometry won't |
| 5764 | /// be affect after the update. (e.g. the size doesn't change, or new clip |
| 5765 | /// rect doesn't clip the content). |
| 5766 | void ensureGeometry() { |
| 5767 | if (isRoot) { |
| 5768 | if (geometry?.rect != renderObject.semanticBounds) { |
| 5769 | markNeedsBuild(); |
| 5770 | } |
| 5771 | geometry = _SemanticsGeometry.root(renderObject.semanticBounds); |
| 5772 | } |
| 5773 | assert(geometry != null); |
| 5774 | _updateChildGeometry(); |
| 5775 | } |
| 5776 | |
| 5777 | void _updateChildGeometry() { |
| 5778 | assert(geometry != null); |
| 5779 | final _SemanticsGeometry parentGeometry = geometry!; |
| 5780 | for (final _RenderObjectSemantics child in _children) { |
| 5781 | final _SemanticsGeometry childGeometry = _SemanticsGeometry.computeChildGeometry( |
| 5782 | parentPaintClipRect: parentGeometry.paintClipRect, |
| 5783 | parentSemanticsClipRect: parentGeometry.semanticsClipRect, |
| 5784 | parentTransform: null, |
| 5785 | parent: this, |
| 5786 | child: child, |
| 5787 | ); |
| 5788 | child._updateGeometry(newGeometry: childGeometry); |
| 5789 | } |
| 5790 | for (final _RenderObjectSemantics explicitSiblingChild |
| 5791 | in siblingMergeGroups |
| 5792 | .expand<_SemanticsFragment>((List<_SemanticsFragment> group) => group) |
| 5793 | .whereType<_RenderObjectSemantics>() |
| 5794 | .expand( |
| 5795 | (_RenderObjectSemantics siblingChild) => siblingChild.shouldFormSemanticsNode |
| 5796 | ? <_RenderObjectSemantics>[siblingChild] |
| 5797 | : siblingChild._children, |
| 5798 | )) { |
| 5799 | final _SemanticsGeometry childGeometry = _SemanticsGeometry.computeChildGeometry( |
| 5800 | parentPaintClipRect: parentGeometry.paintClipRect, |
| 5801 | parentSemanticsClipRect: parentGeometry.semanticsClipRect, |
| 5802 | parentTransform: parentGeometry.transform, |
| 5803 | parent: this, |
| 5804 | child: explicitSiblingChild, |
| 5805 | ); |
| 5806 | explicitSiblingChild._updateGeometry(newGeometry: childGeometry); |
| 5807 | } |
| 5808 | } |
| 5809 | |
| 5810 | void _updateGeometry({required _SemanticsGeometry newGeometry}) { |
| 5811 | final _SemanticsGeometry? currentGeometry = geometry; |
| 5812 | geometry = newGeometry; |
| 5813 | markNeedsBuild(); |
| 5814 | if (currentGeometry != null) { |
| 5815 | final bool isSemanticsHidden = |
| 5816 | configProvider.original.isHidden || |
| 5817 | (!(parentData?.mergeIntoParent ?? false) && newGeometry.hidden); |
| 5818 | final bool sizeChanged = currentGeometry.rect.size != newGeometry.rect.size; |
| 5819 | final bool visibilityChanged = configProvider.effective.isHidden != isSemanticsHidden; |
| 5820 | if (!sizeChanged && !visibilityChanged) { |
| 5821 | return; |
| 5822 | } |
| 5823 | } |
| 5824 | _updateChildGeometry(); |
| 5825 | } |
| 5826 | |
| 5827 | /// Ensures the semantics nodes from this render object semantics subtree are |
| 5828 | /// generated and up to date. |
| 5829 | /// |
| 5830 | /// This method does the the phase 4 of the four phases documented on |
| 5831 | /// [_RenderObjectSemantics]. |
| 5832 | /// |
| 5833 | /// This can only be called if the owning rendering object is a semantics |
| 5834 | /// boundary. For non boundary rendering objects, they require semantics |
| 5835 | /// information from both their parent and child rendering objects to update |
| 5836 | /// its cache, so it can't update by themselves. |
| 5837 | void ensureSemanticsNode() { |
| 5838 | assert(configProvider.effective.isSemanticBoundary || isRoot); |
| 5839 | if (!built) { |
| 5840 | _buildSemantics(usedSemanticsIds: <int>{}); |
| 5841 | } else { |
| 5842 | assert(built); |
| 5843 | // parent data and parent geometry didn't change, there isn't anything to |
| 5844 | // update for semantics nodes generated in this render object semantics. |
| 5845 | // |
| 5846 | // Therefore, we only need to update the subtree. |
| 5847 | _buildSemanticsSubtree(usedSemanticsIds: <int>{}); |
| 5848 | } |
| 5849 | } |
| 5850 | |
| 5851 | /// Builds the semantics node and its semantics node subtree. |
| 5852 | /// |
| 5853 | /// This method will in turn call [_buildSemanticsSubtree]. |
| 5854 | /// |
| 5855 | /// This method will short-circuit itself if [cachedSemanticsNode] is |
| 5856 | /// already up-to-date. |
| 5857 | void _buildSemantics({required Set<int> usedSemanticsIds}) { |
| 5858 | assert(shouldFormSemanticsNode); |
| 5859 | if (cachedSemanticsNode != null) { |
| 5860 | // Any node other than producedNode in _semanticsNodes are sibling nodes |
| 5861 | // from children fragments. This fragment is responsible for updating |
| 5862 | // tags as well as cleaning up. |
| 5863 | // |
| 5864 | // Clean up the properties now so that we don't have stale data in them |
| 5865 | // after the _produceSemanticsNode. |
| 5866 | for (final SemanticsNode node in semanticsNodes) { |
| 5867 | if (node != cachedSemanticsNode) { |
| 5868 | node.tags = null; |
| 5869 | } |
| 5870 | } |
| 5871 | } |
| 5872 | if (!built) { |
| 5873 | semanticsNodes.clear(); |
| 5874 | _producedSiblingNodesAndOwners.clear(); |
| 5875 | _produceSemanticsNode(usedSemanticsIds: usedSemanticsIds); |
| 5876 | } |
| 5877 | assert(built); |
| 5878 | |
| 5879 | // Any node other than producedNode in _semanticsNodes are sibling nodes |
| 5880 | // from children fragments. They share the same tags as the producedNode. |
| 5881 | final SemanticsNode producedNode = cachedSemanticsNode!; |
| 5882 | for (final SemanticsNode node in semanticsNodes) { |
| 5883 | if (node != producedNode) { |
| 5884 | if (parentData?.tagsForChildren != null) { |
| 5885 | node.tags ??= <SemanticsTag>{}; |
| 5886 | node.tags!.addAll(parentData!.tagsForChildren!); |
| 5887 | } else if (node.tags?.isEmpty ?? false) { |
| 5888 | node.tags = null; |
| 5889 | } |
| 5890 | } |
| 5891 | } |
| 5892 | } |
| 5893 | |
| 5894 | /// Builds the semantics subtree under the [cachedSemanticsNode]. |
| 5895 | void _buildSemanticsSubtree({ |
| 5896 | required Set<int> usedSemanticsIds, |
| 5897 | List<SemanticsNode>? semanticsNodes, |
| 5898 | }) { |
| 5899 | final List<SemanticsNode> children = <SemanticsNode>[]; |
| 5900 | for (final _RenderObjectSemantics child in _children) { |
| 5901 | assert(child.shouldFormSemanticsNode); |
| 5902 | // Cached semantics node may be part of sibling merging group prior |
| 5903 | // to this update. In this case, the semantics node may continue to |
| 5904 | // be reused in that sibling merging group. |
| 5905 | if (child.cachedSemanticsNode != null && |
| 5906 | usedSemanticsIds.contains(child.cachedSemanticsNode!.id)) { |
| 5907 | child.markNeedsBuild(); |
| 5908 | child.cachedSemanticsNode = null; |
| 5909 | } |
| 5910 | |
| 5911 | child._buildSemantics(usedSemanticsIds: usedSemanticsIds); |
| 5912 | children.addAll(child.semanticsNodes); |
| 5913 | } |
| 5914 | |
| 5915 | final SemanticsNode node = cachedSemanticsNode!; |
| 5916 | children.removeWhere(shouldDrop); |
| 5917 | if (configProvider.effective.isSemanticBoundary) { |
| 5918 | renderObject.assembleSemanticsNode(node, configProvider.effective, children); |
| 5919 | } else { |
| 5920 | node.updateWith(config: configProvider.effective, childrenInInversePaintOrder: children); |
| 5921 | } |
| 5922 | } |
| 5923 | |
| 5924 | void _produceSemanticsNode({required Set<int> usedSemanticsIds}) { |
| 5925 | assert(!built); |
| 5926 | built = true; |
| 5927 | final SemanticsNode node = cachedSemanticsNode ??= _createSemanticsNode(); |
| 5928 | semanticsNodes.add(node); |
| 5929 | node |
| 5930 | ..isMergedIntoParent = (parentData?.mergeIntoParent ?? false) |
| 5931 | ..tags = parentData?.tagsForChildren; |
| 5932 | _updateSemanticsNodeGeometry(); |
| 5933 | |
| 5934 | _mergeSiblingGroup(usedSemanticsIds); |
| 5935 | _buildSemanticsSubtree(semanticsNodes: semanticsNodes, usedSemanticsIds: usedSemanticsIds); |
| 5936 | } |
| 5937 | |
| 5938 | SemanticsNode _createSemanticsNode() { |
| 5939 | if (isRoot) { |
| 5940 | return SemanticsNode.root( |
| 5941 | showOnScreen: owner.renderObject.showOnScreen, |
| 5942 | owner: owner.renderObject.owner!.semanticsOwner!, |
| 5943 | ); |
| 5944 | } |
| 5945 | return SemanticsNode(showOnScreen: owner.renderObject.showOnScreen); |
| 5946 | } |
| 5947 | |
| 5948 | void _mergeSiblingGroup(Set<int> usedSemanticsIds) { |
| 5949 | for (final List<_SemanticsFragment> group in siblingMergeGroups) { |
| 5950 | SemanticsConfiguration? configuration; |
| 5951 | SemanticsNode? node; |
| 5952 | final List<_RenderObjectSemantics> explicitChildren = <_RenderObjectSemantics>[]; |
| 5953 | for (final _SemanticsFragment fragment in group) { |
| 5954 | if (fragment is _RenderObjectSemantics) { |
| 5955 | if (fragment.shouldFormSemanticsNode) { |
| 5956 | explicitChildren.add(fragment); |
| 5957 | assert(fragment.configToMergeUp == null); |
| 5958 | continue; |
| 5959 | } |
| 5960 | explicitChildren.addAll(fragment._children); |
| 5961 | } |
| 5962 | if (fragment.configToMergeUp != null) { |
| 5963 | fragment.mergesToSibling = true; |
| 5964 | node ??= fragment.owner.cachedSemanticsNode; |
| 5965 | configuration ??= SemanticsConfiguration(); |
| 5966 | configuration.absorb(fragment.configToMergeUp!); |
| 5967 | } |
| 5968 | } |
| 5969 | final List<SemanticsNode> childrenNodes = <SemanticsNode>[]; |
| 5970 | for (final _RenderObjectSemantics explicitChild in explicitChildren) { |
| 5971 | explicitChild._buildSemantics(usedSemanticsIds: usedSemanticsIds); |
| 5972 | childrenNodes.addAll(explicitChild.semanticsNodes); |
| 5973 | } |
| 5974 | // Can be null if all fragments in switchableFragments are marked as explicit. |
| 5975 | if (configuration != null) { |
| 5976 | if (node == null || usedSemanticsIds.contains(node.id)) { |
| 5977 | node = SemanticsNode(showOnScreen: renderObject.showOnScreen); |
| 5978 | } |
| 5979 | usedSemanticsIds.add(node.id); |
| 5980 | for (final _SemanticsFragment fragment in group) { |
| 5981 | if (fragment.configToMergeUp != null) { |
| 5982 | fragment.owner.built = true; |
| 5983 | fragment.owner.cachedSemanticsNode = node; |
| 5984 | } |
| 5985 | } |
| 5986 | node.updateWith(config: configuration, childrenInInversePaintOrder: childrenNodes); |
| 5987 | _producedSiblingNodesAndOwners[node] = group; |
| 5988 | semanticsNodes.add(node); |
| 5989 | |
| 5990 | final Set<SemanticsTag> tags = group |
| 5991 | .map<Set<SemanticsTag>?>( |
| 5992 | (_SemanticsFragment fragment) => fragment.owner.parentData!.tagsForChildren, |
| 5993 | ) |
| 5994 | .whereType<Set<SemanticsTag>>() |
| 5995 | .expand<SemanticsTag>((Set<SemanticsTag> tags) => tags) |
| 5996 | .toSet(); |
| 5997 | // This fragment is only allowed to add tags into the node instead of |
| 5998 | // cleaning it since some of the tags may be added by the parent fragment |
| 5999 | // who actually take these node as their siblings. |
| 6000 | // |
| 6001 | // It will be that fragment's responsibility to clean up the tags. |
| 6002 | // |
| 6003 | // This is the same for the transform as well. |
| 6004 | // |
| 6005 | // See _SwitchableFragment.compileSemanticsNodes |
| 6006 | if (tags.isNotEmpty) { |
| 6007 | if (node.tags == null) { |
| 6008 | node.tags = tags; |
| 6009 | } else { |
| 6010 | node.tags!.addAll(tags); |
| 6011 | } |
| 6012 | } |
| 6013 | node.isMergedIntoParent = parentData?.mergeIntoParent ?? false; |
| 6014 | } |
| 6015 | } |
| 6016 | _updateSiblingNodesGeometries(); |
| 6017 | } |
| 6018 | |
| 6019 | /// Updates the semantics geometry of the cached semantics node. |
| 6020 | /// |
| 6021 | /// Returns true if geometry changes that may result in children's geometries |
| 6022 | /// change as well. |
| 6023 | void _updateSemanticsNodeGeometry() { |
| 6024 | final SemanticsNode node = cachedSemanticsNode!; |
| 6025 | final _SemanticsGeometry nodeGeometry = geometry!; |
| 6026 | final bool isSemanticsHidden = |
| 6027 | configProvider.original.isHidden || |
| 6028 | (!(parentData?.mergeIntoParent ?? false) && nodeGeometry.hidden); |
| 6029 | node |
| 6030 | ..rect = nodeGeometry.rect |
| 6031 | ..transform = nodeGeometry.transform |
| 6032 | ..parentSemanticsClipRect = nodeGeometry.semanticsClipRect |
| 6033 | ..parentPaintClipRect = nodeGeometry.paintClipRect; |
| 6034 | if (configProvider.effective.isHidden != isSemanticsHidden) { |
| 6035 | configProvider.updateConfig((SemanticsConfiguration config) { |
| 6036 | config.isHidden = isSemanticsHidden; |
| 6037 | }); |
| 6038 | } |
| 6039 | } |
| 6040 | |
| 6041 | void _updateSiblingNodesGeometries() { |
| 6042 | final _SemanticsGeometry mainGeometry = geometry!; |
| 6043 | for (final MapEntry<SemanticsNode, List<_SemanticsFragment>> entry |
| 6044 | in _producedSiblingNodesAndOwners.entries) { |
| 6045 | Rect? rect; |
| 6046 | Rect? semanticsClipRect; |
| 6047 | Rect? paintClipRect; |
| 6048 | for (final _SemanticsFragment fragment in entry.value) { |
| 6049 | if (fragment.owner.shouldFormSemanticsNode) { |
| 6050 | continue; |
| 6051 | } |
| 6052 | final _SemanticsGeometry parentGeometry = _SemanticsGeometry.computeChildGeometry( |
| 6053 | parentTransform: mainGeometry.transform, |
| 6054 | parentSemanticsClipRect: mainGeometry.semanticsClipRect, |
| 6055 | parentPaintClipRect: mainGeometry.paintClipRect, |
| 6056 | parent: this, |
| 6057 | child: fragment.owner, |
| 6058 | ); |
| 6059 | final Rect rectInFragmentOwnerCoordinates = |
| 6060 | parentGeometry.semanticsClipRect?.intersect( |
| 6061 | fragment.owner.renderObject.semanticBounds, |
| 6062 | ) ?? |
| 6063 | fragment.owner.renderObject.semanticBounds; |
| 6064 | final Rect rectInParentCoordinates = MatrixUtils.transformRect( |
| 6065 | parentGeometry.transform, |
| 6066 | rectInFragmentOwnerCoordinates, |
| 6067 | ); |
| 6068 | rect = rect?.expandToInclude(rectInParentCoordinates) ?? rectInParentCoordinates; |
| 6069 | if (parentGeometry.semanticsClipRect != null) { |
| 6070 | final Rect rect = MatrixUtils.transformRect( |
| 6071 | parentGeometry.transform, |
| 6072 | parentGeometry.semanticsClipRect!, |
| 6073 | ); |
| 6074 | semanticsClipRect = semanticsClipRect?.intersect(rect) ?? rect; |
| 6075 | } |
| 6076 | if (parentGeometry.paintClipRect != null) { |
| 6077 | final Rect rect = MatrixUtils.transformRect( |
| 6078 | parentGeometry.transform, |
| 6079 | parentGeometry.paintClipRect!, |
| 6080 | ); |
| 6081 | paintClipRect = paintClipRect?.intersect(rect) ?? rect; |
| 6082 | } |
| 6083 | } |
| 6084 | final SemanticsNode node = entry.key; |
| 6085 | node |
| 6086 | ..rect = rect! |
| 6087 | ..transform = |
| 6088 | null // transform has be taking into account when |
| 6089 | // calculating the rect. |
| 6090 | ..parentSemanticsClipRect = semanticsClipRect |
| 6091 | ..parentPaintClipRect = paintClipRect; |
| 6092 | } |
| 6093 | } |
| 6094 | |
| 6095 | /// The [renderObject]'s semantics information has changed. |
| 6096 | void markNeedsUpdate() { |
| 6097 | final SemanticsNode? producedSemanticsNode = cachedSemanticsNode; |
| 6098 | // Dirty the semantics tree starting at `this` until we have reached a |
| 6099 | // RenderObject that is a semantics boundary. All semantics past this |
| 6100 | // RenderObject are still up-to date. Therefore, we will later only rebuild |
| 6101 | // the semantics subtree starting at the identified semantics boundary. |
| 6102 | final bool wasSemanticsBoundary = |
| 6103 | producedSemanticsNode != null && configProvider.wasSemanticsBoundary; |
| 6104 | |
| 6105 | configProvider.clear(); |
| 6106 | _containsIncompleteFragment = false; |
| 6107 | |
| 6108 | bool mayProduceSiblingNodes = configProvider.effective.childConfigurationsDelegate != null; |
| 6109 | bool isEffectiveSemanticsBoundary = |
| 6110 | configProvider.effective.isSemanticBoundary && wasSemanticsBoundary; |
| 6111 | RenderObject node = renderObject; |
| 6112 | |
| 6113 | // The sibling nodes will be attached to the parent of immediate semantics |
| 6114 | // node, thus marking this semantics boundary dirty is not enough, it needs |
| 6115 | // to find the first parent semantics boundary that does not have any |
| 6116 | // possible sibling node. |
| 6117 | while (node.semanticsParent != null && |
| 6118 | (mayProduceSiblingNodes || !isEffectiveSemanticsBoundary)) { |
| 6119 | if (node != renderObject && node._semantics.parentDataDirty && !mayProduceSiblingNodes) { |
| 6120 | break; |
| 6121 | } |
| 6122 | node._semantics.geometry = null; |
| 6123 | node._semantics.parentData = null; |
| 6124 | node._semantics._blocksPreviousSibling = null; |
| 6125 | // Since this node is a semantics boundary, the produced sibling nodes will |
| 6126 | // be attached to the parent semantics boundary. Thus, these sibling nodes |
| 6127 | // will not be carried to the next loop. |
| 6128 | if (isEffectiveSemanticsBoundary) { |
| 6129 | mayProduceSiblingNodes = false; |
| 6130 | } |
| 6131 | mayProduceSiblingNodes |= |
| 6132 | node._semantics.configProvider.effective.childConfigurationsDelegate != null; |
| 6133 | |
| 6134 | node = node.semanticsParent!; |
| 6135 | // If node._semantics.built is false, this branch is currently blocked. |
| 6136 | // In that case, it should continue dirty upward until it reach a |
| 6137 | // unblocked semantics boundary because blocked branch will not rebuild |
| 6138 | // semantics during PipelineOwner.flushSemantics. |
| 6139 | // |
| 6140 | // If we stop here and not dirty the rendering parent and a flush semantics |
| 6141 | // is called, it will end up with a blocked branch where some sub-branch |
| 6142 | // is dirty. There won't be a way to rebuild these dirty sub-branch |
| 6143 | // without rebuilding the entire blocked branch (which is costly) when the |
| 6144 | // branch is later unblocked. |
| 6145 | isEffectiveSemanticsBoundary = |
| 6146 | node._semantics.configProvider.effective.isSemanticBoundary && node._semantics.built; |
| 6147 | } |
| 6148 | if (node != renderObject && producedSemanticsNode != null && node._semantics.parentDataDirty) { |
| 6149 | // If `this` node has already been added to [owner._nodesNeedingSemantics] |
| 6150 | // remove it as it is no longer guaranteed that its semantics |
| 6151 | // node will continue to be in the tree. If it still is in the tree, the |
| 6152 | // ancestor `node` added to [owner._nodesNeedingSemantics] at the end of |
| 6153 | // this block will ensure that the semantics of `this` node actually gets |
| 6154 | // updated. |
| 6155 | // (See semantics_10_test.dart for an example why this is required). |
| 6156 | renderObject.owner!._nodesNeedingSemantics.remove(renderObject); |
| 6157 | } |
| 6158 | if (!node._semantics.parentDataDirty) { |
| 6159 | if (renderObject.owner != null) { |
| 6160 | assert( |
| 6161 | node._semantics.configProvider.effective.isSemanticBoundary || |
| 6162 | node.semanticsParent == null, |
| 6163 | ); |
| 6164 | if (renderObject.owner!._nodesNeedingSemantics.add(node)) { |
| 6165 | renderObject.owner!.requestVisualUpdate(); |
| 6166 | } |
| 6167 | } |
| 6168 | } |
| 6169 | } |
| 6170 | |
| 6171 | void _marksConflictsInMergeGroup(List<_SemanticsFragment> mergeGroup, {bool isMergeUp = false}) { |
| 6172 | final Set<_SemanticsFragment> hasSiblingConflict = <_SemanticsFragment>{}; |
| 6173 | for (int i = 0; i < mergeGroup.length; i += 1) { |
| 6174 | final _SemanticsFragment fragment = mergeGroup[i]; |
| 6175 | // Remove old value |
| 6176 | fragment.markSiblingConfigurationConflict(false); |
| 6177 | if (fragment.configToMergeUp == null) { |
| 6178 | continue; |
| 6179 | } |
| 6180 | if (isMergeUp && !configProvider.original.isCompatibleWith(fragment.configToMergeUp)) { |
| 6181 | hasSiblingConflict.add(fragment); |
| 6182 | } |
| 6183 | final int siblingLength = i; |
| 6184 | for (int j = 0; j < siblingLength; j += 1) { |
| 6185 | final _SemanticsFragment siblingFragment = mergeGroup[j]; |
| 6186 | if (!fragment.configToMergeUp!.isCompatibleWith(siblingFragment.configToMergeUp)) { |
| 6187 | hasSiblingConflict.add(fragment); |
| 6188 | hasSiblingConflict.add(siblingFragment); |
| 6189 | } |
| 6190 | } |
| 6191 | } |
| 6192 | for (final _SemanticsFragment fragment in hasSiblingConflict) { |
| 6193 | fragment.markSiblingConfigurationConflict(true); |
| 6194 | } |
| 6195 | } |
| 6196 | |
| 6197 | /// Removes any cache stored in this object as if it is newly created. |
| 6198 | void clear() { |
| 6199 | built = false; |
| 6200 | cachedSemanticsNode = null; |
| 6201 | parentData = null; |
| 6202 | geometry = null; |
| 6203 | _blocksPreviousSibling = null; |
| 6204 | _containsIncompleteFragment = false; |
| 6205 | mergeUp.clear(); |
| 6206 | siblingMergeGroups.clear(); |
| 6207 | _children.clear(); |
| 6208 | semanticsNodes.clear(); |
| 6209 | configProvider.clear(); |
| 6210 | } |
| 6211 | |
| 6212 | @override |
| 6213 | List<DiagnosticsNode> debugDescribeChildren() { |
| 6214 | return _getNonBlockedChildren() |
| 6215 | .map<DiagnosticsNode>((_RenderObjectSemantics child) => child.toDiagnosticsNode()) |
| 6216 | .toList(); |
| 6217 | } |
| 6218 | |
| 6219 | @protected |
| 6220 | @override |
| 6221 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 6222 | super.debugFillProperties(properties); |
| 6223 | properties.add(StringProperty('owner' , describeIdentity(renderObject))); |
| 6224 | properties.add( |
| 6225 | FlagProperty('noParentData' , value: parentData == null, ifTrue: 'NO PARENT DATA' ), |
| 6226 | ); |
| 6227 | properties.add( |
| 6228 | FlagProperty( |
| 6229 | 'semanticsBlock' , |
| 6230 | value: configProvider.effective.isBlockingSemanticsOfPreviouslyPaintedNodes, |
| 6231 | ifTrue: 'BLOCK PREVIOUS' , |
| 6232 | ), |
| 6233 | ); |
| 6234 | if (contributesToSemanticsTree) { |
| 6235 | final String semanticsNodeStatus; |
| 6236 | if (built) { |
| 6237 | semanticsNodeStatus = 'formed ${cachedSemanticsNode?.id}' ; |
| 6238 | } else if (!built && shouldFormSemanticsNode) { |
| 6239 | semanticsNodeStatus = 'needs build' ; |
| 6240 | } else { |
| 6241 | semanticsNodeStatus = 'no semantics node' ; |
| 6242 | } |
| 6243 | properties.add(StringProperty('formedSemanticsNode' , semanticsNodeStatus, quoted: false)); |
| 6244 | } |
| 6245 | properties.add( |
| 6246 | FlagProperty( |
| 6247 | 'isSemanticBoundary' , |
| 6248 | value: configProvider.effective.isSemanticBoundary, |
| 6249 | ifTrue: 'semantic boundary' , |
| 6250 | ), |
| 6251 | ); |
| 6252 | properties.add( |
| 6253 | FlagProperty('blocksSemantics' , value: isBlockingPreviousSibling, ifTrue: 'BLOCKS SEMANTICS' ), |
| 6254 | ); |
| 6255 | if (contributesToSemanticsTree && siblingMergeGroups.isNotEmpty) { |
| 6256 | properties.add(StringProperty('Sibling group' , siblingMergeGroups.toString(), quoted: false)); |
| 6257 | } |
| 6258 | } |
| 6259 | } |
| 6260 | |
| 6261 | /// Dumps the render object semantics tree. |
| 6262 | void debugDumpRenderObjectSemanticsTree() { |
| 6263 | debugPrint(_debugCollectRenderObjectSemanticsTrees()); |
| 6264 | } |
| 6265 | |
| 6266 | String _debugCollectRenderObjectSemanticsTrees() { |
| 6267 | if (RendererBinding.instance.renderViews.isEmpty) { |
| 6268 | return 'No render tree root was added to the binding.' ; |
| 6269 | } |
| 6270 | return <String>[ |
| 6271 | for (final RenderObject renderView in RendererBinding.instance.renderViews) |
| 6272 | renderView._semantics.toStringDeep(), |
| 6273 | ].join('\n\n' ); |
| 6274 | } |
| 6275 | |
| 6276 | typedef _SemanticsGeometryClips = (Rect? paintClipRect, Rect? semanticsClipRect); |
| 6277 | |
| 6278 | /// Helper class that keeps track of the geometry of a [SemanticsNode]. |
| 6279 | /// |
| 6280 | /// It is used to annotate a [SemanticsNode] with the current information for |
| 6281 | /// [SemanticsNode.rect] and [SemanticsNode.transform]. |
| 6282 | @immutable |
| 6283 | final class _SemanticsGeometry { |
| 6284 | /// The `paintClipRect` may be null if no clip is to be applied. |
| 6285 | const _SemanticsGeometry({ |
| 6286 | required this.paintClipRect, |
| 6287 | required this.semanticsClipRect, |
| 6288 | required this.transform, |
| 6289 | required this.rect, |
| 6290 | required this.hidden, |
| 6291 | }); |
| 6292 | |
| 6293 | factory _SemanticsGeometry.root(Rect rect) { |
| 6294 | return _SemanticsGeometry( |
| 6295 | paintClipRect: null, |
| 6296 | semanticsClipRect: null, |
| 6297 | transform: Matrix4.identity(), |
| 6298 | hidden: false, |
| 6299 | rect: rect, |
| 6300 | ); |
| 6301 | } |
| 6302 | |
| 6303 | /// Value for [SemanticsNode.transform]. |
| 6304 | final Matrix4 transform; |
| 6305 | |
| 6306 | /// Value for [SemanticsNode.parentSemanticsClipRect]. |
| 6307 | final Rect? semanticsClipRect; |
| 6308 | |
| 6309 | /// Value for [SemanticsNode.parentPaintClipRect]. |
| 6310 | final Rect? paintClipRect; |
| 6311 | |
| 6312 | /// Value for [SemanticsNode.rect]. |
| 6313 | final Rect rect; |
| 6314 | |
| 6315 | /// Whether the semantics node is completely clipped from ui, i.e. by |
| 6316 | /// paintClipRect, but is still present in semantics tree. |
| 6317 | final bool hidden; |
| 6318 | |
| 6319 | static _SemanticsGeometry computeChildGeometry({ |
| 6320 | required Matrix4? parentTransform, |
| 6321 | required Rect? parentPaintClipRect, |
| 6322 | required Rect? parentSemanticsClipRect, |
| 6323 | required _RenderObjectSemantics parent, |
| 6324 | required _RenderObjectSemantics child, |
| 6325 | }) { |
| 6326 | final Matrix4 transform = parentTransform?.clone() ?? Matrix4.identity(); |
| 6327 | Matrix4? parentToCommonAncestorTransform; |
| 6328 | RenderObject childRenderObject = child.renderObject; |
| 6329 | RenderObject parentRenderObject = parent.renderObject; |
| 6330 | |
| 6331 | final List<RenderObject> childToCommonAncestor = <RenderObject>[childRenderObject]; |
| 6332 | |
| 6333 | // Find the common ancestor. |
| 6334 | while (!identical(childRenderObject, parentRenderObject)) { |
| 6335 | final int fromDepth = childRenderObject.depth; |
| 6336 | final int toDepth = parentRenderObject.depth; |
| 6337 | |
| 6338 | if (fromDepth >= toDepth) { |
| 6339 | assert( |
| 6340 | childRenderObject.parent != null, |
| 6341 | ' $parent and $child are not in the same render tree.' , |
| 6342 | ); |
| 6343 | childRenderObject = childRenderObject.parent!; |
| 6344 | childToCommonAncestor.add(childRenderObject); |
| 6345 | } |
| 6346 | if (fromDepth <= toDepth) { |
| 6347 | assert( |
| 6348 | parentRenderObject.parent != null, |
| 6349 | ' $parent and $child are not in the same render tree.' , |
| 6350 | ); |
| 6351 | final RenderObject toParent = parentRenderObject.parent!; |
| 6352 | toParent.applyPaintTransform( |
| 6353 | parentRenderObject, |
| 6354 | parentToCommonAncestorTransform ??= Matrix4.identity(), |
| 6355 | ); |
| 6356 | parentRenderObject = toParent; |
| 6357 | } |
| 6358 | } |
| 6359 | |
| 6360 | // Calculate transform. |
| 6361 | assert(childToCommonAncestor.length >= 2); |
| 6362 | for (int i = childToCommonAncestor.length - 1; i > 0; i -= 1) { |
| 6363 | childToCommonAncestor[i].applyPaintTransform(childToCommonAncestor[i - 1], transform); |
| 6364 | } |
| 6365 | |
| 6366 | if (parentToCommonAncestorTransform != null) { |
| 6367 | if (parentToCommonAncestorTransform.invert() != 0) { |
| 6368 | transform.multiply(parentToCommonAncestorTransform); |
| 6369 | } else { |
| 6370 | transform.setZero(); |
| 6371 | } |
| 6372 | } |
| 6373 | |
| 6374 | // Calculate clips. |
| 6375 | Rect? paintClipRect; |
| 6376 | Rect? semanticsClipRect; |
| 6377 | if (childToCommonAncestor.last == parent.renderObject) { |
| 6378 | // This is most common case, i.e. parent is the common ancestor. |
| 6379 | paintClipRect = parentPaintClipRect; |
| 6380 | semanticsClipRect = parentSemanticsClipRect; |
| 6381 | assert(parentToCommonAncestorTransform == null); |
| 6382 | for (int i = childToCommonAncestor.length - 1; i > 0; i -= 1) { |
| 6383 | (paintClipRect, semanticsClipRect) = _computeClipRect( |
| 6384 | childToCommonAncestor[i], |
| 6385 | childToCommonAncestor[i - 1], |
| 6386 | semanticsClipRect, |
| 6387 | paintClipRect, |
| 6388 | ); |
| 6389 | } |
| 6390 | } else { |
| 6391 | // Otherwise we have to find the closest ancestor RenderObject that |
| 6392 | // has up-to-date semantics geometry and compute the clip rects from there. |
| 6393 | // |
| 6394 | // Currently it can only happen when the subtree contains an OverlayPortal. |
| 6395 | final List<RenderObject> clipPath = <RenderObject>[child.renderObject]; |
| 6396 | |
| 6397 | RenderObject? ancestor = child.renderObject.parent; |
| 6398 | while (ancestor != null && ancestor._semantics.cachedSemanticsNode == null) { |
| 6399 | clipPath.add(ancestor); |
| 6400 | ancestor = ancestor.parent; |
| 6401 | } |
| 6402 | final SemanticsNode? ancestorNode = ancestor?._semantics.cachedSemanticsNode; |
| 6403 | paintClipRect = ancestorNode?.parentPaintClipRect; |
| 6404 | semanticsClipRect = ancestorNode?.parentSemanticsClipRect; |
| 6405 | if (ancestor != null) { |
| 6406 | RenderObject parent = ancestor; |
| 6407 | for (int i = clipPath.length - 1; i >= 0; i -= 1) { |
| 6408 | (paintClipRect, semanticsClipRect) = _computeClipRect( |
| 6409 | parent, |
| 6410 | clipPath[i], |
| 6411 | semanticsClipRect, |
| 6412 | paintClipRect, |
| 6413 | ); |
| 6414 | parent = clipPath[i]; |
| 6415 | } |
| 6416 | } |
| 6417 | } |
| 6418 | |
| 6419 | Rect rect = |
| 6420 | semanticsClipRect?.intersect(child.renderObject.semanticBounds) ?? |
| 6421 | child.renderObject.semanticBounds; |
| 6422 | bool isRectHidden = false; |
| 6423 | if (paintClipRect != null) { |
| 6424 | final Rect paintRect = paintClipRect.intersect(rect); |
| 6425 | isRectHidden = paintRect.isEmpty && !rect.isEmpty; |
| 6426 | if (!isRectHidden) { |
| 6427 | rect = paintRect; |
| 6428 | } |
| 6429 | } |
| 6430 | |
| 6431 | return _SemanticsGeometry( |
| 6432 | transform: transform, |
| 6433 | paintClipRect: paintClipRect, |
| 6434 | semanticsClipRect: semanticsClipRect, |
| 6435 | rect: rect, |
| 6436 | hidden: isRectHidden, |
| 6437 | ); |
| 6438 | } |
| 6439 | |
| 6440 | /// From parent to child coordinate system. |
| 6441 | static Rect? _transformRect(Rect? rect, Matrix4 transform) { |
| 6442 | if (rect == null) { |
| 6443 | return null; |
| 6444 | } |
| 6445 | if (rect.isEmpty || transform.isZero()) { |
| 6446 | return Rect.zero; |
| 6447 | } |
| 6448 | return MatrixUtils.inverseTransformRect(transform, rect); |
| 6449 | } |
| 6450 | |
| 6451 | // A matrix used to store transient transform data. |
| 6452 | // |
| 6453 | // Reusing this matrix avoids allocating a new matrix every time a temporary |
| 6454 | // matrix is needed. |
| 6455 | // |
| 6456 | // This instance should never be returned to the caller. Otherwise, the data |
| 6457 | // stored in it will be overwritten unpredictably by subsequent reuses. |
| 6458 | static final Matrix4 _temporaryTransformHolder = Matrix4.zero(); |
| 6459 | |
| 6460 | // Computes the semantics and painting clip rects for the given child and |
| 6461 | // assigns the rects to _semanticsClipRect and _paintClipRect respectively. |
| 6462 | // |
| 6463 | // The caller must guarantee that child.parent == parent. The resulting rects |
| 6464 | // are in `child`'s coordinate system. |
| 6465 | static _SemanticsGeometryClips _computeClipRect( |
| 6466 | RenderObject parent, |
| 6467 | RenderObject child, |
| 6468 | Rect? parentSemanticsClipRect, |
| 6469 | Rect? parentPaintClipRect, |
| 6470 | ) { |
| 6471 | assert(identical(child.parent, parent)); |
| 6472 | final Rect? additionalPaintClip = parent.describeApproximatePaintClip(child); |
| 6473 | if (parentPaintClipRect == null && additionalPaintClip == null) { |
| 6474 | return (null, null); |
| 6475 | } |
| 6476 | // Computes the paint transform from child to parent. The _transformRect |
| 6477 | // method will compute the inverse. |
| 6478 | _temporaryTransformHolder.setIdentity(); // clears data from previous call(s) |
| 6479 | parent.applyPaintTransform(child, _temporaryTransformHolder); |
| 6480 | |
| 6481 | final Rect paintClipRect = _transformRect( |
| 6482 | _intersectRects(additionalPaintClip, parentPaintClipRect), |
| 6483 | _temporaryTransformHolder, |
| 6484 | )!; |
| 6485 | final Rect? semanticsClip = |
| 6486 | parent.describeSemanticsClip(child) ?? |
| 6487 | _intersectRects(parentSemanticsClipRect, additionalPaintClip); |
| 6488 | return (paintClipRect, _transformRect(semanticsClip, _temporaryTransformHolder)); |
| 6489 | } |
| 6490 | |
| 6491 | static Rect? _intersectRects(Rect? a, Rect? b) { |
| 6492 | if (b == null) { |
| 6493 | return a; |
| 6494 | } |
| 6495 | return a?.intersect(b) ?? b; |
| 6496 | } |
| 6497 | } |
| 6498 | |
| 6499 | /// A class that creates [DiagnosticsNode] by wrapping [RenderObject.debugCreator]. |
| 6500 | /// |
| 6501 | /// Attach a [DiagnosticsDebugCreator] into [FlutterErrorDetails.informationCollector] |
| 6502 | /// when a [RenderObject.debugCreator] is available. This will lead to improved |
| 6503 | /// error message. |
| 6504 | class DiagnosticsDebugCreator extends DiagnosticsProperty<Object> { |
| 6505 | /// Create a [DiagnosticsProperty] with its [value] initialized to input |
| 6506 | /// [RenderObject.debugCreator]. |
| 6507 | DiagnosticsDebugCreator(Object value) |
| 6508 | : super('debugCreator' , value, level: DiagnosticLevel.hidden); |
| 6509 | } |
| 6510 | |