-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlayer.dart
2560 lines (2369 loc) · 91 KB
/
layer.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/painting.dart';
import 'package:vector_math/vector_math_64.dart';
import 'debug.dart';
/// Information collected for an annotation that is found in the layer tree.
///
/// See also:
///
/// * [Layer.findAnnotations], which create and use objects of this class.
@immutable
class AnnotationEntry<T> {
/// Create an entry of found annotation by providing the object and related
/// information.
const AnnotationEntry({
@required this.annotation,
@required this.localPosition,
}) : assert(localPosition != null);
/// The annotation object that is found.
final T annotation;
/// The target location described by the local coordinate space of the layer
/// that contains the annotation.
final Offset localPosition;
@override
String toString() {
return '${objectRuntimeType(this, 'AnnotationEntry')}(annotation: $annotation, localPostion: $localPosition)';
}
}
/// Information collected about a list of annotations that are found in the
/// layer tree.
///
/// See also:
///
/// * [AnnotationEntry], which are members of this class.
/// * [Layer.findAllAnnotations], and [Layer.findAnnotations], which create and
/// use an object of this class.
class AnnotationResult<T> {
final List<AnnotationEntry<T>> _entries = <AnnotationEntry<T>>[];
/// Add a new entry to the end of the result.
///
/// Usually, entries should be added in order from most specific to least
/// specific, typically during an upward walk of the tree.
void add(AnnotationEntry<T> entry) => _entries.add(entry);
/// An unmodifiable list of [AnnotationEntry] objects recorded.
///
/// The first entry is the most specific, typically the one at the leaf of
/// tree.
Iterable<AnnotationEntry<T>> get entries => _entries;
/// An unmodifiable list of annotations recorded.
///
/// The first entry is the most specific, typically the one at the leaf of
/// tree.
///
/// It is similar to [entries] but does not contain other information.
Iterable<T> get annotations sync* {
for (final AnnotationEntry<T> entry in _entries)
yield entry.annotation;
}
}
/// A composited layer.
///
/// During painting, the render tree generates a tree of composited layers that
/// are uploaded into the engine and displayed by the compositor. This class is
/// the base class for all composited layers.
///
/// Most layers can have their properties mutated, and layers can be moved to
/// different parents. The scene must be explicitly recomposited after such
/// changes are made; the layer tree does not maintain its own dirty state.
///
/// To composite the tree, create a [SceneBuilder] object, pass it to the
/// root [Layer] object's [addToScene] method, and then call
/// [SceneBuilder.build] to obtain a [Scene]. A [Scene] can then be painted
/// using [Window.render].
///
/// See also:
///
/// * [RenderView.compositeFrame], which implements this recomposition protocol
/// for painting [RenderObject] trees on the display.
abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
/// This layer's parent in the layer tree.
///
/// The [parent] of the root node in the layer tree is null.
///
/// Only subclasses of [ContainerLayer] can have children in the layer tree.
/// All other layer classes are used for leaves in the layer tree.
@override
ContainerLayer get parent => super.parent as ContainerLayer;
// Whether this layer has any changes since its last call to [addToScene].
//
// Initialized to true as a new layer has never called [addToScene], and is
// set to false after calling [addToScene]. The value can become true again
// if [markNeedsAddToScene] is called, or when [updateSubtreeNeedsAddToScene]
// is called on this layer or on an ancestor layer.
//
// The values of [_needsAddToScene] in a tree of layers are said to be
// _consistent_ if every layer in the tree satisfies the following:
//
// - If [alwaysNeedsAddToScene] is true, then [_needsAddToScene] is also true.
// - If [_needsAddToScene] is true and [parent] is not null, then
// `parent._needsAddToScene` is true.
//
// Typically, this value is set during the paint phase and during compositing.
// During the paint phase render objects create new layers and call
// [markNeedsAddToScene] on existing layers, causing this value to become
// true. After the paint phase the tree may be in an inconsistent state.
// During compositing [ContainerLayer.buildScene] first calls
// [updateSubtreeNeedsAddToScene] to bring this tree to a consistent state,
// then it calls [addToScene], and finally sets this field to false.
bool _needsAddToScene = true;
/// Mark that this layer has changed and [addToScene] needs to be called.
@protected
@visibleForTesting
void markNeedsAddToScene() {
assert(
!alwaysNeedsAddToScene,
'$runtimeType with alwaysNeedsAddToScene set called markNeedsAddToScene.\n'
'The layer\'s alwaysNeedsAddToScene is set to true, and therefore it should not call markNeedsAddToScene.',
);
// Already marked. Short-circuit.
if (_needsAddToScene) {
return;
}
_needsAddToScene = true;
}
/// Mark that this layer is in sync with engine.
///
/// This is for debugging and testing purposes only. In release builds
/// this method has no effect.
@visibleForTesting
void debugMarkClean() {
assert(() {
_needsAddToScene = false;
return true;
}());
}
/// Subclasses may override this to true to disable retained rendering.
@protected
bool get alwaysNeedsAddToScene => false;
/// Whether this or any descendant layer in the subtree needs [addToScene].
///
/// This is for debug and test purpose only. It only becomes valid after
/// calling [updateSubtreeNeedsAddToScene].
@visibleForTesting
bool get debugSubtreeNeedsAddToScene {
bool result;
assert(() {
result = _needsAddToScene;
return true;
}());
return result;
}
/// Stores the engine layer created for this layer in order to reuse engine
/// resources across frames for better app performance.
///
/// This value may be passed to [ui.SceneBuilder.addRetained] to communicate
/// to the engine that nothing in this layer or any of its descendants
/// changed. The native engine could, for example, reuse the texture rendered
/// in a previous frame. The web engine could, for example, reuse the HTML
/// DOM nodes created for a previous frame.
///
/// This value may be passed as `oldLayer` argument to a "push" method to
/// communicate to the engine that a layer is updating a previously rendered
/// layer. The web engine could, for example, update the properties of
/// previously rendered HTML DOM nodes rather than creating new nodes.
@protected
ui.EngineLayer get engineLayer => _engineLayer;
/// Sets the engine layer used to render this layer.
///
/// Typically this field is set to the value returned by [addToScene], which
/// in turn returns the engine layer produced by one of [ui.SceneBuilder]'s
/// "push" methods, such as [ui.SceneBuilder.pushOpacity].
@protected
set engineLayer(ui.EngineLayer value) {
_engineLayer = value;
if (!alwaysNeedsAddToScene) {
// The parent must construct a new engine layer to add this layer to, and
// so we mark it as needing [addToScene].
//
// This is designed to handle two situations:
//
// 1. When rendering the complete layer tree as normal. In this case we
// call child `addToScene` methods first, then we call `set engineLayer`
// for the parent. The children will call `markNeedsAddToScene` on the
// parent to signal that they produced new engine layers and therefore
// the parent needs to update. In this case, the parent is already adding
// itself to the scene via [addToScene], and so after it's done, its
// `set engineLayer` is called and it clears the `_needsAddToScene` flag.
//
// 2. When rendering an interior layer (e.g. `OffsetLayer.toImage`). In
// this case we call `addToScene` for one of the children but not the
// parent, i.e. we produce new engine layers for children but not for the
// parent. Here the children will mark the parent as needing
// `addToScene`, but the parent does not clear the flag until some future
// frame decides to render it, at which point the parent knows that it
// cannot retain its engine layer and will call `addToScene` again.
if (parent != null && !parent.alwaysNeedsAddToScene) {
parent.markNeedsAddToScene();
}
}
}
ui.EngineLayer _engineLayer;
/// Traverses the layer subtree starting from this layer and determines whether it needs [addToScene].
///
/// A layer needs [addToScene] if any of the following is true:
///
/// - [alwaysNeedsAddToScene] is true.
/// - [markNeedsAddToScene] has been called.
/// - Any of its descendants need [addToScene].
///
/// [ContainerLayer] overrides this method to recursively call it on its children.
@protected
@visibleForTesting
void updateSubtreeNeedsAddToScene() {
_needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
}
/// This layer's next sibling in the parent layer's child list.
Layer get nextSibling => _nextSibling;
Layer _nextSibling;
/// This layer's previous sibling in the parent layer's child list.
Layer get previousSibling => _previousSibling;
Layer _previousSibling;
@override
void dropChild(AbstractNode child) {
if (!alwaysNeedsAddToScene) {
markNeedsAddToScene();
}
super.dropChild(child);
}
@override
void adoptChild(AbstractNode child) {
if (!alwaysNeedsAddToScene) {
markNeedsAddToScene();
}
super.adoptChild(child);
}
/// Removes this layer from its parent layer's child list.
///
/// This has no effect if the layer's parent is already null.
@mustCallSuper
void remove() {
parent?._removeChild(this);
}
/// Search this layer and its subtree for annotations of type `S` at the
/// location described by `localPosition`.
///
/// This method is called by the default implementation of [find] and
/// [findAllAnnotations]. Override this method to customize how the layer
/// should search for annotations, or if the layer has its own annotations to
/// add.
///
/// The default implementation simply returns `false`, which means neither
/// the layer nor its children has annotations, and the annotation search
/// is not absorbed either (see below for explanation).
///
/// ## About layer annotations
///
/// {@template flutter.rendering.layer.findAnnotations.aboutAnnotations}
/// An annotation is an optional object of any type that can be carried with a
/// layer. An annotation can be found at a location as long as the owner layer
/// contains the location and is walked to.
///
/// The annotations are searched by first visiting each child recursively,
/// then this layer, resulting in an order from visually front to back.
/// Annotations must meet the given restrictions, such as type and position.
///
/// The common way for a value to be found here is by pushing an
/// [AnnotatedRegionLayer] into the layer tree, or by adding the desired
/// annotation by overriding [findAnnotations].
/// {@endtemplate}
///
/// ## Parameters and return value
///
/// The [result] parameter is where the method outputs the resulting
/// annotations. New annotations found during the walk are added to the tail.
///
/// The [onlyFirst] parameter indicates that, if true, the search will stop
/// when it finds the first qualified annotation; otherwise, it will walk the
/// entire subtree.
///
/// The return value indicates the opacity of this layer and its subtree at
/// this position. If it returns true, then this layer's parent should skip
/// the children behind this layer. In other words, it is opaque to this type
/// of annotation and has absorbed the search so that its siblings behind it
/// are not aware of the search. If the return value is false, then the parent
/// might continue with other siblings.
///
/// The return value does not affect whether the parent adds its own
/// annotations; in other words, if a layer is supposed to add an annotation,
/// it will always add it even if its children are opaque to this type of
/// annotation. However, the opacity that the parents return might be affected
/// by their children, hence making all of its ancestors opaque to this type
/// of annotation.
@protected
bool findAnnotations<S>(
AnnotationResult<S> result,
Offset localPosition, {
@required bool onlyFirst,
}) {
return false;
}
/// Search this layer and its subtree for the first annotation of type `S`
/// under the point described by `localPosition`.
///
/// Returns null if no matching annotations are found.
///
/// By default this method simply calls [findAnnotations] with `onlyFirst:
/// true` and returns the annotation of the first result. Prefer overriding
/// [findAnnotations] instead of this method, because during an annotation
/// search, only [findAnnotations] is recursively called, while custom
/// behavior in this method is ignored.
///
/// ## About layer annotations
///
/// {@macro flutter.rendering.layer.findAnnotations.aboutAnnotations}
///
/// See also:
///
/// * [findAllAnnotations], which is similar but returns all annotations found
/// at the given position.
/// * [AnnotatedRegionLayer], for placing values in the layer tree.
S find<S>(Offset localPosition) {
final AnnotationResult<S> result = AnnotationResult<S>();
findAnnotations<S>(result, localPosition, onlyFirst: true);
return result.entries.isEmpty ? null : result.entries.first.annotation;
}
/// Search this layer and its subtree for all annotations of type `S` under
/// the point described by `localPosition`.
///
/// Returns a result with empty entries if no matching annotations are found.
///
/// By default this method simply calls [findAnnotations] with `onlyFirst:
/// false` and returns the annotations of its result. Prefer overriding
/// [findAnnotations] instead of this method, because during an annotation
/// search, only [findAnnotations] is recursively called, while custom
/// behavior in this method is ignored.
///
/// ## About layer annotations
///
/// {@macro flutter.rendering.layer.findAnnotations.aboutAnnotations}
///
/// See also:
///
/// * [find], which is similar but returns the first annotation found at the
/// given position.
/// * [findAllAnnotations], which is similar but returns an
/// [AnnotationResult], which contains more information, such as the local
/// position of the event related to each annotation, and is equally fast,
/// hence is preferred over [findAll].
/// * [AnnotatedRegionLayer], for placing values in the layer tree.
@Deprecated(
'Use findAllAnnotations(...).annotations instead. '
'This feature was deprecated after v1.10.14.'
)
Iterable<S> findAll<S>(Offset localPosition) {
final AnnotationResult<S> result = findAllAnnotations(localPosition);
return result.entries.map((AnnotationEntry<S> entry) => entry.annotation);
}
/// Search this layer and its subtree for all annotations of type `S` under
/// the point described by `localPosition`.
///
/// Returns a result with empty entries if no matching annotations are found.
///
/// By default this method simply calls [findAnnotations] with `onlyFirst:
/// false` and returns the annotations of its result. Prefer overriding
/// [findAnnotations] instead of this method, because during an annotation
/// search, only [findAnnotations] is recursively called, while custom
/// behavior in this method is ignored.
///
/// ## About layer annotations
///
/// {@macro flutter.rendering.layer.findAnnotations.aboutAnnotations}
///
/// See also:
///
/// * [find], which is similar but returns the first annotation found at the
/// given position.
/// * [AnnotatedRegionLayer], for placing values in the layer tree.
AnnotationResult<S> findAllAnnotations<S>(Offset localPosition) {
final AnnotationResult<S> result = AnnotationResult<S>();
findAnnotations<S>(result, localPosition, onlyFirst: false);
return result;
}
/// Override this method to upload this layer to the engine.
///
/// Return the engine layer for retained rendering. When there's no
/// corresponding engine layer, null is returned.
@protected
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);
void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
// There can't be a loop by adding a retained layer subtree whose
// _needsAddToScene is false.
//
// Proof by contradiction:
//
// If we introduce a loop, this retained layer must be appended to one of
// its descendant layers, say A. That means the child structure of A has
// changed so A's _needsAddToScene is true. This contradicts
// _needsAddToScene being false.
if (!_needsAddToScene && _engineLayer != null) {
builder.addRetained(_engineLayer);
return;
}
addToScene(builder);
// Clearing the flag _after_ calling `addToScene`, not _before_. This is
// because `addToScene` calls children's `addToScene` methods, which may
// mark this layer as dirty.
_needsAddToScene = false;
}
/// The object responsible for creating this layer.
///
/// Defaults to the value of [RenderObject.debugCreator] for the render object
/// that created this layer. Used in debug messages.
dynamic debugCreator;
@override
String toStringShort() => '${super.toStringShort()}${ owner == null ? " DETACHED" : ""}';
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Object>('owner', owner, level: parent != null ? DiagnosticLevel.hidden : DiagnosticLevel.info, defaultValue: null));
properties.add(DiagnosticsProperty<dynamic>('creator', debugCreator, defaultValue: null, level: DiagnosticLevel.debug));
}
}
/// A composited layer containing a [Picture].
///
/// Picture layers are always leaves in the layer tree.
class PictureLayer extends Layer {
/// Creates a leaf layer for the layer tree.
PictureLayer(this.canvasBounds);
/// The bounds that were used for the canvas that drew this layer's [picture].
///
/// This is purely advisory. It is included in the information dumped with
/// [debugDumpLayerTree] (which can be triggered by pressing "L" when using
/// "flutter run" at the console), which can help debug why certain drawing
/// commands are being culled.
final Rect canvasBounds;
/// The picture recorded for this layer.
///
/// The picture's coordinate system matches this layer's coordinate system.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
ui.Picture get picture => _picture;
ui.Picture _picture;
set picture(ui.Picture picture) {
markNeedsAddToScene();
_picture = picture;
}
/// Hints that the painting in this layer is complex and would benefit from
/// caching.
///
/// If this hint is not set, the compositor will apply its own heuristics to
/// decide whether the this layer is complex enough to benefit from caching.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
bool get isComplexHint => _isComplexHint;
bool _isComplexHint = false;
set isComplexHint(bool value) {
if (value != _isComplexHint) {
_isComplexHint = value;
markNeedsAddToScene();
}
}
/// Hints that the painting in this layer is likely to change next frame.
///
/// This hint tells the compositor not to cache this layer because the cache
/// will not be used in the future. If this hint is not set, the compositor
/// will apply its own heuristics to decide whether this layer is likely to be
/// reused in the future.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
bool get willChangeHint => _willChangeHint;
bool _willChangeHint = false;
set willChangeHint(bool value) {
if (value != _willChangeHint) {
_willChangeHint = value;
markNeedsAddToScene();
}
}
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Rect>('paint bounds', canvasBounds));
}
@override
@protected
bool findAnnotations<S>(AnnotationResult<S> result, Offset localPosition, { @required bool onlyFirst }) {
return false;
}
}
/// A composited layer that maps a backend texture to a rectangle.
///
/// Backend textures are images that can be applied (mapped) to an area of the
/// Flutter view. They are created, managed, and updated using a
/// platform-specific texture registry. This is typically done by a plugin
/// that integrates with host platform video player, camera, or OpenGL APIs,
/// or similar image sources.
///
/// A texture layer refers to its backend texture using an integer ID. Texture
/// IDs are obtained from the texture registry and are scoped to the Flutter
/// view. Texture IDs may be reused after deregistration, at the discretion
/// of the registry. The use of texture IDs currently unknown to the registry
/// will silently result in a blank rectangle.
///
/// Once inserted into the layer tree, texture layers are repainted autonomously
/// as dictated by the backend (e.g. on arrival of a video frame). Such
/// repainting generally does not involve executing Dart code.
///
/// Texture layers are always leaves in the layer tree.
///
/// See also:
///
/// * <https://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.html>
/// for how to create and manage backend textures on Android.
/// * <https://api.flutter.dev/objcdoc/Protocols/FlutterTextureRegistry.html>
/// for how to create and manage backend textures on iOS.
class TextureLayer extends Layer {
/// Creates a texture layer bounded by [rect] and with backend texture
/// identified by [textureId], if [freeze] is true new texture frames will not be
/// populated to the texture.
TextureLayer({
@required this.rect,
@required this.textureId,
this.freeze = false,
}) : assert(rect != null),
assert(textureId != null);
/// Bounding rectangle of this layer.
final Rect rect;
/// The identity of the backend texture.
final int textureId;
/// When true the texture that will not be updated with new frames.
///
/// This is used when resizing an embedded Android views: When resizing there
/// is a short period during which the framework cannot tell if the newest
/// texture frame has the previous or new size, to workaround this the
/// framework "freezes" the texture just before resizing the Android view and
/// un-freezes it when it is certain that a frame with the new size is ready.
final bool freeze;
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
final Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset);
builder.addTexture(
textureId,
offset: shiftedRect.topLeft,
width: shiftedRect.width,
height: shiftedRect.height,
freeze: freeze,
);
}
@override
@protected
bool findAnnotations<S>(AnnotationResult<S> result, Offset localPosition, { @required bool onlyFirst }) {
return false;
}
}
/// A layer that shows an embedded [UIView](https://developer.apple.com/documentation/uikit/uiview)
/// on iOS.
class PlatformViewLayer extends Layer {
/// Creates a platform view layer.
///
/// The `rect` and `viewId` parameters must not be null.
PlatformViewLayer({
@required this.rect,
@required this.viewId,
this.hoverAnnotation,
}) : assert(rect != null),
assert(viewId != null);
/// Bounding rectangle of this layer in the global coordinate space.
final Rect rect;
/// The unique identifier of the UIView displayed on this layer.
///
/// A UIView with this identifier must have been created by [PlatformViewsServices.initUiKitView].
final int viewId;
/// [MouseTrackerAnnotation] that handles mouse events for this layer.
///
/// If [hoverAnnotation] is non-null, [PlatformViewLayer] will annotate the
/// region of this platform view such that annotation callbacks will receive
/// mouse events, including mouse enter, exit, and hover, but not including
/// mouse down, move, and up. The layer will be treated as opaque during an
/// annotation search, which will prevent layers behind it from receiving
/// these events.
///
/// By default, [hoverAnnotation] is null, and [PlatformViewLayer] will not
/// receive mouse events, and will therefore appear translucent during the
/// annotation search.
///
/// See also:
///
/// * [MouseRegion], which explains more about the mouse events and opacity
/// during annotation search.
final MouseTrackerAnnotation hoverAnnotation;
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
final Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset);
builder.addPlatformView(
viewId,
offset: shiftedRect.topLeft,
width: shiftedRect.width,
height: shiftedRect.height,
);
}
@override
@protected
bool findAnnotations<S>(AnnotationResult<S> result, Offset localPosition, { @required bool onlyFirst }) {
if (hoverAnnotation == null || !rect.contains(localPosition)) {
return false;
}
if (S == MouseTrackerAnnotation) {
final Object untypedValue = hoverAnnotation;
final S typedValue = untypedValue as S;
result.add(AnnotationEntry<S>(
annotation: typedValue,
localPosition: localPosition,
));
return true;
}
return false;
}
}
/// A layer that indicates to the compositor that it should display
/// certain performance statistics within it.
///
/// Performance overlay layers are always leaves in the layer tree.
class PerformanceOverlayLayer extends Layer {
/// Creates a layer that displays a performance overlay.
PerformanceOverlayLayer({
@required Rect overlayRect,
@required this.optionsMask,
@required this.rasterizerThreshold,
@required this.checkerboardRasterCacheImages,
@required this.checkerboardOffscreenLayers,
}) : _overlayRect = overlayRect;
/// The rectangle in this layer's coordinate system that the overlay should occupy.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
Rect get overlayRect => _overlayRect;
Rect _overlayRect;
set overlayRect(Rect value) {
if (value != _overlayRect) {
_overlayRect = value;
markNeedsAddToScene();
}
}
/// The mask is created by shifting 1 by the index of the specific
/// [PerformanceOverlayOption] to enable.
final int optionsMask;
/// The rasterizer threshold is an integer specifying the number of frame
/// intervals that the rasterizer must miss before it decides that the frame
/// is suitable for capturing an SkPicture trace for further analysis.
final int rasterizerThreshold;
/// Whether the raster cache should checkerboard cached entries.
///
/// The compositor can sometimes decide to cache certain portions of the
/// widget hierarchy. Such portions typically don't change often from frame to
/// frame and are expensive to render. This can speed up overall rendering. However,
/// there is certain upfront cost to constructing these cache entries. And, if
/// the cache entries are not used very often, this cost may not be worth the
/// speedup in rendering of subsequent frames. If the developer wants to be certain
/// that populating the raster cache is not causing stutters, this option can be
/// set. Depending on the observations made, hints can be provided to the compositor
/// that aid it in making better decisions about caching.
final bool checkerboardRasterCacheImages;
/// Whether the compositor should checkerboard layers that are rendered to offscreen
/// bitmaps. This can be useful for debugging rendering performance.
///
/// Render target switches are caused by using opacity layers (via a [FadeTransition] or
/// [Opacity] widget), clips, shader mask layers, etc. Selecting a new render target
/// and merging it with the rest of the scene has a performance cost. This can sometimes
/// be avoided by using equivalent widgets that do not require these layers (for example,
/// replacing an [Opacity] widget with an [widgets.Image] using a [BlendMode]).
final bool checkerboardOffscreenLayers;
@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
assert(optionsMask != null);
final Rect shiftedOverlayRect = layerOffset == Offset.zero ? overlayRect : overlayRect.shift(layerOffset);
builder.addPerformanceOverlay(optionsMask, shiftedOverlayRect);
builder.setRasterizerTracingThreshold(rasterizerThreshold);
builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers);
}
@override
@protected
bool findAnnotations<S>(AnnotationResult<S> result, Offset localPosition, { @required bool onlyFirst }) {
return false;
}
}
/// A composited layer that has a list of children.
///
/// A [ContainerLayer] instance merely takes a list of children and inserts them
/// into the composited rendering in order. There are subclasses of
/// [ContainerLayer] which apply more elaborate effects in the process.
class ContainerLayer extends Layer {
/// The first composited layer in this layer's child list.
Layer get firstChild => _firstChild;
Layer _firstChild;
/// The last composited layer in this layer's child list.
Layer get lastChild => _lastChild;
Layer _lastChild;
/// Returns whether this layer has at least one child layer.
bool get hasChildren => _firstChild != null;
/// Consider this layer as the root and build a scene (a tree of layers)
/// in the engine.
// The reason this method is in the `ContainerLayer` class rather than
// `PipelineOwner` or other singleton level is because this method can be used
// both to render the whole layer tree (e.g. a normal application frame) and
// to render a subtree (e.g. `OffsetLayer.toImage`).
ui.Scene buildScene(ui.SceneBuilder builder) {
List<PictureLayer> temporaryLayers;
assert(() {
if (debugCheckElevationsEnabled) {
temporaryLayers = _debugCheckElevations();
}
return true;
}());
updateSubtreeNeedsAddToScene();
addToScene(builder);
// Clearing the flag _after_ calling `addToScene`, not _before_. This is
// because `addToScene` calls children's `addToScene` methods, which may
// mark this layer as dirty.
_needsAddToScene = false;
final ui.Scene scene = builder.build();
assert(() {
// We should remove any layers that got added to highlight the incorrect
// PhysicalModelLayers. If we don't, we'll end up adding duplicate layers
// or continuing to render stale outlines.
if (temporaryLayers != null) {
for (final PictureLayer temporaryLayer in temporaryLayers) {
temporaryLayer.remove();
}
}
return true;
}());
return scene;
}
bool _debugUltimatePreviousSiblingOf(Layer child, { Layer equals }) {
assert(child.attached == attached);
while (child.previousSibling != null) {
assert(child.previousSibling != child);
child = child.previousSibling;
assert(child.attached == attached);
}
return child == equals;
}
bool _debugUltimateNextSiblingOf(Layer child, { Layer equals }) {
assert(child.attached == attached);
while (child._nextSibling != null) {
assert(child._nextSibling != child);
child = child._nextSibling;
assert(child.attached == attached);
}
return child == equals;
}
PictureLayer _highlightConflictingLayer(PhysicalModelLayer child) {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.drawPath(
child.clipPath,
Paint()
..color = const Color(0xFFAA0000)
..style = PaintingStyle.stroke
// The elevation may be 0 or otherwise too small to notice.
// Adding 10 to it makes it more visually obvious.
..strokeWidth = child.elevation + 10.0,
);
final PictureLayer pictureLayer = PictureLayer(child.clipPath.getBounds())
..picture = recorder.endRecording()
..debugCreator = child;
child.append(pictureLayer);
return pictureLayer;
}
List<PictureLayer> _processConflictingPhysicalLayers(PhysicalModelLayer predecessor, PhysicalModelLayer child) {
FlutterError.reportError(FlutterErrorDetails(
exception: FlutterError('Painting order is out of order with respect to elevation.\n'
'See https://api.flutter.dev/flutter/rendering/debugCheckElevationsEnabled.html '
'for more details.'),
library: 'rendering library',
context: ErrorDescription('during compositing'),
informationCollector: () {
return <DiagnosticsNode>[
child.toDiagnosticsNode(name: 'Attempted to composite layer', style: DiagnosticsTreeStyle.errorProperty),
predecessor.toDiagnosticsNode(name: 'after layer', style: DiagnosticsTreeStyle.errorProperty),
ErrorDescription('which occupies the same area at a higher elevation.'),
];
},
));
return <PictureLayer>[
_highlightConflictingLayer(predecessor),
_highlightConflictingLayer(child),
];
}
/// Checks that no [PhysicalModelLayer] would paint after another overlapping
/// [PhysicalModelLayer] that has a higher elevation.
///
/// Returns a list of [PictureLayer] objects it added to the tree to highlight
/// bad nodes. These layers should be removed from the tree after building the
/// [Scene].
List<PictureLayer> _debugCheckElevations() {
final List<PhysicalModelLayer> physicalModelLayers = depthFirstIterateChildren().whereType<PhysicalModelLayer>().toList();
final List<PictureLayer> addedLayers = <PictureLayer>[];
for (int i = 0; i < physicalModelLayers.length; i++) {
final PhysicalModelLayer physicalModelLayer = physicalModelLayers[i];
assert(
physicalModelLayer.lastChild?.debugCreator != physicalModelLayer,
'debugCheckElevations has either already visited this layer or failed '
'to remove the added picture from it.',
);
double accumulatedElevation = physicalModelLayer.elevation;
Layer ancestor = physicalModelLayer.parent;
while (ancestor != null) {
if (ancestor is PhysicalModelLayer) {
accumulatedElevation += ancestor.elevation;
}
ancestor = ancestor.parent;
}
for (int j = 0; j <= i; j++) {
final PhysicalModelLayer predecessor = physicalModelLayers[j];
double predecessorAccumulatedElevation = predecessor.elevation;
ancestor = predecessor.parent;
while (ancestor != null) {
if (ancestor == predecessor) {
continue;
}
if (ancestor is PhysicalModelLayer) {
predecessorAccumulatedElevation += ancestor.elevation;
}
ancestor = ancestor.parent;
}
if (predecessorAccumulatedElevation <= accumulatedElevation) {
continue;
}
final Path intersection = Path.combine(
PathOperation.intersect,
predecessor._debugTransformedClipPath,
physicalModelLayer._debugTransformedClipPath,
);
if (intersection != null && intersection.computeMetrics().any((ui.PathMetric metric) => metric.length > 0)) {
addedLayers.addAll(_processConflictingPhysicalLayers(predecessor, physicalModelLayer));
}
}
}
return addedLayers;
}
@override
void updateSubtreeNeedsAddToScene() {
super.updateSubtreeNeedsAddToScene();
Layer child = firstChild;
while (child != null) {
child.updateSubtreeNeedsAddToScene();
_needsAddToScene = _needsAddToScene || child._needsAddToScene;
child = child.nextSibling;
}
}
@override
@protected
bool findAnnotations<S>(AnnotationResult<S> result, Offset localPosition, { @required bool onlyFirst }) {
for (Layer child = lastChild; child != null; child = child.previousSibling) {
final bool isAbsorbed = child.findAnnotations<S>(result, localPosition, onlyFirst: onlyFirst);
if (isAbsorbed)
return true;
if (onlyFirst && result.entries.isNotEmpty)
return isAbsorbed;
}
return false;
}
@override
void attach(Object owner) {
super.attach(owner);
Layer child = firstChild;
while (child != null) {
child.attach(owner);
child = child.nextSibling;
}
}
@override
void detach() {
super.detach();
Layer child = firstChild;
while (child != null) {
child.detach();
child = child.nextSibling;
}
}
/// Adds the given layer to the end of this layer's child list.
void append(Layer child) {
assert(child != this);
assert(child != firstChild);
assert(child != lastChild);
assert(child.parent == null);
assert(!child.attached);
assert(child.nextSibling == null);
assert(child.previousSibling == null);
assert(() {
Layer node = this;
while (node.parent != null)
node = node.parent;
assert(node != child); // indicates we are about to create a cycle
return true;
}());
adoptChild(child);
child._previousSibling = lastChild;
if (lastChild != null)
lastChild._nextSibling = child;
_lastChild = child;
_firstChild ??= child;
assert(child.attached == attached);
}
// Implementation of [Layer.remove].
void _removeChild(Layer child) {