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.
|
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).
|
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.
|
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
|
## Editing the client.flutter buildbot master
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
/// The Flutter animation system.
|
/// 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.
|
/// This library depends only on core Dart libraries and the `newton` package.
|
||||||
library animation;
|
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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:ui' as ui show WindowPadding, window;
|
|
||||||
|
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'colors.dart';
|
||||||
import 'page.dart';
|
import 'page.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
@ -21,204 +18,75 @@ const TextStyle _errorTextStyle = const TextStyle(
|
|||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
decorationColor: const Color(0xFFFF00),
|
decorationColor: const Color(0xFFFFFF00),
|
||||||
decorationStyle: TextDecorationStyle.double
|
decorationStyle: TextDecorationStyle.double
|
||||||
);
|
);
|
||||||
|
|
||||||
AssetBundle _initDefaultBundle() {
|
|
||||||
if (rootBundle != null)
|
|
||||||
return rootBundle;
|
|
||||||
return new NetworkAssetBundle(Uri.base);
|
|
||||||
}
|
|
||||||
|
|
||||||
final AssetBundle _defaultBundle = _initDefaultBundle();
|
class MaterialApp extends WidgetsApp {
|
||||||
|
|
||||||
class RouteArguments {
|
|
||||||
const RouteArguments({ this.context });
|
|
||||||
final BuildContext context;
|
|
||||||
}
|
|
||||||
typedef Widget RouteBuilder(RouteArguments args);
|
|
||||||
|
|
||||||
typedef Future<LocaleQueryData> LocaleChangedCallback(Locale locale);
|
|
||||||
|
|
||||||
class MaterialApp extends StatefulComponent {
|
|
||||||
MaterialApp({
|
MaterialApp({
|
||||||
Key key,
|
Key key,
|
||||||
this.title,
|
String title,
|
||||||
this.theme,
|
ThemeData theme,
|
||||||
this.routes: const <String, RouteBuilder>{},
|
Map<String, RouteBuilder> routes: const <String, RouteBuilder>{},
|
||||||
this.onGenerateRoute,
|
RouteFactory onGenerateRoute,
|
||||||
this.onLocaleChanged,
|
LocaleChangedCallback onLocaleChanged,
|
||||||
this.debugShowMaterialGrid: false,
|
this.debugShowMaterialGrid: false,
|
||||||
this.showPerformanceOverlay: false,
|
bool showPerformanceOverlay: false,
|
||||||
this.showSemanticsDebugger: false,
|
bool showSemanticsDebugger: false,
|
||||||
this.debugShowCheckedModeBanner: true
|
bool debugShowCheckedModeBanner: true
|
||||||
}) : super(key: key) {
|
}) : theme = theme,
|
||||||
assert(routes != null);
|
super(
|
||||||
assert(routes.containsKey(Navigator.defaultRouteName) || onGenerateRoute != null);
|
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(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.
|
/// The colors to use for the application's widgets.
|
||||||
final ThemeData theme;
|
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
|
/// Turns on a [GridPaper] overlay that paints a baseline grid
|
||||||
/// Material apps:
|
/// Material apps:
|
||||||
/// https://www.google.com/design/spec/layout/metrics-keylines.html
|
/// https://www.google.com/design/spec/layout/metrics-keylines.html
|
||||||
/// Only available in checked mode.
|
/// Only available in checked mode.
|
||||||
final bool debugShowMaterialGrid;
|
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();
|
_MaterialAppState createState() => new _MaterialAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeDims _getPadding(ui.WindowPadding padding) {
|
class _MaterialAppState extends WidgetsAppState<MaterialApp> {
|
||||||
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) { }
|
|
||||||
|
|
||||||
final HeroController _heroController = new HeroController();
|
final HeroController _heroController = new HeroController();
|
||||||
|
NavigatorObserver get navigatorObserver => _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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
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();
|
ThemeData theme = config.theme ?? new ThemeData.fallback();
|
||||||
Widget result = new MediaQuery(
|
Widget result = new AnimatedTheme(
|
||||||
data: new MediaQueryData(
|
data: theme,
|
||||||
size: ui.window.size,
|
duration: kThemeAnimationDuration,
|
||||||
devicePixelRatio: ui.window.devicePixelRatio,
|
child: super.build(context)
|
||||||
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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
assert(() {
|
assert(() {
|
||||||
if (config.debugShowMaterialGrid) {
|
if (config.debugShowMaterialGrid) {
|
||||||
@ -232,27 +100,6 @@ class _MaterialAppState extends State<MaterialApp> implements BindingObserver {
|
|||||||
}
|
}
|
||||||
return true;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ class _BottomSheetState extends State<BottomSheet> {
|
|||||||
onVerticalDragUpdate: _handleDragUpdate,
|
onVerticalDragUpdate: _handleDragUpdate,
|
||||||
onVerticalDragEnd: _handleDragEnd,
|
onVerticalDragEnd: _handleDragEnd,
|
||||||
child: new Material(
|
child: new Material(
|
||||||
key: _childKey,
|
key: _childKey,
|
||||||
child: config.builder(context)
|
child: config.builder(context)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -10,6 +10,7 @@ import 'package:intl/date_symbols.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
|
import 'debug.dart';
|
||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
import 'typography.dart';
|
import 'typography.dart';
|
||||||
@ -403,6 +404,7 @@ class _YearPickerState extends State<YearPicker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
return new ScrollableLazyList(
|
return new ScrollableLazyList(
|
||||||
itemExtent: _itemExtent,
|
itemExtent: _itemExtent,
|
||||||
itemCount: config.lastDate.year - config.firstDate.year + 1,
|
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) {
|
if (context.widget is! Material && context.ancestorWidgetOfExactType(Material) == null) {
|
||||||
Element element = context;
|
Element element = context;
|
||||||
throw new WidgetError(
|
throw new WidgetError(
|
||||||
'Missing Material widget.',
|
'No Material widget found.\n'
|
||||||
'${context.widget} needs to be placed inside a Material widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}'
|
'${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;
|
return true;
|
||||||
@ -27,8 +36,12 @@ bool debugCheckHasScaffold(BuildContext context) {
|
|||||||
if (Scaffold.of(context) == null) {
|
if (Scaffold.of(context) == null) {
|
||||||
Element element = context;
|
Element element = context;
|
||||||
throw new WidgetError(
|
throw new WidgetError(
|
||||||
'Missing Scaffold widget.',
|
'No Scaffold widget found.\n'
|
||||||
'${context.widget} needs to be placed inside the body of a Scaffold widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}'
|
'${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;
|
return true;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
|
import 'debug.dart';
|
||||||
import 'icon.dart';
|
import 'icon.dart';
|
||||||
import 'icons.dart';
|
import 'icons.dart';
|
||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
@ -55,6 +56,7 @@ class DrawerItem extends StatelessComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
ThemeData themeData = Theme.of(context);
|
ThemeData themeData = Theme.of(context);
|
||||||
|
|
||||||
List<Widget> children = <Widget>[];
|
List<Widget> children = <Widget>[];
|
||||||
|
@ -182,6 +182,7 @@ class _DropDownRoute<T> extends PopupRoute<_DropDownRouteResult<T>> {
|
|||||||
|
|
||||||
ModalPosition getPosition(BuildContext context) {
|
ModalPosition getPosition(BuildContext context) {
|
||||||
RenderBox overlayBox = Overlay.of(context).context.findRenderObject();
|
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;
|
Size overlaySize = overlayBox.size;
|
||||||
RelativeRect menuRect = new RelativeRect.fromSize(rect, overlaySize);
|
RelativeRect menuRect = new RelativeRect.fromSize(rect, overlaySize);
|
||||||
return new ModalPosition(
|
return new ModalPosition(
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'debug.dart';
|
||||||
import 'icon.dart';
|
import 'icon.dart';
|
||||||
import 'icons.dart';
|
import 'icons.dart';
|
||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
@ -53,6 +54,7 @@ class IconButton extends StatelessComponent {
|
|||||||
final String tooltip;
|
final String tooltip;
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
Widget result = new Padding(
|
Widget result = new Padding(
|
||||||
padding: const EdgeDims.all(8.0),
|
padding: const EdgeDims.all(8.0),
|
||||||
child: new Icon(
|
child: new Icon(
|
||||||
|
@ -12,6 +12,18 @@ import 'debug.dart';
|
|||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
import 'theme.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 {
|
class InkResponse extends StatefulComponent {
|
||||||
InkResponse({
|
InkResponse({
|
||||||
Key key,
|
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 {
|
class InkWell extends InkResponse {
|
||||||
InkWell({
|
InkWell({
|
||||||
Key key,
|
Key key,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'debug.dart';
|
||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ class ListItem extends StatelessComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
final bool isTwoLine = !isThreeLine && secondary != null;
|
final bool isTwoLine = !isThreeLine && secondary != null;
|
||||||
final bool isOneLine = !isThreeLine && !isTwoLine;
|
final bool isOneLine = !isThreeLine && !isTwoLine;
|
||||||
double itemHeight;
|
double itemHeight;
|
||||||
|
@ -39,9 +39,8 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
|||||||
|
|
||||||
final EdgeDims padding;
|
final EdgeDims padding;
|
||||||
|
|
||||||
void performLayout(Size size, BoxConstraints constraints) {
|
void performLayout(Size size) {
|
||||||
|
BoxConstraints looseConstraints = new BoxConstraints.loose(size);
|
||||||
BoxConstraints looseConstraints = constraints.loosen();
|
|
||||||
|
|
||||||
// This part of the layout has the same effect as putting the toolbar and
|
// 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
|
// 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 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
|
import 'debug.dart';
|
||||||
import 'icon.dart';
|
import 'icon.dart';
|
||||||
import 'icons.dart';
|
import 'icons.dart';
|
||||||
import 'icon_theme.dart';
|
import 'icon_theme.dart';
|
||||||
@ -330,6 +331,7 @@ class _Tab extends StatelessComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
Widget labelContent;
|
Widget labelContent;
|
||||||
if (label.icon == null && label.iconBuilder == null) {
|
if (label.icon == null && label.iconBuilder == null) {
|
||||||
labelContent = _buildLabelText();
|
labelContent = _buildLabelText();
|
||||||
|
@ -46,6 +46,7 @@ class Tooltip extends StatefulComponent {
|
|||||||
assert(preferBelow != null);
|
assert(preferBelow != null);
|
||||||
assert(fadeDuration != null);
|
assert(fadeDuration != null);
|
||||||
assert(showDuration != null);
|
assert(showDuration != null);
|
||||||
|
assert(child != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String message;
|
final String message;
|
||||||
@ -150,7 +151,7 @@ class _TooltipState extends State<Tooltip> {
|
|||||||
preferBelow: config.preferBelow
|
preferBelow: config.preferBelow
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Overlay.of(context).insert(_entry);
|
Overlay.of(context, debugRequiredFor: config).insert(_entry);
|
||||||
}
|
}
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
if (_controller.status != AnimationStatus.completed) {
|
if (_controller.status != AnimationStatus.completed) {
|
||||||
@ -175,7 +176,7 @@ class _TooltipState extends State<Tooltip> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(Overlay.of(context) != null);
|
assert(Overlay.of(context, debugRequiredFor: config) != null);
|
||||||
return new GestureDetector(
|
return new GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onLongPress: showTooltip,
|
onLongPress: showTooltip,
|
||||||
|
@ -31,22 +31,22 @@ class TextStyle {
|
|||||||
/// The color to use when painting the text.
|
/// The color to use when painting the text.
|
||||||
final Color color;
|
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;
|
final String fontFamily;
|
||||||
|
|
||||||
/// The size of gyphs (in logical pixels) to use when painting the text.
|
/// The size of gyphs (in logical pixels) to use when painting the text.
|
||||||
final double fontSize;
|
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;
|
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;
|
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;
|
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;
|
final double wordSpacing;
|
||||||
|
|
||||||
/// How the text should be aligned (applies only to the outermost
|
/// 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.
|
/// The distance between the text baselines, as a multiple of the font size.
|
||||||
final double height;
|
final double height;
|
||||||
|
|
||||||
/// The decorations to paint near the text.
|
/// The decorations to paint near the text (e.g., an underline).
|
||||||
final TextDecoration decoration;
|
final TextDecoration decoration;
|
||||||
|
|
||||||
/// The color in which to paint the text decorations.
|
/// The color in which to paint the text decorations.
|
||||||
final Color decorationColor;
|
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;
|
final TextDecorationStyle decorationStyle;
|
||||||
|
|
||||||
/// Returns a new text style that matches this text style but with the given
|
/// 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;
|
export 'package:flutter/gestures.dart' show HitTestResult;
|
||||||
|
|
||||||
/// The glue between the render tree and the Flutter engine.
|
/// 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 {
|
implements HitTestable {
|
||||||
|
|
||||||
void initInstances() {
|
void initInstances() {
|
||||||
@ -67,7 +67,7 @@ abstract class Renderer extends Object with Scheduler, MojoShell
|
|||||||
|
|
||||||
void initSemantics() {
|
void initSemantics() {
|
||||||
SemanticsNode.onSemanticsEnabled = renderView.scheduleInitialSemantics;
|
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);
|
mojom.SemanticsServerStub server = new mojom.SemanticsServerStub.fromEndpoint(endpoint);
|
||||||
server.impl = new SemanticsServer();
|
server.impl = new SemanticsServer();
|
||||||
});
|
});
|
||||||
@ -116,7 +116,7 @@ void debugDumpSemanticsTree() {
|
|||||||
|
|
||||||
/// A concrete binding for applications that use the Rendering framework
|
/// A concrete binding for applications that use the Rendering framework
|
||||||
/// directly. This is the glue that binds the framework to the Flutter engine.
|
/// 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 }) {
|
RenderingFlutterBinding({ RenderBox root }) {
|
||||||
assert(renderView != null);
|
assert(renderView != null);
|
||||||
renderView.child = root;
|
renderView.child = root;
|
||||||
|
@ -20,7 +20,7 @@ import 'object.dart';
|
|||||||
mojom.ViewProxy _initViewProxy() {
|
mojom.ViewProxy _initViewProxy() {
|
||||||
int viewHandle = ui.takeViewHandle();
|
int viewHandle = ui.takeViewHandle();
|
||||||
assert(() {
|
assert(() {
|
||||||
if (viewHandle == 0)
|
if (viewHandle == core.MojoHandle.INVALID)
|
||||||
debugPrint('Child view are supported only when running in Mojo shell.');
|
debugPrint('Child view are supported only when running in Mojo shell.');
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -35,8 +35,12 @@ mojom.ViewProxy _initViewProxy() {
|
|||||||
final mojom.ViewProxy _viewProxy = _initViewProxy();
|
final mojom.ViewProxy _viewProxy = _initViewProxy();
|
||||||
final mojom.View _view = _viewProxy?.ptr;
|
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 {
|
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();
|
mojom.ViewProviderProxy viewProvider = new mojom.ViewProviderProxy.unbound();
|
||||||
shell.connectToService(url, viewProvider);
|
shell.connectToService(url, viewProvider);
|
||||||
mojom.ServiceProviderProxy incomingServices = new mojom.ServiceProviderProxy.unbound();
|
mojom.ServiceProviderProxy incomingServices = new mojom.ServiceProviderProxy.unbound();
|
||||||
@ -47,8 +51,16 @@ class ChildViewConnection {
|
|||||||
_connection = new ApplicationConnection(outgoingServices, incomingServices);
|
_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 get connection => _connection;
|
||||||
ApplicationConnection _connection;
|
ApplicationConnection _connection;
|
||||||
|
|
||||||
@ -120,18 +132,16 @@ class ChildViewConnection {
|
|||||||
..devicePixelRatio = scale;
|
..devicePixelRatio = scale;
|
||||||
return (await _view.layoutChild(_viewKey, layoutParams)).info;
|
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 {
|
class RenderChildView extends RenderBox {
|
||||||
RenderChildView({
|
RenderChildView({
|
||||||
ChildViewConnection child,
|
ChildViewConnection child,
|
||||||
double scale
|
double scale
|
||||||
}) : _child = child, _scale = scale;
|
}) : _child = child, _scale = scale;
|
||||||
|
|
||||||
|
/// The child to display.
|
||||||
ChildViewConnection get child => _child;
|
ChildViewConnection get child => _child;
|
||||||
ChildViewConnection _child;
|
ChildViewConnection _child;
|
||||||
void set child (ChildViewConnection value) {
|
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 get scale => _scale;
|
||||||
double _scale;
|
double _scale;
|
||||||
void set scale (double value) {
|
void set scale (double value) {
|
||||||
|
@ -103,7 +103,7 @@ abstract class MultiChildLayoutDelegate {
|
|||||||
return '${childParentData.id}: $child';
|
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
|
// 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
|
// by both a parent and a child. So, we must restore the _idToChild map when
|
||||||
// we return.
|
// we return.
|
||||||
@ -138,7 +138,7 @@ abstract class MultiChildLayoutDelegate {
|
|||||||
});
|
});
|
||||||
child = childParentData.nextSibling;
|
child = childParentData.nextSibling;
|
||||||
}
|
}
|
||||||
performLayout(size, constraints);
|
performLayout(size);
|
||||||
assert(() {
|
assert(() {
|
||||||
if (_debugChildrenNeedingLayout.isNotEmpty) {
|
if (_debugChildrenNeedingLayout.isNotEmpty) {
|
||||||
if (_debugChildrenNeedingLayout.length > 1) {
|
if (_debugChildrenNeedingLayout.length > 1) {
|
||||||
@ -176,11 +176,10 @@ abstract class MultiChildLayoutDelegate {
|
|||||||
/// possible given the constraints.
|
/// possible given the constraints.
|
||||||
Size getSize(BoxConstraints constraints) => constraints.biggest;
|
Size getSize(BoxConstraints constraints) => constraints.biggest;
|
||||||
|
|
||||||
/// Override this method to lay out and position all children given
|
/// Override this method to lay out and position all children given this
|
||||||
/// this widget's size and the specified constraints. This method
|
/// widget's size. This method must call [layoutChild] for each child. It
|
||||||
/// must call [layoutChild] for each child. It should also specify
|
/// should also specify the final position of each child with [positionChild].
|
||||||
/// the final position of each child with [positionChild].
|
void performLayout(Size size);
|
||||||
void performLayout(Size size, BoxConstraints constraints);
|
|
||||||
|
|
||||||
/// Override this method to return true when the children need to be
|
/// Override this method to return true when the children need to be
|
||||||
/// laid out. This should compare the fields of the current delegate
|
/// laid out. This should compare the fields of the current delegate
|
||||||
@ -257,7 +256,7 @@ class RenderCustomMultiChildLayoutBox extends RenderBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
delegate._callPerformLayout(size, constraints, firstChild);
|
delegate._callPerformLayout(size, firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
@ -53,9 +53,6 @@ bool debugPaintPointersEnabled = false;
|
|||||||
/// The color to use when reporting pointers.
|
/// The color to use when reporting pointers.
|
||||||
int debugPaintPointersColorValue = 0x00BBBB;
|
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.
|
/// Overlay a rotating set of colors when repainting layers in checked mode.
|
||||||
bool debugRepaintRainbowEnabled = false;
|
bool debugRepaintRainbowEnabled = false;
|
||||||
|
|
||||||
|
@ -2,15 +2,56 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphStyle, TextStyle;
|
||||||
|
|
||||||
import 'box.dart';
|
import 'box.dart';
|
||||||
import 'debug.dart';
|
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
|
|
||||||
const double _kMaxWidth = 100000.0;
|
const double _kMaxWidth = 100000.0;
|
||||||
const double _kMaxHeight = 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 {
|
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) {
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||||
return constraints.constrainWidth(0.0);
|
return constraints.constrainWidth(0.0);
|
||||||
@ -36,8 +77,36 @@ class RenderErrorBox extends RenderBox {
|
|||||||
size = constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
|
size = constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
/// The color to use when painting the background of [RenderErrorBox] objects.
|
||||||
context.canvas.drawRect(offset & size, new Paint() .. color = debugErrorBoxColor);
|
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() {
|
AssetBundle _initRootBundle() {
|
||||||
try {
|
int h = ui.takeRootBundleHandle();
|
||||||
AssetBundleProxy bundle = new AssetBundleProxy.fromHandle(
|
if (h == core.MojoHandle.INVALID)
|
||||||
new core.MojoHandle(ui.takeRootBundleHandle())
|
|
||||||
);
|
|
||||||
return new MojoAssetBundle(bundle);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
core.MojoHandle handle = new core.MojoHandle(h);
|
||||||
|
return new MojoAssetBundle(new AssetBundleProxy.fromHandle(handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
final AssetBundle rootBundle = _initRootBundle();
|
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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:ui' as ui;
|
import 'package:flutter/shell.dart';
|
||||||
|
|
||||||
import 'package:mojo/application.dart';
|
export 'package:flutter/shell.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;
|
|
||||||
|
|
||||||
/// Base class for mixins that provide singleton services (also known as
|
/// Base class for mixins that provide singleton services (also known as
|
||||||
/// "bindings").
|
/// "bindings").
|
||||||
@ -41,84 +37,9 @@ abstract class BindingBase {
|
|||||||
String toString() => '<$runtimeType>';
|
String toString() => '<$runtimeType>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// A replacement for shell.connectToService. Implementations should return true
|
abstract class Services extends BindingBase {
|
||||||
// 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 {
|
|
||||||
|
|
||||||
void initInstances() {
|
void initInstances() {
|
||||||
super.initInstances();
|
super.initInstances();
|
||||||
_instance = this;
|
new MojoShell();
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MojoShell get shell => MojoShell.instance;
|
|
||||||
|
@ -46,9 +46,5 @@ void _debugPrintTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void debugPrintStack() {
|
void debugPrintStack() {
|
||||||
try {
|
debugPrint(StackTrace.current.toString());
|
||||||
throw new Exception();
|
|
||||||
} catch (e, stack) {
|
|
||||||
debugPrint(stack.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 {
|
abstract class StackRenderObjectWidgetBase extends MultiChildRenderObjectWidget {
|
||||||
StackRenderObjectWidgetBase({
|
StackRenderObjectWidgetBase({
|
||||||
List<Widget> children: _emptyWidgetList,
|
List<Widget> children: _emptyWidgetList,
|
||||||
|
@ -24,7 +24,7 @@ class BindingObserver {
|
|||||||
|
|
||||||
/// A concrete binding for applications based on the Widgets framework.
|
/// A concrete binding for applications based on the Widgets framework.
|
||||||
/// This is the glue that binds the framework to the Flutter engine.
|
/// 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
|
/// Creates and initializes the WidgetFlutterBinding. This constructor is
|
||||||
/// idempotent; calling it a second time will just return the
|
/// idempotent; calling it a second time will just return the
|
||||||
|
@ -256,7 +256,7 @@ class _DismissableState extends State<Dismissable> {
|
|||||||
assert(_resizeAnimation.status == AnimationStatus.completed);
|
assert(_resizeAnimation.status == AnimationStatus.completed);
|
||||||
throw new WidgetError(
|
throw new WidgetError(
|
||||||
'Dismissable widget completed its resize animation without being removed from the tree.\n'
|
'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.'
|
'widget from the application once that handler has fired.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> {
|
|||||||
_activeCount += 1;
|
_activeCount += 1;
|
||||||
});
|
});
|
||||||
return new _DragAvatar<T>(
|
return new _DragAvatar<T>(
|
||||||
overlay: Overlay.of(context),
|
overlay: Overlay.of(context, debugRequiredFor: config),
|
||||||
data: config.data,
|
data: config.data,
|
||||||
initialPosition: position,
|
initialPosition: position,
|
||||||
dragStartPoint: dragStartPoint,
|
dragStartPoint: dragStartPoint,
|
||||||
@ -249,6 +249,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
assert(Overlay.of(context, debugRequiredFor: config) != null);
|
||||||
final bool canDrag = config.maxSimultaneousDrags == null ||
|
final bool canDrag = config.maxSimultaneousDrags == null ||
|
||||||
_activeCount < config.maxSimultaneousDrags;
|
_activeCount < config.maxSimultaneousDrags;
|
||||||
final bool showChild = _activeCount == 0 || config.childWhenDragging == null;
|
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 following GlobalKey was found multiple times among mounted elements: $key (${_debugDuplicates[key]} instances)\n';
|
||||||
message += 'The most recently registered instance is: ${_registry[key]}\n';
|
message += 'The most recently registered instance is: ${_registry[key]}\n';
|
||||||
}
|
}
|
||||||
if (!_debugDuplicates.isEmpty)
|
if (!_debugDuplicates.isEmpty) {
|
||||||
throw new WidgetError('Incorrect GlobalKey usage.', message);
|
throw new WidgetError(
|
||||||
|
'Incorrect GlobalKey usage.\n'
|
||||||
|
'$message'
|
||||||
|
);
|
||||||
|
}
|
||||||
return true;
|
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 {
|
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);
|
typedef void BuildScheduler(BuildableElement element);
|
||||||
@ -1109,9 +1129,10 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
|
|||||||
}
|
}
|
||||||
if (_debugStateLocked && (!_debugAllowIgnoredCallsToMarkNeedsBuild || !dirty)) {
|
if (_debugStateLocked && (!_debugAllowIgnoredCallsToMarkNeedsBuild || !dirty)) {
|
||||||
throw new WidgetError(
|
throw new WidgetError(
|
||||||
'Cannot mark this component as needing to build because the framework is '
|
'setState() or markNeedsBuild() called during build.\n'
|
||||||
'already in the process of building widgets. A widget can be marked as '
|
'This component cannot be marked as needing to build because the framework '
|
||||||
'needing to be built during the build phase only if one if its ancestor '
|
'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 '
|
'is currently building. This exception is allowed because the framework '
|
||||||
'builds parent widgets before children, which means a dirty descendant '
|
'builds parent widgets before children, which means a dirty descendant '
|
||||||
'will always be built. Otherwise, the framework might not visit this '
|
'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(() {
|
assert(() {
|
||||||
if (built == null) {
|
if (built == null) {
|
||||||
throw new WidgetError(
|
throw new WidgetError(
|
||||||
'A build function returned null. Build functions must never return null.',
|
'A build function returned null.\n'
|
||||||
'The offending widget is: $widget'
|
'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;
|
return true;
|
||||||
});
|
});
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
_debugReportException('building $_widget', e, stack);
|
_debugReportException('building $_widget', e, stack);
|
||||||
built = new ErrorWidget();
|
built = new ErrorWidget(e);
|
||||||
} finally {
|
} finally {
|
||||||
// We delay marking the element as clean until after calling _builder so
|
// We delay marking the element as clean until after calling _builder so
|
||||||
// that attempts to markNeedsBuild() during build() will be ignored.
|
// 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);
|
assert(_child != null);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
_debugReportException('building $_widget', e, stack);
|
_debugReportException('building $_widget', e, stack);
|
||||||
built = new ErrorWidget();
|
built = new ErrorWidget(e);
|
||||||
_child = updateChild(null, built, slot);
|
_child = updateChild(null, built, slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1289,7 +1313,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
|
|||||||
assert(() {
|
assert(() {
|
||||||
if (_state._debugLifecycleState == _StateLifecycle.initialized)
|
if (_state._debugLifecycleState == _StateLifecycle.initialized)
|
||||||
return true;
|
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; });
|
assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
|
||||||
super._firstBuild();
|
super._firstBuild();
|
||||||
@ -1324,7 +1352,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
|
|||||||
assert(() {
|
assert(() {
|
||||||
if (_state._debugLifecycleState == _StateLifecycle.defunct)
|
if (_state._debugLifecycleState == _StateLifecycle.defunct)
|
||||||
return true;
|
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.
|
assert(!dirty); // See BuildableElement.unmount for why this is important.
|
||||||
_state._element = null;
|
_state._element = null;
|
||||||
@ -1381,12 +1413,15 @@ class ParentDataElement extends _ProxyElement<ParentDataWidget> {
|
|||||||
}
|
}
|
||||||
if (ancestor != null && badAncestors.isEmpty)
|
if (ancestor != null && badAncestors.isEmpty)
|
||||||
return true;
|
return true;
|
||||||
throw new WidgetError('Incorrect use of ParentDataWidget.', widget.debugDescribeInvalidAncestorChain(
|
throw new WidgetError(
|
||||||
description: "$this",
|
'Incorrect use of ParentDataWidget.\n' +
|
||||||
ownershipChain: parent.debugGetOwnershipChain(10),
|
widget.debugDescribeInvalidAncestorChain(
|
||||||
foundValidAncestor: ancestor != null,
|
description: "$this",
|
||||||
badAncestors: badAncestors
|
ownershipChain: parent.debugGetOwnershipChain(10),
|
||||||
));
|
foundValidAncestor: ancestor != null,
|
||||||
|
badAncestors: badAncestors
|
||||||
|
)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
super.mount(parent, slot);
|
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
|
// 'BuildContext' argument which you can pass to Theme.of() and other
|
||||||
// InheritedWidget APIs which eventually trigger a rebuild.)
|
// InheritedWidget APIs which eventually trigger a rebuild.)
|
||||||
assert(() {
|
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
|
continue; // when these nodes are reordered, we just reassign the data
|
||||||
|
|
||||||
if (!idSet.add(child.key)) {
|
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;
|
return false;
|
||||||
@ -1841,16 +1887,10 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WidgetError extends Error {
|
class WidgetError extends AssertionError {
|
||||||
WidgetError(String message, [ String rawDetails = '' ]) {
|
WidgetError(this.message);
|
||||||
rawDetails = rawDetails.trimRight(); // remove trailing newlines
|
final String message;
|
||||||
if (rawDetails != '')
|
String toString() => message;
|
||||||
_message = '$message\n$rawDetails';
|
|
||||||
else
|
|
||||||
_message = message;
|
|
||||||
}
|
|
||||||
String _message;
|
|
||||||
String toString() => _message;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack);
|
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 havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
|
||||||
bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
|
bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
|
||||||
if (havePan || haveScale) {
|
if (havePan || haveScale) {
|
||||||
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.');
|
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';
|
String recognizer = havePan ? 'pan' : 'scale';
|
||||||
if (haveVerticalDrag && haveHorizontalDrag)
|
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.');
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
@ -279,8 +288,15 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
|
|||||||
/// the gesture detector should be enabled.
|
/// the gesture detector should be enabled.
|
||||||
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
|
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
|
||||||
assert(() {
|
assert(() {
|
||||||
if (!RenderObject.debugDoingLayout)
|
if (!RenderObject.debugDoingLayout) {
|
||||||
throw new WidgetError('replaceGestureRecognizers() can only be called during the layout phase.');
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
_syncAll(gestures);
|
_syncAll(gestures);
|
||||||
|
@ -113,8 +113,8 @@ class Hero extends StatefulComponent {
|
|||||||
if (tagHeroes.containsKey(key)) {
|
if (tagHeroes.containsKey(key)) {
|
||||||
new WidgetError(
|
new WidgetError(
|
||||||
'There are multiple heroes that share the same key within the same subtree.\n'
|
'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'
|
'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\n'
|
'either each Hero must have a unique tag, or, all the heroes with a particular tag must '
|
||||||
'have different keys.\n'
|
'have different keys.\n'
|
||||||
'In this case, the tag "$tag" had multiple heroes with the key "$key".'
|
'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].
|
/// 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 {
|
class Mimicable extends StatefulComponent {
|
||||||
Mimicable({ Key key, this.child }) : super(key: key);
|
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
|
/// passing it to a [Mimic] widget. To mimic the child in the
|
||||||
/// [Overlay], consider using [liftToOverlay()] instead.
|
/// [Overlay], consider using [liftToOverlay()] instead.
|
||||||
MimicableHandle startMimic() {
|
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();
|
RenderBox box = context.findRenderObject();
|
||||||
assert(box != null);
|
assert(box != null);
|
||||||
assert(box.hasSize);
|
assert(box.hasSize);
|
||||||
@ -193,8 +207,7 @@ class MimicableState extends State<Mimicable> {
|
|||||||
/// had when the mimicking process started and (2) the child will be
|
/// had when the mimicking process started and (2) the child will be
|
||||||
/// placed in the enclosing overlay.
|
/// placed in the enclosing overlay.
|
||||||
MimicOverlayEntry liftToOverlay() {
|
MimicOverlayEntry liftToOverlay() {
|
||||||
OverlayState overlay = Overlay.of(context);
|
OverlayState overlay = Overlay.of(context, debugRequiredFor: config);
|
||||||
assert(overlay != null); // You need an overlay to lift into.
|
|
||||||
MimicOverlayEntry entry = new MimicOverlayEntry._(startMimic());
|
MimicOverlayEntry entry = new MimicOverlayEntry._(startMimic());
|
||||||
overlay.insert(entry._overlayEntry);
|
overlay.insert(entry._overlayEntry);
|
||||||
return entry;
|
return entry;
|
||||||
|
@ -260,8 +260,12 @@ class Navigator extends StatefulComponent {
|
|||||||
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
|
static void openTransaction(BuildContext context, NavigatorTransactionCallback callback) {
|
||||||
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
|
NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
|
||||||
assert(() {
|
assert(() {
|
||||||
if (navigator == null)
|
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.');
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
navigator.openTransaction(callback);
|
navigator.openTransaction(callback);
|
||||||
|
@ -71,7 +71,32 @@ class Overlay extends StatefulComponent {
|
|||||||
final List<OverlayEntry> initialEntries;
|
final List<OverlayEntry> initialEntries;
|
||||||
|
|
||||||
/// The state from the closest instance of this class that encloses the given context.
|
/// 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();
|
OverlayState createState() => new OverlayState();
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,10 @@ enum ItemsSnapAlignment {
|
|||||||
adjacentItem
|
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 {
|
class PageableList extends Scrollable {
|
||||||
PageableList({
|
PageableList({
|
||||||
Key key,
|
Key key,
|
||||||
@ -46,17 +50,34 @@ class PageableList extends Scrollable {
|
|||||||
snapOffsetCallback: snapOffsetCallback
|
snapOffsetCallback: snapOffsetCallback
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Whether the first item should be revealed after scrolling past the last item.
|
||||||
final bool itemsWrap;
|
final bool itemsWrap;
|
||||||
|
|
||||||
|
/// Controls whether a fling always reveals the adjacent item or whether flings can traverse many items.
|
||||||
final ItemsSnapAlignment itemsSnapAlignment;
|
final ItemsSnapAlignment itemsSnapAlignment;
|
||||||
|
|
||||||
|
/// Called when the currently visible page changes.
|
||||||
final ValueChanged<int> onPageChanged;
|
final ValueChanged<int> onPageChanged;
|
||||||
|
|
||||||
|
/// Used to paint the scrollbar for this list.
|
||||||
final ScrollableListPainter scrollableListPainter;
|
final ScrollableListPainter scrollableListPainter;
|
||||||
|
|
||||||
|
/// The duration used when animating to a given page.
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
|
|
||||||
|
/// The animation curve to use when animating to a given page.
|
||||||
final Curve curve;
|
final Curve curve;
|
||||||
|
|
||||||
|
/// The list of pages themselves.
|
||||||
final Iterable<Widget> children;
|
final Iterable<Widget> children;
|
||||||
|
|
||||||
PageableListState createState() => new PageableListState();
|
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> {
|
class PageableListState<T extends PageableList> extends ScrollableState<T> {
|
||||||
int get _itemCount => config.children?.length ?? 0;
|
int get _itemCount => config.children?.length ?? 0;
|
||||||
int _previousItemCount;
|
int _previousItemCount;
|
||||||
|
@ -12,6 +12,9 @@ class Placeholder extends StatefulComponent {
|
|||||||
PlaceholderState createState() => new PlaceholderState();
|
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> {
|
class PlaceholderState extends State<Placeholder> {
|
||||||
/// The child that this widget builds.
|
/// 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);
|
PageStorage.of(context)?.writeState(context, _scrollOffset);
|
||||||
new ScrollNotification(this, _scrollOffset).dispatch(context);
|
new ScrollNotification(this, _scrollOffset).dispatch(context);
|
||||||
final needsScrollStart = !_isBetweenOnScrollStartAndOnScrollEnd;
|
_startScroll();
|
||||||
if (needsScrollStart) {
|
|
||||||
dispatchOnScrollStart();
|
|
||||||
assert(_isBetweenOnScrollStartAndOnScrollEnd);
|
|
||||||
}
|
|
||||||
dispatchOnScroll();
|
dispatchOnScroll();
|
||||||
assert(_isBetweenOnScrollStartAndOnScrollEnd);
|
_endScroll();
|
||||||
if (needsScrollStart)
|
|
||||||
dispatchOnScrollEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scroll this widget by the given scroll delta.
|
/// 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) {
|
Future _animateTo(double newScrollOffset, Duration duration, Curve curve) {
|
||||||
_controller.stop();
|
_controller.stop();
|
||||||
_controller.value = scrollOffset;
|
_controller.value = scrollOffset;
|
||||||
_dispatchOnScrollStartIfNeeded();
|
_startScroll();
|
||||||
return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then(_dispatchOnScrollEndIfNeeded);
|
return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then(_endScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fling the scroll offset with the given velocity.
|
/// 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);
|
Simulation simulation = _createSnapSimulation(scrollVelocity) ?? _createFlingSimulation(scrollVelocity);
|
||||||
if (simulation == null)
|
if (simulation == null)
|
||||||
return new Future.value();
|
return new Future.value();
|
||||||
_dispatchOnScrollStartIfNeeded();
|
_startScroll();
|
||||||
return _controller.animateWith(simulation).then(_dispatchOnScrollEndIfNeeded);
|
return _controller.animateWith(simulation).then(_endScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this scrollable should attempt to snap scroll offsets.
|
/// Whether this scrollable should attempt to snap scroll offsets.
|
||||||
@ -453,14 +447,21 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
|||||||
return simulation;
|
return simulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When we start an scroll animation, we stop any previous scroll animation.
|
||||||
bool _isBetweenOnScrollStartAndOnScrollEnd = false;
|
// 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.
|
/// Calls the onScroll callback.
|
||||||
///
|
///
|
||||||
/// Subclasses can override this function to hook the scroll callback.
|
/// Subclasses can override this function to hook the scroll callback.
|
||||||
void dispatchOnScroll() {
|
void dispatchOnScroll() {
|
||||||
assert(_isBetweenOnScrollStartAndOnScrollEnd);
|
assert(_numberOfInProgressScrolls > 0);
|
||||||
if (config.onScroll != null)
|
if (config.onScroll != null)
|
||||||
config.onScroll(_scrollOffset);
|
config.onScroll(_scrollOffset);
|
||||||
}
|
}
|
||||||
@ -470,11 +471,12 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragStart(_) {
|
void _handleDragStart(_) {
|
||||||
_dispatchOnScrollStartIfNeeded();
|
_startScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _dispatchOnScrollStartIfNeeded() {
|
void _startScroll() {
|
||||||
if (!_isBetweenOnScrollStartAndOnScrollEnd)
|
_numberOfInProgressScrolls += 1;
|
||||||
|
if (_numberOfInProgressScrolls == 1)
|
||||||
dispatchOnScrollStart();
|
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.
|
/// Subclasses can override this function to hook the scroll start callback.
|
||||||
void dispatchOnScrollStart() {
|
void dispatchOnScrollStart() {
|
||||||
assert(!_isBetweenOnScrollStartAndOnScrollEnd);
|
assert(_numberOfInProgressScrolls == 1);
|
||||||
_isBetweenOnScrollStartAndOnScrollEnd = true;
|
|
||||||
if (config.onScrollStart != null)
|
if (config.onScrollStart != null)
|
||||||
config.onScrollStart(_scrollOffset);
|
config.onScrollStart(_scrollOffset);
|
||||||
}
|
}
|
||||||
@ -495,11 +496,12 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
|||||||
Future _handleDragEnd(Velocity velocity) {
|
Future _handleDragEnd(Velocity velocity) {
|
||||||
double scrollVelocity = pixelDeltaToScrollOffset(velocity.pixelsPerSecond) / Duration.MILLISECONDS_PER_SECOND;
|
double scrollVelocity = pixelDeltaToScrollOffset(velocity.pixelsPerSecond) / Duration.MILLISECONDS_PER_SECOND;
|
||||||
// The gesture velocity properties are pixels/second, config min,max limits are pixels/ms
|
// 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(_) {
|
void _endScroll([_]) {
|
||||||
if (_isBetweenOnScrollStartAndOnScrollEnd)
|
_numberOfInProgressScrolls -= 1;
|
||||||
|
if (_numberOfInProgressScrolls == 0)
|
||||||
dispatchOnScrollEnd();
|
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.
|
/// Subclasses can override this function to hook the scroll end callback.
|
||||||
void dispatchOnScrollEnd() {
|
void dispatchOnScrollEnd() {
|
||||||
assert(_isBetweenOnScrollStartAndOnScrollEnd);
|
assert(_numberOfInProgressScrolls == 0);
|
||||||
_isBetweenOnScrollStartAndOnScrollEnd = false;
|
|
||||||
if (config.onScrollEnd != null)
|
if (config.onScrollEnd != null)
|
||||||
config.onScrollEnd(_scrollOffset);
|
config.onScrollEnd(_scrollOffset);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
/// The Flutter widget framework.
|
/// The Flutter widget framework.
|
||||||
library widgets;
|
library widgets;
|
||||||
|
|
||||||
|
export 'src/widgets/app.dart';
|
||||||
export 'src/widgets/asset_vendor.dart';
|
export 'src/widgets/asset_vendor.dart';
|
||||||
export 'src/widgets/auto_layout.dart';
|
export 'src/widgets/auto_layout.dart';
|
||||||
export 'src/widgets/basic.dart';
|
export 'src/widgets/basic.dart';
|
||||||
|
@ -26,7 +26,7 @@ enum EnginePhase {
|
|||||||
composite
|
composite
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestRenderingFlutterBinding extends BindingBase with Scheduler, MojoShell, Renderer, Gesturer {
|
class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services, Renderer, Gesturer {
|
||||||
void initRenderView() {
|
void initRenderView() {
|
||||||
if (renderView == null) {
|
if (renderView == null) {
|
||||||
renderView = new TestRenderView();
|
renderView = new TestRenderView();
|
||||||
|
@ -17,16 +17,15 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Size performLayoutSize;
|
Size performLayoutSize;
|
||||||
BoxConstraints performLayoutConstraints;
|
|
||||||
Size performLayoutSize0;
|
Size performLayoutSize0;
|
||||||
Size performLayoutSize1;
|
Size performLayoutSize1;
|
||||||
bool performLayoutIsChild;
|
bool performLayoutIsChild;
|
||||||
|
|
||||||
void performLayout(Size size, BoxConstraints constraints) {
|
void performLayout(Size size) {
|
||||||
assert(!RenderObject.debugCheckingIntrinsics);
|
assert(!RenderObject.debugCheckingIntrinsics);
|
||||||
expect(() {
|
expect(() {
|
||||||
performLayoutSize = size;
|
performLayoutSize = size;
|
||||||
performLayoutConstraints = constraints;
|
BoxConstraints constraints = new BoxConstraints.loose(size);
|
||||||
performLayoutSize0 = layoutChild(0, constraints);
|
performLayoutSize0 = layoutChild(0, constraints);
|
||||||
performLayoutSize1 = layoutChild(1, constraints);
|
performLayoutSize1 = layoutChild(1, constraints);
|
||||||
performLayoutIsChild = isChild('fred');
|
performLayoutIsChild = isChild('fred');
|
||||||
@ -68,10 +67,6 @@ void main() {
|
|||||||
|
|
||||||
expect(delegate.performLayoutSize.width, 200.0);
|
expect(delegate.performLayoutSize.width, 200.0);
|
||||||
expect(delegate.performLayoutSize.height, 300.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.width, 150.0);
|
||||||
expect(delegate.performLayoutSize0.height, 100.0);
|
expect(delegate.performLayoutSize0.height, 100.0);
|
||||||
expect(delegate.performLayoutSize1.width, 100.0);
|
expect(delegate.performLayoutSize1.width, 100.0);
|
||||||
|
@ -95,4 +95,45 @@ void main() {
|
|||||||
expect(log, equals(['scrollstart', 'scroll', 'scroll', 'scrollend']));
|
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()) {
|
super(binding: _SteppedWidgetFlutterBinding.ensureInitialized()) {
|
||||||
timeDilation = 1.0;
|
timeDilation = 1.0;
|
||||||
ui.window.onBeginFrame = null;
|
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;
|
final FakeAsync async;
|
||||||
|
@ -128,21 +128,19 @@ All done! In order to run your application, type:
|
|||||||
|
|
||||||
void _renderTemplates(String projectName, String dirPath,
|
void _renderTemplates(String projectName, String dirPath,
|
||||||
String flutterPackagesDirectory, { bool renderDriverTest: false }) {
|
String flutterPackagesDirectory, { bool renderDriverTest: false }) {
|
||||||
String relativePackagesDirectory = path.relative(
|
new Directory(dirPath).createSync(recursive: true);
|
||||||
flutterPackagesDirectory,
|
|
||||||
from: path.join(dirPath, 'pubspec.yaml')
|
flutterPackagesDirectory = path.normalize(flutterPackagesDirectory);
|
||||||
);
|
flutterPackagesDirectory = _relativePath(from: dirPath, to: flutterPackagesDirectory);
|
||||||
|
|
||||||
printStatus('Creating project ${path.basename(projectName)}:');
|
printStatus('Creating project ${path.basename(projectName)}:');
|
||||||
|
|
||||||
new Directory(dirPath).createSync(recursive: true);
|
|
||||||
|
|
||||||
Map templateContext = <String, dynamic>{
|
Map templateContext = <String, dynamic>{
|
||||||
'projectName': projectName,
|
'projectName': projectName,
|
||||||
'androidIdentifier': _createAndroidIdentifier(projectName),
|
'androidIdentifier': _createAndroidIdentifier(projectName),
|
||||||
'iosIdentifier': _createUTIIdentifier(projectName),
|
'iosIdentifier': _createUTIIdentifier(projectName),
|
||||||
'description': description,
|
'description': description,
|
||||||
'flutterPackagesDirectory': relativePackagesDirectory,
|
'flutterPackagesDirectory': flutterPackagesDirectory,
|
||||||
'androidMinApiLevel': android.minApiLevel
|
'androidMinApiLevel': android.minApiLevel
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -211,3 +209,11 @@ String _validateProjectName(String projectName) {
|
|||||||
|
|
||||||
return null;
|
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';
|
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
|
/// The current position of the object in the simulation
|
||||||
double x(double time);
|
double x(double time);
|
||||||
|
|
||||||
/// The current velocity of the object in the simulation
|
/// The current velocity of the object in the simulation
|
||||||
double dx(double time);
|
double dx(double time); // TODO(ianh): remove this; see https://github.com/flutter/flutter/issues/2092
|
||||||
}
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
/// Returns if the simulation is done at a given time
|
/// Returns if the simulation is done at a given time
|
||||||
bool isDone(double time);
|
bool isDone(double time);
|
||||||
|
@ -10,7 +10,8 @@ import 'utils.dart';
|
|||||||
/// must implement the appropriate methods to select the appropriate simulation
|
/// must implement the appropriate methods to select the appropriate simulation
|
||||||
/// at a given time interval. The simulation group takes care to call the `step`
|
/// 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
|
/// 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 {
|
abstract class SimulationGroup extends Simulation {
|
||||||
|
|
||||||
/// The currently active simulation
|
/// The currently active simulation
|
||||||
|
@ -7,120 +7,154 @@ import 'dart:math' as math;
|
|||||||
import 'simulation.dart';
|
import 'simulation.dart';
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
|
|
||||||
abstract class _SpringSolution implements Simulatable {
|
enum SpringType { unknown, criticallyDamped, underDamped, overDamped }
|
||||||
|
|
||||||
|
abstract class _SpringSolution {
|
||||||
factory _SpringSolution(
|
factory _SpringSolution(
|
||||||
SpringDescription desc, double initialPosition, double initialVelocity) {
|
SpringDescription desc,
|
||||||
double cmk =
|
double initialPosition,
|
||||||
desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
|
double initialVelocity
|
||||||
|
) {
|
||||||
if (cmk == 0.0) {
|
double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
|
||||||
|
if (cmk == 0.0)
|
||||||
return new _CriticalSolution(desc, initialPosition, initialVelocity);
|
return new _CriticalSolution(desc, initialPosition, initialVelocity);
|
||||||
} else if (cmk > 0.0) {
|
if (cmk > 0.0)
|
||||||
return new _OverdampedSolution(desc, initialPosition, initialVelocity);
|
return new _OverdampedSolution(desc, initialPosition, initialVelocity);
|
||||||
} else {
|
return new _UnderdampedSolution(desc, initialPosition, initialVelocity);
|
||||||
return new _UnderdampedSolution(desc, initialPosition, initialVelocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double x(double time);
|
||||||
|
double dx(double time);
|
||||||
SpringType get type;
|
SpringType get type;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CriticalSolution implements _SpringSolution {
|
class _CriticalSolution implements _SpringSolution {
|
||||||
final double _r, _c1, _c2;
|
|
||||||
|
|
||||||
factory _CriticalSolution(
|
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 r = -desc.damping / (2.0 * desc.mass);
|
||||||
final double c1 = distance;
|
final double c1 = distance;
|
||||||
final double c2 = velocity / (r * distance);
|
final double c2 = velocity / (r * distance);
|
||||||
return new _CriticalSolution.withArgs(r, c1, c2);
|
return new _CriticalSolution.withArgs(r, c1, c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpringType get type => SpringType.criticallyDamped;
|
|
||||||
|
|
||||||
_CriticalSolution.withArgs(double r, double c1, double c2)
|
_CriticalSolution.withArgs(double r, double c1, double c2)
|
||||||
: _r = r,
|
: _r = r,
|
||||||
_c1 = c1,
|
_c1 = c1,
|
||||||
_c2 = c2;
|
_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) {
|
double dx(double time) {
|
||||||
final double power = math.pow(math.E, _r * time);
|
final double power = math.pow(math.E, _r * time);
|
||||||
return _r * (_c1 + _c2 * time) * power + _c2 * power;
|
return _r * (_c1 + _c2 * time) * power + _c2 * power;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpringType get type => SpringType.criticallyDamped;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OverdampedSolution implements _SpringSolution {
|
class _OverdampedSolution implements _SpringSolution {
|
||||||
final double _r1, _r2, _c1, _c2;
|
|
||||||
|
|
||||||
factory _OverdampedSolution(
|
factory _OverdampedSolution(
|
||||||
SpringDescription desc, double distance, double velocity) {
|
SpringDescription desc,
|
||||||
final double cmk =
|
double distance,
|
||||||
desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
|
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 r1 = (-desc.damping - math.sqrt(cmk)) / (2.0 * desc.mass);
|
||||||
final double r2 = (-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 c2 = (velocity - r1 * distance) / (r2 - r1);
|
||||||
final double c1 = distance - c2;
|
final double c1 = distance - c2;
|
||||||
|
|
||||||
return new _OverdampedSolution.withArgs(r1, r2, c1, c2);
|
return new _OverdampedSolution.withArgs(r1, r2, c1, c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
_OverdampedSolution.withArgs(double r1, double r2, double c1, double c2)
|
_OverdampedSolution.withArgs(double r1, double r2, double c1, double c2)
|
||||||
: _r1 = r1,
|
: _r1 = r1,
|
||||||
_r2 = r2,
|
_r2 = r2,
|
||||||
_c1 = c1,
|
_c1 = c1,
|
||||||
_c2 = c2;
|
_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;
|
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 {
|
class _UnderdampedSolution implements _SpringSolution {
|
||||||
final double _w, _r, _c1, _c2;
|
|
||||||
|
|
||||||
factory _UnderdampedSolution(
|
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 -
|
final double w = math.sqrt(4.0 * desc.mass * desc.springConstant -
|
||||||
desc.damping * desc.damping) /
|
desc.damping * desc.damping) / (2.0 * desc.mass);
|
||||||
(2.0 * desc.mass);
|
|
||||||
final double r = -(desc.damping / 2.0 * desc.mass);
|
final double r = -(desc.damping / 2.0 * desc.mass);
|
||||||
final double c1 = distance;
|
final double c1 = distance;
|
||||||
final double c2 = (velocity - r * distance) / w;
|
final double c2 = (velocity - r * distance) / w;
|
||||||
|
|
||||||
return new _UnderdampedSolution.withArgs(w, r, c1, c2);
|
return new _UnderdampedSolution.withArgs(w, r, c1, c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
_UnderdampedSolution.withArgs(double w, double r, double c1, double c2)
|
_UnderdampedSolution.withArgs(double w, double r, double c1, double c2)
|
||||||
: _w = w,
|
: _w = w,
|
||||||
_r = r,
|
_r = r,
|
||||||
_c1 = c1,
|
_c1 = c1,
|
||||||
_c2 = c2;
|
_c2 = c2;
|
||||||
|
|
||||||
SpringType get type => SpringType.underDamped;
|
final double _w, _r, _c1, _c2;
|
||||||
|
|
||||||
double x(double time) => math.pow(math.E, _r * time) *
|
double x(double time) {
|
||||||
(_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
|
return math.pow(math.E, _r * time) *
|
||||||
|
(_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
|
||||||
|
}
|
||||||
|
|
||||||
double dx(double time) {
|
double dx(double time) {
|
||||||
final double power = math.pow(math.E, _r * time);
|
final double power = math.pow(math.E, _r * time);
|
||||||
final double cosine = math.cos(_w * time);
|
final double cosine = math.cos(_w * time);
|
||||||
final double sine = math.sin(_w * time);
|
final double sine = math.sin(_w * time);
|
||||||
|
return power * (_c2 * _w * cosine - _c1 * _w * sine) +
|
||||||
return power * (_c2 * _w * cosine - _c1 * _w * sine) +
|
_r * power * (_c2 * sine + _c1 * cosine);
|
||||||
_r * power * (_c2 * sine + _c1 * cosine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpringType get type => SpringType.underDamped;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpringDescription {
|
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)
|
/// The mass of the spring (m)
|
||||||
final double mass;
|
final double mass;
|
||||||
|
|
||||||
@ -131,41 +165,23 @@ class SpringDescription {
|
|||||||
/// Not to be confused with the damping ratio. Use the separate
|
/// Not to be confused with the damping ratio. Use the separate
|
||||||
/// constructor provided for this purpose
|
/// constructor provided for this purpose
|
||||||
final double damping;
|
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
|
/// Creates a spring simulation. Depending on the spring description, a
|
||||||
/// critically, under or overdamped spring will be created.
|
/// critically, under or overdamped spring will be created.
|
||||||
class SpringSimulation extends Simulation {
|
class SpringSimulation extends Simulation {
|
||||||
final double _endPosition;
|
|
||||||
|
|
||||||
final _SpringSolution _solution;
|
|
||||||
|
|
||||||
/// A spring description with the provided spring description, start distance,
|
/// A spring description with the provided spring description, start distance,
|
||||||
/// end distance and velocity.
|
/// end distance and velocity.
|
||||||
SpringSimulation(
|
SpringSimulation(
|
||||||
SpringDescription desc, double start, double end, double velocity)
|
SpringDescription desc,
|
||||||
: this._endPosition = end,
|
double start,
|
||||||
_solution = new _SpringSolution(desc, start - end, velocity);
|
double end,
|
||||||
|
double velocity
|
||||||
|
) : _endPosition = end,
|
||||||
|
_solution = new _SpringSolution(desc, start - end, velocity);
|
||||||
|
|
||||||
|
final double _endPosition;
|
||||||
|
final _SpringSolution _solution;
|
||||||
|
|
||||||
SpringType get type => _solution.type;
|
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
|
/// A SpringSimulation where the value of x() is guaranteed to have exactly the
|
||||||
/// end value when the simulation isDone().
|
/// end value when the simulation isDone().
|
||||||
class ScrollSpringSimulation extends SpringSimulation {
|
class ScrollSpringSimulation extends SpringSimulation {
|
||||||
ScrollSpringSimulation(SpringDescription desc, double start, double end, double velocity)
|
ScrollSpringSimulation(
|
||||||
: super(desc, start, end, velocity);
|
SpringDescription desc,
|
||||||
|
double start,
|
||||||
|
double end,
|
||||||
|
double velocity
|
||||||
|
) : super(desc, start, end, velocity);
|
||||||
|
|
||||||
double x(double time) => isDone(time) ? _endPosition : super.x(time);
|
double x(double time) => isDone(time) ? _endPosition : super.x(time);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user