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