| 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 'binding.dart'; |
| 6 | library; |
| 7 | |
| 8 | import 'package:flutter/foundation.dart'; |
| 9 | import 'package:flutter/rendering.dart'; |
| 10 | |
| 11 | import 'framework.dart'; |
| 12 | |
| 13 | /// A bridge from a [RenderObject] to an [Element] tree. |
| 14 | /// |
| 15 | /// The given container is the [RenderObject] that the [Element] tree should be |
| 16 | /// inserted into. It must be a [RenderObject] that implements the |
| 17 | /// [RenderObjectWithChildMixin] protocol. The type argument `T` is the kind of |
| 18 | /// [RenderObject] that the container expects as its child. |
| 19 | /// |
| 20 | /// The [RenderObjectToWidgetAdapter] is an alternative to [RootWidget] for |
| 21 | /// bootstrapping an element tree. Unlike [RootWidget] it requires the |
| 22 | /// existence of a render tree (the [container]) to attach the element tree to. |
| 23 | class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget { |
| 24 | /// Creates a bridge from a [RenderObject] to an [Element] tree. |
| 25 | RenderObjectToWidgetAdapter({this.child, required this.container, 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( |
| 54 | BuildOwner owner, [ |
| 55 | RenderObjectToWidgetElement<T>? element, |
| 56 | ]) { |
| 57 | if (element == null) { |
| 58 | owner.lockState(() { |
| 59 | element = createElement(); |
| 60 | assert(element != null); |
| 61 | element!.assignOwner(owner); |
| 62 | }); |
| 63 | owner.buildScope(element!, () { |
| 64 | element!.mount(null, null); |
| 65 | }); |
| 66 | } else { |
| 67 | element._newWidget = this; |
| 68 | element.markNeedsBuild(); |
| 69 | } |
| 70 | return element!; |
| 71 | } |
| 72 | |
| 73 | @override |
| 74 | String toStringShort() => debugShortDescription ?? super.toStringShort(); |
| 75 | } |
| 76 | |
| 77 | /// The root of an element tree that is hosted by a [RenderObject]. |
| 78 | /// |
| 79 | /// This element class is the instantiation of a [RenderObjectToWidgetAdapter] |
| 80 | /// widget. It can be used only as the root of an [Element] tree (it cannot be |
| 81 | /// mounted into another [Element]; it's parent must be null). |
| 82 | /// |
| 83 | /// In typical usage, it will be instantiated for a [RenderObjectToWidgetAdapter] |
| 84 | /// whose container is the [RenderView]. |
| 85 | class RenderObjectToWidgetElement<T extends RenderObject> extends RenderTreeRootElement |
| 86 | with RootElementMixin { |
| 87 | /// Creates an element that is hosted by a [RenderObject]. |
| 88 | /// |
| 89 | /// The [RenderObject] created by this element is not automatically set as a |
| 90 | /// child of the hosting [RenderObject]. To actually attach this element to |
| 91 | /// the render tree, call [RenderObjectToWidgetAdapter.attachToRenderTree]. |
| 92 | RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> super.widget); |
| 93 | |
| 94 | Element? _child; |
| 95 | |
| 96 | static const Object _rootChildSlot = Object(); |
| 97 | |
| 98 | @override |
| 99 | void visitChildren(ElementVisitor visitor) { |
| 100 | if (_child != null) { |
| 101 | visitor(_child!); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | @override |
| 106 | void forgetChild(Element child) { |
| 107 | assert(child == _child); |
| 108 | _child = null; |
| 109 | super.forgetChild(child); |
| 110 | } |
| 111 | |
| 112 | @override |
| 113 | void mount(Element? parent, Object? newSlot) { |
| 114 | assert(parent == null); |
| 115 | super.mount(parent, newSlot); |
| 116 | _rebuild(); |
| 117 | assert(_child != null); |
| 118 | } |
| 119 | |
| 120 | @override |
| 121 | void update(RenderObjectToWidgetAdapter<T> newWidget) { |
| 122 | super.update(newWidget); |
| 123 | assert(widget == newWidget); |
| 124 | _rebuild(); |
| 125 | } |
| 126 | |
| 127 | // When we are assigned a new widget, we store it here |
| 128 | // until we are ready to update to it. |
| 129 | Widget? _newWidget; |
| 130 | |
| 131 | @override |
| 132 | void performRebuild() { |
| 133 | if (_newWidget != null) { |
| 134 | // _newWidget can be null if, for instance, we were rebuilt |
| 135 | // due to a reassemble. |
| 136 | final Widget newWidget = _newWidget!; |
| 137 | _newWidget = null; |
| 138 | update(newWidget as RenderObjectToWidgetAdapter<T>); |
| 139 | } |
| 140 | super.performRebuild(); |
| 141 | assert(_newWidget == null); |
| 142 | } |
| 143 | |
| 144 | @pragma('vm:notify-debugger-on-exception' ) |
| 145 | void _rebuild() { |
| 146 | try { |
| 147 | _child = updateChild( |
| 148 | _child, |
| 149 | (widget as RenderObjectToWidgetAdapter<T>).child, |
| 150 | _rootChildSlot, |
| 151 | ); |
| 152 | } catch (exception, stack) { |
| 153 | final FlutterErrorDetails details = FlutterErrorDetails( |
| 154 | exception: exception, |
| 155 | stack: stack, |
| 156 | library: 'widgets library' , |
| 157 | context: ErrorDescription('attaching to the render tree' ), |
| 158 | ); |
| 159 | FlutterError.reportError(details); |
| 160 | final Widget error = ErrorWidget.builder(details); |
| 161 | _child = updateChild(null, error, _rootChildSlot); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | @override |
| 166 | RenderObjectWithChildMixin<T> get renderObject => |
| 167 | super.renderObject as RenderObjectWithChildMixin<T>; |
| 168 | |
| 169 | @override |
| 170 | void insertRenderObjectChild(RenderObject child, Object? slot) { |
| 171 | assert(slot == _rootChildSlot); |
| 172 | assert(renderObject.debugValidateChild(child)); |
| 173 | renderObject.child = child as T; |
| 174 | } |
| 175 | |
| 176 | @override |
| 177 | void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) { |
| 178 | assert(false); |
| 179 | } |
| 180 | |
| 181 | @override |
| 182 | void removeRenderObjectChild(RenderObject child, Object? slot) { |
| 183 | assert(renderObject.child == child); |
| 184 | renderObject.child = null; |
| 185 | } |
| 186 | } |
| 187 | |