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