Commit 8cc53120 authored by Steve Messick's avatar Steve Messick

Merge remote-tracking branch 'upstream/master'

parents cea21fb7 f04fd43f
......@@ -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
......
......@@ -4,7 +4,7 @@
/// The Flutter animation system.
///
/// See [https://flutter.io/animations/] for an overview.
/// See <https://flutter.io/animations/> for an overview.
///
/// This library depends only on core Dart libraries and the `newton` package.
library animation;
......
// 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;
......@@ -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<LocaleQueryData> LocaleChangedCallback(Locale locale);
class MaterialApp extends StatefulComponent {
class MaterialApp extends WidgetsApp {
MaterialApp({
Key key,
this.title,
this.theme,
this.routes: const <String, RouteBuilder>{},
this.onGenerateRoute,
this.onLocaleChanged,
String title,
ThemeData theme,
Map<String, RouteBuilder> routes: const <String, RouteBuilder>{},
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<String, RouteBuilder> 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<MaterialApp> 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<MaterialApp> {
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<MaterialApp> implements BindingObserver {
}
return true;
});
if (config.showPerformanceOverlay) {
result = new Stack(
children: <Widget>[
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;
}
......
......@@ -80,7 +80,7 @@ class _BottomSheetState extends State<BottomSheet> {
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: new Material(
key: _childKey,
key: _childKey,
child: config.builder(context)
)
);
......
......@@ -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<YearPicker> {
}
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return new ScrollableLazyList(
itemExtent: _itemExtent,
itemCount: config.lastDate.year - config.firstDate.year + 1,
......
......@@ -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;
......
......@@ -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<Widget> children = <Widget>[];
......
......@@ -182,6 +182,7 @@ class _DropDownRoute<T> extends PopupRoute<_DropDownRouteResult<T>> {
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(
......
......@@ -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(
......
......@@ -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<T extends InkResponse> extends State<T> {
}
/// 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.
///
/// If a Widget uses this class directly, it should include the following line
/// at the top of its [build] function to call [debugCheckHasMaterial]:
///
/// Must have an ancestor Material widget in which to cause ink reactions.
/// assert(debugCheckHasMaterial(context));
class InkWell extends InkResponse {
InkWell({
Key key,
......
......@@ -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;
......
......@@ -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
......
......@@ -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();
......
......@@ -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<Tooltip> {
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<Tooltip> {
}
Widget build(BuildContext context) {
assert(Overlay.of(context) != null);
assert(Overlay.of(context, debugRequiredFor: config) != null);
return new GestureDetector(
behavior: HitTestBehavior.opaque,
onLongPress: showTooltip,
......
......@@ -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
......
......@@ -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;
......
......@@ -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) {
......
......@@ -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) {
......
......@@ -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;
......
......@@ -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));
}
/// 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) {
context.canvas.drawRect(offset & size, new Paint() .. color = debugErrorBoxColor);
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) { }
}
}
......@@ -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();
// 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;
......@@ -46,9 +46,5 @@ void _debugPrintTask() {
}
void debugPrintStack() {
try {
throw new Exception();
} catch (e, stack) {
debugPrint(stack.toString());
}
debugPrint(StackTrace.current.toString());
}
// 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<LocaleQueryData> LocaleChangedCallback(Locale locale);
class WidgetsApp extends StatefulComponent {
WidgetsApp({
Key key,
this.title,
this.textStyle,
this.color,
this.routes: const <String, RouteBuilder>{},
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<String, RouteBuilder> 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<WidgetsApp> createState() => new WidgetsAppState<WidgetsApp>();
}
EdgeDims _getPadding(ui.WindowPadding padding) {
return new EdgeDims.TRBL(padding.top, padding.right, padding.bottom, padding.left);
}
class WidgetsAppState<T extends WidgetsApp> extends State<T> 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: <Widget>[
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;
}
}
......@@ -999,6 +999,7 @@ class BlockBody extends MultiChildRenderObjectWidget {
}
}
/// A base class for widgets that accept [Positioned] children.
abstract class StackRenderObjectWidgetBase extends MultiChildRenderObjectWidget {
StackRenderObjectWidgetBase({
List<Widget> children: _emptyWidgetList,
......
......@@ -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
......
......@@ -256,7 +256,7 @@ class _DismissableState extends State<Dismissable> {
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.'
);
}
......
......@@ -234,7 +234,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> {
_activeCount += 1;
});
return new _DragAvatar<T>(
overlay: Overlay.of(context),
overlay: Overlay.of(context, debugRequiredFor: config),
data: config.data,
initialPosition: position,
dragStartPoint: dragStartPoint,
......@@ -249,6 +249,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> {
}
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;
......
......@@ -149,8 +149,12 @@ abstract class GlobalKey<T extends State<StatefulComponent>> 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<T extends Widget> 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<T extends Widget> extends Element<T> {
}
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<T extends Widget> extends BuildableElement<T> {
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<T extends Widget> extends BuildableElement<T> {
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<T extends StatefulComponent, U extends State<T>>
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<T extends StatefulComponent, U extends State<T>>
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<ParentDataWidget> {
}
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<T extends RenderObjectWidget> 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<T extends MultiChildRenderObjectWidget> 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<T extends MultiChildRenderObjectWidget> 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);
......
......@@ -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<RawGestureDetector> {
/// the gesture detector should be enabled.
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> 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);
......
......@@ -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".'
);
......
......@@ -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<Mimicable> {
/// 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<Mimicable> {
/// 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;
......
......@@ -260,8 +260,12 @@ class Navigator extends StatefulComponent {
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
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);
......
......@@ -71,7 +71,32 @@ class Overlay extends StatefulComponent {
final List<OverlayEntry> initialEntries;
/// The state from the closest instance of this class that encloses the given context.
static OverlayState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<OverlayState>());
///
/// 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<OverlayState>());
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();
}
......
......@@ -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<int> 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<Widget> 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<T extends PageableList> extends ScrollableState<T> {
int get _itemCount => config.children?.length ?? 0;
int _previousItemCount;
......
......@@ -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<Placeholder> {
/// The child that this widget builds.
///
......
......@@ -327,15 +327,9 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
});
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<T extends Scrollable> extends State<T> {
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<T extends Scrollable> extends State<T> {
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<T extends Scrollable> extends State<T> {
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<T extends Scrollable> extends State<T> {
}
void _handleDragStart(_) {
_dispatchOnScrollStartIfNeeded();
_startScroll();
}
void _dispatchOnScrollStartIfNeeded() {
if (!_isBetweenOnScrollStartAndOnScrollEnd)
void _startScroll() {
_numberOfInProgressScrolls += 1;
if (_numberOfInProgressScrolls == 1)
dispatchOnScrollStart();
}
......@@ -482,8 +484,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
///
/// 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<T extends Scrollable> extends State<T> {
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<T extends Scrollable> extends State<T> {
///
/// 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);
}
......
......@@ -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';
......
......@@ -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(
......
......@@ -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);
......
......@@ -95,4 +95,45 @@ void main() {
expect(log, equals(['scrollstart', 'scroll', 'scroll', 'scrollend']));
});
});
test('Scroll during animation', () {
testWidgets((WidgetTester tester) {
GlobalKey<ScrollableState> scrollKey = new GlobalKey<ScrollableState>();
List<String> log = <String>[];
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<ScrollableState> scrollKey = new GlobalKey<ScrollableState>();
List<String> log = <String>[];
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']));
});
});
}
......@@ -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;
......
......@@ -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);
printStatus('Creating project ${path.basename(projectName)}:');
flutterPackagesDirectory = path.normalize(flutterPackagesDirectory);
flutterPackagesDirectory = _relativePath(from: dirPath, to: flutterPackagesDirectory);
new Directory(dirPath).createSync(recursive: true);
printStatus('Creating project ${path.basename(projectName)}:');
Map templateContext = <String, dynamic>{
'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;
}
......@@ -4,20 +4,18 @@
import 'tolerance.dart';
abstract class Simulatable {
/// 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 {
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); // 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);
}
......@@ -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
......
......@@ -7,120 +7,154 @@ import 'dart:math' as math;
import 'simulation.dart';
import 'utils.dart';
abstract class _SpringSolution implements Simulatable {
factory _SpringSolution(
SpringDescription desc, double initialPosition, double initialVelocity) {
double cmk =
desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
enum SpringType { unknown, criticallyDamped, underDamped, overDamped }
if (cmk == 0.0) {
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)
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;
SpringType get type => SpringType.overDamped;
final double _r1, _r2, _c1, _c2;
double x(double time) =>
(_c1 * math.pow(math.E, _r1 * time) + _c2 * math.pow(math.E, _r2 * time));
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);
}
double dx(double time) => (_c1 * _r1 * math.pow(math.E, _r1 * time) +
_c2 * _r2 * math.pow(math.E, _r2 * time));
SpringType get type => SpringType.overDamped;
}
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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment