@@ -135,6 +135,91 @@ class AnnotationResult<T> {
135
135
/// * [RenderView.compositeFrame] , which implements this recomposition protocol
136
136
/// for painting [RenderObject] trees on the display.
137
137
abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
138
+ final Map <int , VoidCallback > _callbacks = < int , VoidCallback > {};
139
+ static int _nextCallbackId = 0 ;
140
+
141
+ /// Whether the subtree rooted at this layer has any composition callback
142
+ /// observers.
143
+ ///
144
+ /// This only evaluates to true if the subtree rooted at this node has
145
+ /// observers. For example, it may evaluate to true on a parent node but false
146
+ /// on a child if the parent has observers but the child does not.
147
+ ///
148
+ /// See also:
149
+ ///
150
+ /// * [Layer.addCompositionCallback] .
151
+ bool get subtreeHasCompositionCallbacks => _compositionCallbackCount > 0 ;
152
+
153
+ int _compositionCallbackCount = 0 ;
154
+ void _updateSubtreeCompositionObserverCount (int delta) {
155
+ assert (delta != 0 );
156
+ _compositionCallbackCount += delta;
157
+ assert (_compositionCallbackCount >= 0 );
158
+ if (parent != null ) {
159
+ parent! ._updateSubtreeCompositionObserverCount (delta);
160
+ }
161
+ }
162
+
163
+ void _fireCompositionCallbacks ({required bool includeChildren}) {
164
+ for (final VoidCallback callback in List <VoidCallback >.of (_callbacks.values)) {
165
+ callback ();
166
+ }
167
+ }
168
+
169
+ bool _debugMutationsLocked = false ;
170
+
171
+ /// Describes the clip that would be applied to contents of this layer,
172
+ /// if any.
173
+ Rect ? describeClipBounds () => null ;
174
+
175
+ /// Adds a callback for when the layer tree that this layer is part of gets
176
+ /// composited, or when it is detached and will not be rendered again.
177
+ ///
178
+ /// This callback will fire even if an ancestor layer is added with retained
179
+ /// rendering, meaning that it will fire even if this layer gets added to the
180
+ /// scene via some call to [ui.SceneBuilder.addRetained] on one of its
181
+ /// ancestor layers.
182
+ ///
183
+ /// The callback receives a reference to this layer. The recipient must not
184
+ /// mutate the layer during the scope of the callback, but may traverse the
185
+ /// tree to find information about the current transform or clip. The layer
186
+ /// may not be [attached] anymore in this state, but even if it is detached it
187
+ /// may still have an also detached parent it can visit.
188
+ ///
189
+ /// If new callbacks are added or removed within the [callback] , the new
190
+ /// callbacks will fire (or stop firing) on the _next_ compositing event.
191
+ ///
192
+ /// {@template flutter.rendering.Layer.compositionCallbacks}
193
+ /// Composition callbacks are useful in place of pushing a layer that would
194
+ /// otherwise try to observe the layer tree without actually affecting
195
+ /// compositing. For example, a composition callback may be used to observe
196
+ /// the total transform and clip of the current container layer to determine
197
+ /// whether a render object drawn into it is visible or not.
198
+ ///
199
+ /// Calling the returned callback will remove [callback] from the composition
200
+ /// callbacks.
201
+ /// {@endtemplate}
202
+ VoidCallback addCompositionCallback (CompositionCallback callback) {
203
+ _updateSubtreeCompositionObserverCount (1 );
204
+ final int callbackId = _nextCallbackId += 1 ;
205
+ _callbacks[callbackId] = () {
206
+ assert (() {
207
+ _debugMutationsLocked = true ;
208
+ return true ;
209
+ }());
210
+ callback (this );
211
+ assert (() {
212
+ _debugMutationsLocked = false ;
213
+ return true ;
214
+ }());
215
+ };
216
+ return () {
217
+ assert (_callbacks.containsKey (callbackId));
218
+ _callbacks.remove (callbackId);
219
+ _updateSubtreeCompositionObserverCount (- 1 );
220
+ };
221
+ }
222
+
138
223
/// If asserts are enabled, returns whether [dispose] has
139
224
/// been called since the last time any retained resources were created.
140
225
///
@@ -164,6 +249,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
164
249
165
250
/// Called by [LayerHandle] .
166
251
void _unref () {
252
+ assert (! _debugMutationsLocked);
167
253
assert (_refCount > 0 );
168
254
_refCount -= 1 ;
169
255
if (_refCount == 0 ) {
@@ -205,6 +291,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
205
291
@protected
206
292
@visibleForTesting
207
293
void dispose () {
294
+ assert (! _debugMutationsLocked);
208
295
assert (
209
296
! _debugDisposed,
210
297
'Layers must only be disposed once. This is typically handled by '
@@ -261,6 +348,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
261
348
@protected
262
349
@visibleForTesting
263
350
void markNeedsAddToScene () {
351
+ assert (! _debugMutationsLocked);
264
352
assert (
265
353
! alwaysNeedsAddToScene,
266
354
'$runtimeType with alwaysNeedsAddToScene set called markNeedsAddToScene.\n '
@@ -282,6 +370,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
282
370
/// this method has no effect.
283
371
@visibleForTesting
284
372
void debugMarkClean () {
373
+ assert (! _debugMutationsLocked);
285
374
assert (() {
286
375
_needsAddToScene = false ;
287
376
return true ;
@@ -331,6 +420,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
331
420
@protected
332
421
@visibleForTesting
333
422
set engineLayer (ui.EngineLayer ? value) {
423
+ assert (! _debugMutationsLocked);
334
424
assert (! _debugDisposed);
335
425
336
426
_engineLayer? .dispose ();
@@ -375,6 +465,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
375
465
@protected
376
466
@visibleForTesting
377
467
void updateSubtreeNeedsAddToScene () {
468
+ assert (! _debugMutationsLocked);
378
469
_needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
379
470
}
380
471
@@ -387,18 +478,26 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
387
478
Layer ? _previousSibling;
388
479
389
480
@override
390
- void dropChild (AbstractNode child) {
481
+ void dropChild (Layer child) {
482
+ assert (! _debugMutationsLocked);
391
483
if (! alwaysNeedsAddToScene) {
392
484
markNeedsAddToScene ();
393
485
}
486
+ if (child._compositionCallbackCount != 0 ) {
487
+ _updateSubtreeCompositionObserverCount (- child._compositionCallbackCount);
488
+ }
394
489
super .dropChild (child);
395
490
}
396
491
397
492
@override
398
- void adoptChild (AbstractNode child) {
493
+ void adoptChild (Layer child) {
494
+ assert (! _debugMutationsLocked);
399
495
if (! alwaysNeedsAddToScene) {
400
496
markNeedsAddToScene ();
401
497
}
498
+ if (child._compositionCallbackCount != 0 ) {
499
+ _updateSubtreeCompositionObserverCount (child._compositionCallbackCount);
500
+ }
402
501
super .adoptChild (child);
403
502
}
404
503
@@ -407,6 +506,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
407
506
/// This has no effect if the layer's parent is already null.
408
507
@mustCallSuper
409
508
void remove () {
509
+ assert (! _debugMutationsLocked);
410
510
parent? ._removeChild (this );
411
511
}
412
512
@@ -529,6 +629,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
529
629
void addToScene (ui.SceneBuilder builder);
530
630
531
631
void _addToSceneWithRetainedRendering (ui.SceneBuilder builder) {
632
+ assert (! _debugMutationsLocked);
532
633
// There can't be a loop by adding a retained layer subtree whose
533
634
// _needsAddToScene is false.
534
635
//
@@ -909,12 +1010,28 @@ class PerformanceOverlayLayer extends Layer {
909
1010
}
910
1011
}
911
1012
1013
+ /// The signature of the callback added in [Layer.addCompositionCallback] .
1014
+ typedef CompositionCallback = void Function (Layer );
1015
+
912
1016
/// A composited layer that has a list of children.
913
1017
///
914
1018
/// A [ContainerLayer] instance merely takes a list of children and inserts them
915
1019
/// into the composited rendering in order. There are subclasses of
916
1020
/// [ContainerLayer] which apply more elaborate effects in the process.
917
1021
class ContainerLayer extends Layer {
1022
+ @override
1023
+ void _fireCompositionCallbacks ({required bool includeChildren}) {
1024
+ super ._fireCompositionCallbacks (includeChildren: includeChildren);
1025
+ if (! includeChildren) {
1026
+ return ;
1027
+ }
1028
+ Layer ? child = firstChild;
1029
+ while (child != null ) {
1030
+ child._fireCompositionCallbacks (includeChildren: includeChildren);
1031
+ child = child.nextSibling;
1032
+ }
1033
+ }
1034
+
918
1035
/// The first composited layer in this layer's child list.
919
1036
Layer ? get firstChild => _firstChild;
920
1037
Layer ? _firstChild;
@@ -935,6 +1052,9 @@ class ContainerLayer extends Layer {
935
1052
ui.Scene buildScene (ui.SceneBuilder builder) {
936
1053
updateSubtreeNeedsAddToScene ();
937
1054
addToScene (builder);
1055
+ if (subtreeHasCompositionCallbacks) {
1056
+ _fireCompositionCallbacks (includeChildren: true );
1057
+ }
938
1058
// Clearing the flag _after_ calling `addToScene`, not _before_. This is
939
1059
// because `addToScene` calls children's `addToScene` methods, which may
940
1060
// mark this layer as dirty.
@@ -966,6 +1086,7 @@ class ContainerLayer extends Layer {
966
1086
@override
967
1087
void dispose () {
968
1088
removeAllChildren ();
1089
+ _callbacks.clear ();
969
1090
super .dispose ();
970
1091
}
971
1092
@@ -994,6 +1115,7 @@ class ContainerLayer extends Layer {
994
1115
995
1116
@override
996
1117
void attach (Object owner) {
1118
+ assert (! _debugMutationsLocked);
997
1119
super .attach (owner);
998
1120
Layer ? child = firstChild;
999
1121
while (child != null ) {
@@ -1004,16 +1126,25 @@ class ContainerLayer extends Layer {
1004
1126
1005
1127
@override
1006
1128
void detach () {
1129
+ assert (! _debugMutationsLocked);
1007
1130
super .detach ();
1008
1131
Layer ? child = firstChild;
1009
1132
while (child != null ) {
1010
1133
child.detach ();
1011
1134
child = child.nextSibling;
1012
1135
}
1136
+ // Detach indicates that we may never be composited again. Clients
1137
+ // interested in observing composition need to get an update here because
1138
+ // they might otherwise never get another one even though the layer is no
1139
+ // longer visible.
1140
+ //
1141
+ // Children fired them already in child.detach().
1142
+ _fireCompositionCallbacks (includeChildren: false );
1013
1143
}
1014
1144
1015
1145
/// Adds the given layer to the end of this layer's child list.
1016
1146
void append (Layer child) {
1147
+ assert (! _debugMutationsLocked);
1017
1148
assert (child != this );
1018
1149
assert (child != firstChild);
1019
1150
assert (child != lastChild);
@@ -1072,6 +1203,7 @@ class ContainerLayer extends Layer {
1072
1203
1073
1204
/// Removes all of this layer's children from its child list.
1074
1205
void removeAllChildren () {
1206
+ assert (! _debugMutationsLocked);
1075
1207
Layer ? child = firstChild;
1076
1208
while (child != null ) {
1077
1209
final Layer ? next = child.nextSibling;
@@ -1221,7 +1353,7 @@ class OffsetLayer extends ContainerLayer {
1221
1353
void applyTransform (Layer ? child, Matrix4 transform) {
1222
1354
assert (child != null );
1223
1355
assert (transform != null );
1224
- transform.multiply ( Matrix4 . translationValues ( offset.dx, offset.dy, 0.0 ) );
1356
+ transform.translate ( offset.dx, offset.dy);
1225
1357
}
1226
1358
1227
1359
@override
@@ -1321,6 +1453,9 @@ class ClipRectLayer extends ContainerLayer {
1321
1453
}
1322
1454
}
1323
1455
1456
+ @override
1457
+ Rect ? describeClipBounds () => clipRect;
1458
+
1324
1459
/// {@template flutter.rendering.ClipRectLayer.clipBehavior}
1325
1460
/// Controls how to clip.
1326
1461
///
@@ -1408,6 +1543,9 @@ class ClipRRectLayer extends ContainerLayer {
1408
1543
}
1409
1544
}
1410
1545
1546
+ @override
1547
+ Rect ? describeClipBounds () => clipRRect? .outerRect;
1548
+
1411
1549
/// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
1412
1550
///
1413
1551
/// Defaults to [Clip.antiAlias] .
@@ -1491,6 +1629,9 @@ class ClipPathLayer extends ContainerLayer {
1491
1629
}
1492
1630
}
1493
1631
1632
+ @override
1633
+ Rect ? describeClipBounds () => clipPath? .getBounds ();
1634
+
1494
1635
/// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
1495
1636
///
1496
1637
/// Defaults to [Clip.antiAlias] .
0 commit comments