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/material.dart';
6///
7/// @docImport 'basic.dart';
8library;
9
10import 'dart:collection';
11import 'dart:ui'
12 show FlutterView, SemanticsUpdate, ViewFocusDirection, ViewFocusEvent, ViewFocusState;
13
14import 'package:flutter/foundation.dart';
15import 'package:flutter/rendering.dart';
16
17import 'binding.dart';
18import 'focus_manager.dart';
19import 'focus_scope.dart';
20import 'focus_traversal.dart';
21import 'framework.dart';
22import 'lookup_boundary.dart';
23import 'media_query.dart';
24
25/// Bootstraps a render tree that is rendered into the provided [FlutterView].
26///
27/// The content rendered into that view is determined by the provided [child].
28/// Descendants within the same [LookupBoundary] can look up the view they are
29/// rendered into via [View.of] and [View.maybeOf].
30///
31/// The provided [child] is wrapped in a [MediaQuery] constructed from the given
32/// [view], a [FocusScope], and a [RawView] widget.
33///
34/// For most use cases, using [MediaQuery.of], or its associated "...Of" methods
35/// are a more appropriate way of obtaining the information that a [FlutterView]
36/// exposes. For example, using [MediaQuery.sizeOf] will expose the _logical_
37/// device size ([MediaQueryData.size]) rather than the physical size
38/// ([FlutterView.physicalSize]). Similarly, while [FlutterView.padding] conveys
39/// the information from the operating system, the [MediaQueryData.padding]
40/// attribute (obtained from [MediaQuery.paddingOf]) further adjusts this
41/// information to be aware of the context of the widget; e.g. the [Scaffold]
42/// widget adjusts the values for its various children.
43///
44/// Each [FlutterView] can be associated with at most one [View] or [RawView]
45/// widget in the widget tree. Two or more [View] or [RawView] widgets
46/// configured with the same [FlutterView] must never exist within the same
47/// widget tree at the same time. This limitation is enforced by a
48/// [GlobalObjectKey] that derives its identity from the [view] provided to this
49/// widget.
50///
51/// Since the [View] widget bootstraps its own independent render tree using its
52/// embedded [RawView], neither it nor any of its descendants will insert a
53/// [RenderObject] into an existing render tree. Therefore, the [View] widget
54/// can only be used in those parts of the widget tree where it is not required
55/// to participate in the construction of the surrounding render tree. In other
56/// words, the widget may only be used in a non-rendering zone of the widget
57/// tree (see [WidgetsBinding] for a definition of rendering and non-rendering
58/// zones).
59///
60/// In practical terms, the widget is typically used at the root of the widget
61/// tree outside of any other [View] or [RawView] widget, as a child of a
62/// [ViewCollection] widget, or in the [ViewAnchor.view] slot of a [ViewAnchor]
63/// widget. It is not required to be a direct child, though, since other
64/// non-[RenderObjectWidget]s (e.g. [InheritedWidget]s, [Builder]s, or
65/// [StatefulWidget]s/[StatelessWidget]s that only produce
66/// non-[RenderObjectWidget]s) are allowed to be present between those widgets
67/// and the [View] widget.
68///
69/// See also:
70///
71/// * [RawView], the workhorse that [View] uses to create the render tree, but
72/// without the [MediaQuery] and [FocusScope] that [View] adds.
73/// * [Element.debugExpectsRenderObjectForSlot], which defines whether a [View]
74/// widget is allowed in a given child slot.
75class View extends StatefulWidget {
76 /// Create a [View] widget to bootstrap a render tree that is rendered into
77 /// the provided [FlutterView].
78 ///
79 /// The content rendered into that [view] is determined by the given [child]
80 /// widget.
81 View({
82 super.key,
83 required this.view,
84 @Deprecated(
85 'Do not use. '
86 'This parameter only exists to implement the deprecated RendererBinding.pipelineOwner property until it is removed. '
87 'This feature was deprecated after v3.10.0-12.0.pre.',
88 )
89 PipelineOwner? deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner,
90 @Deprecated(
91 'Do not use. '
92 'This parameter only exists to implement the deprecated RendererBinding.renderView property until it is removed. '
93 'This feature was deprecated after v3.10.0-12.0.pre.',
94 )
95 RenderView? deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView,
96 required this.child,
97 }) : _deprecatedPipelineOwner = deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner,
98 _deprecatedRenderView = deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView,
99 assert(
100 (deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner == null) ==
101 (deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null),
102 ),
103 assert(
104 deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null ||
105 deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView.flutterView == view,
106 );
107
108 /// The [FlutterView] into which [child] is drawn.
109 final FlutterView view;
110
111 /// The widget below this widget in the tree, which will be drawn into the
112 /// [view].
113 ///
114 /// {@macro flutter.widgets.ProxyWidget.child}
115 final Widget child;
116
117 final PipelineOwner? _deprecatedPipelineOwner;
118 final RenderView? _deprecatedRenderView;
119
120 /// Returns the [FlutterView] that the provided `context` will render into.
121 ///
122 /// Returns null if the `context` is not associated with a [FlutterView].
123 ///
124 /// The method creates a dependency on the `context`, which will be informed
125 /// when the identity of the [FlutterView] changes (i.e. the `context` is
126 /// moved to render into a different [FlutterView] then before). The context
127 /// will not be informed when the _properties_ on the [FlutterView] itself
128 /// change their values. To access the property values of a [FlutterView] it
129 /// is best practice to use [MediaQuery.maybeOf] instead, which will ensure
130 /// that the `context` is informed when the view properties change.
131 ///
132 /// See also:
133 ///
134 /// * [View.of], which throws instead of returning null if no [FlutterView]
135 /// is found.
136 static FlutterView? maybeOf(BuildContext context) {
137 return LookupBoundary.dependOnInheritedWidgetOfExactType<_ViewScope>(context)?.view;
138 }
139
140 /// Returns the [FlutterView] that the provided `context` will render into.
141 ///
142 /// Throws if the `context` is not associated with a [FlutterView].
143 ///
144 /// The method creates a dependency on the `context`, which will be informed
145 /// when the identity of the [FlutterView] changes (i.e. the `context` is
146 /// moved to render into a different [FlutterView] then before). The context
147 /// will not be informed when the _properties_ on the [FlutterView] itself
148 /// change their values. To access the property values of a [FlutterView]
149 /// prefer using the access methods on [MediaQuery], such as
150 /// [MediaQuery.sizeOf], which will ensure that the `context` is informed when
151 /// the view properties change.
152 ///
153 /// See also:
154 ///
155 /// * [View.maybeOf], which throws instead of returning null if no
156 /// [FlutterView] is found.
157 static FlutterView of(BuildContext context) {
158 final FlutterView? result = maybeOf(context);
159 assert(() {
160 if (result == null) {
161 final bool hiddenByBoundary =
162 LookupBoundary.debugIsHidingAncestorWidgetOfExactType<_ViewScope>(context);
163 final List<DiagnosticsNode> information = <DiagnosticsNode>[
164 if (hiddenByBoundary) ...<DiagnosticsNode>[
165 ErrorSummary(
166 'View.of() was called with a context that does not have access to a View widget.',
167 ),
168 ErrorDescription(
169 'The context provided to View.of() does have a View widget ancestor, but it is hidden by a LookupBoundary.',
170 ),
171 ] else ...<DiagnosticsNode>[
172 ErrorSummary(
173 'View.of() was called with a context that does not contain a View widget.',
174 ),
175 ErrorDescription(
176 'No View widget ancestor could be found starting from the context that was passed to View.of().',
177 ),
178 ],
179 ErrorDescription(
180 'The context used was:\n'
181 ' $context',
182 ),
183 ErrorHint('This usually means that the provided context is not associated with a View.'),
184 ];
185 throw FlutterError.fromParts(information);
186 }
187 return true;
188 }());
189 return result!;
190 }
191
192 /// Returns the [PipelineOwner] parent to which a child [View] should attach
193 /// its [PipelineOwner] to.
194 ///
195 /// If `context` has a [View] ancestor, it returns the [PipelineOwner]
196 /// responsible for managing the render tree of that view. If there is no
197 /// [View] ancestor, [RendererBinding.rootPipelineOwner] is returned instead.
198 static PipelineOwner pipelineOwnerOf(BuildContext context) {
199 return context.dependOnInheritedWidgetOfExactType<_PipelineOwnerScope>()?.pipelineOwner ??
200 RendererBinding.instance.rootPipelineOwner;
201 }
202
203 @override
204 State<View> createState() => _ViewState();
205}
206
207class _ViewState extends State<View> with WidgetsBindingObserver {
208 final FocusScopeNode _scopeNode = FocusScopeNode(debugLabel: kReleaseMode ? null : 'View Scope');
209 final FocusTraversalPolicy _policy = ReadingOrderTraversalPolicy();
210 bool _viewHasFocus = false;
211
212 @override
213 void initState() {
214 super.initState();
215 WidgetsBinding.instance.addObserver(this);
216 _scopeNode.addListener(_scopeFocusChangeListener);
217 }
218
219 @override
220 void dispose() {
221 WidgetsBinding.instance.removeObserver(this);
222 _scopeNode.removeListener(_scopeFocusChangeListener);
223 _scopeNode.dispose();
224 super.dispose();
225 }
226
227 void _scopeFocusChangeListener() {
228 if (_viewHasFocus == _scopeNode.hasFocus || !_scopeNode.hasFocus) {
229 return;
230 }
231 // Scope has gained focus, and it doesn't match the view focus, so inform
232 // the view so it knows to change its focus.
233 WidgetsBinding.instance.platformDispatcher.requestViewFocusChange(
234 direction: ViewFocusDirection.forward,
235 state: ViewFocusState.focused,
236 viewId: widget.view.viewId,
237 );
238 }
239
240 @override
241 void didChangeViewFocus(ViewFocusEvent event) {
242 _viewHasFocus = switch (event.state) {
243 ViewFocusState.focused => event.viewId == widget.view.viewId,
244 ViewFocusState.unfocused => false,
245 };
246 if (event.viewId != widget.view.viewId) {
247 return;
248 }
249 FocusNode nextFocus;
250 switch (event.state) {
251 case ViewFocusState.focused:
252 switch (event.direction) {
253 case ViewFocusDirection.forward:
254 nextFocus = _policy.findFirstFocus(_scopeNode, ignoreCurrentFocus: true) ?? _scopeNode;
255 case ViewFocusDirection.backward:
256 nextFocus = _policy.findLastFocus(_scopeNode, ignoreCurrentFocus: true);
257 case ViewFocusDirection.undefined:
258 nextFocus = _scopeNode;
259 }
260 nextFocus.requestFocus();
261 case ViewFocusState.unfocused:
262 // Focusing on the root scope node will "park" the focus, so that no
263 // descendant node will be given focus, and there's no widget that can
264 // receive keyboard events.
265 FocusManager.instance.rootScope.requestScopeFocus();
266 }
267 }
268
269 @override
270 Widget build(BuildContext context) {
271 return RawView(
272 view: widget.view,
273 deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: widget._deprecatedPipelineOwner,
274 deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: widget._deprecatedRenderView,
275 child: MediaQuery.fromView(
276 view: widget.view,
277 child: FocusTraversalGroup(
278 policy: _policy,
279 child: FocusScope.withExternalFocusNode(
280 includeSemantics: false,
281 focusScopeNode: _scopeNode,
282 child: widget.child,
283 ),
284 ),
285 ),
286 );
287 }
288}
289
290/// The lower level workhorse widget for [View] that bootstraps a render tree
291/// for a view.
292///
293/// Typically, the [View] widget is used instead of a [RawView] widget to create
294/// a view, since, in addition to creating a view, it also adds some useful
295/// widgets, such as a [MediaQuery] and [FocusScope]. The [RawView] widget is
296/// only used directly if it is not desirable to have these additional widgets
297/// around the resulting widget tree. The [View] widget uses the [RawView]
298/// widget internally to manage its [FlutterView].
299///
300/// This widget can be used at the root of the widget tree outside of any other
301/// [View] or [RawView] widget, as a child to a [ViewCollection], or in the
302/// [ViewAnchor.view] slot of a [ViewAnchor] widget. It is not required to be a
303/// direct child of those widgets; other non-[RenderObjectWidget]s may appear in
304/// between the two (such as an [InheritedWidget]).
305///
306/// Each [FlutterView] can be associated with at most one [View] or [RawView]
307/// widget in the widget tree. Two or more [View] or [RawView] widgets
308/// configured with the same [FlutterView] must never exist within the same
309/// widget tree at the same time. This limitation is enforced by a
310/// [GlobalObjectKey] that derives its identity from the [view] provided to this
311/// widget.
312///
313/// Since the [RawView] widget bootstraps its own independent render tree,
314/// neither it nor any of its descendants will insert a [RenderObject] into an
315/// existing render tree. Therefore, the [RawView] widget can only be used in
316/// those parts of the widget tree where it is not required to participate in
317/// the construction of the surrounding render tree. In other words, the widget
318/// may only be used in a non-rendering zone of the widget tree (see
319/// [WidgetsBinding] for a definition of rendering and non-rendering zones).
320///
321/// To find the [FlutterView] associated with a [BuildContext], use [View.of] or
322/// [View.maybeOf], even if the view was created using [RawView] instead of
323/// [View].
324///
325/// See also:
326///
327/// * [View] for a higher level interface that also sets up a [MediaQuery] and
328/// [FocusScope] for the view's widget tree.
329class RawView extends StatelessWidget {
330 /// Creates a [RawView] widget.
331 RawView({
332 super.key,
333 required this.view,
334 @Deprecated(
335 'Do not use. '
336 'This parameter only exists to implement the deprecated RendererBinding.pipelineOwner property until it is removed. '
337 'This feature was deprecated after v3.10.0-12.0.pre.',
338 )
339 PipelineOwner? deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner,
340 @Deprecated(
341 'Do not use. '
342 'This parameter only exists to implement the deprecated RendererBinding.renderView property until it is removed. '
343 'This feature was deprecated after v3.10.0-12.0.pre.',
344 )
345 RenderView? deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView,
346 required this.child,
347 }) : _deprecatedPipelineOwner = deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner,
348 _deprecatedRenderView = deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView,
349 assert(
350 (deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner == null) ==
351 (deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null),
352 ),
353 assert(
354 deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null ||
355 deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView.flutterView == view,
356 );
357
358 /// The [FlutterView] into which [child] is drawn.
359 final FlutterView view;
360
361 /// The widget below this widget in the tree, which will be drawn into the
362 /// [view].
363 ///
364 /// {@macro flutter.widgets.ProxyWidget.child}
365 final Widget child;
366
367 final PipelineOwner? _deprecatedPipelineOwner;
368 final RenderView? _deprecatedRenderView;
369
370 @override
371 Widget build(BuildContext context) {
372 return _RawViewInternal(
373 view: view,
374 deprecatedPipelineOwner: _deprecatedPipelineOwner,
375 deprecatedRenderView: _deprecatedRenderView,
376 builder: (BuildContext context, PipelineOwner owner) {
377 return _ViewScope(
378 view: view,
379 child: _PipelineOwnerScope(pipelineOwner: owner, child: child),
380 );
381 },
382 );
383 }
384}
385
386/// A builder for the content [Widget] of a [_RawViewInternal].
387///
388/// The widget returned by the builder defines the content that is drawn into
389/// the [FlutterView] configured on the [_RawViewInternal].
390///
391/// The builder is given the [PipelineOwner] that the [_RawViewInternal] uses to
392/// manage its render tree. Typical builder implementations make that pipeline
393/// owner available as an attachment point for potential child views.
394///
395/// Used by [_RawViewInternal.builder].
396typedef _RawViewContentBuilder = Widget Function(BuildContext context, PipelineOwner owner);
397
398/// The workhorse behind the [RawView] widget that actually bootstraps a render
399/// tree.
400///
401/// It instantiates the [RenderView] as the root of that render tree and adds it
402/// to the [RendererBinding] via [RendererBinding.addRenderView]. It also owns
403/// the [PipelineOwner] that manages this render tree and adds it as a child to
404/// the surrounding parent [PipelineOwner] obtained with [View.pipelineOwnerOf].
405/// This ensures that the render tree bootstrapped by this widget participates
406/// properly in frame production and hit testing.
407class _RawViewInternal extends RenderObjectWidget {
408 /// Create a [_RawViewInternal] widget to bootstrap a render tree that is
409 /// rendered into the provided [FlutterView].
410 ///
411 /// The content rendered into that [view] is determined by the [Widget]
412 /// returned by [builder].
413 _RawViewInternal({
414 required this.view,
415 required PipelineOwner? deprecatedPipelineOwner,
416 required RenderView? deprecatedRenderView,
417 required this.builder,
418 }) : _deprecatedPipelineOwner = deprecatedPipelineOwner,
419 _deprecatedRenderView = deprecatedRenderView,
420 assert(deprecatedRenderView == null || deprecatedRenderView.flutterView == view),
421 // TODO(goderbauer): Replace this with GlobalObjectKey(view) when the deprecated properties are removed.
422 super(key: _DeprecatedRawViewKey(view, deprecatedPipelineOwner, deprecatedRenderView));
423
424 /// The [FlutterView] into which the [Widget] returned by [builder] is drawn.
425 final FlutterView view;
426
427 /// Determines the content [Widget] that is drawn into the [view].
428 ///
429 /// The [builder] is given the [PipelineOwner] responsible for the render tree
430 /// bootstrapped by this widget and typically makes it available as an
431 /// attachment point for potential child views.
432 final _RawViewContentBuilder builder;
433
434 final PipelineOwner? _deprecatedPipelineOwner;
435 final RenderView? _deprecatedRenderView;
436
437 @override
438 RenderObjectElement createElement() => _RawViewElement(this);
439
440 @override
441 RenderObject createRenderObject(BuildContext context) {
442 return _deprecatedRenderView ?? RenderView(view: view);
443 }
444
445 // No need to implement updateRenderObject: RawView uses the view as a
446 // GlobalKey, so we never need to update the RenderObject with a new view.
447}
448
449class _RawViewElement extends RenderTreeRootElement {
450 _RawViewElement(super.widget);
451
452 late final PipelineOwner _pipelineOwner = PipelineOwner(
453 onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
454 onSemanticsUpdate: _handleSemanticsUpdate,
455 onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
456 );
457
458 PipelineOwner get _effectivePipelineOwner =>
459 (widget as _RawViewInternal)._deprecatedPipelineOwner ?? _pipelineOwner;
460
461 void _handleSemanticsOwnerCreated() {
462 (_effectivePipelineOwner.rootNode as RenderView?)?.scheduleInitialSemantics();
463 }
464
465 void _handleSemanticsOwnerDisposed() {
466 (_effectivePipelineOwner.rootNode as RenderView?)?.clearSemantics();
467 }
468
469 void _handleSemanticsUpdate(SemanticsUpdate update) {
470 (widget as _RawViewInternal).view.updateSemantics(update);
471 }
472
473 @override
474 RenderView get renderObject => super.renderObject as RenderView;
475
476 Element? _child;
477
478 void _updateChild() {
479 try {
480 final Widget child = (widget as _RawViewInternal).builder(this, _effectivePipelineOwner);
481 _child = updateChild(_child, child, null);
482 } catch (e, stack) {
483 final FlutterErrorDetails details = FlutterErrorDetails(
484 exception: e,
485 stack: stack,
486 library: 'widgets library',
487 context: ErrorDescription('building $this'),
488 informationCollector: !kDebugMode
489 ? null
490 : () => <DiagnosticsNode>[DiagnosticsDebugCreator(DebugCreator(this))],
491 );
492 FlutterError.reportError(details);
493 final Widget error = ErrorWidget.builder(details);
494 _child = updateChild(null, error, slot);
495 }
496 }
497
498 @override
499 void mount(Element? parent, Object? newSlot) {
500 super.mount(parent, newSlot);
501 assert(_effectivePipelineOwner.rootNode == null);
502 _effectivePipelineOwner.rootNode = renderObject;
503 _attachView();
504 _updateChild();
505 renderObject.prepareInitialFrame();
506 if (_effectivePipelineOwner.semanticsOwner != null) {
507 renderObject.scheduleInitialSemantics();
508 }
509 }
510
511 PipelineOwner? _parentPipelineOwner; // Is null if view is currently not attached.
512
513 void _attachView([PipelineOwner? parentPipelineOwner]) {
514 assert(_parentPipelineOwner == null);
515 parentPipelineOwner ??= View.pipelineOwnerOf(this);
516 parentPipelineOwner.adoptChild(_effectivePipelineOwner);
517 RendererBinding.instance.addRenderView(renderObject);
518 _parentPipelineOwner = parentPipelineOwner;
519 }
520
521 void _detachView() {
522 final PipelineOwner? parentPipelineOwner = _parentPipelineOwner;
523 if (parentPipelineOwner != null) {
524 RendererBinding.instance.removeRenderView(renderObject);
525 parentPipelineOwner.dropChild(_effectivePipelineOwner);
526 _parentPipelineOwner = null;
527 }
528 }
529
530 @override
531 void didChangeDependencies() {
532 super.didChangeDependencies();
533 if (_parentPipelineOwner == null) {
534 return;
535 }
536 final PipelineOwner newParentPipelineOwner = View.pipelineOwnerOf(this);
537 if (newParentPipelineOwner != _parentPipelineOwner) {
538 _detachView();
539 _attachView(newParentPipelineOwner);
540 }
541 }
542
543 @override
544 void performRebuild() {
545 super.performRebuild();
546 _updateChild();
547 }
548
549 @override
550 void activate() {
551 super.activate();
552 assert(_effectivePipelineOwner.rootNode == null);
553 _effectivePipelineOwner.rootNode = renderObject;
554 _attachView();
555 }
556
557 @override
558 void deactivate() {
559 _detachView();
560 assert(_effectivePipelineOwner.rootNode == renderObject);
561 _effectivePipelineOwner.rootNode = null; // To satisfy the assert in the super class.
562 super.deactivate();
563 }
564
565 @override
566 void update(_RawViewInternal newWidget) {
567 super.update(newWidget);
568 _updateChild();
569 }
570
571 @override
572 void visitChildren(ElementVisitor visitor) {
573 if (_child != null) {
574 visitor(_child!);
575 }
576 }
577
578 @override
579 void forgetChild(Element child) {
580 assert(child == _child);
581 _child = null;
582 super.forgetChild(child);
583 }
584
585 @override
586 void insertRenderObjectChild(RenderBox child, Object? slot) {
587 assert(slot == null);
588 assert(renderObject.debugValidateChild(child));
589 renderObject.child = child;
590 }
591
592 @override
593 void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
594 assert(false);
595 }
596
597 @override
598 void removeRenderObjectChild(RenderObject child, Object? slot) {
599 assert(slot == null);
600 assert(renderObject.child == child);
601 renderObject.child = null;
602 }
603
604 @override
605 void unmount() {
606 if (_effectivePipelineOwner != (widget as _RawViewInternal)._deprecatedPipelineOwner) {
607 _effectivePipelineOwner.dispose();
608 }
609 super.unmount();
610 }
611}
612
613class _ViewScope extends InheritedWidget {
614 const _ViewScope({required this.view, required super.child});
615
616 final FlutterView view;
617
618 @override
619 bool updateShouldNotify(_ViewScope oldWidget) => view != oldWidget.view;
620}
621
622class _PipelineOwnerScope extends InheritedWidget {
623 const _PipelineOwnerScope({required this.pipelineOwner, required super.child});
624
625 final PipelineOwner pipelineOwner;
626
627 @override
628 bool updateShouldNotify(_PipelineOwnerScope oldWidget) =>
629 pipelineOwner != oldWidget.pipelineOwner;
630}
631
632class _MultiChildComponentWidget extends Widget {
633 const _MultiChildComponentWidget({
634 super.key,
635 List<Widget> views = const <Widget>[],
636 Widget? child,
637 }) : _views = views,
638 _child = child;
639
640 // It is up to the subclasses to make the relevant properties public.
641 final List<Widget> _views;
642 final Widget? _child;
643
644 @override
645 Element createElement() => _MultiChildComponentElement(this);
646}
647
648/// A collection of sibling [View]s.
649///
650/// This widget can only be used in places were a [View] widget is allowed, i.e.
651/// in a non-rendering zone of the widget tree. In practical terms, it can be
652/// used at the root of the widget tree outside of any [View] widget, as a child
653/// to a another [ViewCollection], or in the [ViewAnchor.view] slot of a
654/// [ViewAnchor] widget. It is not required to be a direct child of those
655/// widgets; other non-[RenderObjectWidget]s may appear in between the two (such
656/// as an [InheritedWidget]).
657///
658/// Similarly, the [views] children of this widget must be [View]s, but they
659/// may be wrapped in additional non-[RenderObjectWidget]s (e.g.
660/// [InheritedWidget]s).
661///
662/// See also:
663///
664/// * [WidgetsBinding] for an explanation of rendering and non-rendering zones.
665class ViewCollection extends _MultiChildComponentWidget {
666 /// Creates a [ViewCollection] widget.
667 const ViewCollection({super.key, required super.views});
668
669 /// The [View] descendants of this widget.
670 ///
671 /// The [View]s may be wrapped in other non-[RenderObjectWidget]s (e.g.
672 /// [InheritedWidget]s). However, no [RenderObjectWidget] is allowed to appear
673 /// between the [ViewCollection] and the next [View] widget.
674 List<Widget> get views => _views;
675}
676
677/// Decorates a [child] widget with a side [View].
678///
679/// This widget must have a [View] ancestor, into which the [child] widget
680/// is rendered.
681///
682/// Typically, a [View] or [ViewCollection] widget is used in the [view] slot to
683/// define the content of the side view(s). Those widgets may be wrapped in
684/// other non-[RenderObjectWidget]s (e.g. [InheritedWidget]s). However, no
685/// [RenderObjectWidget] is allowed to appear between the [ViewAnchor] and the
686/// next [View] widget in the [view] slot. The widgets in the [view] slot have
687/// access to all [InheritedWidget]s above the [ViewAnchor] in the tree.
688///
689/// In technical terms, the [ViewAnchor] can only be used in a rendering zone of
690/// the widget tree and the [view] slot marks the start of a new non-rendering
691/// zone (see [WidgetsBinding] for a definition of these zones). Typically,
692/// it is occupied by a [View] widget, which will start a new rendering zone.
693///
694/// {@template flutter.widgets.ViewAnchor}
695/// An example use case for this widget is a tooltip for a button. The tooltip
696/// should be able to extend beyond the bounds of the main view. For this, the
697/// tooltip can be implemented as a separate [View], which is anchored to the
698/// button in the main view by wrapping that button with a [ViewAnchor]. In this
699/// example, the [view] slot is configured with the tooltip [View] and the
700/// [child] is the button widget rendered into the surrounding view.
701/// {@endtemplate}
702class ViewAnchor extends StatelessWidget {
703 /// Creates a [ViewAnchor] widget.
704 const ViewAnchor({super.key, this.view, required this.child});
705
706 /// The widget that defines the view anchored to this widget.
707 ///
708 /// Typically, a [View] or [ViewCollection] widget is used, which may be
709 /// wrapped in other non-[RenderObjectWidget]s (e.g. [InheritedWidget]s).
710 ///
711 /// {@macro flutter.widgets.ViewAnchor}
712 final Widget? view;
713
714 /// The widget below this widget in the tree.
715 ///
716 /// It is rendered into the surrounding view, not in the view defined by
717 /// [view].
718 ///
719 /// {@macro flutter.widgets.ViewAnchor}
720 final Widget child;
721
722 @override
723 Widget build(BuildContext context) {
724 return _MultiChildComponentWidget(
725 views: <Widget>[if (view != null) LookupBoundary(child: view!)],
726 child: child,
727 );
728 }
729}
730
731class _MultiChildComponentElement extends Element {
732 _MultiChildComponentElement(super.widget);
733
734 List<Element> _viewElements = <Element>[];
735 final Set<Element> _forgottenViewElements = HashSet<Element>();
736 Element? _childElement;
737
738 bool _debugAssertChildren() {
739 final _MultiChildComponentWidget typedWidget = widget as _MultiChildComponentWidget;
740 // Each view widget must have a corresponding element.
741 assert(_viewElements.length == typedWidget._views.length);
742 // Iff there is a child widget, it must have a corresponding element.
743 assert((_childElement == null) == (typedWidget._child == null));
744 // The child element is not also a view element.
745 assert(!_viewElements.contains(_childElement));
746 return true;
747 }
748
749 @override
750 void attachRenderObject(Object? newSlot) {
751 super.attachRenderObject(newSlot);
752 assert(_debugCheckMustAttachRenderObject(newSlot));
753 }
754
755 @override
756 void mount(Element? parent, Object? newSlot) {
757 super.mount(parent, newSlot);
758 assert(_debugCheckMustAttachRenderObject(newSlot));
759 assert(_viewElements.isEmpty);
760 assert(_childElement == null);
761 rebuild();
762 assert(_debugAssertChildren());
763 }
764
765 @override
766 void updateSlot(Object? newSlot) {
767 super.updateSlot(newSlot);
768 assert(_debugCheckMustAttachRenderObject(newSlot));
769 }
770
771 bool _debugCheckMustAttachRenderObject(Object? slot) {
772 // Check only applies in the ViewCollection configuration.
773 if (!kDebugMode || (widget as _MultiChildComponentWidget)._child != null) {
774 return true;
775 }
776 bool hasAncestorRenderObjectElement = false;
777 bool ancestorWantsRenderObject = true;
778 visitAncestorElements((Element ancestor) {
779 if (!ancestor.debugExpectsRenderObjectForSlot(slot)) {
780 ancestorWantsRenderObject = false;
781 return false;
782 }
783 if (ancestor is RenderObjectElement) {
784 hasAncestorRenderObjectElement = true;
785 return false;
786 }
787 return true;
788 });
789 if (hasAncestorRenderObjectElement && ancestorWantsRenderObject) {
790 FlutterError.reportError(
791 FlutterErrorDetails(
792 exception: FlutterError.fromParts(<DiagnosticsNode>[
793 ErrorSummary(
794 'The Element for ${toStringShort()} cannot be inserted into slot "$slot" of its ancestor. ',
795 ),
796 ErrorDescription(
797 'The ownership chain for the Element in question was:\n ${debugGetCreatorChain(10)}',
798 ),
799 ErrorDescription(
800 'This Element allows the creation of multiple independent render trees, which cannot '
801 'be attached to an ancestor in an existing render tree. However, an ancestor RenderObject '
802 'is expecting that a child will be attached.',
803 ),
804 ErrorHint(
805 'Try moving the subtree that contains the ${toStringShort()} widget into the '
806 'view property of a ViewAnchor widget or to the root of the widget tree, where '
807 'it is not expected to attach its RenderObject to its ancestor.',
808 ),
809 ]),
810 ),
811 );
812 }
813 return true;
814 }
815
816 @override
817 void update(_MultiChildComponentWidget newWidget) {
818 // Cannot switch from ViewAnchor config to ViewCollection config.
819 assert((newWidget._child == null) == ((widget as _MultiChildComponentWidget)._child == null));
820 super.update(newWidget);
821 rebuild(force: true);
822 assert(_debugAssertChildren());
823 }
824
825 static const Object _viewSlot = Object();
826
827 @override
828 bool debugExpectsRenderObjectForSlot(Object? slot) => slot != _viewSlot;
829
830 @override
831 void performRebuild() {
832 final _MultiChildComponentWidget typedWidget = widget as _MultiChildComponentWidget;
833
834 _childElement = updateChild(_childElement, typedWidget._child, slot);
835
836 final List<Widget> views = typedWidget._views;
837 _viewElements = updateChildren(
838 _viewElements,
839 views,
840 forgottenChildren: _forgottenViewElements,
841 slots: List<Object>.generate(views.length, (_) => _viewSlot),
842 );
843 _forgottenViewElements.clear();
844
845 super.performRebuild(); // clears the dirty flag
846 assert(_debugAssertChildren());
847 }
848
849 @override
850 void forgetChild(Element child) {
851 if (child == _childElement) {
852 _childElement = null;
853 } else {
854 assert(_viewElements.contains(child));
855 assert(!_forgottenViewElements.contains(child));
856 _forgottenViewElements.add(child);
857 }
858 super.forgetChild(child);
859 }
860
861 @override
862 void visitChildren(ElementVisitor visitor) {
863 if (_childElement != null) {
864 visitor(_childElement!);
865 }
866 for (final Element child in _viewElements) {
867 if (!_forgottenViewElements.contains(child)) {
868 visitor(child);
869 }
870 }
871 }
872
873 @override
874 bool get debugDoingBuild => false; // This element does not have a concept of "building".
875
876 @override
877 Element? get renderObjectAttachingChild => _childElement;
878
879 @override
880 List<DiagnosticsNode> debugDescribeChildren() {
881 return <DiagnosticsNode>[
882 if (_childElement != null) _childElement!.toDiagnosticsNode(),
883 for (int i = 0; i < _viewElements.length; i++)
884 _viewElements[i].toDiagnosticsNode(
885 name: 'view ${i + 1}',
886 style: DiagnosticsTreeStyle.offstage,
887 ),
888 ];
889 }
890}
891
892// A special [GlobalKey] to support passing the deprecated
893// [RendererBinding.renderView] and [RendererBinding.pipelineOwner] to the
894// [_RawView]. Will be removed when those deprecated properties are removed.
895@optionalTypeArgs
896class _DeprecatedRawViewKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
897 const _DeprecatedRawViewKey(this.view, this.owner, this.renderView) : super.constructor();
898
899 final FlutterView view;
900 final PipelineOwner? owner;
901 final RenderView? renderView;
902
903 @override
904 bool operator ==(Object other) {
905 if (other.runtimeType != runtimeType) {
906 return false;
907 }
908 return other is _DeprecatedRawViewKey<T> &&
909 identical(other.view, view) &&
910 identical(other.owner, owner) &&
911 identical(other.renderView, renderView);
912 }
913
914 @override
915 int get hashCode => Object.hash(view, owner, renderView);
916
917 @override
918 String toString() => '[_DeprecatedRawViewKey ${describeIdentity(view)}]';
919}
920