Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
8cc5312054
@ -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;
|
||||
|
99
packages/flutter/lib/shell.dart
Normal file
99
packages/flutter/lib/shell.dart
Normal file
@ -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;
|
@ -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.
|
||||
/// 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,
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
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) { }
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -46,9 +46,5 @@ void _debugPrintTask() {
|
||||
}
|
||||
|
||||
void debugPrintStack() {
|
||||
try {
|
||||
throw new Exception();
|
||||
} catch (e, stack) {
|
||||
debugPrint(stack.toString());
|
||||
}
|
||||
debugPrint(StackTrace.current.toString());
|
||||
}
|
||||
|
223
packages/flutter/lib/src/widgets/app.dart
Normal file
223
packages/flutter/lib/src/widgets/app.dart
Normal file
@ -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<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);
|
||||
|
||||
flutterPackagesDirectory = path.normalize(flutterPackagesDirectory);
|
||||
flutterPackagesDirectory = _relativePath(from: dirPath, to: flutterPackagesDirectory);
|
||||
|
||||
printStatus('Creating project ${path.basename(projectName)}:');
|
||||
|
||||
new Directory(dirPath).createSync(recursive: true);
|
||||
|
||||
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,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);
|
||||
|
@ -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 {
|
||||
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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user