| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:developer'; |
| import 'dart:ui' as ui show window; |
| import 'dart:ui' show AppLifecycleState, Locale; |
| |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/scheduler.dart'; |
| import 'package:flutter/services.dart'; |
| |
| import 'framework.dart'; |
| |
| export 'dart:ui' show AppLifecycleState, Locale; |
| |
| class BindingObserver { |
| bool didPopRoute() => false; |
| void didChangeMetrics() { } |
| void didChangeLocale(Locale locale) { } |
| void didChangeAppLifecycleState(AppLifecycleState state) { } |
| } |
| |
| /// A concrete binding for applications based on the Widgets framework. |
| /// This is the glue that binds the framework to the Flutter engine. |
| class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Services, Renderer { |
| |
| /// Creates and initializes the WidgetFlutterBinding. This constructor is |
| /// idempotent; calling it a second time will just return the |
| /// previously-created instance. |
| static WidgetFlutterBinding ensureInitialized() { |
| if (_instance == null) |
| new WidgetFlutterBinding(); |
| return _instance; |
| } |
| |
| @override |
| void initInstances() { |
| super.initInstances(); |
| _instance = this; |
| BuildableElement.scheduleBuildFor = scheduleBuildFor; |
| ui.window.onLocaleChanged = handleLocaleChanged; |
| ui.window.onPopRoute = handlePopRoute; |
| ui.window.onAppLifecycleStateChanged = handleAppLifecycleStateChanged; |
| } |
| |
| /// The one static instance of this class. |
| /// |
| /// Only valid after the WidgetFlutterBinding constructor) has been called. |
| /// Only one binding class can be instantiated per process. If another |
| /// BindingBase implementation has been instantiated before this one (e.g. |
| /// bindings from other frameworks based on the Flutter "rendering" library), |
| /// then WidgetFlutterBinding.instance will not be valid (and will throw in |
| /// checked mode). |
| static WidgetFlutterBinding _instance; |
| static WidgetFlutterBinding get instance => _instance; |
| |
| final List<BindingObserver> _observers = new List<BindingObserver>(); |
| |
| void addObserver(BindingObserver observer) => _observers.add(observer); |
| bool removeObserver(BindingObserver observer) => _observers.remove(observer); |
| |
| @override |
| void handleMetricsChanged() { |
| super.handleMetricsChanged(); |
| for (BindingObserver observer in _observers) |
| observer.didChangeMetrics(); |
| } |
| |
| void handleLocaleChanged() { |
| dispatchLocaleChanged(ui.window.locale); |
| } |
| |
| void dispatchLocaleChanged(Locale locale) { |
| for (BindingObserver observer in _observers) |
| observer.didChangeLocale(locale); |
| } |
| |
| void handlePopRoute() { |
| for (BindingObserver observer in _observers) { |
| if (observer.didPopRoute()) |
| return; |
| } |
| activity.finishCurrentActivity(); |
| } |
| |
| void handleAppLifecycleStateChanged(AppLifecycleState state) { |
| for (BindingObserver observer in _observers) |
| observer.didChangeAppLifecycleState(state); |
| } |
| |
| @override |
| void beginFrame() { |
| buildDirtyElements(); |
| super.beginFrame(); |
| Element.finalizeTree(); |
| } |
| |
| List<BuildableElement> _dirtyElements = <BuildableElement>[]; |
| |
| /// Adds an element to the dirty elements list so that it will be rebuilt |
| /// when buildDirtyElements is called. |
| void scheduleBuildFor(BuildableElement element) { |
| assert(!_dirtyElements.contains(element)); |
| assert(element.dirty); |
| if (_dirtyElements.isEmpty) |
| ensureVisualUpdate(); |
| _dirtyElements.add(element); |
| } |
| |
| static int _elementSort(BuildableElement a, BuildableElement b) { |
| if (a.depth < b.depth) |
| return -1; |
| if (b.depth < a.depth) |
| return 1; |
| if (b.dirty && !a.dirty) |
| return -1; |
| if (a.dirty && !b.dirty) |
| return 1; |
| return 0; |
| } |
| |
| /// Builds all the elements that were marked as dirty using schedule(), in depth order. |
| /// If elements are marked as dirty while this runs, they must be deeper than the algorithm |
| /// has yet reached. |
| /// This is called by beginFrame(). |
| void buildDirtyElements() { |
| if (_dirtyElements.isEmpty) |
| return; |
| Timeline.startSync('Build'); |
| BuildableElement.lockState(() { |
| _dirtyElements.sort(_elementSort); |
| int dirtyCount = _dirtyElements.length; |
| int index = 0; |
| while (index < dirtyCount) { |
| _dirtyElements[index].rebuild(); |
| index += 1; |
| if (dirtyCount < _dirtyElements.length) { |
| _dirtyElements.sort(_elementSort); |
| dirtyCount = _dirtyElements.length; |
| } |
| } |
| assert(!_dirtyElements.any((BuildableElement element) => element.dirty)); |
| _dirtyElements.clear(); |
| }, building: true); |
| assert(_dirtyElements.isEmpty); |
| Timeline.finishSync(); |
| } |
| |
| /// The [Element] that is at the root of the hierarchy (and which wraps the |
| /// [RenderView] object at the root of the rendering hierarchy). |
| Element get renderViewElement => _renderViewElement; |
| Element _renderViewElement; |
| void _runApp(Widget app) { |
| _renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>( |
| container: renderView, |
| debugShortDescription: '[root]', |
| child: app |
| ).attachToRenderTree(_renderViewElement); |
| beginFrame(); |
| } |
| } |
| |
| /// Inflate the given widget and attach it to the screen. |
| void runApp(Widget app) { |
| WidgetFlutterBinding.ensureInitialized()._runApp(app); |
| } |
| |
| /// Print a string representation of the currently running app. |
| void debugDumpApp() { |
| assert(WidgetFlutterBinding.instance != null); |
| assert(WidgetFlutterBinding.instance.renderViewElement != null); |
| String mode = 'RELEASE MODE'; |
| assert(() { mode = 'CHECKED MODE'; return true; }); |
| debugPrint('${WidgetFlutterBinding.instance.runtimeType} - $mode'); |
| debugPrint(WidgetFlutterBinding.instance.renderViewElement.toStringDeep()); |
| } |
| |
| /// This class provides a bridge from a RenderObject to an Element tree. The |
| /// given container is the RenderObject that the Element tree should be inserted |
| /// into. It must be a RenderObject that implements the |
| /// RenderObjectWithChildMixin protocol. The type argument T is the kind of |
| /// RenderObject that the container expects as its child. |
| class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget { |
| RenderObjectToWidgetAdapter({ |
| this.child, |
| RenderObjectWithChildMixin<T> container, |
| this.debugShortDescription |
| }) : container = container, super(key: new GlobalObjectKey(container)); |
| |
| final Widget child; |
| final RenderObjectWithChildMixin<T> container; |
| final String debugShortDescription; |
| |
| @override |
| RenderObjectToWidgetElement<T> createElement() => new RenderObjectToWidgetElement<T>(this); |
| |
| @override |
| RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container; |
| |
| @override |
| void updateRenderObject(BuildContext context, RenderObject renderObject) { } |
| |
| RenderObjectToWidgetElement<T> attachToRenderTree([RenderObjectToWidgetElement<T> element]) { |
| BuildableElement.lockState(() { |
| if (element == null) { |
| element = createElement(); |
| element.mount(null, null); |
| } else { |
| element.update(this); |
| } |
| }, building: true); |
| return element; |
| } |
| |
| @override |
| String toStringShort() => debugShortDescription ?? super.toStringShort(); |
| } |
| |
| /// This element class is the instantiation of a [RenderObjectToWidgetAdapter]. |
| /// It can only be used as the root of an Element tree (it cannot be mounted |
| /// into another Element, it's parent must be null). |
| /// |
| /// In typical usage, it will be instantiated for a RenderObjectToWidgetAdapter |
| /// whose container is the RenderView that connects to the Flutter engine. In |
| /// this usage, it is normally instantiated by the bootstrapping logic in the |
| /// WidgetFlutterBinding singleton created by runApp(). |
| class RenderObjectToWidgetElement<T extends RenderObject> extends RenderObjectElement { |
| RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> widget) : super(widget); |
| |
| @override |
| RenderObjectToWidgetAdapter<T> get widget => super.widget; |
| |
| Element _child; |
| |
| static const Object _rootChildSlot = const Object(); |
| |
| @override |
| void visitChildren(ElementVisitor visitor) { |
| if (_child != null) |
| visitor(_child); |
| } |
| |
| @override |
| void mount(Element parent, dynamic newSlot) { |
| assert(parent == null); |
| super.mount(parent, newSlot); |
| _child = updateChild(_child, widget.child, _rootChildSlot); |
| } |
| |
| @override |
| void update(RenderObjectToWidgetAdapter<T> newWidget) { |
| super.update(newWidget); |
| assert(widget == newWidget); |
| _child = updateChild(_child, widget.child, _rootChildSlot); |
| } |
| |
| @override |
| RenderObjectWithChildMixin<T> get renderObject => super.renderObject; |
| |
| @override |
| void insertChildRenderObject(RenderObject child, dynamic slot) { |
| assert(slot == _rootChildSlot); |
| renderObject.child = child; |
| } |
| |
| @override |
| void moveChildRenderObject(RenderObject child, dynamic slot) { |
| assert(false); |
| } |
| |
| @override |
| void removeChildRenderObject(RenderObject child) { |
| assert(renderObject.child == child); |
| renderObject.child = null; |
| } |
| } |