diff --git a/infra/README.md b/infra/README.md
index 16fc60b339..d0ae91f25f 100644
--- a/infra/README.md
+++ b/infra/README.md
@@ -53,7 +53,7 @@ The typical cycle for editing a recipe is:
1. Make your edits.
2. Run `build/scripts/slave/recipes.py simulation_test train flutter` to update expected files (remove the flutter if you need to do a global update).
-3. Run `build/scripts/slave/recipes.py run flutter/flutter` (or flutter/engine) if something was strange during training and you need to run it locally.
+3. Run `build/scripts/tools/run_recipe.py flutter/flutter` (or flutter/engine) if something was strange during training and you need to run it locally.
4. Upload the patch (`git commit`, `git cl upload`) and send it to someone in the `recipes/flutter/OWNERS` file for review.
## Editing the client.flutter buildbot master
diff --git a/packages/flutter/lib/animation.dart b/packages/flutter/lib/animation.dart
index c386df379e..ad8efec305 100644
--- a/packages/flutter/lib/animation.dart
+++ b/packages/flutter/lib/animation.dart
@@ -4,7 +4,7 @@
/// The Flutter animation system.
///
-/// See [https://flutter.io/animations/] for an overview.
+/// See for an overview.
///
/// This library depends only on core Dart libraries and the `newton` package.
library animation;
diff --git a/packages/flutter/lib/shell.dart b/packages/flutter/lib/shell.dart
new file mode 100644
index 0000000000..27256becc8
--- /dev/null
+++ b/packages/flutter/lib/shell.dart
@@ -0,0 +1,99 @@
+// 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.
+
+/// Manages connections with embedder-provided services.
+library shell;
+
+import 'dart:ui' as ui;
+
+import 'package:mojo/application.dart';
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+import 'package:mojo/mojo/service_provider.mojom.dart' as mojom;
+import 'package:mojo/mojo/shell.mojom.dart' as mojom;
+
+/// A replacement for shell.connectToService. Implementations should return true
+/// if they handled the request, or false if the request should fall through
+/// to the default requestService.
+typedef bool OverrideConnectToService(String url, Object proxy);
+
+/// Manages connections with embedder-provided services.
+class MojoShell {
+ MojoShell() {
+ assert(_instance == null);
+ _instance = this;
+ }
+
+ /// The unique instance of this class.
+ static MojoShell get instance => _instance;
+ static MojoShell _instance;
+
+ static mojom.ShellProxy _initShellProxy() {
+ core.MojoHandle shellHandle = new core.MojoHandle(ui.takeShellProxyHandle());
+ if (!shellHandle.isValid)
+ return null;
+ return new mojom.ShellProxy.fromHandle(shellHandle);
+ }
+ final mojom.Shell _shell = _initShellProxy()?.ptr;
+
+ static ApplicationConnection _initEmbedderConnection() {
+ core.MojoHandle servicesHandle = new core.MojoHandle(ui.takeServicesProvidedByEmbedder());
+ core.MojoHandle exposedServicesHandle = new core.MojoHandle(ui.takeServicesProvidedToEmbedder());
+ if (!servicesHandle.isValid || !exposedServicesHandle.isValid)
+ return null;
+ mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.fromHandle(servicesHandle);
+ mojom.ServiceProviderStub exposedServices = new mojom.ServiceProviderStub.fromHandle(exposedServicesHandle);
+ return new ApplicationConnection(exposedServices, services);
+ }
+ final ApplicationConnection _embedderConnection = _initEmbedderConnection();
+
+ /// Attempts to connect to an application via the Mojo shell.
+ ApplicationConnection connectToApplication(String url) {
+ if (_shell == null)
+ return null;
+ mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.unbound();
+ mojom.ServiceProviderStub exposedServices = new mojom.ServiceProviderStub.unbound();
+ _shell.connectToApplication(url, services, exposedServices);
+ return new ApplicationConnection(exposedServices, services);
+ }
+
+ /// Set this to intercept calls to [connectToService()] and supply an
+ /// alternative implementation of a service (for example, a mock for testing).
+ OverrideConnectToService overrideConnectToService;
+
+ /// Attempts to connect to a service implementing the interface for the given proxy.
+ /// If an application URL is specified, the service will be requested from that application.
+ /// Otherwise, it will be requested from the embedder (the Flutter engine).
+ void connectToService(String url, bindings.ProxyBase proxy) {
+ if (overrideConnectToService != null && overrideConnectToService(url, proxy))
+ return;
+ if (url == null || _shell == null) {
+ // If the application URL is null, it means the service to connect
+ // to is one provided by the embedder.
+ // If the applircation URL isn't null but there's no shell, then
+ // ask the embedder in case it provides it. (For example, if you're
+ // running on Android without the Mojo shell, then you can obtain
+ // the media service from the embedder directly, instead of having
+ // to ask the media application for it.)
+ // This makes it easier to write an application that works both
+ // with and without a Mojo environment.
+ _embedderConnection?.requestService(proxy);
+ return;
+ }
+ mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.unbound();
+ _shell.connectToApplication(url, services, null);
+ core.MojoMessagePipe pipe = new core.MojoMessagePipe();
+ proxy.impl.bind(pipe.endpoints[0]);
+ services.ptr.connectToService(proxy.serviceName, pipe.endpoints[1]);
+ services.close();
+ }
+
+ /// Registers a service to expose to the embedder.
+ void provideService(String interfaceName, ServiceFactory factory) {
+ _embedderConnection?.provideService(interfaceName, factory);
+ }
+}
+
+/// The singleton object that manages connections with embedder-provided services.
+MojoShell get shell => MojoShell.instance;
diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart
index f9832022a6..d958ee709b 100644
--- a/packages/flutter/lib/src/material/app.dart
+++ b/packages/flutter/lib/src/material/app.dart
@@ -2,13 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:async';
-import 'dart:ui' as ui show WindowPadding, window;
-
import 'package:flutter/rendering.dart';
-import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
+import 'colors.dart';
import 'page.dart';
import 'theme.dart';
@@ -21,204 +18,75 @@ const TextStyle _errorTextStyle = const TextStyle(
fontWeight: FontWeight.w900,
textAlign: TextAlign.right,
decoration: TextDecoration.underline,
- decorationColor: const Color(0xFFFF00),
+ decorationColor: const Color(0xFFFFFF00),
decorationStyle: TextDecorationStyle.double
);
-AssetBundle _initDefaultBundle() {
- if (rootBundle != null)
- return rootBundle;
- return new NetworkAssetBundle(Uri.base);
-}
-final AssetBundle _defaultBundle = _initDefaultBundle();
-
-class RouteArguments {
- const RouteArguments({ this.context });
- final BuildContext context;
-}
-typedef Widget RouteBuilder(RouteArguments args);
-
-typedef Future LocaleChangedCallback(Locale locale);
-
-class MaterialApp extends StatefulComponent {
+class MaterialApp extends WidgetsApp {
MaterialApp({
Key key,
- this.title,
- this.theme,
- this.routes: const {},
- this.onGenerateRoute,
- this.onLocaleChanged,
+ String title,
+ ThemeData theme,
+ Map routes: const {},
+ RouteFactory onGenerateRoute,
+ LocaleChangedCallback onLocaleChanged,
this.debugShowMaterialGrid: false,
- this.showPerformanceOverlay: false,
- this.showSemanticsDebugger: false,
- this.debugShowCheckedModeBanner: true
- }) : super(key: key) {
- assert(routes != null);
- assert(routes.containsKey(Navigator.defaultRouteName) || onGenerateRoute != null);
+ bool showPerformanceOverlay: false,
+ bool showSemanticsDebugger: false,
+ bool debugShowCheckedModeBanner: true
+ }) : theme = theme,
+ super(
+ key: key,
+ title: title,
+ textStyle: _errorTextStyle,
+ color: theme?.primaryColor ?? Colors.blue[500], // blue[500] is the primary color of the default theme
+ routes: routes,
+ onGenerateRoute: (RouteSettings settings) {
+ RouteBuilder builder = routes[settings.name];
+ if (builder != null) {
+ return new MaterialPageRoute(
+ builder: (BuildContext context) {
+ return builder(new RouteArguments(context: context));
+ },
+ settings: settings
+ );
+ }
+ if (onGenerateRoute != null)
+ return onGenerateRoute(settings);
+ return null;
+ },
+ onLocaleChanged: onLocaleChanged,
+ showPerformanceOverlay: showPerformanceOverlay,
+ showSemanticsDebugger: showSemanticsDebugger,
+ debugShowCheckedModeBanner: debugShowCheckedModeBanner
+ ) {
assert(debugShowMaterialGrid != null);
- assert(showPerformanceOverlay != null);
- assert(showSemanticsDebugger != null);
}
- /// A one-line description of this app for use in the window manager.
- final String title;
-
/// The colors to use for the application's widgets.
final ThemeData theme;
- /// The default table of routes for the application. When the
- /// [Navigator] is given a named route, the name will be looked up
- /// in this table first. If the name is not available, then
- /// [onGenerateRoute] will be called instead.
- final Map routes;
-
- /// The route generator callback used when the app is navigated to a
- /// named route but the name is not in the [routes] table.
- final RouteFactory onGenerateRoute;
-
- /// Callback that is invoked when the operating system changes the
- /// current locale.
- final LocaleChangedCallback onLocaleChanged;
-
/// Turns on a [GridPaper] overlay that paints a baseline grid
/// Material apps:
/// https://www.google.com/design/spec/layout/metrics-keylines.html
/// Only available in checked mode.
final bool debugShowMaterialGrid;
- /// Turns on a performance overlay.
- /// https://flutter.io/debugging/#performanceoverlay
- final bool showPerformanceOverlay;
-
- /// Turns on an overlay that shows the accessibility information
- /// reported by the framework.
- final bool showSemanticsDebugger;
-
- /// Turns on a "SLOW MODE" little banner in checked mode to indicate
- /// that the app is in checked mode. This is on by default (in
- /// checked mode), to turn it off, set the constructor argument to
- /// false. In release mode this has no effect.
- ///
- /// To get this banner in your application if you're not using
- /// MaterialApp, include a [CheckedModeBanner] widget in your app.
- ///
- /// This banner is intended to avoid people complaining that your
- /// app is slow when it's in checked mode. In checked mode, Flutter
- /// enables a large number of expensive diagnostics to aid in
- /// development, and so performance in checked mode is not
- /// representative of what will happen in release mode.
- final bool debugShowCheckedModeBanner;
-
_MaterialAppState createState() => new _MaterialAppState();
}
-EdgeDims _getPadding(ui.WindowPadding padding) {
- return new EdgeDims.TRBL(padding.top, padding.right, padding.bottom, padding.left);
-}
-
-class _MaterialAppState extends State implements BindingObserver {
-
- GlobalObjectKey _navigator;
-
- LocaleQueryData _localeData;
-
- void initState() {
- super.initState();
- _navigator = new GlobalObjectKey(this);
- didChangeLocale(ui.window.locale);
- WidgetFlutterBinding.instance.addObserver(this);
- }
-
- void dispose() {
- WidgetFlutterBinding.instance.removeObserver(this);
- super.dispose();
- }
-
- bool didPopRoute() {
- assert(mounted);
- NavigatorState navigator = _navigator.currentState;
- assert(navigator != null);
- bool result = false;
- navigator.openTransaction((NavigatorTransaction transaction) {
- result = transaction.pop();
- });
- return result;
- }
-
- void didChangeMetrics() {
- setState(() {
- // The properties of ui.window have changed. We use them in our build
- // function, so we need setState(), but we don't cache anything locally.
- });
- }
-
- void didChangeLocale(Locale locale) {
- if (config.onLocaleChanged != null) {
- config.onLocaleChanged(locale).then((LocaleQueryData data) {
- if (mounted)
- setState(() { _localeData = data; });
- });
- }
- }
-
- void didChangeAppLifecycleState(AppLifecycleState state) { }
+class _MaterialAppState extends WidgetsAppState {
final HeroController _heroController = new HeroController();
-
- Route _generateRoute(RouteSettings settings) {
- RouteBuilder builder = config.routes[settings.name];
- if (builder != null) {
- return new MaterialPageRoute(
- builder: (BuildContext context) {
- return builder(new RouteArguments(context: context));
- },
- settings: settings
- );
- }
- if (config.onGenerateRoute != null)
- return config.onGenerateRoute(settings);
- return null;
- }
+ NavigatorObserver get navigatorObserver => _heroController;
Widget build(BuildContext context) {
- if (config.onLocaleChanged != null && _localeData == null) {
- // If the app expects a locale but we don't yet know the locale, then
- // don't build the widgets now.
- return new Container();
- }
-
ThemeData theme = config.theme ?? new ThemeData.fallback();
- Widget result = new MediaQuery(
- data: new MediaQueryData(
- size: ui.window.size,
- devicePixelRatio: ui.window.devicePixelRatio,
- padding: _getPadding(ui.window.padding)
- ),
- child: new LocaleQuery(
- data: _localeData,
- child: new AnimatedTheme(
- data: theme,
- duration: kThemeAnimationDuration,
- child: new DefaultTextStyle(
- style: _errorTextStyle,
- child: new AssetVendor(
- bundle: _defaultBundle,
- devicePixelRatio: ui.window.devicePixelRatio,
- child: new Title(
- title: config.title,
- color: theme.primaryColor,
- child: new Navigator(
- key: _navigator,
- initialRoute: ui.window.defaultRouteName,
- onGenerateRoute: _generateRoute,
- observer: _heroController
- )
- )
- )
- )
- )
- )
+ Widget result = new AnimatedTheme(
+ data: theme,
+ duration: kThemeAnimationDuration,
+ child: super.build(context)
);
assert(() {
if (config.debugShowMaterialGrid) {
@@ -232,27 +100,6 @@ class _MaterialAppState extends State implements BindingObserver {
}
return true;
});
- if (config.showPerformanceOverlay) {
- result = new Stack(
- children: [
- result,
- new Positioned(bottom: 0.0, left: 0.0, right: 0.0, child: new PerformanceOverlay.allEnabled()),
- ]
- );
- }
- if (config.showSemanticsDebugger) {
- result = new SemanticsDebugger(
- child: result
- );
- }
- assert(() {
- if (config.debugShowCheckedModeBanner) {
- result = new CheckedModeBanner(
- child: result
- );
- }
- return true;
- });
return result;
}
diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart
index e636a81662..b92e671ba0 100644
--- a/packages/flutter/lib/src/material/bottom_sheet.dart
+++ b/packages/flutter/lib/src/material/bottom_sheet.dart
@@ -80,7 +80,7 @@ class _BottomSheetState extends State {
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: new Material(
- key: _childKey,
+ key: _childKey,
child: config.builder(context)
)
);
diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart
index 7b74a93258..85a1f18f67 100644
--- a/packages/flutter/lib/src/material/date_picker.dart
+++ b/packages/flutter/lib/src/material/date_picker.dart
@@ -10,6 +10,7 @@ import 'package:intl/date_symbols.dart';
import 'package:intl/intl.dart';
import 'colors.dart';
+import 'debug.dart';
import 'ink_well.dart';
import 'theme.dart';
import 'typography.dart';
@@ -403,6 +404,7 @@ class _YearPickerState extends State {
}
Widget build(BuildContext context) {
+ assert(debugCheckHasMaterial(context));
return new ScrollableLazyList(
itemExtent: _itemExtent,
itemCount: config.lastDate.year - config.firstDate.year + 1,
diff --git a/packages/flutter/lib/src/material/debug.dart b/packages/flutter/lib/src/material/debug.dart
index 45e05c2320..a2ea646049 100644
--- a/packages/flutter/lib/src/material/debug.dart
+++ b/packages/flutter/lib/src/material/debug.dart
@@ -12,8 +12,17 @@ bool debugCheckHasMaterial(BuildContext context) {
if (context.widget is! Material && context.ancestorWidgetOfExactType(Material) == null) {
Element element = context;
throw new WidgetError(
- 'Missing Material widget.',
- '${context.widget} needs to be placed inside a Material widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}'
+ 'No Material widget found.\n'
+ '${context.widget.runtimeType} widgets require a Material widget ancestor.\n'
+ 'In material design, most widgets are conceptually "printed" on a sheet of material. In Flutter\'s material library, '
+ 'that material is represented by the Material widget. It is the Material widget that renders ink splashes, for instance. '
+ 'Because of this, many material library widgets require that there be a Material widget in the tree above them.\n'
+ 'To introduce a Material widget, you can either directly include one, or use a widget that contains Material itself, '
+ 'such as a Card, Dialog, Drawer, or Scaffold.\n'
+ 'The specific widget that could not find a Material ancestor was:\n'
+ ' ${context.widget}'
+ 'The ownership chain for the affected widget is:\n'
+ ' ${element.debugGetOwnershipChain(10)}'
);
}
return true;
@@ -27,8 +36,12 @@ bool debugCheckHasScaffold(BuildContext context) {
if (Scaffold.of(context) == null) {
Element element = context;
throw new WidgetError(
- 'Missing Scaffold widget.',
- '${context.widget} needs to be placed inside the body of a Scaffold widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}'
+ 'No Scaffold widget found.\n'
+ '${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n'
+ 'The specific widget that could not find a Scaffold ancestor was:\n'
+ ' ${context.widget}'
+ 'The ownership chain for the affected widget is:\n'
+ ' ${element.debugGetOwnershipChain(10)}'
);
}
return true;
diff --git a/packages/flutter/lib/src/material/drawer_item.dart b/packages/flutter/lib/src/material/drawer_item.dart
index 60b63e7d09..8f91ce7ca4 100644
--- a/packages/flutter/lib/src/material/drawer_item.dart
+++ b/packages/flutter/lib/src/material/drawer_item.dart
@@ -5,6 +5,7 @@
import 'package:flutter/widgets.dart';
import 'colors.dart';
+import 'debug.dart';
import 'icon.dart';
import 'icons.dart';
import 'ink_well.dart';
@@ -55,6 +56,7 @@ class DrawerItem extends StatelessComponent {
}
Widget build(BuildContext context) {
+ assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context);
List children = [];
diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart
index 84c976ec9e..cb79e557e1 100644
--- a/packages/flutter/lib/src/material/dropdown.dart
+++ b/packages/flutter/lib/src/material/dropdown.dart
@@ -182,6 +182,7 @@ class _DropDownRoute extends PopupRoute<_DropDownRouteResult> {
ModalPosition getPosition(BuildContext context) {
RenderBox overlayBox = Overlay.of(context).context.findRenderObject();
+ assert(overlayBox != null); // can't be null; routes get inserted by Navigator which has its own Overlay
Size overlaySize = overlayBox.size;
RelativeRect menuRect = new RelativeRect.fromSize(rect, overlaySize);
return new ModalPosition(
diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart
index 8626974157..83e46e378b 100644
--- a/packages/flutter/lib/src/material/icon_button.dart
+++ b/packages/flutter/lib/src/material/icon_button.dart
@@ -4,6 +4,7 @@
import 'package:flutter/widgets.dart';
+import 'debug.dart';
import 'icon.dart';
import 'icons.dart';
import 'ink_well.dart';
@@ -53,6 +54,7 @@ class IconButton extends StatelessComponent {
final String tooltip;
Widget build(BuildContext context) {
+ assert(debugCheckHasMaterial(context));
Widget result = new Padding(
padding: const EdgeDims.all(8.0),
child: new Icon(
diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart
index f30ba0d662..50393bdbe3 100644
--- a/packages/flutter/lib/src/material/ink_well.dart
+++ b/packages/flutter/lib/src/material/ink_well.dart
@@ -12,6 +12,18 @@ import 'debug.dart';
import 'material.dart';
import 'theme.dart';
+/// An area of a Material that responds to touch. Has a configurable shape and
+/// can be configured to clip splashes that extend outside its bounds or not.
+///
+/// For a variant of this widget that is specialised for rectangular areas that
+/// always clip splashes, see [InkWell].
+///
+/// Must have an ancestor [Material] widget in which to cause ink reactions.
+///
+/// If a Widget uses this class directly, it should include the following line
+/// at the top of its [build] function to call [debugCheckHasMaterial]:
+///
+/// assert(debugCheckHasMaterial(context));
class InkResponse extends StatefulComponent {
InkResponse({
Key key,
@@ -156,9 +168,14 @@ class _InkResponseState extends State {
}
-/// An area of a Material that responds to touch.
+/// A rectangular area of a Material that responds to touch.
///
-/// Must have an ancestor Material widget in which to cause ink reactions.
+/// Must have an ancestor [Material] widget in which to cause ink reactions.
+///
+/// If a Widget uses this class directly, it should include the following line
+/// at the top of its [build] function to call [debugCheckHasMaterial]:
+///
+/// assert(debugCheckHasMaterial(context));
class InkWell extends InkResponse {
InkWell({
Key key,
diff --git a/packages/flutter/lib/src/material/list_item.dart b/packages/flutter/lib/src/material/list_item.dart
index 549345978e..84d4202ee2 100644
--- a/packages/flutter/lib/src/material/list_item.dart
+++ b/packages/flutter/lib/src/material/list_item.dart
@@ -4,6 +4,7 @@
import 'package:flutter/widgets.dart';
+import 'debug.dart';
import 'ink_well.dart';
import 'theme.dart';
@@ -85,6 +86,7 @@ class ListItem extends StatelessComponent {
}
Widget build(BuildContext context) {
+ assert(debugCheckHasMaterial(context));
final bool isTwoLine = !isThreeLine && secondary != null;
final bool isOneLine = !isThreeLine && !isTwoLine;
double itemHeight;
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index e1787f29cb..f606b41fe7 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -39,9 +39,8 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
final EdgeDims padding;
- void performLayout(Size size, BoxConstraints constraints) {
-
- BoxConstraints looseConstraints = constraints.loosen();
+ void performLayout(Size size) {
+ BoxConstraints looseConstraints = new BoxConstraints.loose(size);
// This part of the layout has the same effect as putting the toolbar and
// body in a column and making the body flexible. What's different is that
diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart
index 794c266a31..0c216fd45d 100644
--- a/packages/flutter/lib/src/material/tabs.dart
+++ b/packages/flutter/lib/src/material/tabs.dart
@@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
+import 'debug.dart';
import 'icon.dart';
import 'icons.dart';
import 'icon_theme.dart';
@@ -330,6 +331,7 @@ class _Tab extends StatelessComponent {
}
Widget build(BuildContext context) {
+ assert(debugCheckHasMaterial(context));
Widget labelContent;
if (label.icon == null && label.iconBuilder == null) {
labelContent = _buildLabelText();
diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart
index 35757bc25c..66292f64db 100644
--- a/packages/flutter/lib/src/material/tooltip.dart
+++ b/packages/flutter/lib/src/material/tooltip.dart
@@ -46,6 +46,7 @@ class Tooltip extends StatefulComponent {
assert(preferBelow != null);
assert(fadeDuration != null);
assert(showDuration != null);
+ assert(child != null);
}
final String message;
@@ -150,7 +151,7 @@ class _TooltipState extends State {
preferBelow: config.preferBelow
);
});
- Overlay.of(context).insert(_entry);
+ Overlay.of(context, debugRequiredFor: config).insert(_entry);
}
_timer?.cancel();
if (_controller.status != AnimationStatus.completed) {
@@ -175,7 +176,7 @@ class _TooltipState extends State {
}
Widget build(BuildContext context) {
- assert(Overlay.of(context) != null);
+ assert(Overlay.of(context, debugRequiredFor: config) != null);
return new GestureDetector(
behavior: HitTestBehavior.opaque,
onLongPress: showTooltip,
diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart
index d03c1ade87..b832214aa7 100644
--- a/packages/flutter/lib/src/painting/text_style.dart
+++ b/packages/flutter/lib/src/painting/text_style.dart
@@ -31,22 +31,22 @@ class TextStyle {
/// The color to use when painting the text.
final Color color;
- /// The name of the font to use when painting the text.
+ /// The name of the font to use when painting the text (e.g., Roboto).
final String fontFamily;
/// The size of gyphs (in logical pixels) to use when painting the text.
final double fontSize;
- /// The font weight to use when painting the text.
+ /// The typeface thickness to use when painting the text (e.g., bold).
final FontWeight fontWeight;
- /// The font style to use when painting the text.
+ /// The typeface variant to use when drawing the letters (e.g., italics).
final FontStyle fontStyle;
- /// The amount of space to add between each letter.
+ /// The amount of space (in logical pixels) to add between each letter.
final double letterSpacing;
- /// The amount of space to add at each sequence of white-space (i.e. between each word).
+ /// The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word).
final double wordSpacing;
/// How the text should be aligned (applies only to the outermost
@@ -59,13 +59,13 @@ class TextStyle {
/// The distance between the text baselines, as a multiple of the font size.
final double height;
- /// The decorations to paint near the text.
+ /// The decorations to paint near the text (e.g., an underline).
final TextDecoration decoration;
/// The color in which to paint the text decorations.
final Color decorationColor;
- /// The style in which to paint the text decorations.
+ /// The style in which to paint the text decorations (e.g., dashed).
final TextDecorationStyle decorationStyle;
/// Returns a new text style that matches this text style but with the given
diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart
index 52105fd602..8fddbf5ce9 100644
--- a/packages/flutter/lib/src/rendering/binding.dart
+++ b/packages/flutter/lib/src/rendering/binding.dart
@@ -19,7 +19,7 @@ import 'semantics.dart';
export 'package:flutter/gestures.dart' show HitTestResult;
/// The glue between the render tree and the Flutter engine.
-abstract class Renderer extends Object with Scheduler, MojoShell
+abstract class Renderer extends Object with Scheduler, Services
implements HitTestable {
void initInstances() {
@@ -67,7 +67,7 @@ abstract class Renderer extends Object with Scheduler, MojoShell
void initSemantics() {
SemanticsNode.onSemanticsEnabled = renderView.scheduleInitialSemantics;
- provideService(mojom.SemanticsServer.serviceName, (core.MojoMessagePipeEndpoint endpoint) {
+ shell.provideService(mojom.SemanticsServer.serviceName, (core.MojoMessagePipeEndpoint endpoint) {
mojom.SemanticsServerStub server = new mojom.SemanticsServerStub.fromEndpoint(endpoint);
server.impl = new SemanticsServer();
});
@@ -116,7 +116,7 @@ void debugDumpSemanticsTree() {
/// A concrete binding for applications that use the Rendering framework
/// directly. This is the glue that binds the framework to the Flutter engine.
-class RenderingFlutterBinding extends BindingBase with Scheduler, Gesturer, MojoShell, Renderer {
+class RenderingFlutterBinding extends BindingBase with Scheduler, Gesturer, Services, Renderer {
RenderingFlutterBinding({ RenderBox root }) {
assert(renderView != null);
renderView.child = root;
diff --git a/packages/flutter/lib/src/rendering/child_view.dart b/packages/flutter/lib/src/rendering/child_view.dart
index 7e16099972..4bebb5d7ad 100644
--- a/packages/flutter/lib/src/rendering/child_view.dart
+++ b/packages/flutter/lib/src/rendering/child_view.dart
@@ -20,7 +20,7 @@ import 'object.dart';
mojom.ViewProxy _initViewProxy() {
int viewHandle = ui.takeViewHandle();
assert(() {
- if (viewHandle == 0)
+ if (viewHandle == core.MojoHandle.INVALID)
debugPrint('Child view are supported only when running in Mojo shell.');
return true;
});
@@ -35,8 +35,12 @@ mojom.ViewProxy _initViewProxy() {
final mojom.ViewProxy _viewProxy = _initViewProxy();
final mojom.View _view = _viewProxy?.ptr;
+/// (mojo-only) A connection with a child view.
+///
+/// Used with the [ChildView] widget to display a child view.
class ChildViewConnection {
- ChildViewConnection({ this.url }) {
+ /// Establishes a connection to the app at the given URL.
+ ChildViewConnection({ String url }) {
mojom.ViewProviderProxy viewProvider = new mojom.ViewProviderProxy.unbound();
shell.connectToService(url, viewProvider);
mojom.ServiceProviderProxy incomingServices = new mojom.ServiceProviderProxy.unbound();
@@ -47,8 +51,16 @@ class ChildViewConnection {
_connection = new ApplicationConnection(outgoingServices, incomingServices);
}
- final String url;
+ /// Wraps an already-established connection ot a child app.
+ ChildViewConnection.fromViewOwner({
+ mojom.ViewOwnerProxy viewOwner,
+ ApplicationConnection connection
+ }) : _connection = connection, _viewOwner = viewOwner;
+ /// The underlying application connection to the child app.
+ ///
+ /// Useful for requesting services from the child app and for providing
+ /// services to the child app.
ApplicationConnection get connection => _connection;
ApplicationConnection _connection;
@@ -120,18 +132,16 @@ class ChildViewConnection {
..devicePixelRatio = scale;
return (await _view.layoutChild(_viewKey, layoutParams)).info;
}
-
- String toString() {
- return '$runtimeType(url: $url)';
- }
}
+/// (mojo-only) A view of a child application.
class RenderChildView extends RenderBox {
RenderChildView({
ChildViewConnection child,
double scale
}) : _child = child, _scale = scale;
+ /// The child to display.
ChildViewConnection get child => _child;
ChildViewConnection _child;
void set child (ChildViewConnection value) {
@@ -150,6 +160,7 @@ class RenderChildView extends RenderBox {
}
}
+ /// The device pixel ratio to provide the child.
double get scale => _scale;
double _scale;
void set scale (double value) {
diff --git a/packages/flutter/lib/src/rendering/custom_layout.dart b/packages/flutter/lib/src/rendering/custom_layout.dart
index 8bf35ee157..f6ae9349dc 100644
--- a/packages/flutter/lib/src/rendering/custom_layout.dart
+++ b/packages/flutter/lib/src/rendering/custom_layout.dart
@@ -103,7 +103,7 @@ abstract class MultiChildLayoutDelegate {
return '${childParentData.id}: $child';
}
- void _callPerformLayout(Size size, BoxConstraints constraints, RenderBox firstChild) {
+ void _callPerformLayout(Size size, RenderBox firstChild) {
// A particular layout delegate could be called reentrantly, e.g. if it used
// by both a parent and a child. So, we must restore the _idToChild map when
// we return.
@@ -138,7 +138,7 @@ abstract class MultiChildLayoutDelegate {
});
child = childParentData.nextSibling;
}
- performLayout(size, constraints);
+ performLayout(size);
assert(() {
if (_debugChildrenNeedingLayout.isNotEmpty) {
if (_debugChildrenNeedingLayout.length > 1) {
@@ -176,11 +176,10 @@ abstract class MultiChildLayoutDelegate {
/// possible given the constraints.
Size getSize(BoxConstraints constraints) => constraints.biggest;
- /// Override this method to lay out and position all children given
- /// this widget's size and the specified constraints. This method
- /// must call [layoutChild] for each child. It should also specify
- /// the final position of each child with [positionChild].
- void performLayout(Size size, BoxConstraints constraints);
+ /// Override this method to lay out and position all children given this
+ /// widget's size. This method must call [layoutChild] for each child. It
+ /// should also specify the final position of each child with [positionChild].
+ void performLayout(Size size);
/// Override this method to return true when the children need to be
/// laid out. This should compare the fields of the current delegate
@@ -257,7 +256,7 @@ class RenderCustomMultiChildLayoutBox extends RenderBox
}
void performLayout() {
- delegate._callPerformLayout(size, constraints, firstChild);
+ delegate._callPerformLayout(size, firstChild);
}
void paint(PaintingContext context, Offset offset) {
diff --git a/packages/flutter/lib/src/rendering/debug.dart b/packages/flutter/lib/src/rendering/debug.dart
index 396b98bc83..a0950a68bc 100644
--- a/packages/flutter/lib/src/rendering/debug.dart
+++ b/packages/flutter/lib/src/rendering/debug.dart
@@ -53,9 +53,6 @@ bool debugPaintPointersEnabled = false;
/// The color to use when reporting pointers.
int debugPaintPointersColorValue = 0x00BBBB;
-/// The color to use when painting [RenderErrorBox] objects in checked mode.
-Color debugErrorBoxColor = const Color(0xFFFF0000);
-
/// Overlay a rotating set of colors when repainting layers in checked mode.
bool debugRepaintRainbowEnabled = false;
diff --git a/packages/flutter/lib/src/rendering/error.dart b/packages/flutter/lib/src/rendering/error.dart
index 4e4b8c2e94..e6c3d00258 100644
--- a/packages/flutter/lib/src/rendering/error.dart
+++ b/packages/flutter/lib/src/rendering/error.dart
@@ -2,15 +2,56 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphStyle, TextStyle;
+
import 'box.dart';
-import 'debug.dart';
import 'object.dart';
const double _kMaxWidth = 100000.0;
const double _kMaxHeight = 100000.0;
-/// A render object used as a placeholder when an error occurs
+/// A render object used as a placeholder when an error occurs.
+///
+/// The box will be painted in the color given by the
+/// [RenderErrorBox.backgroundColor] static property.
+///
+/// A message can be provided. To simplify the class and thus help reduce the
+/// likelihood of this class itself being the source of errors, the message
+/// cannot be changed once the object has been created. If provided, the text
+/// will be painted on top of the background, using the styles given by the
+/// [RenderErrorBox.textStyle] and [RenderErrorBox.paragraphStyle] static
+/// properties.
+///
+/// Again to help simplify the class, this box tries to be 100000.0 pixels wide
+/// and high, to approximate being infinitely high but without using infinities.
class RenderErrorBox extends RenderBox {
+ /// Constructs a RenderErrorBox render object.
+ ///
+ /// A message can optionally be provided. If a message is provided, an attempt
+ /// will be made to render the message when the box paints.
+ RenderErrorBox([ this.message = '' ]) {
+ try {
+ if (message != '') {
+ // This class is intentionally doing things using the low-level
+ // primitives to avoid depending on any subsystems that may have ended
+ // up in an unstable state -- after all, this class is mainly used when
+ // things have gone wrong.
+ //
+ // Generally, the much better way to draw text in a RenderObject is to
+ // use the TextPainter class. If you're looking for code to crib from,
+ // see the paragraph.dart file and the RenderParagraph class.
+ ui.ParagraphBuilder builder = new ui.ParagraphBuilder();
+ builder.pushStyle(textStyle);
+ builder.addText(message);
+ _paragraph = builder.build(paragraphStyle);
+ }
+ } catch (e) { }
+ }
+
+ /// The message to attempt to display at paint time.
+ final String message;
+
+ ui.Paragraph _paragraph;
double getMinIntrinsicWidth(BoxConstraints constraints) {
return constraints.constrainWidth(0.0);
@@ -36,8 +77,36 @@ class RenderErrorBox extends RenderBox {
size = constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
}
- void paint(PaintingContext context, Offset offset) {
- context.canvas.drawRect(offset & size, new Paint() .. color = debugErrorBoxColor);
- }
+ /// The color to use when painting the background of [RenderErrorBox] objects.
+ static Color backgroundColor = const Color(0xF0900000);
+ /// The text style to use when painting [RenderErrorBox] objects.
+ static ui.TextStyle textStyle = new ui.TextStyle(
+ color: const Color(0xFFFFFF00),
+ fontFamily: 'monospace',
+ fontSize: 7.0
+ );
+
+ /// The paragraph style to use when painting [RenderErrorBox] objects.
+ static ui.ParagraphStyle paragraphStyle = new ui.ParagraphStyle(
+ lineHeight: 0.25 // TODO(ianh): https://github.com/flutter/flutter/issues/2460 will affect this
+ );
+
+ void paint(PaintingContext context, Offset offset) {
+ try {
+ context.canvas.drawRect(offset & size, new Paint() .. color = backgroundColor);
+ if (_paragraph != null) {
+ // See the comment in the RenderErrorBox constructor. This is not the
+ // code you want to be copying and pasting. :-)
+ if (parent is RenderBox) {
+ RenderBox parentBox = parent;
+ _paragraph.maxWidth = parentBox.size.width;
+ } else {
+ _paragraph.maxWidth = size.width;
+ }
+ _paragraph.layout();
+ _paragraph.paint(context.canvas, offset);
+ }
+ } catch (e) { }
+ }
}
diff --git a/packages/flutter/lib/src/services/asset_bundle.dart b/packages/flutter/lib/src/services/asset_bundle.dart
index 0569939ae8..2b82857a7a 100644
--- a/packages/flutter/lib/src/services/asset_bundle.dart
+++ b/packages/flutter/lib/src/services/asset_bundle.dart
@@ -96,14 +96,11 @@ class MojoAssetBundle extends CachingAssetBundle {
}
AssetBundle _initRootBundle() {
- try {
- AssetBundleProxy bundle = new AssetBundleProxy.fromHandle(
- new core.MojoHandle(ui.takeRootBundleHandle())
- );
- return new MojoAssetBundle(bundle);
- } catch (e) {
+ int h = ui.takeRootBundleHandle();
+ if (h == core.MojoHandle.INVALID)
return null;
- }
+ core.MojoHandle handle = new core.MojoHandle(h);
+ return new MojoAssetBundle(new AssetBundleProxy.fromHandle(handle));
}
final AssetBundle rootBundle = _initRootBundle();
diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart
index 51f71aa141..ff0023ca9b 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -1,14 +1,10 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright 2016 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:ui' as ui;
+import 'package:flutter/shell.dart';
-import 'package:mojo/application.dart';
-import 'package:mojo/bindings.dart' as bindings;
-import 'package:mojo/core.dart' as core;
-import 'package:mojo/mojo/service_provider.mojom.dart' as mojom;
-import 'package:mojo/mojo/shell.mojom.dart' as mojom;
+export 'package:flutter/shell.dart';
/// Base class for mixins that provide singleton services (also known as
/// "bindings").
@@ -41,84 +37,9 @@ abstract class BindingBase {
String toString() => '<$runtimeType>';
}
-// A replacement for shell.connectToService. Implementations should return true
-// if they handled the request, or false if the request should fall through
-// to the default requestService.
-typedef bool OverrideConnectToService(String url, Object proxy);
-
-abstract class MojoShell extends BindingBase {
-
+abstract class Services extends BindingBase {
void initInstances() {
super.initInstances();
- _instance = this;
- }
-
- static MojoShell _instance;
- static MojoShell get instance => _instance;
-
- static mojom.ShellProxy _initShellProxy() {
- core.MojoHandle shellHandle = new core.MojoHandle(ui.takeShellProxyHandle());
- if (!shellHandle.isValid)
- return null;
- return new mojom.ShellProxy.fromHandle(shellHandle);
- }
- final mojom.Shell _shell = _initShellProxy()?.ptr;
-
- static ApplicationConnection _initEmbedderConnection() {
- core.MojoHandle servicesHandle = new core.MojoHandle(ui.takeServicesProvidedByEmbedder());
- core.MojoHandle exposedServicesHandle = new core.MojoHandle(ui.takeServicesProvidedToEmbedder());
- if (!servicesHandle.isValid || !exposedServicesHandle.isValid)
- return null;
- mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.fromHandle(servicesHandle);
- mojom.ServiceProviderStub exposedServices = new mojom.ServiceProviderStub.fromHandle(exposedServicesHandle);
- return new ApplicationConnection(exposedServices, services);
- }
- final ApplicationConnection _embedderConnection = _initEmbedderConnection();
-
- /// Attempts to connect to an application via the Mojo shell.
- ApplicationConnection connectToApplication(String url) {
- if (_shell == null)
- return null;
- mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.unbound();
- mojom.ServiceProviderStub exposedServices = new mojom.ServiceProviderStub.unbound();
- _shell.connectToApplication(url, services, exposedServices);
- return new ApplicationConnection(exposedServices, services);
- }
-
- /// Set this to intercept calls to [connectToService()] and supply an
- /// alternative implementation of a service (for example, a mock for testing).
- OverrideConnectToService overrideConnectToService;
-
- /// Attempts to connect to a service implementing the interface for the given proxy.
- /// If an application URL is specified, the service will be requested from that application.
- /// Otherwise, it will be requested from the embedder (the Flutter engine).
- void connectToService(String url, bindings.ProxyBase proxy) {
- if (overrideConnectToService != null && overrideConnectToService(url, proxy))
- return;
- if (url == null || _shell == null) {
- // If the application URL is null, it means the service to connect
- // to is one provided by the embedder.
- // If the applircation URL isn't null but there's no shell, then
- // ask the embedder in case it provides it. (For example, if you're
- // running on Android without the Mojo shell, then you can obtain
- // the media service from the embedder directly, instead of having
- // to ask the media application for it.)
- // This makes it easier to write an application that works both
- // with and without a Mojo environment.
- _embedderConnection?.requestService(proxy);
- return;
- }
- mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.unbound();
- _shell.connectToApplication(url, services, null);
- core.MojoMessagePipe pipe = new core.MojoMessagePipe();
- proxy.impl.bind(pipe.endpoints[0]);
- services.ptr.connectToService(proxy.serviceName, pipe.endpoints[1]);
- services.close();
- }
-
- /// Registers a service to expose to the embedder.
- void provideService(String interfaceName, ServiceFactory factory) {
- _embedderConnection?.provideService(interfaceName, factory);
+ new MojoShell();
}
}
-MojoShell get shell => MojoShell.instance;
diff --git a/packages/flutter/lib/src/services/print.dart b/packages/flutter/lib/src/services/print.dart
index 86dc01f386..4d594a6e01 100644
--- a/packages/flutter/lib/src/services/print.dart
+++ b/packages/flutter/lib/src/services/print.dart
@@ -46,9 +46,5 @@ void _debugPrintTask() {
}
void debugPrintStack() {
- try {
- throw new Exception();
- } catch (e, stack) {
- debugPrint(stack.toString());
- }
+ debugPrint(StackTrace.current.toString());
}
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
new file mode 100644
index 0000000000..553676a3bb
--- /dev/null
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -0,0 +1,223 @@
+// 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:async';
+import 'dart:ui' as ui show Locale, WindowPadding, window;
+
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
+
+import 'asset_vendor.dart';
+import 'basic.dart';
+import 'binding.dart';
+import 'checked_mode_banner.dart';
+import 'framework.dart';
+import 'locale_query.dart';
+import 'media_query.dart';
+import 'navigator.dart';
+import 'performance_overlay.dart';
+import 'semantics_debugger.dart';
+import 'title.dart';
+
+AssetBundle _initDefaultBundle() {
+ if (rootBundle != null)
+ return rootBundle;
+ return new NetworkAssetBundle(Uri.base);
+}
+
+final AssetBundle _defaultBundle = _initDefaultBundle();
+
+class RouteArguments {
+ const RouteArguments({ this.context });
+ final BuildContext context;
+}
+typedef Widget RouteBuilder(RouteArguments args);
+
+typedef Future LocaleChangedCallback(Locale locale);
+
+class WidgetsApp extends StatefulComponent {
+ WidgetsApp({
+ Key key,
+ this.title,
+ this.textStyle,
+ this.color,
+ this.routes: const {},
+ this.onGenerateRoute,
+ this.onLocaleChanged,
+ this.showPerformanceOverlay: false,
+ this.showSemanticsDebugger: false,
+ this.debugShowCheckedModeBanner: true
+ }) : super(key: key) {
+ assert(routes != null);
+ assert(routes.containsKey(Navigator.defaultRouteName) || onGenerateRoute != null);
+ assert(showPerformanceOverlay != null);
+ assert(showSemanticsDebugger != null);
+ }
+
+ /// A one-line description of this app for use in the window manager.
+ final String title;
+
+ /// The default text style for [Text] in the application.
+ final TextStyle textStyle;
+
+ /// The primary color to use for the application in the operating system
+ /// interface.
+ ///
+ /// For example, on Android this is the color used for the application in the
+ /// application switcher.
+ final Color color;
+
+ /// The default table of routes for the application. When the
+ /// [Navigator] is given a named route, the name will be looked up
+ /// in this table first. If the name is not available, then
+ /// [onGenerateRoute] will be called instead.
+ final Map routes;
+
+ /// The route generator callback used when the app is navigated to a
+ /// named route but the name is not in the [routes] table.
+ final RouteFactory onGenerateRoute;
+
+ /// Callback that is invoked when the operating system changes the
+ /// current locale.
+ final LocaleChangedCallback onLocaleChanged;
+
+ /// Turns on a performance overlay.
+ /// https://flutter.io/debugging/#performanceoverlay
+ final bool showPerformanceOverlay;
+
+ /// Turns on an overlay that shows the accessibility information
+ /// reported by the framework.
+ final bool showSemanticsDebugger;
+
+ /// Turns on a "SLOW MODE" little banner in checked mode to indicate
+ /// that the app is in checked mode. This is on by default (in
+ /// checked mode), to turn it off, set the constructor argument to
+ /// false. In release mode this has no effect.
+ ///
+ /// To get this banner in your application if you're not using
+ /// WidgetsApp, include a [CheckedModeBanner] widget in your app.
+ ///
+ /// This banner is intended to avoid people complaining that your
+ /// app is slow when it's in checked mode. In checked mode, Flutter
+ /// enables a large number of expensive diagnostics to aid in
+ /// development, and so performance in checked mode is not
+ /// representative of what will happen in release mode.
+ final bool debugShowCheckedModeBanner;
+
+ WidgetsAppState createState() => new WidgetsAppState();
+}
+
+EdgeDims _getPadding(ui.WindowPadding padding) {
+ return new EdgeDims.TRBL(padding.top, padding.right, padding.bottom, padding.left);
+}
+
+class WidgetsAppState extends State implements BindingObserver {
+
+ GlobalObjectKey _navigator;
+
+ LocaleQueryData _localeData;
+
+ void initState() {
+ super.initState();
+ _navigator = new GlobalObjectKey(this);
+ didChangeLocale(ui.window.locale);
+ WidgetFlutterBinding.instance.addObserver(this);
+ }
+
+ void dispose() {
+ WidgetFlutterBinding.instance.removeObserver(this);
+ super.dispose();
+ }
+
+ bool didPopRoute() {
+ assert(mounted);
+ NavigatorState navigator = _navigator.currentState;
+ assert(navigator != null);
+ bool result = false;
+ navigator.openTransaction((NavigatorTransaction transaction) {
+ result = transaction.pop();
+ });
+ return result;
+ }
+
+ void didChangeMetrics() {
+ setState(() {
+ // The properties of ui.window have changed. We use them in our build
+ // function, so we need setState(), but we don't cache anything locally.
+ });
+ }
+
+ void didChangeLocale(Locale locale) {
+ if (config.onLocaleChanged != null) {
+ config.onLocaleChanged(locale).then((LocaleQueryData data) {
+ if (mounted)
+ setState(() { _localeData = data; });
+ });
+ }
+ }
+
+ void didChangeAppLifecycleState(AppLifecycleState state) { }
+
+ NavigatorObserver get navigatorObserver => null;
+
+ Widget build(BuildContext context) {
+ if (config.onLocaleChanged != null && _localeData == null) {
+ // If the app expects a locale but we don't yet know the locale, then
+ // don't build the widgets now.
+ // TODO(ianh): Make this unnecessary. See https://github.com/flutter/flutter/issues/1865
+ return new Container();
+ }
+
+ Widget result = new MediaQuery(
+ data: new MediaQueryData(
+ size: ui.window.size,
+ devicePixelRatio: ui.window.devicePixelRatio,
+ padding: _getPadding(ui.window.padding)
+ ),
+ child: new LocaleQuery(
+ data: _localeData,
+ child: new DefaultTextStyle(
+ style: config.textStyle,
+ child: new AssetVendor(
+ bundle: _defaultBundle,
+ devicePixelRatio: ui.window.devicePixelRatio,
+ child: new Title(
+ title: config.title,
+ color: config.color,
+ child: new Navigator(
+ key: _navigator,
+ initialRoute: ui.window.defaultRouteName,
+ onGenerateRoute: config.onGenerateRoute,
+ observer: navigatorObserver
+ )
+ )
+ )
+ )
+ )
+ );
+ if (config.showPerformanceOverlay) {
+ result = new Stack(
+ children: [
+ result,
+ new Positioned(bottom: 0.0, left: 0.0, right: 0.0, child: new PerformanceOverlay.allEnabled()),
+ ]
+ );
+ }
+ if (config.showSemanticsDebugger) {
+ result = new SemanticsDebugger(
+ child: result
+ );
+ }
+ assert(() {
+ if (config.debugShowCheckedModeBanner) {
+ result = new CheckedModeBanner(
+ child: result
+ );
+ }
+ return true;
+ });
+ return result;
+ }
+
+}
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index ab538b1feb..51759f7b42 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -999,6 +999,7 @@ class BlockBody extends MultiChildRenderObjectWidget {
}
}
+/// A base class for widgets that accept [Positioned] children.
abstract class StackRenderObjectWidgetBase extends MultiChildRenderObjectWidget {
StackRenderObjectWidgetBase({
List children: _emptyWidgetList,
diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart
index 58247e763f..0b353bc0dc 100644
--- a/packages/flutter/lib/src/widgets/binding.dart
+++ b/packages/flutter/lib/src/widgets/binding.dart
@@ -24,7 +24,7 @@ class BindingObserver {
/// 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, MojoShell, Renderer {
+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
diff --git a/packages/flutter/lib/src/widgets/dismissable.dart b/packages/flutter/lib/src/widgets/dismissable.dart
index 05158c551f..08058a43cd 100644
--- a/packages/flutter/lib/src/widgets/dismissable.dart
+++ b/packages/flutter/lib/src/widgets/dismissable.dart
@@ -256,7 +256,7 @@ class _DismissableState extends State {
assert(_resizeAnimation.status == AnimationStatus.completed);
throw new WidgetError(
'Dismissable widget completed its resize animation without being removed from the tree.\n'
- 'Make sure to implement the onDismissed handler and to immediately remove the Dismissable\n'
+ 'Make sure to implement the onDismissed handler and to immediately remove the Dismissable '
'widget from the application once that handler has fired.'
);
}
diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart
index 015094ae77..8f129d099a 100644
--- a/packages/flutter/lib/src/widgets/drag_target.dart
+++ b/packages/flutter/lib/src/widgets/drag_target.dart
@@ -234,7 +234,7 @@ class _DraggableState extends State> {
_activeCount += 1;
});
return new _DragAvatar(
- overlay: Overlay.of(context),
+ overlay: Overlay.of(context, debugRequiredFor: config),
data: config.data,
initialPosition: position,
dragStartPoint: dragStartPoint,
@@ -249,6 +249,7 @@ class _DraggableState extends State> {
}
Widget build(BuildContext context) {
+ assert(Overlay.of(context, debugRequiredFor: config) != null);
final bool canDrag = config.maxSimultaneousDrags == null ||
_activeCount < config.maxSimultaneousDrags;
final bool showChild = _activeCount == 0 || config.childWhenDragging == null;
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index 1c65f8942f..35358bec45 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -149,8 +149,12 @@ abstract class GlobalKey> extends Key {
message += 'The following GlobalKey was found multiple times among mounted elements: $key (${_debugDuplicates[key]} instances)\n';
message += 'The most recently registered instance is: ${_registry[key]}\n';
}
- if (!_debugDuplicates.isEmpty)
- throw new WidgetError('Incorrect GlobalKey usage.', message);
+ if (!_debugDuplicates.isEmpty) {
+ throw new WidgetError(
+ 'Incorrect GlobalKey usage.\n'
+ '$message'
+ );
+ }
return true;
}
@@ -1012,8 +1016,24 @@ abstract class Element implements BuildContext {
}
}
+/// A widget that renders an exception's message. This widget is used when a
+/// build function fails, to help with determining where the problem lies.
+/// Exceptions are also logged to the console, which you can read using `flutter
+/// logs`. The console will also include additional information such as the
+/// stack trace for the exception.
class ErrorWidget extends LeafRenderObjectWidget {
- RenderBox createRenderObject() => new RenderErrorBox();
+ ErrorWidget(
+ Object exception
+ ) : message = _stringify(exception),
+ super(key: new UniqueKey());
+ final String message;
+ static String _stringify(Object exception) {
+ try {
+ return exception.toString();
+ } catch (e) { }
+ return 'Error';
+ }
+ RenderBox createRenderObject() => new RenderErrorBox(message);
}
typedef void BuildScheduler(BuildableElement element);
@@ -1109,9 +1129,10 @@ abstract class BuildableElement extends Element {
}
if (_debugStateLocked && (!_debugAllowIgnoredCallsToMarkNeedsBuild || !dirty)) {
throw new WidgetError(
- 'Cannot mark this component as needing to build because the framework is '
- 'already in the process of building widgets. A widget can be marked as '
- 'needing to be built during the build phase only if one if its ancestor '
+ 'setState() or markNeedsBuild() called during build.\n'
+ 'This component cannot be marked as needing to build because the framework '
+ 'is already in the process of building widgets. A widget can be marked as '
+ 'needing to be built during the build phase only if one if its ancestors '
'is currently building. This exception is allowed because the framework '
'builds parent widgets before children, which means a dirty descendant '
'will always be built. Otherwise, the framework might not visit this '
@@ -1208,15 +1229,18 @@ abstract class ComponentElement extends BuildableElement {
assert(() {
if (built == null) {
throw new WidgetError(
- 'A build function returned null. Build functions must never return null.',
- 'The offending widget is: $widget'
+ 'A build function returned null.\n'
+ 'The offending widget is: $widget\n'
+ 'Build functions must never return null. '
+ 'To return an empty space that causes the building widget to fill available room, return "new Container()". '
+ 'To return an empty space that takes as little room as possible, return "new Container(width: 0.0, height: 0.0)".'
);
}
return true;
});
} catch (e, stack) {
_debugReportException('building $_widget', e, stack);
- built = new ErrorWidget();
+ built = new ErrorWidget(e);
} finally {
// We delay marking the element as clean until after calling _builder so
// that attempts to markNeedsBuild() during build() will be ignored.
@@ -1228,7 +1252,7 @@ abstract class ComponentElement extends BuildableElement {
assert(_child != null);
} catch (e, stack) {
_debugReportException('building $_widget', e, stack);
- built = new ErrorWidget();
+ built = new ErrorWidget(e);
_child = updateChild(null, built, slot);
}
}
@@ -1289,7 +1313,11 @@ class StatefulComponentElement>
assert(() {
if (_state._debugLifecycleState == _StateLifecycle.initialized)
return true;
- throw new WidgetError('${_state.runtimeType}.initState failed to call super.initState.');
+ throw new WidgetError(
+ '${_state.runtimeType}.initState failed to call super.initState.\n'
+ 'initState() implementations must always call their superclass initState() method, to ensure '
+ 'that the entire widget is initialized correctly.'
+ );
});
assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
super._firstBuild();
@@ -1324,7 +1352,11 @@ class StatefulComponentElement>
assert(() {
if (_state._debugLifecycleState == _StateLifecycle.defunct)
return true;
- throw new WidgetError('${_state.runtimeType}.dispose failed to call super.dispose.');
+ throw new WidgetError(
+ '${_state.runtimeType}.dispose failed to call super.dispose.\n'
+ 'dispose() implementations must always call their superclass dispose() method, to ensure '
+ 'that all the resources used by the widget are fully released.'
+ );
});
assert(!dirty); // See BuildableElement.unmount for why this is important.
_state._element = null;
@@ -1381,12 +1413,15 @@ class ParentDataElement extends _ProxyElement {
}
if (ancestor != null && badAncestors.isEmpty)
return true;
- throw new WidgetError('Incorrect use of ParentDataWidget.', widget.debugDescribeInvalidAncestorChain(
- description: "$this",
- ownershipChain: parent.debugGetOwnershipChain(10),
- foundValidAncestor: ancestor != null,
- badAncestors: badAncestors
- ));
+ throw new WidgetError(
+ 'Incorrect use of ParentDataWidget.\n' +
+ widget.debugDescribeInvalidAncestorChain(
+ description: "$this",
+ ownershipChain: parent.debugGetOwnershipChain(10),
+ foundValidAncestor: ancestor != null,
+ badAncestors: badAncestors
+ )
+ );
});
super.mount(parent, slot);
}
@@ -1507,7 +1542,14 @@ abstract class RenderObjectElement extends Buildab
// 'BuildContext' argument which you can pass to Theme.of() and other
// InheritedWidget APIs which eventually trigger a rebuild.)
assert(() {
- throw new WidgetError('$runtimeType failed to implement reinvokeBuilders(), but got marked dirty.');
+ throw new WidgetError(
+ '$runtimeType failed to implement reinvokeBuilders(), but got marked dirty.\n'
+ 'If a RenderObjectElement subclass supports being marked dirty, then the '
+ 'reinvokeBuilders() method must be implemented.\n'
+ 'If a RenderObjectElement uses a builder callback, it must support being '
+ 'marked dirty, because builder callbacks can register the object as having '
+ 'an Inherited dependency.'
+ );
});
}
@@ -1812,7 +1854,11 @@ class MultiChildRenderObjectElement exte
continue; // when these nodes are reordered, we just reassign the data
if (!idSet.add(child.key)) {
- throw new WidgetError('If multiple keyed nodes exist as children of another node, they must have unique keys. $widget has multiple children with key "${child.key}".');
+ throw new WidgetError(
+ 'Duplicate keys found.\n'
+ 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n'
+ '$widget has multiple children with key "${child.key}".'
+ );
}
}
return false;
@@ -1841,16 +1887,10 @@ class MultiChildRenderObjectElement exte
}
}
-class WidgetError extends Error {
- WidgetError(String message, [ String rawDetails = '' ]) {
- rawDetails = rawDetails.trimRight(); // remove trailing newlines
- if (rawDetails != '')
- _message = '$message\n$rawDetails';
- else
- _message = message;
- }
- String _message;
- String toString() => _message;
+class WidgetError extends AssertionError {
+ WidgetError(this.message);
+ final String message;
+ String toString() => message;
}
typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack);
diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart
index 58a4567af4..c59575c99f 100644
--- a/packages/flutter/lib/src/widgets/gesture_detector.dart
+++ b/packages/flutter/lib/src/widgets/gesture_detector.dart
@@ -71,11 +71,20 @@ class GestureDetector extends StatelessComponent {
bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
if (havePan || haveScale) {
- if (havePan && haveScale)
- throw new WidgetError('Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.');
+ if (havePan && haveScale) {
+ throw new WidgetError(
+ 'Incorrect GestureDetector arguments.\n'
+ 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.'
+ );
+ }
String recognizer = havePan ? 'pan' : 'scale';
- if (haveVerticalDrag && haveHorizontalDrag)
- throw new WidgetError('Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.');
+ if (haveVerticalDrag && haveHorizontalDrag) {
+ throw new WidgetError(
+ 'Incorrect GestureDetector arguments.\n'
+ 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
+ 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.'
+ );
+ }
}
return true;
});
@@ -279,8 +288,15 @@ class RawGestureDetectorState extends State {
/// the gesture detector should be enabled.
void replaceGestureRecognizers(Map gestures) {
assert(() {
- if (!RenderObject.debugDoingLayout)
- throw new WidgetError('replaceGestureRecognizers() can only be called during the layout phase.');
+ if (!RenderObject.debugDoingLayout) {
+ throw new WidgetError(
+ 'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
+ 'The replaceGestureRecognizers() method can only be called during the layout phase. '
+ 'To set the gesture recognisers at other times, trigger a new build using setState() '
+ 'and provide the new gesture recognisers as constructor arguments to the corresponding '
+ 'RawGestureDetector or GestureDetector object.'
+ );
+ }
return true;
});
_syncAll(gestures);
diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart
index 8f13cdc36e..69ffc8bdfb 100644
--- a/packages/flutter/lib/src/widgets/heroes.dart
+++ b/packages/flutter/lib/src/widgets/heroes.dart
@@ -113,8 +113,8 @@ class Hero extends StatefulComponent {
if (tagHeroes.containsKey(key)) {
new WidgetError(
'There are multiple heroes that share the same key within the same subtree.\n'
- 'Within each subtree for which heroes are to be animated (typically a PageRoute subtree),\n'
- 'either each Hero must have a unique tag, or, all the heroes with a particular tag must\n'
+ 'Within each subtree for which heroes are to be animated (typically a PageRoute subtree), '
+ 'either each Hero must have a unique tag, or, all the heroes with a particular tag must '
'have different keys.\n'
'In this case, the tag "$tag" had multiple heroes with the key "$key".'
);
diff --git a/packages/flutter/lib/src/widgets/mimic.dart b/packages/flutter/lib/src/widgets/mimic.dart
index 5fb4818009..38fb20468f 100644
--- a/packages/flutter/lib/src/widgets/mimic.dart
+++ b/packages/flutter/lib/src/widgets/mimic.dart
@@ -137,6 +137,9 @@ class Mimic extends StatelessComponent {
}
/// A widget that can be copied by a [Mimic].
+///
+/// This widget's State, [MimicableState], contains an API for initiating the
+/// mimic operation.
class Mimicable extends StatefulComponent {
Mimicable({ Key key, this.child }) : super(key: key);
@@ -173,7 +176,18 @@ class MimicableState extends State {
/// passing it to a [Mimic] widget. To mimic the child in the
/// [Overlay], consider using [liftToOverlay()] instead.
MimicableHandle startMimic() {
- assert(_placeholderSize == null);
+ assert(() {
+ if (_placeholderSize != null) {
+ throw new WidgetError(
+ 'Mimicable started while already active.\n'
+ 'When startMimic() or liftToOverlay() is called on a MimicableState, the mimic becomes active. '
+ 'While active, it cannot be reactivated until it is stopped. '
+ 'To stop a Mimicable started with startMimic(), call the MimicableHandle object\'s stopMimic() method. '
+ 'To stop a Mimicable started with liftToOverlay(), call dispose() on the MimicOverlayEntry.'
+ );
+ }
+ return true;
+ });
RenderBox box = context.findRenderObject();
assert(box != null);
assert(box.hasSize);
@@ -193,8 +207,7 @@ class MimicableState extends State {
/// had when the mimicking process started and (2) the child will be
/// placed in the enclosing overlay.
MimicOverlayEntry liftToOverlay() {
- OverlayState overlay = Overlay.of(context);
- assert(overlay != null); // You need an overlay to lift into.
+ OverlayState overlay = Overlay.of(context, debugRequiredFor: config);
MimicOverlayEntry entry = new MimicOverlayEntry._(startMimic());
overlay.insert(entry._overlayEntry);
return entry;
diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart
index f293c960e2..a3941263ce 100644
--- a/packages/flutter/lib/src/widgets/navigator.dart
+++ b/packages/flutter/lib/src/widgets/navigator.dart
@@ -260,8 +260,12 @@ class Navigator extends StatefulComponent {
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher());
assert(() {
- if (navigator == null)
- throw new WidgetError('openTransaction called with a context that does not include a Navigator. The context passed to the Navigator.openTransaction() method must be that of a widget that is a descendant of a Navigator widget.');
+ if (navigator == null) {
+ throw new WidgetError(
+ 'openTransaction called with a context that does not include a Navigator.\n'
+ 'The context passed to the Navigator.openTransaction() method must be that of a widget that is a descendant of a Navigator widget.'
+ );
+ }
return true;
});
navigator.openTransaction(callback);
diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart
index 015248b7f6..6b9bad218c 100644
--- a/packages/flutter/lib/src/widgets/overlay.dart
+++ b/packages/flutter/lib/src/widgets/overlay.dart
@@ -71,7 +71,32 @@ class Overlay extends StatefulComponent {
final List initialEntries;
/// The state from the closest instance of this class that encloses the given context.
- static OverlayState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher());
+ ///
+ /// In checked mode, if the [debugRequiredFor] argument is provided then this
+ /// function will assert that an overlay was found and will throw an exception
+ /// if not. The exception attempts to explain that the calling [Widget] (the
+ /// one given by the [debugRequiredFor] argument) needs an [Overlay] to be
+ /// present to function.
+ static OverlayState of(BuildContext context, { Widget debugRequiredFor }) {
+ OverlayState result = context.ancestorStateOfType(const TypeMatcher());
+ assert(() {
+ if (debugRequiredFor != null && result == null) {
+ String additional = context.widget != debugRequiredFor
+ ? '\nThe context from which that widget was searching for an overlay was:\n $context'
+ : '';
+ throw new WidgetError(
+ 'No Overlay widget found.\n'
+ '${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.\n'
+ 'The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.\n'
+ 'The specific widget that failed to find an overlay was:\n'
+ ' $debugRequiredFor'
+ '$additional'
+ );
+ }
+ return true;
+ });
+ return result;
+ }
OverlayState createState() => new OverlayState();
}
diff --git a/packages/flutter/lib/src/widgets/pageable_list.dart b/packages/flutter/lib/src/widgets/pageable_list.dart
index 1b6b2c2af9..ab88721202 100644
--- a/packages/flutter/lib/src/widgets/pageable_list.dart
+++ b/packages/flutter/lib/src/widgets/pageable_list.dart
@@ -18,6 +18,10 @@ enum ItemsSnapAlignment {
adjacentItem
}
+/// Scrollable widget that scrolls one "page" at a time.
+///
+/// In a pageable list, one child is visible at a time. Scrolling the list
+/// reveals either the next or previous child.
class PageableList extends Scrollable {
PageableList({
Key key,
@@ -46,17 +50,34 @@ class PageableList extends Scrollable {
snapOffsetCallback: snapOffsetCallback
);
+ /// Whether the first item should be revealed after scrolling past the last item.
final bool itemsWrap;
+
+ /// Controls whether a fling always reveals the adjacent item or whether flings can traverse many items.
final ItemsSnapAlignment itemsSnapAlignment;
+
+ /// Called when the currently visible page changes.
final ValueChanged onPageChanged;
+
+ /// Used to paint the scrollbar for this list.
final ScrollableListPainter scrollableListPainter;
+
+ /// The duration used when animating to a given page.
final Duration duration;
+
+ /// The animation curve to use when animating to a given page.
final Curve curve;
+
+ /// The list of pages themselves.
final Iterable children;
PageableListState createState() => new PageableListState();
}
+/// State for a [PageableList] widget.
+///
+/// Widgets that subclass [PageableList] can subclass this class to have
+/// sensible default behaviors for pageable lists.
class PageableListState extends ScrollableState {
int get _itemCount => config.children?.length ?? 0;
int _previousItemCount;
diff --git a/packages/flutter/lib/src/widgets/placeholder.dart b/packages/flutter/lib/src/widgets/placeholder.dart
index 534cec220b..f9ca3104bd 100644
--- a/packages/flutter/lib/src/widgets/placeholder.dart
+++ b/packages/flutter/lib/src/widgets/placeholder.dart
@@ -12,6 +12,9 @@ class Placeholder extends StatefulComponent {
PlaceholderState createState() => new PlaceholderState();
}
+/// State for a [Placeholder] widget.
+///
+/// Useful for setting the child currently displayed by this placeholder widget.
class PlaceholderState extends State {
/// The child that this widget builds.
///
diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart
index 1b4aaf6ec7..2ecbc81f01 100644
--- a/packages/flutter/lib/src/widgets/scrollable.dart
+++ b/packages/flutter/lib/src/widgets/scrollable.dart
@@ -327,15 +327,9 @@ abstract class ScrollableState extends State {
});
PageStorage.of(context)?.writeState(context, _scrollOffset);
new ScrollNotification(this, _scrollOffset).dispatch(context);
- final needsScrollStart = !_isBetweenOnScrollStartAndOnScrollEnd;
- if (needsScrollStart) {
- dispatchOnScrollStart();
- assert(_isBetweenOnScrollStartAndOnScrollEnd);
- }
+ _startScroll();
dispatchOnScroll();
- assert(_isBetweenOnScrollStartAndOnScrollEnd);
- if (needsScrollStart)
- dispatchOnScrollEnd();
+ _endScroll();
}
/// Scroll this widget by the given scroll delta.
@@ -372,8 +366,8 @@ abstract class ScrollableState extends State {
Future _animateTo(double newScrollOffset, Duration duration, Curve curve) {
_controller.stop();
_controller.value = scrollOffset;
- _dispatchOnScrollStartIfNeeded();
- return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then(_dispatchOnScrollEndIfNeeded);
+ _startScroll();
+ return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then(_endScroll);
}
/// Fling the scroll offset with the given velocity.
@@ -401,8 +395,8 @@ abstract class ScrollableState extends State {
Simulation simulation = _createSnapSimulation(scrollVelocity) ?? _createFlingSimulation(scrollVelocity);
if (simulation == null)
return new Future.value();
- _dispatchOnScrollStartIfNeeded();
- return _controller.animateWith(simulation).then(_dispatchOnScrollEndIfNeeded);
+ _startScroll();
+ return _controller.animateWith(simulation).then(_endScroll);
}
/// Whether this scrollable should attempt to snap scroll offsets.
@@ -453,14 +447,21 @@ abstract class ScrollableState extends State {
return simulation;
}
-
- bool _isBetweenOnScrollStartAndOnScrollEnd = false;
+ // When we start an scroll animation, we stop any previous scroll animation.
+ // However, the code that would deliver the onScrollEnd callback is watching
+ // for animations to end using a Future that resolves at the end of the
+ // microtask. That causes animations to "overlap" between the time we start a
+ // new animation and the end of the microtask. By the time the microtask is
+ // over and we check whether to deliver an onScrollEnd callback, we will have
+ // started the new animation (having skipped the onScrollStart) and therefore
+ // we won't deliver the onScrollEnd until the second animation is finished.
+ int _numberOfInProgressScrolls = 0;
/// Calls the onScroll callback.
///
/// Subclasses can override this function to hook the scroll callback.
void dispatchOnScroll() {
- assert(_isBetweenOnScrollStartAndOnScrollEnd);
+ assert(_numberOfInProgressScrolls > 0);
if (config.onScroll != null)
config.onScroll(_scrollOffset);
}
@@ -470,11 +471,12 @@ abstract class ScrollableState extends State {
}
void _handleDragStart(_) {
- _dispatchOnScrollStartIfNeeded();
+ _startScroll();
}
- void _dispatchOnScrollStartIfNeeded() {
- if (!_isBetweenOnScrollStartAndOnScrollEnd)
+ void _startScroll() {
+ _numberOfInProgressScrolls += 1;
+ if (_numberOfInProgressScrolls == 1)
dispatchOnScrollStart();
}
@@ -482,8 +484,7 @@ abstract class ScrollableState extends State {
///
/// Subclasses can override this function to hook the scroll start callback.
void dispatchOnScrollStart() {
- assert(!_isBetweenOnScrollStartAndOnScrollEnd);
- _isBetweenOnScrollStartAndOnScrollEnd = true;
+ assert(_numberOfInProgressScrolls == 1);
if (config.onScrollStart != null)
config.onScrollStart(_scrollOffset);
}
@@ -495,11 +496,12 @@ abstract class ScrollableState extends State {
Future _handleDragEnd(Velocity velocity) {
double scrollVelocity = pixelDeltaToScrollOffset(velocity.pixelsPerSecond) / Duration.MILLISECONDS_PER_SECOND;
// The gesture velocity properties are pixels/second, config min,max limits are pixels/ms
- return fling(scrollVelocity.clamp(-kMaxFlingVelocity, kMaxFlingVelocity)).then(_dispatchOnScrollEndIfNeeded);
+ return fling(scrollVelocity.clamp(-kMaxFlingVelocity, kMaxFlingVelocity)).then(_endScroll);
}
- void _dispatchOnScrollEndIfNeeded(_) {
- if (_isBetweenOnScrollStartAndOnScrollEnd)
+ void _endScroll([_]) {
+ _numberOfInProgressScrolls -= 1;
+ if (_numberOfInProgressScrolls == 0)
dispatchOnScrollEnd();
}
@@ -507,8 +509,7 @@ abstract class ScrollableState extends State {
///
/// Subclasses can override this function to hook the scroll end callback.
void dispatchOnScrollEnd() {
- assert(_isBetweenOnScrollStartAndOnScrollEnd);
- _isBetweenOnScrollStartAndOnScrollEnd = false;
+ assert(_numberOfInProgressScrolls == 0);
if (config.onScrollEnd != null)
config.onScrollEnd(_scrollOffset);
}
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index 5e50f13468..fcea6581e2 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -5,6 +5,7 @@
/// The Flutter widget framework.
library widgets;
+export 'src/widgets/app.dart';
export 'src/widgets/asset_vendor.dart';
export 'src/widgets/auto_layout.dart';
export 'src/widgets/basic.dart';
diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart
index fb700b9cd4..6ac6287faf 100644
--- a/packages/flutter/test/rendering/rendering_tester.dart
+++ b/packages/flutter/test/rendering/rendering_tester.dart
@@ -26,7 +26,7 @@ enum EnginePhase {
composite
}
-class TestRenderingFlutterBinding extends BindingBase with Scheduler, MojoShell, Renderer, Gesturer {
+class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services, Renderer, Gesturer {
void initRenderView() {
if (renderView == null) {
renderView = new TestRenderView();
@@ -58,7 +58,7 @@ void layout(RenderBox box, { BoxConstraints constraints, EnginePhase phase: Engi
_renderer ??= new TestRenderingFlutterBinding();
- renderer.renderView.child = null;
+ renderer.renderView.child = null;
if (constraints != null) {
box = new RenderPositionedBox(
child: new RenderConstrainedBox(
diff --git a/packages/flutter/test/widget/custom_multi_child_layout_test.dart b/packages/flutter/test/widget/custom_multi_child_layout_test.dart
index 6145a77cce..900ec4d463 100644
--- a/packages/flutter/test/widget/custom_multi_child_layout_test.dart
+++ b/packages/flutter/test/widget/custom_multi_child_layout_test.dart
@@ -17,16 +17,15 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
}
Size performLayoutSize;
- BoxConstraints performLayoutConstraints;
Size performLayoutSize0;
Size performLayoutSize1;
bool performLayoutIsChild;
- void performLayout(Size size, BoxConstraints constraints) {
+ void performLayout(Size size) {
assert(!RenderObject.debugCheckingIntrinsics);
expect(() {
performLayoutSize = size;
- performLayoutConstraints = constraints;
+ BoxConstraints constraints = new BoxConstraints.loose(size);
performLayoutSize0 = layoutChild(0, constraints);
performLayoutSize1 = layoutChild(1, constraints);
performLayoutIsChild = isChild('fred');
@@ -68,10 +67,6 @@ void main() {
expect(delegate.performLayoutSize.width, 200.0);
expect(delegate.performLayoutSize.height, 300.0);
- expect(delegate.performLayoutConstraints.minWidth, 0.0);
- expect(delegate.performLayoutConstraints.maxWidth, 800.0);
- expect(delegate.performLayoutConstraints.minHeight, 0.0);
- expect(delegate.performLayoutConstraints.maxHeight, 600.0);
expect(delegate.performLayoutSize0.width, 150.0);
expect(delegate.performLayoutSize0.height, 100.0);
expect(delegate.performLayoutSize1.width, 100.0);
diff --git a/packages/flutter/test/widget/scroll_events_test.dart b/packages/flutter/test/widget/scroll_events_test.dart
index 0bc09ae96e..ad08a14182 100644
--- a/packages/flutter/test/widget/scroll_events_test.dart
+++ b/packages/flutter/test/widget/scroll_events_test.dart
@@ -95,4 +95,45 @@ void main() {
expect(log, equals(['scrollstart', 'scroll', 'scroll', 'scrollend']));
});
});
+
+ test('Scroll during animation', () {
+ testWidgets((WidgetTester tester) {
+ GlobalKey scrollKey = new GlobalKey();
+ List log = [];
+ tester.pumpWidget(_buildScroller(key: scrollKey, log: log));
+
+ expect(log, equals([]));
+ scrollKey.currentState.scrollTo(100.0, duration: const Duration(seconds: 1));
+ expect(log, equals(['scrollstart']));
+ tester.pump(const Duration(milliseconds: 100));
+ expect(log, equals(['scrollstart']));
+ tester.pump(const Duration(milliseconds: 100));
+ expect(log, equals(['scrollstart', 'scroll']));
+ scrollKey.currentState.scrollTo(100.0, duration: const Duration(seconds: 1));
+ expect(log, equals(['scrollstart', 'scroll']));
+ tester.pump(const Duration(milliseconds: 100));
+ expect(log, equals(['scrollstart', 'scroll']));
+ tester.pump(const Duration(milliseconds: 1500));
+ expect(log, equals(['scrollstart', 'scroll', 'scroll', 'scrollend']));
+ });
+ });
+
+ test('fling, fling generates one start/end pair', () {
+ testWidgets((WidgetTester tester) {
+ GlobalKey scrollKey = new GlobalKey();
+ List log = [];
+ tester.pumpWidget(_buildScroller(key: scrollKey, log: log));
+
+ expect(log, equals([]));
+ tester.flingFrom(new Point(100.0, 100.0), new Offset(-50.0, -50.0), 500.0);
+ tester.pump(new Duration(seconds: 1));
+ log.removeWhere((String value) => value == 'scroll');
+ expect(log, equals(['scrollstart']));
+ tester.flingFrom(new Point(100.0, 100.0), new Offset(-50.0, -50.0), 500.0);
+ tester.pump(new Duration(seconds: 1));
+ tester.pump(new Duration(seconds: 1));
+ log.removeWhere((String value) => value == 'scroll');
+ expect(log, equals(['scrollstart', 'scrollend']));
+ });
+ });
}
diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart
index a47842e71f..50e8e30d43 100644
--- a/packages/flutter_test/lib/src/widget_tester.dart
+++ b/packages/flutter_test/lib/src/widget_tester.dart
@@ -78,7 +78,7 @@ class WidgetTester extends Instrumentation {
super(binding: _SteppedWidgetFlutterBinding.ensureInitialized()) {
timeDilation = 1.0;
ui.window.onBeginFrame = null;
- runApp(new ErrorWidget()); // flush out the last build entirely
+ runApp(new Container(key: new UniqueKey())); // flush out the last build entirely
}
final FakeAsync async;
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index d13f9cac05..8e6fc399da 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -128,21 +128,19 @@ All done! In order to run your application, type:
void _renderTemplates(String projectName, String dirPath,
String flutterPackagesDirectory, { bool renderDriverTest: false }) {
- String relativePackagesDirectory = path.relative(
- flutterPackagesDirectory,
- from: path.join(dirPath, 'pubspec.yaml')
- );
+ new Directory(dirPath).createSync(recursive: true);
+
+ flutterPackagesDirectory = path.normalize(flutterPackagesDirectory);
+ flutterPackagesDirectory = _relativePath(from: dirPath, to: flutterPackagesDirectory);
printStatus('Creating project ${path.basename(projectName)}:');
- new Directory(dirPath).createSync(recursive: true);
-
Map templateContext = {
'projectName': projectName,
'androidIdentifier': _createAndroidIdentifier(projectName),
'iosIdentifier': _createUTIIdentifier(projectName),
'description': description,
- 'flutterPackagesDirectory': relativePackagesDirectory,
+ 'flutterPackagesDirectory': flutterPackagesDirectory,
'androidMinApiLevel': android.minApiLevel
};
@@ -211,3 +209,11 @@ String _validateProjectName(String projectName) {
return null;
}
+
+String _relativePath({ String from, String to }) {
+ String result = path.relative(to, from: from);
+ // `path.relative()` doesn't always return a correct result: dart-lang/path#12.
+ if (FileSystemEntity.isDirectorySync(path.join(from, result)))
+ return result;
+ return to;
+}
diff --git a/packages/newton/lib/src/simulation.dart b/packages/newton/lib/src/simulation.dart
index def3b376c2..8e494ac724 100644
--- a/packages/newton/lib/src/simulation.dart
+++ b/packages/newton/lib/src/simulation.dart
@@ -4,19 +4,17 @@
import 'tolerance.dart';
-abstract class Simulatable {
+/// The base class for all simulations. The user is meant to instantiate an
+/// instance of a simulation and query the same for the position and velocity
+/// of the body at a given interval.
+abstract class Simulation {
+ Tolerance tolerance = toleranceDefault;
+
/// The current position of the object in the simulation
double x(double time);
/// The current velocity of the object in the simulation
- double dx(double time);
-}
-
-/// The base class for all simulations. The user is meant to instantiate an
-/// instance of a simulation and query the same for the position and velocity
-/// of the body at a given interval.
-abstract class Simulation implements Simulatable {
- Tolerance tolerance = toleranceDefault;
+ double dx(double time); // TODO(ianh): remove this; see https://github.com/flutter/flutter/issues/2092
/// Returns if the simulation is done at a given time
bool isDone(double time);
diff --git a/packages/newton/lib/src/simulation_group.dart b/packages/newton/lib/src/simulation_group.dart
index 76fb440d61..fe36d739d9 100644
--- a/packages/newton/lib/src/simulation_group.dart
+++ b/packages/newton/lib/src/simulation_group.dart
@@ -10,7 +10,8 @@ import 'utils.dart';
/// must implement the appropriate methods to select the appropriate simulation
/// at a given time interval. The simulation group takes care to call the `step`
/// method at appropriate intervals. If more fine grained control over the the
-/// step is necessary, subclasses may override `Simulatable` methods.
+/// step is necessary, subclasses may override the [x], [dx], and [isDone]
+/// methods.
abstract class SimulationGroup extends Simulation {
/// The currently active simulation
diff --git a/packages/newton/lib/src/spring_simulation.dart b/packages/newton/lib/src/spring_simulation.dart
index 8406a7930a..324a753b16 100644
--- a/packages/newton/lib/src/spring_simulation.dart
+++ b/packages/newton/lib/src/spring_simulation.dart
@@ -7,120 +7,154 @@ import 'dart:math' as math;
import 'simulation.dart';
import 'utils.dart';
-abstract class _SpringSolution implements Simulatable {
+enum SpringType { unknown, criticallyDamped, underDamped, overDamped }
+
+abstract class _SpringSolution {
factory _SpringSolution(
- SpringDescription desc, double initialPosition, double initialVelocity) {
- double cmk =
- desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
-
- if (cmk == 0.0) {
+ SpringDescription desc,
+ double initialPosition,
+ double initialVelocity
+ ) {
+ double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
+ if (cmk == 0.0)
return new _CriticalSolution(desc, initialPosition, initialVelocity);
- } else if (cmk > 0.0) {
+ if (cmk > 0.0)
return new _OverdampedSolution(desc, initialPosition, initialVelocity);
- } else {
- return new _UnderdampedSolution(desc, initialPosition, initialVelocity);
- }
-
- return null;
+ return new _UnderdampedSolution(desc, initialPosition, initialVelocity);
}
+ double x(double time);
+ double dx(double time);
SpringType get type;
}
class _CriticalSolution implements _SpringSolution {
- final double _r, _c1, _c2;
-
factory _CriticalSolution(
- SpringDescription desc, double distance, double velocity) {
+ SpringDescription desc,
+ double distance,
+ double velocity
+ ) {
final double r = -desc.damping / (2.0 * desc.mass);
final double c1 = distance;
final double c2 = velocity / (r * distance);
return new _CriticalSolution.withArgs(r, c1, c2);
}
- SpringType get type => SpringType.criticallyDamped;
-
_CriticalSolution.withArgs(double r, double c1, double c2)
- : _r = r,
- _c1 = c1,
- _c2 = c2;
+ : _r = r,
+ _c1 = c1,
+ _c2 = c2;
- double x(double time) => (_c1 + _c2 * time) * math.pow(math.E, _r * time);
+ final double _r, _c1, _c2;
+
+ double x(double time) {
+ return (_c1 + _c2 * time) * math.pow(math.E, _r * time);
+ }
double dx(double time) {
final double power = math.pow(math.E, _r * time);
return _r * (_c1 + _c2 * time) * power + _c2 * power;
}
+
+ SpringType get type => SpringType.criticallyDamped;
}
class _OverdampedSolution implements _SpringSolution {
- final double _r1, _r2, _c1, _c2;
-
factory _OverdampedSolution(
- SpringDescription desc, double distance, double velocity) {
- final double cmk =
- desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
-
+ SpringDescription desc,
+ double distance,
+ double velocity
+ ) {
+ final double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
final double r1 = (-desc.damping - math.sqrt(cmk)) / (2.0 * desc.mass);
final double r2 = (-desc.damping + math.sqrt(cmk)) / (2.0 * desc.mass);
final double c2 = (velocity - r1 * distance) / (r2 - r1);
final double c1 = distance - c2;
-
return new _OverdampedSolution.withArgs(r1, r2, c1, c2);
}
_OverdampedSolution.withArgs(double r1, double r2, double c1, double c2)
- : _r1 = r1,
- _r2 = r2,
- _c1 = c1,
- _c2 = c2;
+ : _r1 = r1,
+ _r2 = r2,
+ _c1 = c1,
+ _c2 = c2;
+
+ final double _r1, _r2, _c1, _c2;
+
+ double x(double time) {
+ return _c1 * math.pow(math.E, _r1 * time) +
+ _c2 * math.pow(math.E, _r2 * time);
+ }
+
+ double dx(double time) {
+ return _c1 * _r1 * math.pow(math.E, _r1 * time) +
+ _c2 * _r2 * math.pow(math.E, _r2 * time);
+ }
SpringType get type => SpringType.overDamped;
-
- double x(double time) =>
- (_c1 * math.pow(math.E, _r1 * time) + _c2 * math.pow(math.E, _r2 * time));
-
- double dx(double time) => (_c1 * _r1 * math.pow(math.E, _r1 * time) +
- _c2 * _r2 * math.pow(math.E, _r2 * time));
}
class _UnderdampedSolution implements _SpringSolution {
- final double _w, _r, _c1, _c2;
-
factory _UnderdampedSolution(
- SpringDescription desc, double distance, double velocity) {
+ SpringDescription desc,
+ double distance,
+ double velocity
+ ) {
final double w = math.sqrt(4.0 * desc.mass * desc.springConstant -
- desc.damping * desc.damping) /
- (2.0 * desc.mass);
+ desc.damping * desc.damping) / (2.0 * desc.mass);
final double r = -(desc.damping / 2.0 * desc.mass);
final double c1 = distance;
final double c2 = (velocity - r * distance) / w;
-
return new _UnderdampedSolution.withArgs(w, r, c1, c2);
}
_UnderdampedSolution.withArgs(double w, double r, double c1, double c2)
- : _w = w,
- _r = r,
- _c1 = c1,
- _c2 = c2;
+ : _w = w,
+ _r = r,
+ _c1 = c1,
+ _c2 = c2;
- SpringType get type => SpringType.underDamped;
+ final double _w, _r, _c1, _c2;
- double x(double time) => math.pow(math.E, _r * time) *
- (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
+ double x(double time) {
+ return math.pow(math.E, _r * time) *
+ (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
+ }
double dx(double time) {
final double power = math.pow(math.E, _r * time);
final double cosine = math.cos(_w * time);
final double sine = math.sin(_w * time);
-
- return power * (_c2 * _w * cosine - _c1 * _w * sine) +
- _r * power * (_c2 * sine + _c1 * cosine);
+ return power * (_c2 * _w * cosine - _c1 * _w * sine) +
+ _r * power * (_c2 * sine + _c1 * cosine);
}
+
+ SpringType get type => SpringType.underDamped;
}
class SpringDescription {
+ SpringDescription({
+ this.mass,
+ this.springConstant,
+ this.damping
+ }) {
+ assert(mass != null);
+ assert(springConstant != null);
+ assert(damping != null);
+ }
+
+ /// Create a spring given the mass, spring constant and the damping ratio. The
+ /// damping ratio is especially useful trying to determing the type of spring
+ /// to create. A ratio of 1.0 creates a critically damped spring, > 1.0
+ /// creates an overdamped spring and < 1.0 an underdamped one.
+ SpringDescription.withDampingRatio({
+ double mass,
+ double springConstant,
+ double ratio: 1.0
+ }) : mass = mass,
+ springConstant = springConstant,
+ damping = ratio * 2.0 * math.sqrt(mass * springConstant);
+
/// The mass of the spring (m)
final double mass;
@@ -131,41 +165,23 @@ class SpringDescription {
/// Not to be confused with the damping ratio. Use the separate
/// constructor provided for this purpose
final double damping;
-
- SpringDescription(
- { this.mass, this.springConstant, this.damping }
- ) {
- assert(mass != null);
- assert(springConstant != null);
- assert(damping != null);
- }
-
- /// Create a spring given the mass, spring constant and the damping ratio. The
- /// damping ratio is especially useful trying to determing the type of spring
- /// to create. A ratio of 1.0 creates a critically damped spring, > 1.0
- /// creates an overdamped spring and < 1.0 an underdamped one.
- SpringDescription.withDampingRatio(
- {double mass, double springConstant, double ratio: 1.0})
- : mass = mass,
- springConstant = springConstant,
- damping = ratio * 2.0 * math.sqrt(mass * springConstant);
}
-enum SpringType { unknown, criticallyDamped, underDamped, overDamped, }
-
/// Creates a spring simulation. Depending on the spring description, a
/// critically, under or overdamped spring will be created.
class SpringSimulation extends Simulation {
- final double _endPosition;
-
- final _SpringSolution _solution;
-
/// A spring description with the provided spring description, start distance,
/// end distance and velocity.
SpringSimulation(
- SpringDescription desc, double start, double end, double velocity)
- : this._endPosition = end,
- _solution = new _SpringSolution(desc, start - end, velocity);
+ SpringDescription desc,
+ double start,
+ double end,
+ double velocity
+ ) : _endPosition = end,
+ _solution = new _SpringSolution(desc, start - end, velocity);
+
+ final double _endPosition;
+ final _SpringSolution _solution;
SpringType get type => _solution.type;
@@ -182,8 +198,12 @@ class SpringSimulation extends Simulation {
/// A SpringSimulation where the value of x() is guaranteed to have exactly the
/// end value when the simulation isDone().
class ScrollSpringSimulation extends SpringSimulation {
- ScrollSpringSimulation(SpringDescription desc, double start, double end, double velocity)
- : super(desc, start, end, velocity);
+ ScrollSpringSimulation(
+ SpringDescription desc,
+ double start,
+ double end,
+ double velocity
+ ) : super(desc, start, end, velocity);
double x(double time) => isDone(time) ? _endPosition : super.x(time);
}