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
5import 'dart:collection';
6import 'dart:math' as math;
7
8import 'package:flutter/foundation.dart';
9import 'package:flutter/rendering.dart';
10import 'package:flutter/scheduler.dart';
11
12import 'basic.dart';
13import 'framework.dart';
14import 'lookup_boundary.dart';
15import 'ticker_provider.dart';
16
17const String _flutterWidgetsLibrary = 'package:flutter/widgets.dart';
18
19// Examples can assume:
20// late BuildContext context;
21
22// * OverlayEntry Implementation
23
24/// A place in an [Overlay] that can contain a widget.
25///
26/// Overlay entries are inserted into an [Overlay] using the
27/// [OverlayState.insert] or [OverlayState.insertAll] functions. To find the
28/// closest enclosing overlay for a given [BuildContext], use the [Overlay.of]
29/// function.
30///
31/// An overlay entry can be in at most one overlay at a time. To remove an entry
32/// from its overlay, call the [remove] function on the overlay entry.
33///
34/// Because an [Overlay] uses a [Stack] layout, overlay entries can use
35/// [Positioned] and [AnimatedPositioned] to position themselves within the
36/// overlay.
37///
38/// For example, [Draggable] uses an [OverlayEntry] to show the drag avatar that
39/// follows the user's finger across the screen after the drag begins. Using the
40/// overlay to display the drag avatar lets the avatar float over the other
41/// widgets in the app. As the user's finger moves, draggable calls
42/// [markNeedsBuild] on the overlay entry to cause it to rebuild. In its build,
43/// the entry includes a [Positioned] with its top and left property set to
44/// position the drag avatar near the user's finger. When the drag is over,
45/// [Draggable] removes the entry from the overlay to remove the drag avatar
46/// from view.
47///
48/// By default, if there is an entirely [opaque] entry over this one, then this
49/// one will not be included in the widget tree (in particular, stateful widgets
50/// within the overlay entry will not be instantiated). To ensure that your
51/// overlay entry is still built even if it is not visible, set [maintainState]
52/// to true. This is more expensive, so should be done with care. In particular,
53/// if widgets in an overlay entry with [maintainState] set to true repeatedly
54/// call [State.setState], the user's battery will be drained unnecessarily.
55///
56/// [OverlayEntry] is a [Listenable] that notifies when the widget built by
57/// [builder] is mounted or unmounted, whose exact state can be queried by
58/// [mounted]. After the owner of the [OverlayEntry] calls [remove] and then
59/// [dispose], the widget may not be immediately removed from the widget tree.
60/// As a result listeners of the [OverlayEntry] can get notified for one last
61/// time after the [dispose] call, when the widget is eventually unmounted.
62///
63/// {@macro flutter.widgets.overlayPortalVsOverlayEntry}
64///
65/// See also:
66///
67/// * [OverlayPortal], an alternative API for inserting widgets into an
68/// [Overlay] using a builder callback.
69/// * [Overlay], a stack of entries that can be managed independently.
70/// * [OverlayState], the current state of an Overlay.
71/// * [WidgetsApp], a convenience widget that wraps a number of widgets that
72/// are commonly required for an application.
73/// * [MaterialApp], a convenience widget that wraps a number of widgets that
74/// are commonly required for Material Design applications.
75class OverlayEntry implements Listenable {
76 /// Creates an overlay entry.
77 ///
78 /// To insert the entry into an [Overlay], first find the overlay using
79 /// [Overlay.of] and then call [OverlayState.insert]. To remove the entry,
80 /// call [remove] on the overlay entry itself.
81 OverlayEntry({
82 required this.builder,
83 bool opaque = false,
84 bool maintainState = false,
85 this.canSizeOverlay = false,
86 }) : _opaque = opaque,
87 _maintainState = maintainState {
88 if (kFlutterMemoryAllocationsEnabled) {
89 _maybeDispatchObjectCreation();
90 }
91 }
92
93 /// This entry will include the widget built by this builder in the overlay at
94 /// the entry's position.
95 ///
96 /// To cause this builder to be called again, call [markNeedsBuild] on this
97 /// overlay entry.
98 final WidgetBuilder builder;
99
100 /// Whether this entry occludes the entire overlay.
101 ///
102 /// If an entry claims to be opaque, then, for efficiency, the overlay will
103 /// skip building entries below that entry unless they have [maintainState]
104 /// set.
105 bool get opaque => _opaque;
106 bool _opaque;
107 set opaque(bool value) {
108 assert(!_disposedByOwner);
109 if (_opaque == value) {
110 return;
111 }
112 _opaque = value;
113 _overlay?._didChangeEntryOpacity();
114 }
115
116 /// Whether this entry must be included in the tree even if there is a fully
117 /// [opaque] entry above it.
118 ///
119 /// By default, if there is an entirely [opaque] entry over this one, then this
120 /// one will not be included in the widget tree (in particular, stateful widgets
121 /// within the overlay entry will not be instantiated). To ensure that your
122 /// overlay entry is still built even if it is not visible, set [maintainState]
123 /// to true. This is more expensive, so should be done with care. In particular,
124 /// if widgets in an overlay entry with [maintainState] set to true repeatedly
125 /// call [State.setState], the user's battery will be drained unnecessarily.
126 ///
127 /// This is used by the [Navigator] and [Route] objects to ensure that routes
128 /// are kept around even when in the background, so that [Future]s promised
129 /// from subsequent routes will be handled properly when they complete.
130 bool get maintainState => _maintainState;
131 bool _maintainState;
132 set maintainState(bool value) {
133 assert(!_disposedByOwner);
134 if (_maintainState == value) {
135 return;
136 }
137 _maintainState = value;
138 assert(_overlay != null);
139 _overlay!._didChangeEntryOpacity();
140 }
141
142 /// Whether the content of this [OverlayEntry] can be used to size the
143 /// [Overlay].
144 ///
145 /// In most situations the overlay sizes itself based on its incoming
146 /// constraints to be as large as possible. However, if that would result in
147 /// an infinite size, it has to rely on one of its children to size itself. In
148 /// this situation, the overlay will consult the topmost non-[Positioned]
149 /// overlay entry that has this property set to true, lay it out with the
150 /// incoming [BoxConstraints] of the overlay, and force all other
151 /// non-[Positioned] overlay entries to have the same size. The [Positioned]
152 /// entries are laid out as usual based on the calculated size of the overlay.
153 ///
154 /// Overlay entries that set this to true must be able to handle unconstrained
155 /// [BoxConstraints].
156 ///
157 /// Setting this to true has no effect if the overlay entry uses a [Positioned]
158 /// widget to position itself in the overlay.
159 final bool canSizeOverlay;
160
161 /// Whether the [OverlayEntry] is currently mounted in the widget tree.
162 ///
163 /// The [OverlayEntry] notifies its listeners when this value changes.
164 bool get mounted => _overlayEntryStateNotifier?.value != null;
165
166 /// The currently mounted `_OverlayEntryWidgetState` built using this [OverlayEntry].
167 ValueNotifier<_OverlayEntryWidgetState?>? _overlayEntryStateNotifier = ValueNotifier<_OverlayEntryWidgetState?>(null);
168
169 // TODO(polina-c): stop duplicating code across disposables
170 // https://github.com/flutter/flutter/issues/137435
171 /// Dispatches event of object creation to [FlutterMemoryAllocations.instance].
172 void _maybeDispatchObjectCreation() {
173 if (kFlutterMemoryAllocationsEnabled) {
174 FlutterMemoryAllocations.instance.dispatchObjectCreated(
175 library: _flutterWidgetsLibrary,
176 className: '$OverlayEntry',
177 object: this,
178 );
179 }
180 }
181
182 @override
183 void addListener(VoidCallback listener) {
184 assert(!_disposedByOwner);
185 _overlayEntryStateNotifier?.addListener(listener);
186 }
187
188 @override
189 void removeListener(VoidCallback listener) {
190 _overlayEntryStateNotifier?.removeListener(listener);
191 }
192
193 OverlayState? _overlay;
194 final GlobalKey<_OverlayEntryWidgetState> _key = GlobalKey<_OverlayEntryWidgetState>();
195
196 /// Remove this entry from the overlay.
197 ///
198 /// This should only be called once.
199 ///
200 /// This method removes this overlay entry from the overlay immediately. The
201 /// UI will be updated in the same frame if this method is called before the
202 /// overlay rebuild in this frame; otherwise, the UI will be updated in the
203 /// next frame. This means that it is safe to call during builds, but also
204 /// that if you do call this after the overlay rebuild, the UI will not update
205 /// until the next frame (i.e. many milliseconds later).
206 void remove() {
207 assert(_overlay != null);
208 assert(!_disposedByOwner);
209 final OverlayState overlay = _overlay!;
210 _overlay = null;
211 if (!overlay.mounted) {
212 return;
213 }
214
215 overlay._entries.remove(this);
216 if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
217 SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
218 overlay._markDirty();
219 }, debugLabel: 'OverlayEntry.markDirty');
220 } else {
221 overlay._markDirty();
222 }
223 }
224
225 /// Cause this entry to rebuild during the next pipeline flush.
226 ///
227 /// You need to call this function if the output of [builder] has changed.
228 void markNeedsBuild() {
229 assert(!_disposedByOwner);
230 _key.currentState?._markNeedsBuild();
231 }
232
233 void _didUnmount() {
234 assert(!mounted);
235 if (_disposedByOwner) {
236 _overlayEntryStateNotifier?.dispose();
237 _overlayEntryStateNotifier = null;
238 }
239 }
240
241 bool _disposedByOwner = false;
242
243 /// Discards any resources used by this [OverlayEntry].
244 ///
245 /// This method must be called after [remove] if the [OverlayEntry] is
246 /// inserted into an [Overlay].
247 ///
248 /// After this is called, the object is not in a usable state and should be
249 /// discarded (calls to [addListener] will throw after the object is disposed).
250 /// However, the listeners registered may not be immediately released until
251 /// the widget built using this [OverlayEntry] is unmounted from the widget
252 /// tree.
253 ///
254 /// This method should only be called by the object's owner.
255 void dispose() {
256 assert(!_disposedByOwner);
257 assert(_overlay == null, 'An OverlayEntry must first be removed from the Overlay before dispose is called.');
258 if (kFlutterMemoryAllocationsEnabled) {
259 FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
260 }
261 _disposedByOwner = true;
262 if (!mounted) {
263 // If we're still mounted when disposed, then this will be disposed in
264 // _didUnmount, to allow notifications to occur until the entry is
265 // unmounted.
266 _overlayEntryStateNotifier?.dispose();
267 _overlayEntryStateNotifier = null;
268 }
269 }
270
271 @override
272 String toString() => '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)${_disposedByOwner ? "(DISPOSED)" : ""}';
273}
274
275class _OverlayEntryWidget extends StatefulWidget {
276 const _OverlayEntryWidget({
277 required Key key,
278 required this.entry,
279 required this.overlayState,
280 this.tickerEnabled = true,
281 }) : super(key: key);
282
283 final OverlayEntry entry;
284 final OverlayState overlayState;
285 final bool tickerEnabled;
286
287 @override
288 _OverlayEntryWidgetState createState() => _OverlayEntryWidgetState();
289}
290
291class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
292 late _RenderTheater _theater;
293
294 // Manages the stack of theater children whose paint order are sorted by their
295 // _zOrderIndex. The children added by OverlayPortal are added to this linked
296 // list, and they will be shown _above_ the OverlayEntry tied to this widget.
297 // The children with larger zOrderIndex values (i.e. those called `show`
298 // recently) will be painted last.
299 //
300 // This linked list is lazily created in `_add`, and the entries are added/removed
301 // via `_add`/`_remove`, called by OverlayPortals lower in the tree. `_add` or
302 // `_remove` does not cause this widget to rebuild, the linked list will be
303 // read by _RenderTheater as part of its render child model. This would ideally
304 // be in a RenderObject but there may not be RenderObjects between
305 // _RenderTheater and the render subtree OverlayEntry builds.
306 LinkedList<_OverlayEntryLocation>? _sortedTheaterSiblings;
307
308 // Worst-case O(N), N being the number of children added to the top spot in
309 // the same frame. This can be a bit expensive when there's a lot of global
310 // key reparenting in the same frame but N is usually a small number.
311 void _add(_OverlayEntryLocation child) {
312 assert(mounted);
313 final LinkedList<_OverlayEntryLocation> children = _sortedTheaterSiblings ??= LinkedList<_OverlayEntryLocation>();
314 assert(!children.contains(child));
315 _OverlayEntryLocation? insertPosition = children.isEmpty ? null : children.last;
316 while (insertPosition != null && insertPosition._zOrderIndex > child._zOrderIndex) {
317 insertPosition = insertPosition.previous;
318 }
319 if (insertPosition == null) {
320 children.addFirst(child);
321 } else {
322 insertPosition.insertAfter(child);
323 }
324 assert(children.contains(child));
325 }
326
327 void _remove(_OverlayEntryLocation child) {
328 assert(_sortedTheaterSiblings != null);
329 final bool wasInCollection = _sortedTheaterSiblings?.remove(child) ?? false;
330 assert(wasInCollection);
331 }
332
333 // Returns an Iterable that traverse the children in the child model in paint
334 // order (from farthest to the user to the closest to the user).
335 //
336 // The iterator should be safe to use even when the child model is being
337 // mutated. The reason for that is it's allowed to add/remove/move deferred
338 // children to a _RenderTheater during performLayout, but the affected
339 // children don't have to be laid out in the same performLayout call.
340 late final Iterable<RenderBox> _paintOrderIterable = _createChildIterable(reversed: false);
341 // An Iterable that traverse the children in the child model in
342 // hit-test order (from closest to the user to the farthest to the user).
343 late final Iterable<RenderBox> _hitTestOrderIterable = _createChildIterable(reversed: true);
344
345 // The following uses sync* because hit-testing is lazy, and LinkedList as a
346 // Iterable doesn't support concurrent modification.
347 Iterable<RenderBox> _createChildIterable({ required bool reversed }) sync* {
348 final LinkedList<_OverlayEntryLocation>? children = _sortedTheaterSiblings;
349 if (children == null || children.isEmpty) {
350 return;
351 }
352 _OverlayEntryLocation? candidate = reversed ? children.last : children.first;
353 while (candidate != null) {
354 final RenderBox? renderBox = candidate._overlayChildRenderBox;
355 candidate = reversed ? candidate.previous : candidate.next;
356 if (renderBox != null) {
357 yield renderBox;
358 }
359 }
360 }
361
362 @override
363 void initState() {
364 super.initState();
365 widget.entry._overlayEntryStateNotifier!.value = this;
366 _theater = context.findAncestorRenderObjectOfType<_RenderTheater>()!;
367 assert(_sortedTheaterSiblings == null);
368 }
369
370 @override
371 void didUpdateWidget(_OverlayEntryWidget oldWidget) {
372 super.didUpdateWidget(oldWidget);
373 // OverlayState's build method always returns a RenderObjectWidget _Theater,
374 // so it's safe to assume that state equality implies render object equality.
375 assert(oldWidget.entry == widget.entry);
376 if (oldWidget.overlayState != widget.overlayState) {
377 final _RenderTheater newTheater = context.findAncestorRenderObjectOfType<_RenderTheater>()!;
378 assert(_theater != newTheater);
379 _theater = newTheater;
380 }
381 }
382
383 @override
384 void dispose() {
385 widget.entry._overlayEntryStateNotifier?.value = null;
386 widget.entry._didUnmount();
387 _sortedTheaterSiblings = null;
388 super.dispose();
389 }
390
391 @override
392 Widget build(BuildContext context) {
393 return TickerMode(
394 enabled: widget.tickerEnabled,
395 child: _RenderTheaterMarker(
396 theater: _theater,
397 overlayEntryWidgetState: this,
398 child: widget.entry.builder(context),
399 ),
400 );
401 }
402
403 void _markNeedsBuild() {
404 setState(() { /* the state that changed is in the builder */ });
405 }
406}
407
408/// A stack of entries that can be managed independently.
409///
410/// Overlays let independent child widgets "float" visual elements on top of
411/// other widgets by inserting them into the overlay's stack. The overlay lets
412/// each of these widgets manage their participation in the overlay using
413/// [OverlayEntry] objects.
414///
415/// Although you can create an [Overlay] directly, it's most common to use the
416/// overlay created by the [Navigator] in a [WidgetsApp], [CupertinoApp] or a
417/// [MaterialApp]. The navigator uses its overlay to manage the visual
418/// appearance of its routes.
419///
420/// The [Overlay] widget uses a custom stack implementation, which is very
421/// similar to the [Stack] widget. The main use case of [Overlay] is related to
422/// navigation and being able to insert widgets on top of the pages in an app.
423/// For layout purposes unrelated to navigation, consider using [Stack] instead.
424///
425/// An [Overlay] widget requires a [Directionality] widget to be in scope, so
426/// that it can resolve direction-sensitive coordinates of any
427/// [Positioned.directional] children.
428///
429/// For widgets drawn in an [OverlayEntry], do not assume that the size of the
430/// [Overlay] is the size returned by [MediaQuery.sizeOf]. Nested overlays can
431/// have different sizes.
432///
433/// {@tool dartpad}
434/// This example shows how to use the [Overlay] to highlight the [NavigationBar]
435/// destination.
436///
437/// ** See code in examples/api/lib/widgets/overlay/overlay.0.dart **
438/// {@end-tool}
439///
440/// See also:
441///
442/// * [OverlayEntry], the class that is used for describing the overlay entries.
443/// * [OverlayState], which is used to insert the entries into the overlay.
444/// * [WidgetsApp], which inserts an [Overlay] widget indirectly via its [Navigator].
445/// * [MaterialApp], which inserts an [Overlay] widget indirectly via its [Navigator].
446/// * [CupertinoApp], which inserts an [Overlay] widget indirectly via its [Navigator].
447/// * [Stack], which allows directly displaying a stack of widgets.
448class Overlay extends StatefulWidget {
449 /// Creates an overlay.
450 ///
451 /// The initial entries will be inserted into the overlay when its associated
452 /// [OverlayState] is initialized.
453 ///
454 /// Rather than creating an overlay, consider using the overlay that is
455 /// created by the [Navigator] in a [WidgetsApp], [CupertinoApp], or a
456 /// [MaterialApp] for the application.
457 const Overlay({
458 super.key,
459 this.initialEntries = const <OverlayEntry>[],
460 this.clipBehavior = Clip.hardEdge,
461 });
462
463 /// Wrap the provided `child` in an [Overlay] to allow other visual elements
464 /// (packed in [OverlayEntry]s) to float on top of the child.
465 ///
466 /// This is a convenience method over the regular [Overlay] constructor: It
467 /// creates an [Overlay] and puts the provided `child` in an [OverlayEntry]
468 /// at the bottom of that newly created Overlay.
469 static Widget wrap({
470 Key? key,
471 Clip clipBehavior = Clip.hardEdge,
472 required Widget child,
473 }) {
474 return _WrappingOverlay(key: key, clipBehavior: clipBehavior, child: child);
475 }
476
477 /// The entries to include in the overlay initially.
478 ///
479 /// These entries are only used when the [OverlayState] is initialized. If you
480 /// are providing a new [Overlay] description for an overlay that's already in
481 /// the tree, then the new entries are ignored.
482 ///
483 /// To add entries to an [Overlay] that is already in the tree, use
484 /// [Overlay.of] to obtain the [OverlayState] (or assign a [GlobalKey] to the
485 /// [Overlay] widget and obtain the [OverlayState] via
486 /// [GlobalKey.currentState]), and then use [OverlayState.insert] or
487 /// [OverlayState.insertAll].
488 ///
489 /// To remove an entry from an [Overlay], use [OverlayEntry.remove].
490 final List<OverlayEntry> initialEntries;
491
492 /// {@macro flutter.material.Material.clipBehavior}
493 ///
494 /// Defaults to [Clip.hardEdge].
495 final Clip clipBehavior;
496
497 /// The [OverlayState] from the closest instance of [Overlay] that encloses
498 /// the given context within the closest [LookupBoundary], and, in debug mode,
499 /// will throw if one is not found.
500 ///
501 /// In debug mode, if the `debugRequiredFor` argument is provided and an
502 /// overlay isn't found, then this function will throw an exception containing
503 /// the runtime type of the given widget in the error message. The exception
504 /// attempts to explain that the calling [Widget] (the one given by the
505 /// `debugRequiredFor` argument) needs an [Overlay] to be present to function.
506 /// If `debugRequiredFor` is not supplied, then the error message is more
507 /// generic.
508 ///
509 /// Typical usage is as follows:
510 ///
511 /// ```dart
512 /// OverlayState overlay = Overlay.of(context);
513 /// ```
514 ///
515 /// If `rootOverlay` is set to true, the state from the furthest instance of
516 /// this class is given instead. Useful for installing overlay entries above
517 /// all subsequent instances of [Overlay].
518 ///
519 /// This method can be expensive (it walks the element tree).
520 ///
521 /// See also:
522 ///
523 /// * [Overlay.maybeOf] for a similar function that returns null if an
524 /// [Overlay] is not found.
525 static OverlayState of(
526 BuildContext context, {
527 bool rootOverlay = false,
528 Widget? debugRequiredFor,
529 }) {
530 final OverlayState? result = maybeOf(context, rootOverlay: rootOverlay);
531 assert(() {
532 if (result == null) {
533 final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorStateOfType<OverlayState>(context);
534 final List<DiagnosticsNode> information = <DiagnosticsNode>[
535 ErrorSummary('No Overlay widget found${hiddenByBoundary ? ' within the closest LookupBoundary' : ''}.'),
536 if (hiddenByBoundary)
537 ErrorDescription(
538 'There is an ancestor Overlay widget, but it is hidden by a LookupBoundary.'
539 ),
540 ErrorDescription('${debugRequiredFor?.runtimeType ?? 'Some'} widgets require an Overlay widget ancestor for correct operation.'),
541 ErrorHint('The most common way to add an Overlay to an application is to include a MaterialApp, CupertinoApp or Navigator widget in the runApp() call.'),
542 if (debugRequiredFor != null) DiagnosticsProperty<Widget>('The specific widget that failed to find an overlay was', debugRequiredFor, style: DiagnosticsTreeStyle.errorProperty),
543 if (context.widget != debugRequiredFor)
544 context.describeElement('The context from which that widget was searching for an overlay was'),
545 ];
546
547 throw FlutterError.fromParts(information);
548 }
549 return true;
550 }());
551 return result!;
552 }
553
554 /// The [OverlayState] from the closest instance of [Overlay] that encloses
555 /// the given context within the closest [LookupBoundary], if any.
556 ///
557 /// Typical usage is as follows:
558 ///
559 /// ```dart
560 /// OverlayState? overlay = Overlay.maybeOf(context);
561 /// ```
562 ///
563 /// If `rootOverlay` is set to true, the state from the furthest instance of
564 /// this class is given instead. Useful for installing overlay entries above
565 /// all subsequent instances of [Overlay].
566 ///
567 /// This method can be expensive (it walks the element tree).
568 ///
569 /// See also:
570 ///
571 /// * [Overlay.of] for a similar function that returns a non-nullable result
572 /// and throws if an [Overlay] is not found.
573
574 static OverlayState? maybeOf(
575 BuildContext context, {
576 bool rootOverlay = false,
577 }) {
578 return rootOverlay
579 ? LookupBoundary.findRootAncestorStateOfType<OverlayState>(context)
580 : LookupBoundary.findAncestorStateOfType<OverlayState>(context);
581 }
582
583 @override
584 OverlayState createState() => OverlayState();
585}
586
587/// The current state of an [Overlay].
588///
589/// Used to insert [OverlayEntry]s into the overlay using the [insert] and
590/// [insertAll] functions.
591class OverlayState extends State<Overlay> with TickerProviderStateMixin {
592 final List<OverlayEntry> _entries = <OverlayEntry>[];
593
594 @override
595 void initState() {
596 super.initState();
597 insertAll(widget.initialEntries);
598 }
599
600 int _insertionIndex(OverlayEntry? below, OverlayEntry? above) {
601 assert(above == null || below == null);
602 if (below != null) {
603 return _entries.indexOf(below);
604 }
605 if (above != null) {
606 return _entries.indexOf(above) + 1;
607 }
608 return _entries.length;
609 }
610
611 bool _debugCanInsertEntry(OverlayEntry entry) {
612 final List<DiagnosticsNode> operandsInformation = <DiagnosticsNode>[
613 DiagnosticsProperty<OverlayEntry>('The OverlayEntry was', entry, style: DiagnosticsTreeStyle.errorProperty),
614 DiagnosticsProperty<OverlayState>(
615 'The Overlay the OverlayEntry was trying to insert to was', this, style: DiagnosticsTreeStyle.errorProperty,
616 ),
617 ];
618
619 if (!mounted) {
620 throw FlutterError.fromParts(<DiagnosticsNode>[
621 ErrorSummary('Attempted to insert an OverlayEntry to an already disposed Overlay.'),
622 ...operandsInformation,
623 ]);
624 }
625
626 final OverlayState? currentOverlay = entry._overlay;
627 final bool alreadyContainsEntry = _entries.contains(entry);
628
629 if (alreadyContainsEntry) {
630 final bool inconsistentOverlayState = !identical(currentOverlay, this);
631 throw FlutterError.fromParts(<DiagnosticsNode>[
632 ErrorSummary('The specified entry is already present in the target Overlay.'),
633 ...operandsInformation,
634 if (inconsistentOverlayState) ErrorHint('This could be an error in the Flutter framework.')
635 else ErrorHint(
636 'Consider calling remove on the OverlayEntry before inserting it to a different Overlay, '
637 'or switching to the OverlayPortal API to avoid manual OverlayEntry management.'
638 ),
639 if (inconsistentOverlayState) DiagnosticsProperty<OverlayState>(
640 "The OverlayEntry's current Overlay was", currentOverlay, style: DiagnosticsTreeStyle.errorProperty,
641 ),
642 ]);
643 }
644
645 if (currentOverlay == null) {
646 return true;
647 }
648
649 throw FlutterError.fromParts(<DiagnosticsNode>[
650 ErrorSummary('The specified entry is already present in a different Overlay.'),
651 ...operandsInformation,
652 DiagnosticsProperty<OverlayState>("The OverlayEntry's current Overlay was", currentOverlay, style: DiagnosticsTreeStyle.errorProperty,),
653 ErrorHint(
654 'Consider calling remove on the OverlayEntry before inserting it to a different Overlay, '
655 'or switching to the OverlayPortal API to avoid manual OverlayEntry management.'
656 )
657 ]);
658 }
659
660 /// Insert the given entry into the overlay.
661 ///
662 /// If `below` is non-null, the entry is inserted just below `below`.
663 /// If `above` is non-null, the entry is inserted just above `above`.
664 /// Otherwise, the entry is inserted on top.
665 ///
666 /// It is an error to specify both `above` and `below`.
667 void insert(OverlayEntry entry, { OverlayEntry? below, OverlayEntry? above }) {
668 assert(_debugVerifyInsertPosition(above, below));
669 assert(_debugCanInsertEntry(entry));
670 entry._overlay = this;
671 setState(() {
672 _entries.insert(_insertionIndex(below, above), entry);
673 });
674 }
675
676 /// Insert all the entries in the given iterable.
677 ///
678 /// If `below` is non-null, the entries are inserted just below `below`.
679 /// If `above` is non-null, the entries are inserted just above `above`.
680 /// Otherwise, the entries are inserted on top.
681 ///
682 /// It is an error to specify both `above` and `below`.
683 void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry? below, OverlayEntry? above }) {
684 assert(_debugVerifyInsertPosition(above, below));
685 assert(entries.every(_debugCanInsertEntry));
686 if (entries.isEmpty) {
687 return;
688 }
689 for (final OverlayEntry entry in entries) {
690 assert(entry._overlay == null);
691 entry._overlay = this;
692 }
693 setState(() {
694 _entries.insertAll(_insertionIndex(below, above), entries);
695 });
696 }
697
698 bool _debugVerifyInsertPosition(OverlayEntry? above, OverlayEntry? below, { Iterable<OverlayEntry>? newEntries }) {
699 assert(
700 above == null || below == null,
701 'Only one of `above` and `below` may be specified.',
702 );
703 assert(
704 above == null || (above._overlay == this && _entries.contains(above) && (newEntries?.contains(above) ?? true)),
705 'The provided entry used for `above` must be present in the Overlay${newEntries != null ? ' and in the `newEntriesList`' : ''}.',
706 );
707 assert(
708 below == null || (below._overlay == this && _entries.contains(below) && (newEntries?.contains(below) ?? true)),
709 'The provided entry used for `below` must be present in the Overlay${newEntries != null ? ' and in the `newEntriesList`' : ''}.',
710 );
711 return true;
712 }
713
714 /// Remove all the entries listed in the given iterable, then reinsert them
715 /// into the overlay in the given order.
716 ///
717 /// Entries mention in `newEntries` but absent from the overlay are inserted
718 /// as if with [insertAll].
719 ///
720 /// Entries not mentioned in `newEntries` but present in the overlay are
721 /// positioned as a group in the resulting list relative to the entries that
722 /// were moved, as specified by one of `below` or `above`, which, if
723 /// specified, must be one of the entries in `newEntries`:
724 ///
725 /// If `below` is non-null, the group is positioned just below `below`.
726 /// If `above` is non-null, the group is positioned just above `above`.
727 /// Otherwise, the group is left on top, with all the rearranged entries
728 /// below.
729 ///
730 /// It is an error to specify both `above` and `below`.
731 void rearrange(Iterable<OverlayEntry> newEntries, { OverlayEntry? below, OverlayEntry? above }) {
732 final List<OverlayEntry> newEntriesList = newEntries is List<OverlayEntry> ? newEntries : newEntries.toList(growable: false);
733 assert(_debugVerifyInsertPosition(above, below, newEntries: newEntriesList));
734 assert(
735 newEntriesList.every((OverlayEntry entry) => entry._overlay == null || entry._overlay == this),
736 'One or more of the specified entries are already present in another Overlay.',
737 );
738 assert(
739 newEntriesList.every((OverlayEntry entry) => _entries.indexOf(entry) == _entries.lastIndexOf(entry)),
740 'One or more of the specified entries are specified multiple times.',
741 );
742 if (newEntriesList.isEmpty) {
743 return;
744 }
745 if (listEquals(_entries, newEntriesList)) {
746 return;
747 }
748 final LinkedHashSet<OverlayEntry> old = LinkedHashSet<OverlayEntry>.of(_entries);
749 for (final OverlayEntry entry in newEntriesList) {
750 entry._overlay ??= this;
751 }
752 setState(() {
753 _entries.clear();
754 _entries.addAll(newEntriesList);
755 old.removeAll(newEntriesList);
756 _entries.insertAll(_insertionIndex(below, above), old);
757 });
758 }
759
760 void _markDirty() {
761 if (mounted) {
762 setState(() {});
763 }
764 }
765
766 /// (DEBUG ONLY) Check whether a given entry is visible (i.e., not behind an
767 /// opaque entry).
768 ///
769 /// This is an O(N) algorithm, and should not be necessary except for debug
770 /// asserts. To avoid people depending on it, this function is implemented
771 /// only in debug mode, and always returns false in release mode.
772 bool debugIsVisible(OverlayEntry entry) {
773 bool result = false;
774 assert(_entries.contains(entry));
775 assert(() {
776 for (int i = _entries.length - 1; i > 0; i -= 1) {
777 final OverlayEntry candidate = _entries[i];
778 if (candidate == entry) {
779 result = true;
780 break;
781 }
782 if (candidate.opaque) {
783 break;
784 }
785 }
786 return true;
787 }());
788 return result;
789 }
790
791 void _didChangeEntryOpacity() {
792 setState(() {
793 // We use the opacity of the entry in our build function, which means we
794 // our state has changed.
795 });
796 }
797
798 @override
799 Widget build(BuildContext context) {
800 // This list is filled backwards and then reversed below before
801 // it is added to the tree.
802 final List<_OverlayEntryWidget> children = <_OverlayEntryWidget>[];
803 bool onstage = true;
804 int onstageCount = 0;
805 for (final OverlayEntry entry in _entries.reversed) {
806 if (onstage) {
807 onstageCount += 1;
808 children.add(_OverlayEntryWidget(
809 key: entry._key,
810 overlayState: this,
811 entry: entry,
812 ));
813 if (entry.opaque) {
814 onstage = false;
815 }
816 } else if (entry.maintainState) {
817 children.add(_OverlayEntryWidget(
818 key: entry._key,
819 overlayState: this,
820 entry: entry,
821 tickerEnabled: false,
822 ));
823 }
824 }
825 return _Theater(
826 skipCount: children.length - onstageCount,
827 clipBehavior: widget.clipBehavior,
828 children: children.reversed.toList(growable: false),
829 );
830 }
831
832 @override
833 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
834 super.debugFillProperties(properties);
835 // TODO(jacobr): use IterableProperty instead as that would
836 // provide a slightly more consistent string summary of the List.
837 properties.add(DiagnosticsProperty<List<OverlayEntry>>('entries', _entries));
838 }
839}
840
841class _WrappingOverlay extends StatefulWidget {
842 const _WrappingOverlay({super.key, this.clipBehavior = Clip.hardEdge, required this.child});
843
844 final Clip clipBehavior;
845 final Widget child;
846
847 @override
848 State<_WrappingOverlay> createState() => _WrappingOverlayState();
849}
850
851class _WrappingOverlayState extends State<_WrappingOverlay> {
852 late final OverlayEntry _entry = OverlayEntry(
853 canSizeOverlay: true,
854 opaque: true,
855 builder: (BuildContext context) {
856 return widget.child;
857 }
858 );
859
860 @override
861 void didUpdateWidget(_WrappingOverlay oldWidget) {
862 super.didUpdateWidget(oldWidget);
863 _entry.markNeedsBuild();
864 }
865
866 @override
867 Widget build(BuildContext context) {
868 return Overlay(
869 clipBehavior: widget.clipBehavior,
870 initialEntries: <OverlayEntry>[_entry],
871 );
872 }
873}
874
875/// Special version of a [Stack], that doesn't layout and render the first
876/// [skipCount] children.
877///
878/// The first [skipCount] children are considered "offstage".
879class _Theater extends MultiChildRenderObjectWidget {
880 const _Theater({
881 this.skipCount = 0,
882 this.clipBehavior = Clip.hardEdge,
883 required List<_OverlayEntryWidget> super.children,
884 }) : assert(skipCount >= 0),
885 assert(children.length >= skipCount);
886
887 final int skipCount;
888
889 final Clip clipBehavior;
890
891 @override
892 _TheaterElement createElement() => _TheaterElement(this);
893
894 @override
895 _RenderTheater createRenderObject(BuildContext context) {
896 return _RenderTheater(
897 skipCount: skipCount,
898 textDirection: Directionality.of(context),
899 clipBehavior: clipBehavior,
900 );
901 }
902
903 @override
904 void updateRenderObject(BuildContext context, _RenderTheater renderObject) {
905 renderObject
906 ..skipCount = skipCount
907 ..textDirection = Directionality.of(context)
908 ..clipBehavior = clipBehavior;
909 }
910
911 @override
912 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
913 super.debugFillProperties(properties);
914 properties.add(IntProperty('skipCount', skipCount));
915 }
916}
917
918class _TheaterElement extends MultiChildRenderObjectElement {
919 _TheaterElement(_Theater super.widget);
920
921 @override
922 _RenderTheater get renderObject => super.renderObject as _RenderTheater;
923
924 @override
925 void insertRenderObjectChild(RenderBox child, IndexedSlot<Element?> slot) {
926 super.insertRenderObjectChild(child, slot);
927 final _TheaterParentData parentData = child.parentData! as _TheaterParentData;
928 parentData.overlayEntry = ((widget as _Theater).children[slot.index] as _OverlayEntryWidget).entry;
929 assert(parentData.overlayEntry != null);
930 }
931
932 @override
933 void moveRenderObjectChild(RenderBox child, IndexedSlot<Element?> oldSlot, IndexedSlot<Element?> newSlot) {
934 super.moveRenderObjectChild(child, oldSlot, newSlot);
935 assert(() {
936 final _TheaterParentData parentData = child.parentData! as _TheaterParentData;
937 return parentData.overlayEntry == ((widget as _Theater).children[newSlot.index] as _OverlayEntryWidget).entry;
938 }());
939 }
940
941 @override
942 void debugVisitOnstageChildren(ElementVisitor visitor) {
943 final _Theater theater = widget as _Theater;
944 assert(children.length >= theater.skipCount);
945 children.skip(theater.skipCount).forEach(visitor);
946 }
947}
948
949// A `RenderBox` that sizes itself to its parent's size, implements the stack
950// layout algorithm and renders its children in the given `theater`.
951mixin _RenderTheaterMixin on RenderBox {
952 _RenderTheater get theater;
953
954 Iterable<RenderBox> _childrenInPaintOrder();
955 Iterable<RenderBox> _childrenInHitTestOrder();
956
957 @override
958 void setupParentData(RenderBox child) {
959 if (child.parentData is! StackParentData) {
960 child.parentData = StackParentData();
961 }
962 }
963
964 void layoutChild(RenderBox child, BoxConstraints nonPositionedChildConstraints) {
965 final StackParentData childParentData = child.parentData! as StackParentData;
966 final Alignment alignment = theater._resolvedAlignment;
967 if (!childParentData.isPositioned) {
968 child.layout(nonPositionedChildConstraints, parentUsesSize: true);
969 childParentData.offset = Offset.zero;
970 } else {
971 assert(child is! _RenderDeferredLayoutBox, 'all _RenderDeferredLayoutBoxes must be non-positioned children.');
972 RenderStack.layoutPositionedChild(child, childParentData, size, alignment);
973 }
974 assert(child.parentData == childParentData);
975 }
976
977 @override
978 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
979 final Iterator<RenderBox> iterator = _childrenInHitTestOrder().iterator;
980 bool isHit = false;
981 while (!isHit && iterator.moveNext()) {
982 final RenderBox child = iterator.current;
983 final StackParentData childParentData = child.parentData! as StackParentData;
984 final RenderBox localChild = child;
985 bool childHitTest(BoxHitTestResult result, Offset position) => localChild.hitTest(result, position: position);
986 isHit = result.addWithPaintOffset(offset: childParentData.offset, position: position, hitTest: childHitTest);
987 }
988 return isHit;
989 }
990
991 @override
992 void paint(PaintingContext context, Offset offset) {
993 for (final RenderBox child in _childrenInPaintOrder()) {
994 final StackParentData childParentData = child.parentData! as StackParentData;
995 context.paintChild(child, childParentData.offset + offset);
996 }
997 }
998}
999
1000class _TheaterParentData extends StackParentData {
1001 // The OverlayEntry that directly created this child. This field is null for
1002 // children that are created by an OverlayPortal.
1003 OverlayEntry? overlayEntry;
1004
1005 /// A [OverlayPortal] makes its overlay child a render child of an ancestor
1006 /// [Overlay]. Currently, to make sure the overlay child is painted after its
1007 /// [OverlayPortal], and before the next [OverlayEntry] (which could be
1008 /// something that should obstruct the overlay child, such as a [ModalRoute])
1009 /// in the host [Overlay], the paint order of each overlay child is managed by
1010 /// the [OverlayEntry] that hosts its [OverlayPortal].
1011 ///
1012 /// The following methods are exposed to allow easy access to the overlay
1013 /// children's render objects whose order is managed by [overlayEntry], in the
1014 /// right order.
1015
1016 // _overlayStateMounted is set to null in _OverlayEntryWidgetState's dispose
1017 // method. This property is only accessed during layout, paint and hit-test so
1018 // the `value!` should be safe.
1019 Iterator<RenderBox>? get paintOrderIterator => overlayEntry?._overlayEntryStateNotifier?.value!._paintOrderIterable.iterator;
1020 Iterator<RenderBox>? get hitTestOrderIterator => overlayEntry?._overlayEntryStateNotifier?.value!._hitTestOrderIterable.iterator;
1021
1022 // A convenience method for traversing `paintOrderIterator` with a
1023 // [RenderObjectVisitor].
1024 void visitOverlayPortalChildrenOnOverlayEntry(RenderObjectVisitor visitor) => overlayEntry?._overlayEntryStateNotifier?.value!._paintOrderIterable.forEach(visitor);
1025}
1026
1027class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox, StackParentData>, _RenderTheaterMixin {
1028 _RenderTheater({
1029 List<RenderBox>? children,
1030 required TextDirection textDirection,
1031 int skipCount = 0,
1032 Clip clipBehavior = Clip.hardEdge,
1033 }) : assert(skipCount >= 0),
1034 _textDirection = textDirection,
1035 _skipCount = skipCount,
1036 _clipBehavior = clipBehavior {
1037 addAll(children);
1038 }
1039
1040 @override
1041 _RenderTheater get theater => this;
1042
1043 @override
1044 void setupParentData(RenderBox child) {
1045 if (child.parentData is! _TheaterParentData) {
1046 child.parentData = _TheaterParentData();
1047 }
1048 }
1049
1050 @override
1051 void attach(PipelineOwner owner) {
1052 super.attach(owner);
1053 RenderBox? child = firstChild;
1054 while (child != null) {
1055 final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
1056 final Iterator<RenderBox>? iterator = childParentData.paintOrderIterator;
1057 if (iterator != null) {
1058 while (iterator.moveNext()) {
1059 iterator.current.attach(owner);
1060 }
1061 }
1062 child = childParentData.nextSibling;
1063 }
1064 }
1065
1066 static void _detachChild(RenderObject child) => child.detach();
1067
1068 @override
1069 void detach() {
1070 super.detach();
1071 RenderBox? child = firstChild;
1072 while (child != null) {
1073 final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
1074 childParentData.visitOverlayPortalChildrenOnOverlayEntry(_detachChild);
1075 child = childParentData.nextSibling;
1076 }
1077 }
1078
1079 @override
1080 void redepthChildren() => visitChildren(redepthChild);
1081
1082 Alignment? _alignmentCache;
1083 Alignment get _resolvedAlignment => _alignmentCache ??= AlignmentDirectional.topStart.resolve(textDirection);
1084
1085 void _markNeedResolution() {
1086 _alignmentCache = null;
1087 markNeedsLayout();
1088 }
1089
1090 TextDirection get textDirection => _textDirection;
1091 TextDirection _textDirection;
1092 set textDirection(TextDirection value) {
1093 if (_textDirection == value) {
1094 return;
1095 }
1096 _textDirection = value;
1097 _markNeedResolution();
1098 }
1099
1100 int get skipCount => _skipCount;
1101 int _skipCount;
1102 set skipCount(int value) {
1103 if (_skipCount != value) {
1104 _skipCount = value;
1105 markNeedsLayout();
1106 }
1107 }
1108
1109 /// {@macro flutter.material.Material.clipBehavior}
1110 ///
1111 /// Defaults to [Clip.hardEdge].
1112 Clip get clipBehavior => _clipBehavior;
1113 Clip _clipBehavior = Clip.hardEdge;
1114 set clipBehavior(Clip value) {
1115 if (value != _clipBehavior) {
1116 _clipBehavior = value;
1117 markNeedsPaint();
1118 markNeedsSemanticsUpdate();
1119 }
1120 }
1121
1122 // Adding/removing deferred child does not affect the layout of other children,
1123 // or that of the Overlay, so there's no need to invalidate the layout of the
1124 // Overlay.
1125 //
1126 // When _skipMarkNeedsLayout is true, markNeedsLayout does not do anything.
1127 bool _skipMarkNeedsLayout = false;
1128 void _addDeferredChild(_RenderDeferredLayoutBox child) {
1129 assert(!_skipMarkNeedsLayout);
1130 _skipMarkNeedsLayout = true;
1131 adoptChild(child);
1132 // The Overlay still needs repainting when a deferred child is added. Usually
1133 // `markNeedsLayout` implies `markNeedsPaint`, but here `markNeedsLayout` is
1134 // skipped when the `_skipMarkNeedsLayout` flag is set.
1135 markNeedsPaint();
1136 _skipMarkNeedsLayout = false;
1137
1138 // After adding `child` to the render tree, we want to make sure it will be
1139 // laid out in the same frame. This is done by calling markNeedsLayout on the
1140 // layout surrgate. This ensures `child` is reachable via tree walk (see
1141 // _RenderLayoutSurrogateProxyBox.performLayout).
1142 child._layoutSurrogate.markNeedsLayout();
1143 }
1144
1145 void _removeDeferredChild(_RenderDeferredLayoutBox child) {
1146 assert(!_skipMarkNeedsLayout);
1147 _skipMarkNeedsLayout = true;
1148 dropChild(child);
1149 // The Overlay still needs repainting when a deferred child is dropped. See
1150 // the comment in `_addDeferredChild`.
1151 markNeedsPaint();
1152 _skipMarkNeedsLayout = false;
1153 }
1154
1155 @override
1156 void markNeedsLayout() {
1157 if (!_skipMarkNeedsLayout) {
1158 super.markNeedsLayout();
1159 }
1160 }
1161
1162 RenderBox? get _firstOnstageChild {
1163 if (skipCount == super.childCount) {
1164 return null;
1165 }
1166 RenderBox? child = super.firstChild;
1167 for (int toSkip = skipCount; toSkip > 0; toSkip--) {
1168 final StackParentData childParentData = child!.parentData! as StackParentData;
1169 child = childParentData.nextSibling;
1170 assert(child != null);
1171 }
1172 return child;
1173 }
1174
1175 RenderBox? get _lastOnstageChild => skipCount == super.childCount ? null : lastChild;
1176
1177 @override
1178 double computeMinIntrinsicWidth(double height) {
1179 return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
1180 }
1181
1182 @override
1183 double computeMaxIntrinsicWidth(double height) {
1184 return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
1185 }
1186
1187 @override
1188 double computeMinIntrinsicHeight(double width) {
1189 return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
1190 }
1191
1192 @override
1193 double computeMaxIntrinsicHeight(double width) {
1194 return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
1195 }
1196
1197 @override
1198 double? computeDistanceToActualBaseline(TextBaseline baseline) {
1199 assert(!debugNeedsLayout);
1200 double? result;
1201 RenderBox? child = _firstOnstageChild;
1202 while (child != null) {
1203 assert(!child.debugNeedsLayout);
1204 final StackParentData childParentData = child.parentData! as StackParentData;
1205 double? candidate = child.getDistanceToActualBaseline(baseline);
1206 if (candidate != null) {
1207 candidate += childParentData.offset.dy;
1208 if (result != null) {
1209 result = math.min(result, candidate);
1210 } else {
1211 result = candidate;
1212 }
1213 }
1214 child = childParentData.nextSibling;
1215 }
1216 return result;
1217 }
1218
1219 @override
1220 Size computeDryLayout(BoxConstraints constraints) {
1221 if (constraints.biggest.isFinite) {
1222 return constraints.biggest;
1223 }
1224 return _findSizeDeterminingChild().getDryLayout(constraints);
1225 }
1226
1227 @override
1228 // The following uses sync* because concurrent modifications should be allowed
1229 // during layout.
1230 Iterable<RenderBox> _childrenInPaintOrder() sync* {
1231 RenderBox? child = _firstOnstageChild;
1232 while (child != null) {
1233 yield child;
1234 final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
1235 final Iterator<RenderBox>? innerIterator = childParentData.paintOrderIterator;
1236 if (innerIterator != null) {
1237 while (innerIterator.moveNext()) {
1238 yield innerIterator.current;
1239 }
1240 }
1241 child = childParentData.nextSibling;
1242 }
1243 }
1244
1245 @override
1246 // The following uses sync* because hit testing should be lazy.
1247 Iterable<RenderBox> _childrenInHitTestOrder() sync* {
1248 RenderBox? child = _lastOnstageChild;
1249 int childLeft = childCount - skipCount;
1250 while (child != null) {
1251 final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
1252 final Iterator<RenderBox>? innerIterator = childParentData.hitTestOrderIterator;
1253 if (innerIterator != null) {
1254 while (innerIterator.moveNext()) {
1255 yield innerIterator.current;
1256 }
1257 }
1258 yield child;
1259 childLeft -= 1;
1260 child = childLeft <= 0 ? null : childParentData.previousSibling;
1261 }
1262 }
1263
1264 @override
1265 bool get sizedByParent => false;
1266
1267 @override
1268 void performLayout() {
1269 RenderBox? sizeDeterminingChild;
1270 if (constraints.biggest.isFinite) {
1271 size = constraints.biggest;
1272 } else {
1273 sizeDeterminingChild = _findSizeDeterminingChild();
1274 layoutChild(sizeDeterminingChild, constraints);
1275 size = sizeDeterminingChild.size;
1276 }
1277
1278 // Equivalent to BoxConstraints used by RenderStack for StackFit.expand.
1279 final BoxConstraints nonPositionedChildConstraints = BoxConstraints.tight(size);
1280 for (final RenderBox child in _childrenInPaintOrder()) {
1281 if (child != sizeDeterminingChild) {
1282 layoutChild(child, nonPositionedChildConstraints);
1283 }
1284 }
1285 }
1286
1287 RenderBox _findSizeDeterminingChild() {
1288 RenderBox? child = _lastOnstageChild;
1289 while (child != null) {
1290 final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
1291 if ((childParentData.overlayEntry?.canSizeOverlay ?? false) && !childParentData.isPositioned) {
1292 return child;
1293 }
1294 child = childParentData.previousSibling;
1295 }
1296 throw FlutterError.fromParts(<DiagnosticsNode>[
1297 ErrorSummary('Overlay was given infinite constraints and cannot be sized by a suitable child.'),
1298 ErrorDescription(
1299 'The constraints given to the overlay ($constraints) would result in an illegal '
1300 'infinite size (${constraints.biggest}). To avoid that, the Overlay tried to size '
1301 'itself to one of its children, but no suitable non-positioned child that belongs to an '
1302 'OverlayEntry with canSizeOverlay set to true could be found.',
1303 ),
1304 ErrorHint(
1305 'Try wrapping the Overlay in a SizedBox to give it a finite size or '
1306 'use an OverlayEntry with canSizeOverlay set to true.',
1307 ),
1308 ]);
1309 }
1310
1311 final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
1312
1313 @override
1314 void paint(PaintingContext context, Offset offset) {
1315 if (clipBehavior != Clip.none) {
1316 _clipRectLayer.layer = context.pushClipRect(
1317 needsCompositing,
1318 offset,
1319 Offset.zero & size,
1320 super.paint,
1321 clipBehavior: clipBehavior,
1322 oldLayer: _clipRectLayer.layer,
1323 );
1324 } else {
1325 _clipRectLayer.layer = null;
1326 super.paint(context, offset);
1327 }
1328 }
1329
1330 @override
1331 void dispose() {
1332 _clipRectLayer.layer = null;
1333 super.dispose();
1334 }
1335
1336 @override
1337 void visitChildren(RenderObjectVisitor visitor) {
1338 RenderBox? child = firstChild;
1339 while (child != null) {
1340 visitor(child);
1341 final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
1342 childParentData.visitOverlayPortalChildrenOnOverlayEntry(visitor);
1343 child = childParentData.nextSibling;
1344 }
1345 }
1346
1347 @override
1348 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
1349 RenderBox? child = _firstOnstageChild;
1350 while (child != null) {
1351 visitor(child);
1352 final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
1353 child = childParentData.nextSibling;
1354 }
1355 }
1356
1357 @override
1358 Rect? describeApproximatePaintClip(RenderObject child) {
1359 switch (clipBehavior) {
1360 case Clip.none:
1361 return null;
1362 case Clip.hardEdge:
1363 case Clip.antiAlias:
1364 case Clip.antiAliasWithSaveLayer:
1365 return Offset.zero & size;
1366 }
1367 }
1368
1369 @override
1370 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1371 super.debugFillProperties(properties);
1372 properties.add(IntProperty('skipCount', skipCount));
1373 properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
1374 }
1375
1376 @override
1377 List<DiagnosticsNode> debugDescribeChildren() {
1378 final List<DiagnosticsNode> offstageChildren = <DiagnosticsNode>[];
1379 final List<DiagnosticsNode> onstageChildren = <DiagnosticsNode>[];
1380
1381 int count = 1;
1382 bool onstage = false;
1383 RenderBox? child = firstChild;
1384 final RenderBox? firstOnstageChild = _firstOnstageChild;
1385 while (child != null) {
1386 final _TheaterParentData childParentData = child.parentData! as _TheaterParentData;
1387 if (child == firstOnstageChild) {
1388 onstage = true;
1389 count = 1;
1390 }
1391
1392 if (onstage) {
1393 onstageChildren.add(
1394 child.toDiagnosticsNode(
1395 name: 'onstage $count',
1396 ),
1397 );
1398 } else {
1399 offstageChildren.add(
1400 child.toDiagnosticsNode(
1401 name: 'offstage $count',
1402 style: DiagnosticsTreeStyle.offstage,
1403 ),
1404 );
1405 }
1406
1407 int subcount = 1;
1408 childParentData.visitOverlayPortalChildrenOnOverlayEntry((RenderObject renderObject) {
1409 final RenderBox child = renderObject as RenderBox;
1410 if (onstage) {
1411 onstageChildren.add(
1412 child.toDiagnosticsNode(
1413 name: 'onstage $count - $subcount',
1414 ),
1415 );
1416 } else {
1417 offstageChildren.add(
1418 child.toDiagnosticsNode(
1419 name: 'offstage $count - $subcount',
1420 style: DiagnosticsTreeStyle.offstage,
1421 ),
1422 );
1423 }
1424 subcount += 1;
1425 });
1426
1427 child = childParentData.nextSibling;
1428 count += 1;
1429 }
1430
1431 return <DiagnosticsNode>[
1432 ...onstageChildren,
1433 if (offstageChildren.isNotEmpty)
1434 ...offstageChildren
1435 else
1436 DiagnosticsNode.message(
1437 'no offstage children',
1438 style: DiagnosticsTreeStyle.offstage,
1439 ),
1440 ];
1441 }
1442}
1443
1444
1445// * OverlayPortal Implementation
1446// OverlayPortal is inspired by the
1447// [flutter_portal](https://pub.dev/packages/flutter_portal) package.
1448//
1449// ** RenderObject hierarchy
1450// The widget works by inserting its overlay child's render subtree directly
1451// under [Overlay]'s render object (_RenderTheater).
1452// https://user-images.githubusercontent.com/31859944/171971838-62ed3975-4b5d-4733-a9c9-f79e263b8fcc.jpg
1453//
1454// To ensure the overlay child render subtree does not do layout twice, the
1455// subtree must only perform layout after both its _RenderTheater and the
1456// [OverlayPortal]'s render object (_RenderLayoutSurrogateProxyBox) have
1457// finished layout. This is handled by _RenderDeferredLayoutBox.
1458//
1459// ** Z-Index of an overlay child
1460// [_OverlayEntryLocation] is a (currently private) interface that allows an
1461// [OverlayPortal] to insert its overlay child into a specific [Overlay], as
1462// well as specifying the paint order between the overlay child and other
1463// children of the _RenderTheater.
1464//
1465// Since [OverlayPortal] is only allowed to target ancestor [Overlay]s
1466// (_RenderTheater must finish doing layout before _RenderDeferredLayoutBox),
1467// the _RenderTheater should typically be acquired using an [InheritedWidget]
1468// (currently, _RenderTheaterMarker) in case the [OverlayPortal] gets
1469// reparented.
1470
1471/// A class to show, hide and bring to top an [OverlayPortal]'s overlay child
1472/// in the target [Overlay].
1473///
1474/// A [OverlayPortalController] can only be given to at most one [OverlayPortal]
1475/// at a time. When an [OverlayPortalController] is moved from one
1476/// [OverlayPortal] to another, its [isShowing] state does not carry over.
1477///
1478/// [OverlayPortalController.show] and [OverlayPortalController.hide] can be
1479/// called even before the controller is assigned to any [OverlayPortal], but
1480/// they typically should not be called while the widget tree is being rebuilt.
1481class OverlayPortalController {
1482 /// Creates an [OverlayPortalController], optionally with a String identifier
1483 /// `debugLabel`.
1484 OverlayPortalController({ String? debugLabel }) : _debugLabel = debugLabel;
1485
1486 _OverlayPortalState? _attachTarget;
1487
1488 // A separate _zOrderIndex to allow `show()` or `hide()` to be called when the
1489 // controller is not yet attached. Once this controller is attached,
1490 // _attachTarget._zOrderIndex will be used as the source of truth, and this
1491 // variable will be set to null.
1492 int? _zOrderIndex;
1493 final String? _debugLabel;
1494
1495 static int _wallTime = kIsWeb
1496 ? -9007199254740992 // -2^53
1497 : -1 << 63;
1498
1499 // Returns a unique and monotonically increasing timestamp that represents
1500 // now.
1501 //
1502 // The value this method returns increments after each call.
1503 int _now() {
1504 final int now = _wallTime += 1;
1505 assert(_zOrderIndex == null || _zOrderIndex! < now);
1506 assert(_attachTarget?._zOrderIndex == null || _attachTarget!._zOrderIndex! < now);
1507 return now;
1508 }
1509
1510 /// Show the overlay child of the [OverlayPortal] this controller is attached
1511 /// to, at the top of the target [Overlay].
1512 ///
1513 /// When there are more than one [OverlayPortal]s that target the same
1514 /// [Overlay], the overlay child of the last [OverlayPortal] to have called
1515 /// [show] appears at the top level, unobstructed.
1516 ///
1517 /// If [isShowing] is already true, calling this method brings the overlay
1518 /// child it controls to the top.
1519 ///
1520 /// This method should typically not be called while the widget tree is being
1521 /// rebuilt.
1522 void show() {
1523 final _OverlayPortalState? state = _attachTarget;
1524 if (state != null) {
1525 state.show(_now());
1526 } else {
1527 _zOrderIndex = _now();
1528 }
1529 }
1530
1531 /// Hide the [OverlayPortal]'s overlay child.
1532 ///
1533 /// Once hidden, the overlay child will be removed from the widget tree the
1534 /// next time the widget tree rebuilds, and stateful widgets in the overlay
1535 /// child may lose states as a result.
1536 ///
1537 /// This method should typically not be called while the widget tree is being
1538 /// rebuilt.
1539 void hide() {
1540 final _OverlayPortalState? state = _attachTarget;
1541 if (state != null) {
1542 state.hide();
1543 } else {
1544 assert(_zOrderIndex != null);
1545 _zOrderIndex = null;
1546 }
1547 }
1548
1549 /// Whether the associated [OverlayPortal] should build and show its overlay
1550 /// child, using its `overlayChildBuilder`.
1551 bool get isShowing {
1552 final _OverlayPortalState? state = _attachTarget;
1553 return state != null
1554 ? state._zOrderIndex != null
1555 : _zOrderIndex != null;
1556 }
1557
1558 /// Convenience method for toggling the current [isShowing] status.
1559 ///
1560 /// This method should typically not be called while the widget tree is being
1561 /// rebuilt.
1562 void toggle() => isShowing ? hide() : show();
1563
1564 @override
1565 String toString() {
1566 final String? debugLabel = _debugLabel;
1567 final String label = debugLabel == null ? '' : '($debugLabel)';
1568 final String isDetached = _attachTarget != null ? '' : ' DETACHED';
1569 return '${objectRuntimeType(this, 'OverlayPortalController')}$label$isDetached';
1570 }
1571}
1572
1573/// A widget that renders its overlay child on an [Overlay].
1574///
1575/// The overlay child is initially hidden until [OverlayPortalController.show]
1576/// is called on the associated [controller]. The [OverlayPortal] uses
1577/// [overlayChildBuilder] to build its overlay child and renders it on the
1578/// specified [Overlay] as if it was inserted using an [OverlayEntry], while it
1579/// can depend on the same set of [InheritedWidget]s (such as [Theme]) that this
1580/// widget can depend on.
1581///
1582/// This widget requires an [Overlay] ancestor in the widget tree when its
1583/// overlay child is showing. The overlay child is rendered by the [Overlay]
1584/// ancestor, not by the widget itself. This allows the overlay child to float
1585/// above other widgets, independent of its position in the widget tree.
1586///
1587/// When [OverlayPortalController.hide] is called, the widget built using
1588/// [overlayChildBuilder] will be removed from the widget tree the next time the
1589/// widget rebuilds. Stateful descendants in the overlay child subtree may lose
1590/// states as a result.
1591///
1592/// {@tool dartpad}
1593/// This example uses an [OverlayPortal] to build a tooltip that becomes visible
1594/// when the user taps on the [child] widget. There's a [DefaultTextStyle] above
1595/// the [OverlayPortal] controlling the [TextStyle] of both the [child] widget
1596/// and the widget [overlayChildBuilder] builds, which isn't otherwise doable if
1597/// the tooltip was added as an [OverlayEntry].
1598///
1599/// ** See code in examples/api/lib/widgets/overlay/overlay_portal.0.dart **
1600/// {@end-tool}
1601///
1602/// ### Paint Order
1603///
1604/// In an [Overlay], an overlay child is painted after the [OverlayEntry]
1605/// associated with its [OverlayPortal] (that is, the [OverlayEntry] closest to
1606/// the [OverlayPortal] in the widget tree, which usually represents the
1607/// enclosing [Route]), and before the next [OverlayEntry].
1608///
1609/// When an [OverlayEntry] has multiple associated [OverlayPortal]s, the paint
1610/// order between their overlay children is the order in which
1611/// [OverlayPortalController.show] was called. The last [OverlayPortal] to have
1612/// called `show` gets to paint its overlay child in the foreground.
1613///
1614/// ### Semantics
1615///
1616/// The semantics subtree generated by the overlay child is considered attached
1617/// to [OverlayPortal] instead of the target [Overlay]. An [OverlayPortal]'s
1618/// semantics subtree can be dropped from the semantics tree due to invisibility
1619/// while the overlay child is still visible (for example, when the
1620/// [OverlayPortal] is completely invisible in a [ListView] but kept alive by
1621/// a [KeepAlive] widget). When this happens the semantics subtree generated by
1622/// the overlay child is also dropped, even if the overlay child is still visible
1623/// on screen.
1624///
1625/// {@template flutter.widgets.overlayPortalVsOverlayEntry}
1626/// ### Differences between [OverlayPortal] and [OverlayEntry]
1627///
1628/// The main difference between [OverlayEntry] and [OverlayPortal] is that
1629/// [OverlayEntry] builds its widget subtree as a child of the target [Overlay],
1630/// while [OverlayPortal] uses [OverlayPortal.overlayChildBuilder] to build a
1631/// child widget of itself. This allows [OverlayPortal]'s overlay child to depend
1632/// on the same set of [InheritedWidget]s as [OverlayPortal], and it's also
1633/// guaranteed that the overlay child will not outlive its [OverlayPortal].
1634///
1635/// On the other hand, [OverlayPortal]'s implementation is more complex. For
1636/// instance, it does a bit more work than a regular widget during global key
1637/// reparenting. If the content to be shown on the [Overlay] doesn't benefit
1638/// from being a part of [OverlayPortal]'s subtree, consider using an
1639/// [OverlayEntry] instead.
1640/// {@endtemplate}
1641///
1642/// See also:
1643///
1644/// * [OverlayEntry], an alternative API for inserting widgets into an
1645/// [Overlay].
1646/// * [Positioned], which can be used to size and position the overlay child in
1647/// relation to the target [Overlay]'s boundaries.
1648/// * [CompositedTransformFollower], which can be used to position the overlay
1649/// child in relation to the linked [CompositedTransformTarget] widget.
1650class OverlayPortal extends StatefulWidget {
1651 /// Creates an [OverlayPortal] that renders the widget [overlayChildBuilder]
1652 /// builds on the closest [Overlay] when [OverlayPortalController.show] is
1653 /// called.
1654 const OverlayPortal({
1655 super.key,
1656 required this.controller,
1657 required this.overlayChildBuilder,
1658 this.child,
1659 }) : _targetRootOverlay = false;
1660
1661 /// Creates an [OverlayPortal] that renders the widget [overlayChildBuilder]
1662 /// builds on the root [Overlay] when [OverlayPortalController.show] is
1663 /// called.
1664 const OverlayPortal.targetsRootOverlay({
1665 super.key,
1666 required this.controller,
1667 required this.overlayChildBuilder,
1668 this.child,
1669 }) : _targetRootOverlay = true;
1670
1671 /// The controller to show, hide and bring to top the overlay child.
1672 final OverlayPortalController controller;
1673
1674 /// A [WidgetBuilder] used to build a widget below this widget in the tree,
1675 /// that renders on the closest [Overlay].
1676 ///
1677 /// The said widget will only be built and shown in the closest [Overlay] once
1678 /// [OverlayPortalController.show] is called on the associated [controller].
1679 /// It will be painted in front of the [OverlayEntry] closest to this widget
1680 /// in the widget tree (which is usually the enclosing [Route]).
1681 ///
1682 /// The built overlay child widget is inserted below this widget in the widget
1683 /// tree, allowing it to depend on [InheritedWidget]s above it, and be
1684 /// notified when the [InheritedWidget]s change.
1685 ///
1686 /// Unlike [child], the built overlay child can visually extend outside the
1687 /// bounds of this widget without being clipped, and receive hit-test events
1688 /// outside of this widget's bounds, as long as it does not extend outside of
1689 /// the [Overlay] on which it is rendered.
1690 final WidgetBuilder overlayChildBuilder;
1691
1692 /// A widget below this widget in the tree.
1693 final Widget? child;
1694
1695 final bool _targetRootOverlay;
1696
1697 @override
1698 State<OverlayPortal> createState() => _OverlayPortalState();
1699}
1700
1701class _OverlayPortalState extends State<OverlayPortal> {
1702 int? _zOrderIndex;
1703 // The location of the overlay child within the overlay. This object will be
1704 // used as the slot of the overlay child widget.
1705 //
1706 // The developer must call `show` to reveal the overlay so we can get a unique
1707 // timestamp of the user interaction for determining the z-index of the
1708 // overlay child in the overlay.
1709 //
1710 // Avoid invalidating the cache if possible, since the framework uses `==` to
1711 // compare slots, and _OverlayEntryLocation can't override that operator since
1712 // it's mutable. Changing slots can be relatively slow.
1713 bool _childModelMayHaveChanged = true;
1714 _OverlayEntryLocation? _locationCache;
1715 static bool _isTheSameLocation(_OverlayEntryLocation locationCache, _RenderTheaterMarker marker) {
1716 return locationCache._childModel == marker.overlayEntryWidgetState
1717 && locationCache._theater == marker.theater;
1718 }
1719
1720 _OverlayEntryLocation _getLocation(int zOrderIndex, bool targetRootOverlay) {
1721 final _OverlayEntryLocation? cachedLocation = _locationCache;
1722 late final _RenderTheaterMarker marker = _RenderTheaterMarker.of(context, targetRootOverlay: targetRootOverlay);
1723 final bool isCacheValid = cachedLocation != null
1724 && (!_childModelMayHaveChanged || _isTheSameLocation(cachedLocation, marker));
1725 _childModelMayHaveChanged = false;
1726 if (isCacheValid) {
1727 assert(cachedLocation._zOrderIndex == zOrderIndex);
1728 assert(cachedLocation._debugIsLocationValid());
1729 return cachedLocation;
1730 }
1731 // Otherwise invalidate the cache and create a new location.
1732 cachedLocation?._debugMarkLocationInvalid();
1733 final _OverlayEntryLocation newLocation = _OverlayEntryLocation(zOrderIndex, marker.overlayEntryWidgetState, marker.theater);
1734 assert(newLocation._zOrderIndex == zOrderIndex);
1735 return _locationCache = newLocation;
1736 }
1737
1738 @override
1739 void initState() {
1740 super.initState();
1741 _setupController(widget.controller);
1742 }
1743
1744 void _setupController(OverlayPortalController controller) {
1745 assert(
1746 controller._attachTarget == null || controller._attachTarget == this,
1747 'Failed to attach $controller to $this. It is already attached to ${controller._attachTarget}.'
1748 );
1749 final int? controllerZOrderIndex = controller._zOrderIndex;
1750 final int? zOrderIndex = _zOrderIndex;
1751 if (zOrderIndex == null || (controllerZOrderIndex != null && controllerZOrderIndex > zOrderIndex)) {
1752 _zOrderIndex = controllerZOrderIndex;
1753 }
1754 controller._zOrderIndex = null;
1755 controller._attachTarget = this;
1756 }
1757
1758 @override
1759 void didChangeDependencies() {
1760 super.didChangeDependencies();
1761 _childModelMayHaveChanged = true;
1762 }
1763
1764 @override
1765 void didUpdateWidget(OverlayPortal oldWidget) {
1766 super.didUpdateWidget(oldWidget);
1767 _childModelMayHaveChanged = _childModelMayHaveChanged || oldWidget._targetRootOverlay != widget._targetRootOverlay;
1768 if (oldWidget.controller != widget.controller) {
1769 oldWidget.controller._attachTarget = null;
1770 _setupController(widget.controller);
1771 }
1772 }
1773
1774 @override
1775 void dispose() {
1776 assert(widget.controller._attachTarget == this);
1777 widget.controller._attachTarget = null;
1778 _locationCache?._debugMarkLocationInvalid();
1779 _locationCache = null;
1780 super.dispose();
1781 }
1782
1783 void show(int zOrderIndex) {
1784 assert(
1785 SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks,
1786 '${widget.controller.runtimeType}.show() should not be called during build.'
1787 );
1788 setState(() { _zOrderIndex = zOrderIndex; });
1789 _locationCache?._debugMarkLocationInvalid();
1790 _locationCache = null;
1791 }
1792
1793 void hide() {
1794 assert(SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks);
1795 setState(() { _zOrderIndex = null; });
1796 _locationCache?._debugMarkLocationInvalid();
1797 _locationCache = null;
1798 }
1799
1800 @override
1801 Widget build(BuildContext context) {
1802 final int? zOrderIndex = _zOrderIndex;
1803 if (zOrderIndex == null) {
1804 return _OverlayPortal(
1805 overlayLocation: null,
1806 overlayChild: null,
1807 child: widget.child,
1808 );
1809 }
1810 return _OverlayPortal(
1811 overlayLocation: _getLocation(zOrderIndex, widget._targetRootOverlay),
1812 overlayChild: _DeferredLayout(child: Builder(builder: widget.overlayChildBuilder)),
1813 child: widget.child,
1814 );
1815 }
1816}
1817
1818/// A location in an [Overlay].
1819///
1820/// An [_OverlayEntryLocation] determines the [Overlay] the associated
1821/// [OverlayPortal] should put its overlay child onto, as well as the overlay
1822/// child's paint order in relation to other contents painted on the [Overlay].
1823//
1824// An _OverlayEntryLocation is a cursor pointing to a location in a particular
1825// Overlay's child model, and provides methods to insert/remove/move a
1826// _RenderDeferredLayoutBox to/from its target _theater.
1827//
1828// The occupant (a `RenderBox`) will be painted above the associated
1829// [OverlayEntry], but below the [OverlayEntry] above that [OverlayEntry].
1830//
1831// Additionally, `_activate` and `_deactivate` are called when the overlay
1832// child's `_OverlayPortalElement` activates/deactivates (for instance, during
1833// global key reparenting).
1834// `_OverlayPortalElement` removes its overlay child's render object from the
1835// target `_RenderTheater` when it deactivates and puts it back on `activated`.
1836// These 2 methods can be used to "hide" a child in the child model without
1837// removing it, when the child is expensive/difficult to re-insert at the
1838// correct location on `activated`.
1839//
1840// ### Equality
1841//
1842// An `_OverlayEntryLocation` will be used as an Element's slot. These 3 parts
1843// uniquely identify a place in an overlay's child model:
1844// - _theater
1845// - _childModel (the OverlayEntry)
1846// - _zOrderIndex
1847//
1848// Since it can't implement operator== (it's mutable), the same `_OverlayEntryLocation`
1849// instance must not be used to represent more than one locations.
1850final class _OverlayEntryLocation extends LinkedListEntry<_OverlayEntryLocation> {
1851 _OverlayEntryLocation(this._zOrderIndex, this._childModel, this._theater);
1852
1853 final int _zOrderIndex;
1854 final _OverlayEntryWidgetState _childModel;
1855 final _RenderTheater _theater;
1856
1857 _RenderDeferredLayoutBox? _overlayChildRenderBox;
1858 void _addToChildModel(_RenderDeferredLayoutBox child) {
1859 assert(_overlayChildRenderBox == null, 'Failed to add $child. This location ($this) is already occupied by $_overlayChildRenderBox.');
1860 _overlayChildRenderBox = child;
1861 _childModel._add(this);
1862 _theater.markNeedsPaint();
1863 _theater.markNeedsCompositingBitsUpdate();
1864 _theater.markNeedsSemanticsUpdate();
1865 }
1866 void _removeFromChildModel(_RenderDeferredLayoutBox child) {
1867 assert(child == _overlayChildRenderBox);
1868 _overlayChildRenderBox = null;
1869 assert(_childModel._sortedTheaterSiblings?.contains(this) ?? false);
1870 _childModel._remove(this);
1871 _theater.markNeedsPaint();
1872 _theater.markNeedsCompositingBitsUpdate();
1873 _theater.markNeedsSemanticsUpdate();
1874 }
1875
1876 void _addChild(_RenderDeferredLayoutBox child) {
1877 assert(_debugIsLocationValid());
1878 _addToChildModel(child);
1879 _theater._addDeferredChild(child);
1880 assert(child.parent == _theater);
1881 }
1882
1883 void _removeChild(_RenderDeferredLayoutBox child) {
1884 // This call is allowed even when this location is disposed.
1885 _removeFromChildModel(child);
1886 _theater._removeDeferredChild(child);
1887 assert(child.parent == null);
1888 }
1889
1890 void _moveChild(_RenderDeferredLayoutBox child, _OverlayEntryLocation fromLocation) {
1891 assert(fromLocation != this);
1892 assert(_debugIsLocationValid());
1893 final _RenderTheater fromTheater = fromLocation._theater;
1894 final _OverlayEntryWidgetState fromModel = fromLocation._childModel;
1895
1896 if (fromTheater != _theater) {
1897 fromTheater._removeDeferredChild(child);
1898 _theater._addDeferredChild(child);
1899 }
1900
1901 if (fromModel != _childModel || fromLocation._zOrderIndex != _zOrderIndex) {
1902 fromLocation._removeFromChildModel(child);
1903 _addToChildModel(child);
1904 }
1905 }
1906
1907 void _activate(_RenderDeferredLayoutBox child) {
1908 // This call is allowed even when this location is invalidated.
1909 // See _OverlayPortalElement.activate.
1910 assert(_overlayChildRenderBox == null, '$_overlayChildRenderBox');
1911 _theater._addDeferredChild(child);
1912 _overlayChildRenderBox = child;
1913 }
1914
1915 void _deactivate(_RenderDeferredLayoutBox child) {
1916 // This call is allowed even when this location is invalidated.
1917 _theater._removeDeferredChild(child);
1918 _overlayChildRenderBox = null;
1919 }
1920
1921 // Throws a StateError if this location is already invalidated and shouldn't
1922 // be used as an OverlayPortal slot. Must be used in asserts.
1923 //
1924 // Generally, `assert(_debugIsLocationValid())` should be used to prevent
1925 // invalid accesses to an invalid `_OverlayEntryLocation` object. Exceptions
1926 // to this rule are _removeChild, _deactive, which will be called when the
1927 // OverlayPortal is being removed from the widget tree and may use the
1928 // location information to perform cleanup tasks.
1929 //
1930 // Another exception is the _activate method which is called by
1931 // _OverlayPortalElement.activate. See the comment in _OverlayPortalElement.activate.
1932 bool _debugIsLocationValid() {
1933 if (_debugMarkLocationInvalidStackTrace == null) {
1934 return true;
1935 }
1936 throw StateError('$this is already disposed. Stack trace: $_debugMarkLocationInvalidStackTrace');
1937 }
1938
1939 // The StackTrace of the first _debugMarkLocationInvalid call. It's only for
1940 // debugging purposes and the StackTrace will only be captured in debug builds.
1941 //
1942 // The effect of this method is not reversible. Once marked invalid, this
1943 // object can't be marked as valid again.
1944 StackTrace? _debugMarkLocationInvalidStackTrace;
1945 @mustCallSuper
1946 void _debugMarkLocationInvalid() {
1947 assert(_debugIsLocationValid());
1948 assert(() {
1949 _debugMarkLocationInvalidStackTrace = StackTrace.current;
1950 return true;
1951 }());
1952 }
1953
1954 @override
1955 String toString() => '${objectRuntimeType(this, '_OverlayEntryLocation')}[${shortHash(this)}] ${_debugMarkLocationInvalidStackTrace != null ? "(INVALID)":""}';
1956}
1957
1958class _RenderTheaterMarker extends InheritedWidget {
1959 const _RenderTheaterMarker({
1960 required this.theater,
1961 required this.overlayEntryWidgetState,
1962 required super.child,
1963 });
1964
1965 final _RenderTheater theater;
1966 final _OverlayEntryWidgetState overlayEntryWidgetState;
1967
1968 @override
1969 bool updateShouldNotify(_RenderTheaterMarker oldWidget) {
1970 return oldWidget.theater != theater
1971 || oldWidget.overlayEntryWidgetState != overlayEntryWidgetState;
1972 }
1973
1974 static _RenderTheaterMarker of(BuildContext context, { bool targetRootOverlay = false }) {
1975 final _RenderTheaterMarker? marker;
1976 if (targetRootOverlay) {
1977 final InheritedElement? ancestor = _rootRenderTheaterMarkerOf(context.getElementForInheritedWidgetOfExactType<_RenderTheaterMarker>());
1978 assert(ancestor == null || ancestor.widget is _RenderTheaterMarker);
1979 marker = ancestor != null ? context.dependOnInheritedElement(ancestor) as _RenderTheaterMarker? : null;
1980 } else {
1981 marker = context.dependOnInheritedWidgetOfExactType<_RenderTheaterMarker>();
1982 }
1983 if (marker != null) {
1984 return marker;
1985 }
1986 throw FlutterError.fromParts(<DiagnosticsNode>[
1987 ErrorSummary('No Overlay widget found.'),
1988 ErrorDescription(
1989 '${context.widget.runtimeType} widgets require an Overlay widget ancestor.\n'
1990 'An overlay lets widgets float on top of other widget children.',
1991 ),
1992 ErrorHint(
1993 'To introduce an Overlay widget, you can either directly '
1994 'include one, or use a widget that contains an Overlay itself, '
1995 'such as a Navigator, WidgetApp, MaterialApp, or CupertinoApp.',
1996 ),
1997 ...context.describeMissingAncestor(expectedAncestorType: Overlay),
1998 ]);
1999 }
2000
2001 static InheritedElement? _rootRenderTheaterMarkerOf(InheritedElement? theaterMarkerElement) {
2002 assert(theaterMarkerElement == null || theaterMarkerElement.widget is _RenderTheaterMarker);
2003 if (theaterMarkerElement == null) {
2004 return null;
2005 }
2006 InheritedElement? ancestor;
2007 theaterMarkerElement.visitAncestorElements((Element element) {
2008 ancestor = element.getElementForInheritedWidgetOfExactType<_RenderTheaterMarker>();
2009 return false;
2010 });
2011 return ancestor == null ? theaterMarkerElement : _rootRenderTheaterMarkerOf(ancestor);
2012 }
2013}
2014
2015class _OverlayPortal extends RenderObjectWidget {
2016 /// Creates a widget that renders the given [overlayChild] in the [Overlay]
2017 /// specified by `overlayLocation`.
2018 ///
2019 /// The `overlayLocation` parameter must not be null when [overlayChild] is not
2020 /// null.
2021 _OverlayPortal({
2022 required this.overlayLocation,
2023 required this.overlayChild,
2024 required this.child,
2025 }) : assert(overlayChild == null || overlayLocation != null),
2026 assert(overlayLocation == null || overlayLocation._debugIsLocationValid());
2027
2028 final Widget? overlayChild;
2029
2030 /// A widget below this widget in the tree.
2031 final Widget? child;
2032
2033 final _OverlayEntryLocation? overlayLocation;
2034
2035 @override
2036 RenderObjectElement createElement() => _OverlayPortalElement(this);
2037
2038 @override
2039 RenderObject createRenderObject(BuildContext context) => _RenderLayoutSurrogateProxyBox();
2040}
2041
2042class _OverlayPortalElement extends RenderObjectElement {
2043 _OverlayPortalElement(_OverlayPortal super.widget);
2044
2045 @override
2046 _RenderLayoutSurrogateProxyBox get renderObject => super.renderObject as _RenderLayoutSurrogateProxyBox;
2047
2048 Element? _overlayChild;
2049 Element? _child;
2050
2051 @override
2052 void mount(Element? parent, Object? newSlot) {
2053 super.mount(parent, newSlot);
2054 final _OverlayPortal widget = this.widget as _OverlayPortal;
2055 _child = updateChild(_child, widget.child, null);
2056 _overlayChild = updateChild(_overlayChild, widget.overlayChild, widget.overlayLocation);
2057 }
2058
2059 @override
2060 void update(_OverlayPortal newWidget) {
2061 super.update(newWidget);
2062 _child = updateChild(_child, newWidget.child, null);
2063 _overlayChild = updateChild(_overlayChild, newWidget.overlayChild, newWidget.overlayLocation);
2064 }
2065
2066 @override
2067 void forgetChild(Element child) {
2068 // The _overlayChild Element does not have a key because the _DeferredLayout
2069 // widget does not take a Key, so only the regular _child can be taken
2070 // during global key reparenting.
2071 assert(child == _child);
2072 _child = null;
2073 super.forgetChild(child);
2074 }
2075
2076 @override
2077 void visitChildren(ElementVisitor visitor) {
2078 final Element? child = _child;
2079 final Element? overlayChild = _overlayChild;
2080 if (child != null) {
2081 visitor(child);
2082 }
2083 if (overlayChild != null) {
2084 visitor(overlayChild);
2085 }
2086 }
2087
2088 @override
2089 void activate() {
2090 super.activate();
2091 final Element? overlayChild = _overlayChild;
2092 if (overlayChild != null) {
2093 final _RenderDeferredLayoutBox? box = overlayChild.renderObject as _RenderDeferredLayoutBox?;
2094 if (box != null) {
2095 assert(!box.attached);
2096 assert(renderObject._deferredLayoutChild == box);
2097 // updateChild has not been called at this point so the RenderTheater in
2098 // the overlay location could be detached. Adding children to a detached
2099 // RenderObject is still allowed however this isn't the most efficient.
2100 (overlayChild.slot! as _OverlayEntryLocation)._activate(box);
2101 }
2102 }
2103 }
2104
2105 @override
2106 void deactivate() {
2107 final Element? overlayChild = _overlayChild;
2108 // Instead of just detaching the render objects, removing them from the
2109 // render subtree entirely. This is a workaround for the
2110 // !renderObject.attached assert in the `super.deactive()` method.
2111 if (overlayChild != null) {
2112 final _RenderDeferredLayoutBox? box = overlayChild.renderObject as _RenderDeferredLayoutBox?;
2113 if (box != null) {
2114 (overlayChild.slot! as _OverlayEntryLocation)._deactivate(box);
2115 }
2116 }
2117 super.deactivate();
2118 }
2119
2120 @override
2121 void insertRenderObjectChild(RenderBox child, _OverlayEntryLocation? slot) {
2122 assert(child.parent == null, "$child's parent is not null: ${child.parent}");
2123 if (slot != null) {
2124 renderObject._deferredLayoutChild = child as _RenderDeferredLayoutBox;
2125 slot._addChild(child);
2126 } else {
2127 renderObject.child = child;
2128 }
2129 }
2130
2131 // The [_DeferredLayout] widget does not have a key so there will be no
2132 // reparenting between _overlayChild and _child, thus the non-null-typed slots.
2133 @override
2134 void moveRenderObjectChild(_RenderDeferredLayoutBox child, _OverlayEntryLocation oldSlot, _OverlayEntryLocation newSlot) {
2135 assert(newSlot._debugIsLocationValid());
2136 newSlot._moveChild(child, oldSlot);
2137 }
2138
2139 @override
2140 void removeRenderObjectChild(RenderBox child, _OverlayEntryLocation? slot) {
2141 if (slot == null) {
2142 renderObject.child = null;
2143 return;
2144 }
2145 assert(renderObject._deferredLayoutChild == child);
2146 slot._removeChild(child as _RenderDeferredLayoutBox);
2147 renderObject._deferredLayoutChild = null;
2148 }
2149
2150 @override
2151 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2152 super.debugFillProperties(properties);
2153 properties.add(DiagnosticsProperty<Element>('child', _child, defaultValue: null));
2154 properties.add(DiagnosticsProperty<Element>('overlayChild', _overlayChild, defaultValue: null));
2155 properties.add(DiagnosticsProperty<Object>('overlayLocation', _overlayChild?.slot, defaultValue: null));
2156 }
2157}
2158
2159class _DeferredLayout extends SingleChildRenderObjectWidget {
2160 const _DeferredLayout({
2161 // This widget must not be given a key: we currently do not support
2162 // reparenting between the overlayChild and child.
2163 required Widget child,
2164 }) : super(child: child);
2165
2166 _RenderLayoutSurrogateProxyBox getLayoutParent(BuildContext context) {
2167 return context.findAncestorRenderObjectOfType<_RenderLayoutSurrogateProxyBox>()!;
2168 }
2169
2170 @override
2171 _RenderDeferredLayoutBox createRenderObject(BuildContext context) {
2172 final _RenderLayoutSurrogateProxyBox parent = getLayoutParent(context);
2173 final _RenderDeferredLayoutBox renderObject = _RenderDeferredLayoutBox(parent);
2174 parent._deferredLayoutChild = renderObject;
2175 return renderObject;
2176 }
2177
2178 @override
2179 void updateRenderObject(BuildContext context, _RenderDeferredLayoutBox renderObject) {
2180 assert(renderObject._layoutSurrogate == getLayoutParent(context));
2181 assert(getLayoutParent(context)._deferredLayoutChild == renderObject);
2182 }
2183}
2184
2185// A `RenderProxyBox` that defers its layout until its `_layoutSurrogate` (which
2186// is not necessarily an ancestor of this RenderBox, but shares at least one
2187// `_RenderTheater` ancestor with this RenderBox) is laid out.
2188//
2189// This `RenderObject` must be a child of a `_RenderTheater`. It guarantees that:
2190//
2191// 1. It's a relayout boundary, and `markParentNeedsLayout` is overridden such
2192// that it never dirties its `_RenderTheater`.
2193//
2194// 2. Its `layout` implementation is overridden such that `performLayout` does
2195// not do anything when its called from `layout`, preventing the parent
2196// `_RenderTheater` from laying out this subtree prematurely (but this
2197// `RenderObject` may still be resized). Instead, `markNeedsLayout` will be
2198// called from within `layout` to schedule a layout update for this relayout
2199// boundary when needed.
2200//
2201// 3. When invoked from `PipelineOwner.flushLayout`, or
2202// `_layoutSurrogate.performLayout`, this `RenderObject` behaves like an
2203// `Overlay` that has only one entry.
2204final class _RenderDeferredLayoutBox extends RenderProxyBox with _RenderTheaterMixin, LinkedListEntry<_RenderDeferredLayoutBox> {
2205 _RenderDeferredLayoutBox(this._layoutSurrogate);
2206
2207 StackParentData get stackParentData => parentData! as StackParentData;
2208 final _RenderLayoutSurrogateProxyBox _layoutSurrogate;
2209
2210 @override
2211 Iterable<RenderBox> _childrenInPaintOrder() {
2212 final RenderBox? child = this.child;
2213 return child == null
2214 ? const Iterable<RenderBox>.empty()
2215 : Iterable<RenderBox>.generate(1, (int i) => child);
2216 }
2217 @override
2218 Iterable<RenderBox> _childrenInHitTestOrder() => _childrenInPaintOrder();
2219
2220 @override
2221 _RenderTheater get theater {
2222 final RenderObject? parent = this.parent;
2223 return parent is _RenderTheater
2224 ? parent
2225 : throw FlutterError('$parent of $this is not a _RenderTheater');
2226 }
2227
2228 @override
2229 void redepthChildren() {
2230 _layoutSurrogate.redepthChild(this);
2231 super.redepthChildren();
2232 }
2233
2234 bool _callingMarkParentNeedsLayout = false;
2235 @override
2236 void markParentNeedsLayout() {
2237 // No re-entrant calls.
2238 if (_callingMarkParentNeedsLayout) {
2239 return;
2240 }
2241 _callingMarkParentNeedsLayout = true;
2242 markNeedsLayout();
2243 _layoutSurrogate.markNeedsLayout();
2244 _callingMarkParentNeedsLayout = false;
2245 }
2246
2247 @override
2248 bool get sizedByParent => true;
2249
2250 bool _needsLayout = true;
2251 @override
2252 void markNeedsLayout() {
2253 _needsLayout = true;
2254 super.markNeedsLayout();
2255 }
2256
2257 @override
2258 RenderObject? get debugLayoutParent => _layoutSurrogate;
2259
2260 void layoutByLayoutSurrogate() {
2261 assert(!_theaterDoingThisLayout);
2262 final _RenderTheater? theater = parent as _RenderTheater?;
2263 if (theater == null || !attached) {
2264 assert(false, '$this is not attached to parent');
2265 return;
2266 }
2267 super.layout(BoxConstraints.tight(theater.constraints.biggest));
2268 }
2269
2270 bool _theaterDoingThisLayout = false;
2271 @override
2272 void layout(Constraints constraints, { bool parentUsesSize = false }) {
2273 assert(_needsLayout == debugNeedsLayout);
2274 // Only _RenderTheater calls this implementation.
2275 assert(parent != null);
2276 final bool scheduleDeferredLayout = _needsLayout || this.constraints != constraints;
2277 assert(!_theaterDoingThisLayout);
2278 _theaterDoingThisLayout = true;
2279 super.layout(constraints, parentUsesSize: parentUsesSize);
2280 assert(_theaterDoingThisLayout);
2281 _theaterDoingThisLayout = false;
2282 _needsLayout = false;
2283 assert(!debugNeedsLayout);
2284 if (scheduleDeferredLayout) {
2285 final _RenderTheater parent = this.parent! as _RenderTheater;
2286 // Invoking markNeedsLayout as a layout callback allows this node to be
2287 // merged back to the `PipelineOwner`'s dirty list in the right order, if
2288 // it's not already dirty. Otherwise this may cause some dirty descendants
2289 // to performLayout a second time.
2290 parent.invokeLayoutCallback((BoxConstraints constraints) { markNeedsLayout(); });
2291 }
2292 }
2293
2294 @override
2295 void performResize() {
2296 size = constraints.biggest;
2297 }
2298
2299 bool _debugMutationsLocked = false;
2300 @override
2301 void performLayout() {
2302 assert(!_debugMutationsLocked);
2303 if (_theaterDoingThisLayout) {
2304 _needsLayout = false;
2305 return;
2306 }
2307 assert(() {
2308 _debugMutationsLocked = true;
2309 return true;
2310 }());
2311 // This method is directly being invoked from `PipelineOwner.flushLayout`,
2312 // or from `_layoutSurrogate`'s performLayout.
2313 assert(parent != null);
2314 final RenderBox? child = this.child;
2315 if (child == null) {
2316 _needsLayout = false;
2317 return;
2318 }
2319 assert(constraints.isTight);
2320 layoutChild(child, constraints);
2321 assert(() {
2322 _debugMutationsLocked = false;
2323 return true;
2324 }());
2325 _needsLayout = false;
2326 }
2327
2328 @override
2329 void applyPaintTransform(RenderBox child, Matrix4 transform) {
2330 final BoxParentData childParentData = child.parentData! as BoxParentData;
2331 final Offset offset = childParentData.offset;
2332 transform.translate(offset.dx, offset.dy);
2333 }
2334}
2335
2336// A RenderProxyBox that makes sure its `deferredLayoutChild` has a greater
2337// depth than itself.
2338class _RenderLayoutSurrogateProxyBox extends RenderProxyBox {
2339 _RenderDeferredLayoutBox? _deferredLayoutChild;
2340
2341 @override
2342 void redepthChildren() {
2343 super.redepthChildren();
2344 final _RenderDeferredLayoutBox? child = _deferredLayoutChild;
2345 // If child is not attached, this method will be invoked by child's real
2346 // parent when it's attached.
2347 if (child != null && child.attached) {
2348 assert(child.attached);
2349 redepthChild(child);
2350 }
2351 }
2352
2353 @override
2354 void performLayout() {
2355 super.performLayout();
2356 // Try to layout `_deferredLayoutChild` here now that its configuration
2357 // and constraints are up-to-date. Additionally, during the very first
2358 // layout, this makes sure that _deferredLayoutChild is reachable via tree
2359 // walk.
2360 _deferredLayoutChild?.layoutByLayoutSurrogate();
2361 }
2362
2363 @override
2364 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
2365 super.visitChildrenForSemantics(visitor);
2366 final _RenderDeferredLayoutBox? deferredChild = _deferredLayoutChild;
2367 if (deferredChild != null) {
2368 visitor(deferredChild);
2369 }
2370 }
2371}
2372