1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:flutter/rendering.dart';
7
8import 'framework.dart';
9
10/// A bridge from a [RenderObject] to an [Element] tree.
11///
12/// The given container is the [RenderObject] that the [Element] tree should be
13/// inserted into. It must be a [RenderObject] that implements the
14/// [RenderObjectWithChildMixin] protocol. The type argument `T` is the kind of
15/// [RenderObject] that the container expects as its child.
16///
17/// The [RenderObjectToWidgetAdapter] is an alternative to [RootWidget] for
18/// bootstrapping an element tree. Unlike [RootWidget] it requires the
19/// existence of a render tree (the [container]) to attach the element tree to.
20class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
21 /// Creates a bridge from a [RenderObject] to an [Element] tree.
22 RenderObjectToWidgetAdapter({
23 this.child,
24 required this.container,
25 this.debugShortDescription,
26 }) : super(key: GlobalObjectKey(container));
27
28 /// The widget below this widget in the tree.
29 ///
30 /// {@macro flutter.widgets.ProxyWidget.child}
31 final Widget? child;
32
33 /// The [RenderObject] that is the parent of the [Element] created by this widget.
34 final RenderObjectWithChildMixin<T> container;
35
36 /// A short description of this widget used by debugging aids.
37 final String? debugShortDescription;
38
39 @override
40 RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
41
42 @override
43 RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
44
45 @override
46 void updateRenderObject(BuildContext context, RenderObject renderObject) { }
47
48 /// Inflate this widget and actually set the resulting [RenderObject] as the
49 /// child of [container].
50 ///
51 /// If `element` is null, this function will create a new element. Otherwise,
52 /// the given element will have an update scheduled to switch to this widget.
53 RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
54 if (element == null) {
55 owner.lockState(() {
56 element = createElement();
57 assert(element != null);
58 element!.assignOwner(owner);
59 });
60 owner.buildScope(element!, () {
61 element!.mount(null, null);
62 });
63 } else {
64 element._newWidget = this;
65 element.markNeedsBuild();
66 }
67 return element!;
68 }
69
70 @override
71 String toStringShort() => debugShortDescription ?? super.toStringShort();
72}
73
74/// The root of an element tree that is hosted by a [RenderObject].
75///
76/// This element class is the instantiation of a [RenderObjectToWidgetAdapter]
77/// widget. It can be used only as the root of an [Element] tree (it cannot be
78/// mounted into another [Element]; it's parent must be null).
79///
80/// In typical usage, it will be instantiated for a [RenderObjectToWidgetAdapter]
81/// whose container is the [RenderView].
82class RenderObjectToWidgetElement<T extends RenderObject> extends RenderTreeRootElement with RootElementMixin {
83 /// Creates an element that is hosted by a [RenderObject].
84 ///
85 /// The [RenderObject] created by this element is not automatically set as a
86 /// child of the hosting [RenderObject]. To actually attach this element to
87 /// the render tree, call [RenderObjectToWidgetAdapter.attachToRenderTree].
88 RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> super.widget);
89
90 Element? _child;
91
92 static const Object _rootChildSlot = Object();
93
94 @override
95 void visitChildren(ElementVisitor visitor) {
96 if (_child != null) {
97 visitor(_child!);
98 }
99 }
100
101 @override
102 void forgetChild(Element child) {
103 assert(child == _child);
104 _child = null;
105 super.forgetChild(child);
106 }
107
108 @override
109 void mount(Element? parent, Object? newSlot) {
110 assert(parent == null);
111 super.mount(parent, newSlot);
112 _rebuild();
113 assert(_child != null);
114 }
115
116 @override
117 void update(RenderObjectToWidgetAdapter<T> newWidget) {
118 super.update(newWidget);
119 assert(widget == newWidget);
120 _rebuild();
121 }
122
123 // When we are assigned a new widget, we store it here
124 // until we are ready to update to it.
125 Widget? _newWidget;
126
127 @override
128 void performRebuild() {
129 if (_newWidget != null) {
130 // _newWidget can be null if, for instance, we were rebuilt
131 // due to a reassemble.
132 final Widget newWidget = _newWidget!;
133 _newWidget = null;
134 update(newWidget as RenderObjectToWidgetAdapter<T>);
135 }
136 super.performRebuild();
137 assert(_newWidget == null);
138 }
139
140 @pragma('vm:notify-debugger-on-exception')
141 void _rebuild() {
142 try {
143 _child = updateChild(_child, (widget as RenderObjectToWidgetAdapter<T>).child, _rootChildSlot);
144 } catch (exception, stack) {
145 final FlutterErrorDetails details = FlutterErrorDetails(
146 exception: exception,
147 stack: stack,
148 library: 'widgets library',
149 context: ErrorDescription('attaching to the render tree'),
150 );
151 FlutterError.reportError(details);
152 final Widget error = ErrorWidget.builder(details);
153 _child = updateChild(null, error, _rootChildSlot);
154 }
155 }
156
157 @override
158 RenderObjectWithChildMixin<T> get renderObject => super.renderObject as RenderObjectWithChildMixin<T>;
159
160 @override
161 void insertRenderObjectChild(RenderObject child, Object? slot) {
162 assert(slot == _rootChildSlot);
163 assert(renderObject.debugValidateChild(child));
164 renderObject.child = child as T;
165 }
166
167 @override
168 void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
169 assert(false);
170 }
171
172 @override
173 void removeRenderObjectChild(RenderObject child, Object? slot) {
174 assert(renderObject.child == child);
175 renderObject.child = null;
176 }
177}
178