Commit 05f19cfd authored by Adam Barth's avatar Adam Barth

Merge pull request #1881 from abarth/navigator2

Switch to Navigator2
parents a7cd12fc 50a177b7
...@@ -24,22 +24,20 @@ class StockHomeState extends State<StockHome> { ...@@ -24,22 +24,20 @@ class StockHomeState extends State<StockHome> {
String _searchQuery; String _searchQuery;
void _handleSearchBegin() { void _handleSearchBegin() {
Navigator.of(context).pushState(this, (_) { Navigator.of(context).push(new StateRoute(
setState(() { onPop: () {
_isSearching = false; setState(() {
_searchQuery = null; _isSearching = false;
}); _searchQuery = null;
}); });
}
));
setState(() { setState(() {
_isSearching = true; _isSearching = true;
}); });
} }
void _handleSearchEnd() { void _handleSearchEnd() {
assert(() {
final StateRoute currentRoute = Navigator.of(context).currentRoute;
return currentRoute.owner == this;
});
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
......
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/navigator2.dart' as n2;
class Home extends StatelessComponent { class Home extends StatelessComponent {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Container( return new Container(
...@@ -15,11 +13,11 @@ class Home extends StatelessComponent { ...@@ -15,11 +13,11 @@ class Home extends StatelessComponent {
new Text("You are at home"), new Text("You are at home"),
new RaisedButton( new RaisedButton(
child: new Text('GO SHOPPING'), child: new Text('GO SHOPPING'),
onPressed: () => n2.Navigator.of(context).pushNamed('/shopping') onPressed: () => Navigator.of(context).pushNamed('/shopping')
), ),
new RaisedButton( new RaisedButton(
child: new Text('START ADVENTURE'), child: new Text('START ADVENTURE'),
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure') onPressed: () => Navigator.of(context).pushNamed('/adventure')
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
...@@ -36,11 +34,11 @@ class Shopping extends StatelessComponent { ...@@ -36,11 +34,11 @@ class Shopping extends StatelessComponent {
new Text("Village Shop"), new Text("Village Shop"),
new RaisedButton( new RaisedButton(
child: new Text('RETURN HOME'), child: new Text('RETURN HOME'),
onPressed: () => n2.Navigator.of(context).pop() onPressed: () => Navigator.of(context).pop()
), ),
new RaisedButton( new RaisedButton(
child: new Text('GO TO DUNGEON'), child: new Text('GO TO DUNGEON'),
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure') onPressed: () => Navigator.of(context).pushNamed('/adventure')
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
...@@ -57,7 +55,7 @@ class Adventure extends StatelessComponent { ...@@ -57,7 +55,7 @@ class Adventure extends StatelessComponent {
new Text("Monster's Lair"), new Text("Monster's Lair"),
new RaisedButton( new RaisedButton(
child: new Text('RUN!!!'), child: new Text('RUN!!!'),
onPressed: () => n2.Navigator.of(context).pop() onPressed: () => Navigator.of(context).pop()
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
......
...@@ -17,14 +17,9 @@ const double _kMinFlingVelocity = 700.0; ...@@ -17,14 +17,9 @@ const double _kMinFlingVelocity = 700.0;
const double _kFlingVelocityScale = 1.0 / 300.0; const double _kFlingVelocityScale = 1.0 / 300.0;
class _BottomSheet extends StatefulComponent { class _BottomSheet extends StatefulComponent {
_BottomSheet({ _BottomSheet({ Key key, this.route }) : super(key: key);
Key key,
this.child,
this.performance
}) : super(key: key);
final Widget child; final _ModalBottomSheetRoute route;
final Performance performance;
_BottomSheetState createState() => new _BottomSheetState(); _BottomSheetState createState() => new _BottomSheetState();
} }
...@@ -54,47 +49,62 @@ class _BottomSheetState extends State<_BottomSheet> { ...@@ -54,47 +49,62 @@ class _BottomSheetState extends State<_BottomSheet> {
bool _dragEnabled = false; bool _dragEnabled = false;
void _handleDragStart(Point position) { void _handleDragStart(Point position) {
_dragEnabled = !config.performance.isAnimating; _dragEnabled = !config.route._performance.isAnimating;
} }
void _handleDragUpdate(double delta) { void _handleDragUpdate(double delta) {
if (!_dragEnabled) if (!_dragEnabled)
return; return;
config.performance.progress -= delta / _layout.childTop.end; config.route._performance.progress -= delta / _layout.childTop.end;
} }
void _handleDragEnd(Offset velocity) { void _handleDragEnd(Offset velocity) {
if (!_dragEnabled) if (!_dragEnabled)
return; return;
if (velocity.dy > _kMinFlingVelocity) if (velocity.dy > _kMinFlingVelocity)
config.performance.fling(velocity: -velocity.dy * _kFlingVelocityScale); config.route._performance.fling(velocity: -velocity.dy * _kFlingVelocityScale);
else else
config.performance.forward(); config.route._performance.forward();
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new BuilderTransition( return new Focus(
performance: config.performance, key: new GlobalObjectKey(config.route),
variables: <AnimatedValue<double>>[_layout.childTop], autofocus: true,
builder: (BuildContext context) { child: new GestureDetector(
return new ClipRect( onTap: () { Navigator.of(context).pop(); },
child: new CustomOneChildLayout( child: new Stack(<Widget>[
delegate: _layout, // mask
token: _layout.childTop.value, new ColorTransition(
child: new GestureDetector( performance: config.route._performance,
onVerticalDragStart: _handleDragStart, color: new AnimatedColorValue(Colors.transparent, end: Colors.black54),
onVerticalDragUpdate: _handleDragUpdate, child: new Container()
onVerticalDragEnd: _handleDragEnd, ),
child: new Material(child: config.child) new BuilderTransition(
) performance: config.route._performance,
variables: <AnimatedValue<double>>[_layout.childTop],
builder: (BuildContext context) {
return new ClipRect(
child: new CustomOneChildLayout(
delegate: _layout,
token: _layout.childTop.value,
child: new GestureDetector(
onVerticalDragStart: _handleDragStart,
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: new Material(child: config.route.child)
)
)
);
}
) )
); ])
} )
); );
} }
} }
class _ModalBottomSheetRoute extends Route { class _ModalBottomSheetRoute extends TransitionRoute {
_ModalBottomSheetRoute({ this.completer, this.child }) { _ModalBottomSheetRoute({ this.completer, this.child }) {
_performance = new Performance(duration: transitionDuration, debugLabel: 'ModalBottomSheet'); _performance = new Performance(duration: transitionDuration, debugLabel: 'ModalBottomSheet');
} }
...@@ -102,53 +112,27 @@ class _ModalBottomSheetRoute extends Route { ...@@ -102,53 +112,27 @@ class _ModalBottomSheetRoute extends Route {
final Completer completer; final Completer completer;
final Widget child; final Widget child;
PerformanceView get performance => _performance?.view;
Performance _performance;
bool get ephemeral => true;
bool get modal => true;
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration; Duration get transitionDuration => _kBottomSheetDuration;
Widget build(RouteArguments args) { Performance _performance;
return new Focus(
key: new GlobalObjectKey(this),
autofocus: true,
child: new GestureDetector(
onTap: () { navigator.pop(); },
child: new Stack(<Widget>[
// mask
new ColorTransition(
performance: performance,
color: new AnimatedColorValue(Colors.transparent, end: Colors.black54),
child: new Container()
),
// sheet
new _BottomSheet(
performance: _performance,
child: child
)
])
)
);
}
void didPush(NavigatorState navigator) { Performance createPerformance() {
super.didPush(navigator); _performance = super.createPerformance();
_performance?.forward(); return _performance;
} }
List<Widget> createWidgets() => [ new _BottomSheet(route: this) ];
void didPop([dynamic result]) { void didPop([dynamic result]) {
completer.complete(result); completer.complete(result);
super.didPop(result); super.didPop(result);
if (_performance.status != PerformanceStatus.dismissed)
_performance?.reverse();
} }
} }
Future showModalBottomSheet({ BuildContext context, Widget child }) { Future showModalBottomSheet({ BuildContext context, Widget child }) {
final Completer completer = new Completer(); final Completer completer = new Completer();
Navigator.of(context).push(new _ModalBottomSheetRoute( Navigator.of(context).pushEphemeral(new _ModalBottomSheetRoute(
completer: completer, completer: completer,
child: child child: child
)); ));
......
...@@ -100,6 +100,7 @@ class Dialog extends StatelessComponent { ...@@ -100,6 +100,7 @@ class Dialog extends StatelessComponent {
)); ));
} }
// TODO(abarth): We should return the backdrop as a separate entry from createWidgets.
return new Stack(<Widget>[ return new Stack(<Widget>[
new GestureDetector( new GestureDetector(
onTap: onDismiss, onTap: onDismiss,
...@@ -130,21 +131,27 @@ class Dialog extends StatelessComponent { ...@@ -130,21 +131,27 @@ class Dialog extends StatelessComponent {
} }
} }
class _DialogRoute extends PerformanceRoute { class _DialogRoute extends TransitionRoute {
_DialogRoute({ this.completer, this.builder }); _DialogRoute({ this.completer, this.child });
final Completer completer; final Completer completer;
final RouteBuilder builder; final Widget child;
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => const Duration(milliseconds: 150); Duration get transitionDuration => const Duration(milliseconds: 150);
Widget build(RouteArguments args) { List<Widget> createWidgets() {
return new FadeTransition( return [
performance: args.previousPerformance, new Focus(
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut), key: new GlobalObjectKey(this),
child: builder(args) autofocus: true,
); child: new FadeTransition(
performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut),
child: child
)
)
];
} }
void didPop([dynamic result]) { void didPop([dynamic result]) {
...@@ -155,15 +162,6 @@ class _DialogRoute extends PerformanceRoute { ...@@ -155,15 +162,6 @@ class _DialogRoute extends PerformanceRoute {
Future showDialog({ BuildContext context, Widget child }) { Future showDialog({ BuildContext context, Widget child }) {
Completer completer = new Completer(); Completer completer = new Completer();
Navigator.of(context).push(new _DialogRoute( Navigator.of(context).push(new _DialogRoute(completer: completer, child: child));
completer: completer,
builder: (RouteArguments args) {
return new Focus(
key: new GlobalObjectKey(completer),
autofocus: true,
child: child
);
}
));
return completer.future; return completer.future;
} }
...@@ -31,115 +31,90 @@ const Point _kOpenPosition = Point.origin; ...@@ -31,115 +31,90 @@ const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0); const Point _kClosedPosition = const Point(-_kWidth, 0.0);
class _Drawer extends StatelessComponent { class _Drawer extends StatelessComponent {
_Drawer({ Key key, this.route }) : super(key: key);
_Drawer({
Key key,
this.child,
this.level: 3,
this.performance,
this.interactive,
this.route
}) : super(key: key);
final Widget child;
final int level;
final PerformanceView performance;
final bool interactive;
final _DrawerRoute route; final _DrawerRoute route;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new GestureDetector( return new Focus(
onHorizontalDragStart: (_) { key: new GlobalObjectKey(route),
if (interactive) autofocus: true,
route._takeControl(); child: new GestureDetector(
}, onHorizontalDragStart: (_) {
onHorizontalDragUpdate: (double delta) { if (route.interactive)
if (interactive) route._takeControl();
route._moveDrawer(delta); },
}, onHorizontalDragUpdate: (double delta) {
onHorizontalDragEnd: (Offset velocity) { if (route.interactive)
if (interactive) route._moveDrawer(delta);
route._settle(velocity); },
}, onHorizontalDragEnd: (Offset velocity) {
child: new Stack(<Widget>[ if (route.interactive)
// mask route._settle(velocity);
new GestureDetector( },
onTap: () { child: new Stack(<Widget>[
if (interactive) // mask
route._close(); new GestureDetector(
}, onTap: () {
child: new ColorTransition( if (route.interactive)
performance: performance, route._close();
color: new AnimatedColorValue(Colors.transparent, end: Colors.black54), },
child: new Container() child: new ColorTransition(
) performance: route.performance,
), color: new AnimatedColorValue(Colors.transparent, end: Colors.black54),
// drawer child: new Container()
new Positioned( )
top: 0.0, ),
left: 0.0, new Positioned(
bottom: 0.0, top: 0.0,
child: new SlideTransition( left: 0.0,
performance: performance, bottom: 0.0,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition), child: new SlideTransition(
child: new AnimatedContainer( performance: route.performance,
curve: Curves.ease, position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
duration: _kThemeChangeDuration, child: new AnimatedContainer(
decoration: new BoxDecoration( curve: Curves.ease,
backgroundColor: Theme.of(context).canvasColor, duration: _kThemeChangeDuration,
boxShadow: shadows[level]), decoration: new BoxDecoration(
width: _kWidth, backgroundColor: Theme.of(context).canvasColor,
child: child boxShadow: shadows[route.level]),
width: _kWidth,
child: route.child
)
) )
) )
) ])
]) )
); );
} }
} }
class _DrawerRoute extends Route { class _DrawerRoute extends TransitionRoute {
_DrawerRoute({ this.child, this.level }); _DrawerRoute({ this.child, this.level });
final Widget child; final Widget child;
final int level; final int level;
PerformanceView get performance => _performance?.view; Duration get transitionDuration => _kBaseSettleDuration;
Performance _performance = new Performance(duration: _kBaseSettleDuration, debugLabel: 'Drawer');
bool get opaque => false; bool get opaque => false;
bool get interactive => _interactive;
bool _interactive = true; bool _interactive = true;
Widget build(RouteArguments args) { Performance _performance;
return new Focus(
key: new GlobalObjectKey(this),
autofocus: true,
child: new _Drawer(
child: child,
level: level,
performance: performance,
interactive: _interactive,
route: this
)
);
}
void didPush(NavigatorState navigator) { Performance createPerformance() {
super.didPush(navigator); _performance = super.createPerformance();
_performance.forward(); return _performance;
} }
List<Widget> createWidgets() => [ new _Drawer(route: this) ];
void didPop([dynamic result]) { void didPop([dynamic result]) {
assert(result == null); // because we don't do anything with it, so otherwise it'd be lost assert(result == null); // because we don't do anything with it, so otherwise it'd be lost
super.didPop(result); super.didPop(result);
if (_performance.status != PerformanceStatus.dismissed) _interactive = false;
_performance.reverse();
setState(() {
_interactive = false;
// TODO(ianh): https://github.com/flutter/engine/issues/1539
});
} }
void _takeControl() { void _takeControl() {
......
...@@ -22,23 +22,9 @@ const double _kBaselineOffsetFromBottom = 20.0; ...@@ -22,23 +22,9 @@ const double _kBaselineOffsetFromBottom = 20.0;
const Border _kDropdownUnderline = const Border(bottom: const BorderSide(color: const Color(0xFFBDBDBD), width: 2.0)); const Border _kDropdownUnderline = const Border(bottom: const BorderSide(color: const Color(0xFFBDBDBD), width: 2.0));
class _DropdownMenu extends StatelessComponent { class _DropdownMenu extends StatelessComponent {
_DropdownMenu({ _DropdownMenu({ Key key, this.route }) : super(key: key);
Key key,
this.items,
this.rect,
this.performance,
this.selectedIndex,
this.level: 4
}) : super(key: key) {
assert(items != null);
assert(performance != null);
}
final List<DropdownMenuItem> items; final _MenuRoute route;
final Rect rect;
final PerformanceView performance;
final int selectedIndex;
final int level;
Widget build(BuildContext context) { Widget build(BuildContext context) {
// The menu is shown in three stages (unit timing in brackets): // The menu is shown in three stages (unit timing in brackets):
...@@ -50,11 +36,11 @@ class _DropdownMenu extends StatelessComponent { ...@@ -50,11 +36,11 @@ class _DropdownMenu extends StatelessComponent {
// When the menu is dismissed we just fade the entire thing out // When the menu is dismissed we just fade the entire thing out
// in the first 0.25. // in the first 0.25.
final double unit = 0.5 / (items.length + 1.5); final double unit = 0.5 / (route.items.length + 1.5);
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
for (int itemIndex = 0; itemIndex < items.length; ++itemIndex) { for (int itemIndex = 0; itemIndex < route.items.length; ++itemIndex) {
AnimatedValue<double> opacity; AnimatedValue<double> opacity;
if (itemIndex == selectedIndex) { if (itemIndex == route.selectedIndex) {
opacity = new AnimatedValue<double>(0.0, end: 1.0, curve: const Interval(0.0, 0.001), reverseCurve: const Interval(0.75, 1.0)); opacity = new AnimatedValue<double>(0.0, end: 1.0, curve: const Interval(0.0, 0.001), reverseCurve: const Interval(0.75, 1.0));
} else { } else {
final double start = (0.5 + (itemIndex + 1) * unit).clamp(0.0, 1.0); final double start = (0.5 + (itemIndex + 1) * unit).clamp(0.0, 1.0);
...@@ -62,12 +48,12 @@ class _DropdownMenu extends StatelessComponent { ...@@ -62,12 +48,12 @@ class _DropdownMenu extends StatelessComponent {
opacity = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end), reverseCurve: const Interval(0.75, 1.0)); opacity = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end), reverseCurve: const Interval(0.75, 1.0));
} }
children.add(new FadeTransition( children.add(new FadeTransition(
performance: performance, performance: route.performance,
opacity: opacity, opacity: opacity,
child: new InkWell( child: new InkWell(
child: items[itemIndex], child: route.items[itemIndex],
onTap: () { onTap: () {
Navigator.of(context).pop(items[itemIndex].value); Navigator.of(context).pop(route.items[itemIndex].value);
} }
) )
)); ));
...@@ -79,13 +65,13 @@ class _DropdownMenu extends StatelessComponent { ...@@ -79,13 +65,13 @@ class _DropdownMenu extends StatelessComponent {
reverseCurve: new Interval(0.75, 1.0) reverseCurve: new Interval(0.75, 1.0)
); );
final AnimatedValue<double> menuTop = new AnimatedValue<double>(rect.top, final AnimatedValue<double> menuTop = new AnimatedValue<double>(route.rect.top,
end: rect.top - selectedIndex * rect.height, end: route.rect.top - route.selectedIndex * route.rect.height,
curve: new Interval(0.25, 0.5), curve: new Interval(0.25, 0.5),
reverseCurve: const Interval(0.0, 0.001) reverseCurve: const Interval(0.0, 0.001)
); );
final AnimatedValue<double> menuBottom = new AnimatedValue<double>(rect.bottom, final AnimatedValue<double> menuBottom = new AnimatedValue<double>(route.rect.bottom,
end: menuTop.end + items.length * rect.height, end: menuTop.end + route.items.length * route.rect.height,
curve: new Interval(0.25, 0.5), curve: new Interval(0.25, 0.5),
reverseCurve: const Interval(0.0, 0.001) reverseCurve: const Interval(0.0, 0.001)
); );
...@@ -93,32 +79,45 @@ class _DropdownMenu extends StatelessComponent { ...@@ -93,32 +79,45 @@ class _DropdownMenu extends StatelessComponent {
final BoxPainter menuPainter = new BoxPainter(new BoxDecoration( final BoxPainter menuPainter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor, backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0, borderRadius: 2.0,
boxShadow: shadows[level] boxShadow: shadows[route.level]
)); ));
return new FadeTransition( final RenderBox renderBox = Navigator.of(context).context.findRenderObject();
performance: performance, final Size navigatorSize = renderBox.size;
opacity: menuOpacity, final RelativeRect menuRect = new RelativeRect.fromSize(route.rect, navigatorSize);
child: new BuilderTransition(
performance: performance, return new Positioned(
variables: <AnimatedValue<double>>[menuTop, menuBottom], top: menuRect.top - (route.selectedIndex * route.rect.height),
builder: (BuildContext context) { right: menuRect.right - _kMenuHorizontalPadding,
RenderBox renderBox = context.findRenderObject(); left: menuRect.left - _kMenuHorizontalPadding,
return new CustomPaint( child: new Focus(
child: new ScrollableViewport(child: new Container(child: new Column(children))), key: new GlobalObjectKey(route),
onPaint: (ui.Canvas canvas, Size size) { autofocus: true,
double top = renderBox.globalToLocal(new Point(0.0, menuTop.value)).y; child: new FadeTransition(
double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom.value)).y; performance: route.performance,
menuPainter.paint(canvas, new Rect.fromLTRB(0.0, top, size.width, bottom)); opacity: menuOpacity,
child: new BuilderTransition(
performance: route.performance,
variables: <AnimatedValue<double>>[menuTop, menuBottom],
builder: (BuildContext context) {
RenderBox renderBox = context.findRenderObject();
return new CustomPaint(
child: new ScrollableViewport(child: new Container(child: new Column(children))),
onPaint: (ui.Canvas canvas, Size size) {
double top = renderBox.globalToLocal(new Point(0.0, menuTop.value)).y;
double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom.value)).y;
menuPainter.paint(canvas, new Rect.fromLTRB(0.0, top, size.width, bottom));
}
);
} }
); )
} )
) )
); );
} }
} }
class _MenuRoute extends PerformanceRoute { class _MenuRoute extends TransitionRoute {
_MenuRoute({ _MenuRoute({
this.completer, this.completer,
this.items, this.items,
...@@ -133,33 +132,13 @@ class _MenuRoute extends PerformanceRoute { ...@@ -133,33 +132,13 @@ class _MenuRoute extends PerformanceRoute {
final int level; final int level;
final int selectedIndex; final int selectedIndex;
bool get ephemeral => true;
bool get modal => true;
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => _kMenuDuration; Duration get transitionDuration => _kMenuDuration;
Widget build(RouteArguments args) { List<Widget> createWidgets() => [
final RenderBox renderBox = navigator.context.findRenderObject(); new ModalBarrier(),
final Size navigatorSize = renderBox.size; new _DropdownMenu(route: this)
final RelativeRect menuRect = new RelativeRect.fromSize(rect, navigatorSize); ];
return new Positioned(
top: menuRect.top - (selectedIndex * rect.height),
right: menuRect.right - _kMenuHorizontalPadding,
left: menuRect.left - _kMenuHorizontalPadding,
child: new Focus(
key: new GlobalObjectKey(this),
autofocus: true,
child: new _DropdownMenu(
items: items,
selectedIndex: selectedIndex,
rect: rect,
level: level,
performance: performance
)
)
);
}
void didPop([dynamic result]) { void didPop([dynamic result]) {
completer.complete(result); completer.complete(result);
...@@ -210,7 +189,7 @@ class DropdownButton<T> extends StatelessComponent { ...@@ -210,7 +189,7 @@ class DropdownButton<T> extends StatelessComponent {
final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject(); final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject();
final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size; final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size;
final Completer completer = new Completer(); final Completer completer = new Completer();
Navigator.of(context).push(new _MenuRoute( Navigator.of(context).pushEphemeral(new _MenuRoute(
completer: completer, completer: completer,
items: items, items: items,
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
......
...@@ -8,9 +8,6 @@ import 'package:flutter/rendering.dart'; ...@@ -8,9 +8,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/src/widgets/navigator2.dart' as n2;
import 'package:flutter/src/widgets/hero_controller.dart' as n2;
import 'theme.dart'; import 'theme.dart';
import 'title.dart'; import 'title.dart';
...@@ -34,8 +31,6 @@ AssetBundle _initDefaultBundle() { ...@@ -34,8 +31,6 @@ AssetBundle _initDefaultBundle() {
final AssetBundle _defaultBundle = _initDefaultBundle(); final AssetBundle _defaultBundle = _initDefaultBundle();
const bool _kUseNavigator2 = false;
class MaterialApp extends StatefulComponent { class MaterialApp extends StatefulComponent {
MaterialApp({ MaterialApp({
Key key, Key key,
...@@ -87,10 +82,10 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -87,10 +82,10 @@ class _MaterialAppState extends State<MaterialApp> {
void _metricHandler(Size size) => setState(() { _size = size; }); void _metricHandler(Size size) => setState(() { _size = size; });
final n2.HeroController _heroController = new n2.HeroController(); final HeroController _heroController = new HeroController();
n2.Route _generateRoute(n2.NamedRouteSettings settings) { Route _generateRoute(NamedRouteSettings settings) {
return new n2.HeroPageRoute( return new HeroPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name); RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name);
return builder(new RouteArguments(context: context)); return builder(new RouteArguments(context: context));
...@@ -101,19 +96,6 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -101,19 +96,6 @@ class _MaterialAppState extends State<MaterialApp> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget navigator;
if (_kUseNavigator2) {
navigator = new n2.Navigator(
key: _navigator,
onGenerateRoute: _generateRoute
);
} else {
navigator = new Navigator(
key: _navigator,
routes: config.routes,
onGenerateRoute: config.onGenerateRoute
);
}
return new MediaQuery( return new MediaQuery(
data: new MediaQueryData(size: _size), data: new MediaQueryData(size: _size),
child: new Theme( child: new Theme(
...@@ -124,7 +106,10 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -124,7 +106,10 @@ class _MaterialAppState extends State<MaterialApp> {
bundle: _defaultBundle, bundle: _defaultBundle,
child: new Title( child: new Title(
title: config.title, title: config.title,
child: navigator child: new Navigator(
key: _navigator,
onGenerateRoute: _generateRoute
)
) )
) )
) )
......
...@@ -22,80 +22,84 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep; ...@@ -22,80 +22,84 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuHorizontalPadding = 16.0; const double _kMenuHorizontalPadding = 16.0;
const double _kMenuVerticalPadding = 8.0; const double _kMenuVerticalPadding = 8.0;
class PopupMenu extends StatelessComponent { class _PopupMenu extends StatelessComponent {
PopupMenu({ _PopupMenu({
Key key, Key key,
this.items, this.route
this.level: 4, }) : super(key: key);
this.performance
}) : super(key: key) {
assert(items != null);
assert(performance != null);
}
final List<PopupMenuItem> items; final _MenuRoute route;
final int level;
final PerformanceView performance;
Widget build(BuildContext context) { Widget build(BuildContext context) {
final BoxPainter painter = new BoxPainter(new BoxDecoration( final BoxPainter painter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor, backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0, borderRadius: 2.0,
boxShadow: shadows[level] boxShadow: shadows[route.level]
)); ));
double unit = 1.0 / (items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
List<Widget> children = <Widget>[]; List<Widget> children = <Widget>[];
for (int i = 0; i < items.length; ++i) { for (int i = 0; i < route.items.length; ++i) {
double start = (i + 1) * unit; double start = (i + 1) * unit;
double end = (start + 1.5 * unit).clamp(0.0, 1.0); double end = (start + 1.5 * unit).clamp(0.0, 1.0);
children.add(new FadeTransition( children.add(new FadeTransition(
performance: performance, performance: route.performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)),
child: new InkWell( child: new InkWell(
onTap: () { Navigator.of(context).pop(items[i].value); }, onTap: () { Navigator.of(context).pop(route.items[i].value); },
child: items[i] child: route.items[i]
)) ))
); );
} }
final AnimatedValue<double> width = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit)); final AnimatedValue<double> width = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit));
final AnimatedValue<double> height = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit * items.length)); final AnimatedValue<double> height = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit * route.items.length));
return new FadeTransition( return new Positioned(
performance: performance, top: route.position?.top,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)), right: route.position?.right,
child: new BuilderTransition( bottom: route.position?.bottom,
performance: performance, left: route.position?.left,
variables: <AnimatedValue<double>>[width, height], child: new Focus(
builder: (BuildContext context) { key: new GlobalObjectKey(route),
return new CustomPaint( autofocus: true,
onPaint: (ui.Canvas canvas, Size size) { child: new FadeTransition(
double widthValue = width.value * size.width; performance: route.performance,
double heightValue = height.value * size.height; opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)),
painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue)); child: new BuilderTransition(
}, performance: route.performance,
child: new ConstrainedBox( variables: <AnimatedValue<double>>[width, height],
constraints: new BoxConstraints( builder: (BuildContext context) {
minWidth: _kMenuMinWidth, return new CustomPaint(
maxWidth: _kMenuMaxWidth onPaint: (ui.Canvas canvas, Size size) {
), double widthValue = width.value * size.width;
child: new IntrinsicWidth( double heightValue = height.value * size.height;
stepWidth: _kMenuWidthStep, painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
child: new ScrollableViewport( },
child: new Container( child: new ConstrainedBox(
padding: const EdgeDims.symmetric( constraints: new BoxConstraints(
horizontal: _kMenuHorizontalPadding, minWidth: _kMenuMinWidth,
vertical: _kMenuVerticalPadding maxWidth: _kMenuMaxWidth
), ),
child: new BlockBody(children) child: new IntrinsicWidth(
stepWidth: _kMenuWidthStep,
child: new ScrollableViewport(
child: new Container(
// TODO(abarth): Teach Block about padding.
padding: const EdgeDims.symmetric(
horizontal: _kMenuHorizontalPadding,
vertical: _kMenuVerticalPadding
),
child: new BlockBody(children)
)
)
) )
) )
) );
) }
); )
} )
) )
); );
} }
...@@ -109,7 +113,7 @@ class MenuPosition { ...@@ -109,7 +113,7 @@ class MenuPosition {
final double left; final double left;
} }
class _MenuRoute extends PerformanceRoute { class _MenuRoute extends TransitionRoute {
_MenuRoute({ this.completer, this.position, this.items, this.level }); _MenuRoute({ this.completer, this.position, this.items, this.level });
final Completer completer; final Completer completer;
...@@ -125,28 +129,13 @@ class _MenuRoute extends PerformanceRoute { ...@@ -125,28 +129,13 @@ class _MenuRoute extends PerformanceRoute {
return result; return result;
} }
bool get ephemeral => true;
bool get modal => true;
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => _kMenuDuration; Duration get transitionDuration => _kMenuDuration;
Widget build(RouteArguments args) { List<Widget> createWidgets() => [
return new Positioned( new ModalBarrier(),
top: position?.top, new _PopupMenu(route: this),
right: position?.right, ];
bottom: position?.bottom,
left: position?.left,
child: new Focus(
key: new GlobalObjectKey(this),
autofocus: true,
child: new PopupMenu(
items: items,
level: level,
performance: performance
)
)
);
}
void didPop([dynamic result]) { void didPop([dynamic result]) {
completer.complete(result); completer.complete(result);
...@@ -156,7 +145,7 @@ class _MenuRoute extends PerformanceRoute { ...@@ -156,7 +145,7 @@ class _MenuRoute extends PerformanceRoute {
Future showMenu({ BuildContext context, MenuPosition position, List<PopupMenuItem> items, int level: 4 }) { Future showMenu({ BuildContext context, MenuPosition position, List<PopupMenuItem> items, int level: 4 }) {
Completer completer = new Completer(); Completer completer = new Completer();
Navigator.of(context).push(new _MenuRoute( Navigator.of(context).pushEphemeral(new _MenuRoute(
completer: completer, completer: completer,
position: position, position: position,
items: items, items: items,
......
...@@ -34,19 +34,19 @@ class SnackBarAction extends StatelessComponent { ...@@ -34,19 +34,19 @@ class SnackBarAction extends StatelessComponent {
} }
} }
class SnackBar extends StatelessComponent { class _SnackBar extends StatelessComponent {
SnackBar({ _SnackBar({
Key key, Key key,
this.content, this.content,
this.actions, this.actions,
this.performance this.route
}) : super(key: key) { }) : super(key: key) {
assert(content != null); assert(content != null);
} }
final Widget content; final Widget content;
final List<SnackBarAction> actions; final List<SnackBarAction> actions;
final PerformanceView performance; final _SnackBarRoute route;
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> children = <Widget>[ List<Widget> children = <Widget>[
...@@ -63,7 +63,7 @@ class SnackBar extends StatelessComponent { ...@@ -63,7 +63,7 @@ class SnackBar extends StatelessComponent {
if (actions != null) if (actions != null)
children.addAll(actions); children.addAll(actions);
return new SquashTransition( return new SquashTransition(
performance: performance, performance: route.performance,
height: new AnimatedValue<double>( height: new AnimatedValue<double>(
0.0, 0.0,
end: kSnackBarHeight, end: kSnackBarHeight,
...@@ -91,27 +91,18 @@ class SnackBar extends StatelessComponent { ...@@ -91,27 +91,18 @@ class SnackBar extends StatelessComponent {
} }
} }
class _SnackBarRoute extends PerformanceRoute { class _SnackBarRoute extends TransitionRoute {
_SnackBarRoute({ this.content, this.actions }); bool get opaque => false;
final Widget content;
final List<SnackBarAction> actions;
bool get hasContent => false;
bool get ephemeral => true;
bool get modal => false;
Duration get transitionDuration => const Duration(milliseconds: 200); Duration get transitionDuration => const Duration(milliseconds: 200);
Widget build(RouteArguments args) => null;
} }
void showSnackBar({ BuildContext context, GlobalKey<PlaceholderState> placeholderKey, Widget content, List<SnackBarAction> actions }) { void showSnackBar({ BuildContext context, GlobalKey<PlaceholderState> placeholderKey, Widget content, List<SnackBarAction> actions }) {
Route route = new _SnackBarRoute(); _SnackBarRoute route = new _SnackBarRoute();
SnackBar snackBar = new SnackBar( _SnackBar snackBar = new _SnackBar(
route: route,
content: content, content: content,
actions: actions, actions: actions
performance: route.performance
); );
placeholderKey.currentState.child = snackBar; placeholderKey.currentState.child = snackBar;
Navigator.of(context).push(route); Navigator.of(context).pushEphemeral(route);
} }
...@@ -10,6 +10,7 @@ import 'basic.dart'; ...@@ -10,6 +10,7 @@ import 'basic.dart';
import 'binding.dart'; import 'binding.dart';
import 'framework.dart'; import 'framework.dart';
import 'navigator.dart'; import 'navigator.dart';
import 'overlay.dart';
typedef bool DragTargetWillAccept<T>(T data); typedef bool DragTargetWillAccept<T>(T data);
typedef void DragTargetAccept<T>(T data); typedef void DragTargetAccept<T>(T data);
...@@ -61,11 +62,11 @@ class Draggable extends StatefulComponent { ...@@ -61,11 +62,11 @@ class Draggable extends StatefulComponent {
} }
class _DraggableState extends State<Draggable> { class _DraggableState extends State<Draggable> {
DragRoute _route; _DragAvatar _avatar;
void _startDrag(PointerInputEvent event) { void _startDrag(PointerInputEvent event) {
if (_route != null) if (_avatar != null)
return; // TODO(ianh): once we switch to using gestures, just hand the gesture to the route so it can do everything itself. then we can have multiple drags at the same time. return; // TODO(ianh): once we switch to using gestures, just hand the gesture to the avatar so it can do everything itself. then we can have multiple drags at the same time.
final Point point = new Point(event.x, event.y); final Point point = new Point(event.x, event.y);
Point dragStartPoint; Point dragStartPoint;
switch (config.dragAnchor) { switch (config.dragAnchor) {
...@@ -78,39 +79,38 @@ class _DraggableState extends State<Draggable> { ...@@ -78,39 +79,38 @@ class _DraggableState extends State<Draggable> {
break; break;
} }
assert(dragStartPoint != null); assert(dragStartPoint != null);
_route = new DragRoute( _avatar = new _DragAvatar(
data: config.data, data: config.data,
dragStartPoint: dragStartPoint, dragStartPoint: dragStartPoint,
feedback: config.feedback, feedback: config.feedback,
feedbackOffset: config.feedbackOffset, feedbackOffset: config.feedbackOffset,
onDragFinished: () { onDragFinished: () {
_route = null; _avatar = null;
} }
); );
_route.update(point); _avatar.update(point);
Navigator.of(context).push(_route); _avatar.rebuild(context);
} }
void _updateDrag(PointerInputEvent event) { void _updateDrag(PointerInputEvent event) {
if (_route != null) { if (_avatar != null) {
Navigator.of(context).setState(() { _avatar.update(new Point(event.x, event.y));
_route.update(new Point(event.x, event.y)); _avatar.rebuild(context);
});
} }
} }
void _cancelDrag(PointerInputEvent event) { void _cancelDrag(PointerInputEvent event) {
if (_route != null) { if (_avatar != null) {
Navigator.of(context).popRoute(_route, DragEndKind.canceled); _avatar.finish(_DragEndKind.canceled);
assert(_route == null); assert(_avatar == null);
} }
} }
void _drop(PointerInputEvent event) { void _drop(PointerInputEvent event) {
if (_route != null) { if (_avatar != null) {
_route.update(new Point(event.x, event.y)); _avatar.update(new Point(event.x, event.y));
Navigator.of(context).popRoute(_route, DragEndKind.dropped); _avatar.finish(_DragEndKind.dropped);
assert(_route == null); assert(_avatar == null);
} }
} }
...@@ -187,10 +187,10 @@ class DragTargetState<T> extends State<DragTarget<T>> { ...@@ -187,10 +187,10 @@ class DragTargetState<T> extends State<DragTarget<T>> {
} }
enum DragEndKind { dropped, canceled } enum _DragEndKind { dropped, canceled }
class DragRoute extends Route { class _DragAvatar {
DragRoute({ _DragAvatar({
this.data, this.data,
this.dragStartPoint: Point.origin, this.dragStartPoint: Point.origin,
this.feedback, this.feedback,
...@@ -209,6 +209,7 @@ class DragRoute extends Route { ...@@ -209,6 +209,7 @@ class DragRoute extends Route {
DragTargetState _activeTarget; DragTargetState _activeTarget;
bool _activeTargetWillAcceptDrop = false; bool _activeTargetWillAcceptDrop = false;
Offset _lastOffset; Offset _lastOffset;
OverlayEntry _entry;
void update(Point globalPosition) { void update(Point globalPosition) {
_lastOffset = globalPosition - dragStartPoint; _lastOffset = globalPosition - dragStartPoint;
...@@ -222,6 +223,12 @@ class DragRoute extends Route { ...@@ -222,6 +223,12 @@ class DragRoute extends Route {
_activeTargetWillAcceptDrop = _activeTarget != null && _activeTarget.didEnter(data); _activeTargetWillAcceptDrop = _activeTarget != null && _activeTarget.didEnter(data);
} }
void rebuild(BuildContext context) {
_entry?.remove();
_entry = new OverlayEntry(child: _build(context));
Navigator.of(context).overlay.insert(_entry);
}
DragTargetState _getDragTarget(List<HitTestEntry> path) { DragTargetState _getDragTarget(List<HitTestEntry> path) {
// TODO(abarth): Why do we reverse the path here? // TODO(abarth): Why do we reverse the path here?
for (HitTestEntry entry in path.reversed) { for (HitTestEntry entry in path.reversed) {
...@@ -234,25 +241,21 @@ class DragRoute extends Route { ...@@ -234,25 +241,21 @@ class DragRoute extends Route {
return null; return null;
} }
void didPop([DragEndKind endKind]) { void finish(_DragEndKind endKind) {
if (_activeTarget != null) { if (_activeTarget != null) {
if (endKind == DragEndKind.dropped && _activeTargetWillAcceptDrop) if (endKind == _DragEndKind.dropped && _activeTargetWillAcceptDrop)
_activeTarget.didDrop(data); _activeTarget.didDrop(data);
else else
_activeTarget.didLeave(data); _activeTarget.didLeave(data);
} }
_activeTarget = null; _activeTarget = null;
_activeTargetWillAcceptDrop = false; _activeTargetWillAcceptDrop = false;
_entry.remove();
if (onDragFinished != null) if (onDragFinished != null)
onDragFinished(); onDragFinished();
super.didPop(endKind);
} }
bool get ephemeral => true; Widget _build(BuildContext context) {
bool get modal => false;
bool get opaque => false;
Widget build(RouteArguments args) {
return new Positioned( return new Positioned(
left: _lastOffset.dx, left: _lastOffset.dx,
top: _lastOffset.dy, top: _lastOffset.dy,
......
...@@ -8,7 +8,7 @@ import 'package:flutter/rendering.dart'; ...@@ -8,7 +8,7 @@ import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
import 'heroes.dart'; import 'heroes.dart';
import 'navigator2.dart'; import 'navigator.dart';
import 'overlay.dart'; import 'overlay.dart';
import 'page.dart'; import 'page.dart';
...@@ -69,10 +69,9 @@ class HeroController { ...@@ -69,10 +69,9 @@ class HeroController {
} }
void _addHeroesToOverlay(Iterable<Widget> heroes, OverlayState overlay) { void _addHeroesToOverlay(Iterable<Widget> heroes, OverlayState overlay) {
OverlayEntry insertionPoint = _to.topEntry;
for (Widget hero in heroes) { for (Widget hero in heroes) {
OverlayEntry entry = new OverlayEntry(child: hero); OverlayEntry entry = new OverlayEntry(child: hero);
overlay.insert(entry, above: insertionPoint); overlay.insert(entry);
_overlayEntries.add(entry); _overlayEntries.add(entry);
} }
} }
......
// 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 'basic.dart';
import 'framework.dart';
import 'navigator.dart';
class ModalBarrier extends StatelessComponent {
ModalBarrier({ Key key }) : super(key: key);
Widget build(BuildContext context) {
return new Listener(
onPointerDown: (_) {
Navigator.of(context).pop();
},
child: new Container()
);
}
}
...@@ -2,50 +2,48 @@ ...@@ -2,50 +2,48 @@
// 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 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'focus.dart';
import 'framework.dart'; import 'framework.dart';
import 'heroes.dart'; import 'overlay.dart';
import 'transitions.dart';
import 'gridpaper.dart';
/// Set this to true to overlay a pixel grid on the screen, with every 100
/// pixels marked with a thick maroon line and every 10 pixels marked with a
/// thin maroon line. This can help with verifying widget positions.
bool debugShowGrid = false;
Color debugGridColor = const Color(0x7F7F2020);
const String kDefaultRouteName = '/';
// ---------------- Begin scaffolding for Navigator1 to Navigator2 transition
class RouteArguments { class RouteArguments {
const RouteArguments({ this.context, this.previousPerformance, this.nextPerformance }); const RouteArguments({ this.context });
final BuildContext context; final BuildContext context;
final PerformanceView previousPerformance;
final PerformanceView nextPerformance;
} }
typedef Widget RouteBuilder(RouteArguments args); typedef Widget RouteBuilder(RouteArguments args);
typedef RouteBuilder RouteGenerator(String name); typedef RouteBuilder RouteGenerator(String name);
typedef void StateRouteCallback(StateRoute route); // ---------------- End scaffolding for Navigator1 to Navigator2 transition
abstract class Route {
List<OverlayEntry> get overlayEntries;
void didPush(OverlayState overlay, OverlayEntry insertionPoint);
void didMakeCurrent();
void didPop(dynamic result);
}
class NamedRouteSettings {
const NamedRouteSettings({ this.name: '<anonymous>', this.mostValuableKeys });
final String name;
final Set<Key> mostValuableKeys;
}
typedef Route RouteFactory(NamedRouteSettings settings);
class Navigator extends StatefulComponent { class Navigator extends StatefulComponent {
Navigator({ Navigator({
Key key, Key key,
this.routes, this.onGenerateRoute,
this.onGenerateRoute, // you need to implement this if you pushNamed() to names that might not be in routes. this.onUnknownRoute
this.onUnknownRoute // 404 generator. You only need to implement this if you have a way to navigate to arbitrary names.
}) : super(key: key) { }) : super(key: key) {
// To use a navigator, you must at a minimum define the route with the name '/'. assert(onGenerateRoute != null);
assert(routes != null);
assert(routes.containsKey(kDefaultRouteName));
} }
final Map<String, RouteBuilder> routes; final RouteFactory onGenerateRoute;
final RouteGenerator onGenerateRoute; final RouteFactory onUnknownRoute;
final RouteBuilder onUnknownRoute;
static const String defaultRouteName = '/';
static NavigatorState of(BuildContext context) { static NavigatorState of(BuildContext context) {
NavigatorState result; NavigatorState result;
...@@ -62,656 +60,75 @@ class Navigator extends StatefulComponent { ...@@ -62,656 +60,75 @@ class Navigator extends StatefulComponent {
NavigatorState createState() => new NavigatorState(); NavigatorState createState() => new NavigatorState();
} }
// The navigator tracks which "page" we are on.
// It also animates between these pages.
// Pages can have "heroes", which are UI elements that animate from point to point.
// These animations are called journeys.
//
// Journeys can start in two conditions:
// - Everything is calm, and we have no heroes in flight. In this case, we will
// have to collect the heroes from the route we're starting at and the route
// we're going to, and try to transition from one set to the other.
// - We already have heroes in flight. In that case, we just want to look at
// the heroes of our destination, and then try to transition to them from the
// in-flight heroes.
class _HeroTransitionInstruction {
Route from;
Route to;
void update(Route newFrom, Route newTo) {
assert(newFrom != null);
assert(newTo != null);
if (!newFrom.canHaveHeroes || !newTo.canHaveHeroes)
return;
assert(newFrom.performance != null);
assert(newTo.performance != null);
if (from == null)
from = newFrom;
to = newTo;
if (from == to)
reset();
}
void reset() {
assert(hasInstructions);
from = null;
to = null;
}
bool get hasInstructions => from != null || to != null;
}
class NavigatorState extends State<Navigator> { class NavigatorState extends State<Navigator> {
final GlobalKey<OverlayState> _overlayKey = new GlobalKey<OverlayState>();
List<Route> _history = new List<Route>(); final List<Route> _ephemeral = new List<Route>();
int _currentPosition = 0; // which route is "current" final List<Route> _modal = new List<Route>();
Route get currentRoute => _history[_currentPosition];
bool get hasPreviousRoute => _history.length > 1;
void initState() { void initState() {
super.initState(); super.initState();
_activeHeroes = new HeroParty(onQuestFinished: _handleHeroQuestFinished); push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName)));
PageRoute route = new PageRoute(config.routes[kDefaultRouteName], name: kDefaultRouteName);
assert(route.hasContent);
assert(!route.ephemeral);
_insertRoute(route);
} }
void pushState(State owner, StateRouteCallback onPop) { bool get hasPreviousRoute => _modal.length > 1;
push(new StateRoute( OverlayState get overlay => _overlayKey.currentState;
route: currentRoute,
owner: owner,
onPop: onPop
));
}
void pushNamed(String name, { Set<Key> mostValuableKeys }) { OverlayEntry get _currentOverlay {
RouteBuilder generateRoute() { for (Route route in _ephemeral.reversed) {
assert(config.onGenerateRoute != null); if (route.overlayEntries.isNotEmpty)
return config.onGenerateRoute(name); return route.overlayEntries.last;
} }
final RouteBuilder builder = config.routes[name] ?? generateRoute() ?? config.onUnknownRoute; for (Route route in _modal.reversed) {
assert(builder != null); // 404 getting your 404! if (route.overlayEntries.isNotEmpty)
push(new PageRoute(builder, name: name, mostValuableKeys: mostValuableKeys)); return route.overlayEntries.last;
}
final _HeroTransitionInstruction _desiredHeroes = new _HeroTransitionInstruction();
HeroParty _activeHeroes;
void _handleHeroQuestFinished() {
for (Route route in _history)
route._hasActiveHeroes = false;
}
void push(Route route) {
assert(!_debugCurrentlyHaveRoute(route));
setState(() {
// pop ephemeral routes (like popup menus)
while (currentRoute.ephemeral) {
currentRoute.didPop(null);
_currentPosition -= 1;
}
// find the most recent active route that might have heroes
if (route.hasContent) {
int index = _currentPosition;
while (index > 0 && !_history[index].hasContent)
index -= 1;
assert(_history[index].hasContent);
_desiredHeroes.update(_history[index], route);
}
// add the new route
_currentPosition += 1;
_insertRoute(route);
});
}
void popRoute(Route route, [dynamic result]) {
assert(_debugCurrentlyHaveRoute(route));
assert(_currentPosition > 0);
setState(() {
// pop any routes above this one (they must be ephemeral, otherwise there's an error)
while (currentRoute != route) {
assert(currentRoute.ephemeral);
currentRoute.didPop(null);
_currentPosition -= 1;
}
});
pop(result);
assert(!_debugCurrentlyHaveRoute(route));
}
void pop([dynamic result]) {
setState(() {
assert(_currentPosition > 0);
// find the most recent previous route that might have heroes
if (currentRoute.hasContent) {
int index = _currentPosition - 1;
while (index > 0 && !_history[index].hasContent)
index -= 1;
assert(_history[index].hasContent);
_desiredHeroes.update(currentRoute, _history[index]);
}
// pop the route
currentRoute.didPop(result);
_currentPosition -= 1;
});
}
bool _debugCurrentlyHaveRoute(Route route) {
int index = _history.indexOf(route);
return index >= 0 && index <= _currentPosition;
}
void _didCompleteRoute(Route route) {
assert(_history.contains(route));
if (route.isActuallyOpaque) {
setState(() {
// we need to rebuild because our build function depends on
// whether the route is opaque or not.
});
} }
return null;
} }
void _didDismissRoute(Route route) { Route get currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.last;
assert(_history.contains(route));
if (_history.lastIndexOf(route) <= _currentPosition)
popRoute(route);
}
void _insertRoute(Route route) { Route _removeCurrentRoute() {
_history.insert(_currentPosition, route); return _ephemeral.isNotEmpty ? _ephemeral.removeLast() : _modal.removeLast();
route.didPush(this);
} }
void _removeRoute(Route route) { void pushNamed(String name, { Set<Key> mostValuableKeys }) {
assert(_history.contains(route)); NamedRouteSettings settings = new NamedRouteSettings(
setState(() { name: name,
if (_desiredHeroes.hasInstructions) { mostValuableKeys: mostValuableKeys
if (_desiredHeroes.from == route || _desiredHeroes.to == route)
_desiredHeroes.reset();
}
_history.remove(route);
});
}
PerformanceView _currentHeroPerformance;
Widget build(BuildContext context) {
List<Widget> visibleRoutes = <Widget>[];
assert(() {
if (debugShowGrid)
visibleRoutes.add(new GridPaper(color: debugGridColor));
return true;
});
bool alreadyInsertedHeroes = false;
bool alreadyInsertedModalBarrier = false;
Route nextContentRoute;
PerformanceView nextHeroPerformance;
for (int i = _history.length-1; i >= 0; i -= 1) {
final Route route = _history[i];
if (!route.hasContent) {
assert(!route.modal);
assert(!_desiredHeroes.hasInstructions || (_desiredHeroes.from != route && _desiredHeroes.to != route));
assert(!route._hasActiveHeroes);
continue;
}
if (route._hasActiveHeroes && !alreadyInsertedHeroes) {
visibleRoutes.addAll(_activeHeroes.getWidgets(context, _currentHeroPerformance));
alreadyInsertedHeroes = true;
}
if (_desiredHeroes.hasInstructions) {
if ((_desiredHeroes.to == route || _desiredHeroes.from == route) && nextHeroPerformance == null)
nextHeroPerformance = route.performance;
visibleRoutes.add(new _RouteWidget(route: route, nextRoute: nextContentRoute, buildTargetHeroes: _desiredHeroes.to == route));
} else {
visibleRoutes.add(new _RouteWidget(route: route, nextRoute: nextContentRoute));
}
if (route.isActuallyOpaque) {
assert(!_desiredHeroes.hasInstructions ||
(_history.indexOf(_desiredHeroes.from) >= i && _history.indexOf(_desiredHeroes.to) >= i));
break;
}
assert(route.modal || route.ephemeral);
if (route.modal && i > 0 && !alreadyInsertedModalBarrier) {
visibleRoutes.add(new Listener(
onPointerDown: (_) { pop(); },
child: new Container()
));
alreadyInsertedModalBarrier = true;
}
nextContentRoute = route;
}
if (_desiredHeroes.hasInstructions) {
assert(nextHeroPerformance != null);
scheduler.requestPostFrameCallback((Duration timestamp) {
Map<Object, HeroHandle> heroesFrom;
Map<Object, HeroHandle> heroesTo;
Set<Key> mostValuableKeys = new Set<Key>();
if (_desiredHeroes.from.mostValuableKeys != null)
mostValuableKeys.addAll(_desiredHeroes.from.mostValuableKeys);
if (_desiredHeroes.to.mostValuableKeys != null)
mostValuableKeys.addAll(_desiredHeroes.to.mostValuableKeys);
if (_activeHeroes.isEmpty) {
assert(!_desiredHeroes.from._hasActiveHeroes);
heroesFrom = _desiredHeroes.from.getHeroesToAnimate(mostValuableKeys);
_desiredHeroes.from._hasActiveHeroes = heroesFrom.length > 0;
} else {
assert(_desiredHeroes.from._hasActiveHeroes);
heroesFrom = _activeHeroes.getHeroesToAnimate();
}
heroesTo = _desiredHeroes.to.getHeroesToAnimate(mostValuableKeys);
_desiredHeroes.to._hasActiveHeroes = heroesTo.length > 0;
_desiredHeroes.reset();
setState(() {
final RenderBox renderObject = context.findRenderObject();
final Point animationTopLeft = renderObject.localToGlobal(Point.origin);
final Point animationBottomRight = renderObject.localToGlobal(renderObject.size.bottomRight(Point.origin));
final Rect animationArea = new Rect.fromLTRB(animationTopLeft.x, animationTopLeft.y, animationBottomRight.x, animationBottomRight.y);
Curve curve = Curves.ease;
if (nextHeroPerformance.status == PerformanceStatus.reverse) {
nextHeroPerformance = new ReversePerformance(nextHeroPerformance);
curve = new Interval(nextHeroPerformance.progress, 1.0, curve: curve);
}
_activeHeroes.animate(heroesFrom, heroesTo, animationArea, curve);
_currentHeroPerformance = nextHeroPerformance;
});
});
}
return new Focus(child: new Stack(visibleRoutes.reversed.toList()));
}
}
class _RouteWidget extends StatefulComponent {
_RouteWidget({
Route route,
this.nextRoute,
this.buildTargetHeroes: false
}) : route = route,
super(key: new ObjectKey(route)) {
assert(route != null);
}
final Route route;
final Route nextRoute;
final bool buildTargetHeroes;
_RouteWidgetState createState() => new _RouteWidgetState();
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (route.performance != null)
description.add('${route.performance}');
else
description.add('${route.debugLabel}');
if (buildTargetHeroes)
description.add('building target heroes this frame');
}
}
class _RouteWidgetState extends State<_RouteWidget> {
void initState() {
super.initState();
config.route._widgetState = this;
}
void dispose() {
config.route._widgetState = null;
super.dispose();
}
Widget build(BuildContext context) {
return config.route._internalBuild(context, config.nextRoute, buildTargetHeroes: config.buildTargetHeroes);
}
}
class _StorageEntryIdentifier {
Type clientType;
List<Key> keys;
void addKey(Key key) {
assert(key != null);
assert(key is! GlobalKey);
keys ??= <Key>[];
keys.add(key);
}
GlobalKey scopeKey;
bool operator ==(dynamic other) {
if (other is! _StorageEntryIdentifier)
return false;
final _StorageEntryIdentifier typedOther = other;
if (clientType != typedOther.clientType ||
scopeKey != typedOther.scopeKey ||
keys?.length != typedOther.keys?.length)
return false;
if (keys != null) {
for (int index = 0; index < keys.length; index += 1) {
if (keys[index] != typedOther.keys[index])
return false;
}
}
return true;
}
int get hashCode {
int value = 373;
value = 37 * value + clientType.hashCode;
value = 37 * value + scopeKey.hashCode;
if (keys != null) {
for (Key key in keys)
value = 37 * value + key.hashCode;
}
return value;
}
}
abstract class Route {
Route() {
_subtreeKey = new GlobalKey(label: debugLabel);
}
/// If hasContent is true, then the route represents some on-screen state.
///
/// If hasContent is false, then no performance will be created, and the values of
/// ephemeral, modal, and opaque are ignored. This is useful if the route
/// represents some state handled by another widget. See
/// NavigatorState.pushState().
///
/// Set hasContent to false if you have nothing useful to return from build().
///
/// modal must be false if hasContent is false, since otherwise any
/// interaction with the system at all would imply that the current route is
/// popped, which would be pointless.
bool get hasContent => true;
/// If ephemeral is true, then to explicitly pop the route you have to use
/// navigator.popRoute() with a reference to this route. navigator.pop()
/// automatically pops all ephemeral routes before popping the current
/// top-most non-ephemeral route.
///
/// If ephemeral is false, then the route can be popped with navigator.pop().
///
/// Set ephemeral to true if you want to be automatically popped when another
/// route is pushed or popped.
///
/// modal must be true if ephemeral is false.
bool get ephemeral => false;
/// If modal is true, a hidden layer is inserted in the widget tree that
/// catches all touches to widgets created by routes below this one, even if
/// this one is transparent.
///
/// If modal is false, then earlier routes can be interacted with, including
/// causing new routes to be pushed and/or this route (and maybe others) to be
/// popped.
///
/// ephemeral must be true if modal is false.
/// hasContent must be true if modal is true.
bool get modal => true;
/// If opaque is true, then routes below this one will not be built or painted
/// when the transition to this route is complete.
///
/// If opaque is false, then the previous route will always be painted even if
/// this route's transition is complete.
///
/// Set this to true if there's no reason to build and paint the route behind
/// you when your transition is finished, and set it to false if you do not
/// cover the entire application surface or are in any way semi-transparent.
bool get opaque => false;
PerformanceView get performance => null;
bool get isActuallyOpaque => (performance == null || performance.isCompleted) && opaque;
NavigatorState get navigator => _navigator;
NavigatorState _navigator;
_RouteWidgetState _widgetState;
void setState(void fn()) {
if (_widgetState != null)
_widgetState.setState(fn);
else
fn();
}
void didPush(NavigatorState navigator) {
assert(_navigator == null);
_navigator = navigator;
assert(_navigator != null);
performance?.addStatusListener(_handlePerformanceStatusChanged);
}
void didPop([dynamic result]) {
assert(navigator != null);
if (performance == null)
navigator._removeRoute(this);
}
void _handlePerformanceStatusChanged(PerformanceStatus status) {
if (status == PerformanceStatus.completed) {
navigator._didCompleteRoute(this);
} else if (status == PerformanceStatus.dismissed) {
navigator._didDismissRoute(this);
navigator._removeRoute(this);
_navigator = null;
}
}
/// Called (indirectly, via a RouteWidget) by the navigator.build()
/// function if hasContent is true, to get the subtree for this
/// route.
///
/// If buildTargetHeroes is true, then getHeroesToAnimate() will be called
/// after this build, before the next build, and this build should render the
/// route off-screen, at the end of its animation. Next frame, the argument
/// will be false, and the tree should be built at the first frame of the
/// transition animation, whatever that is.
Widget _internalBuild(BuildContext context, Route nextRoute, { bool buildTargetHeroes: false }) {
assert(navigator != null);
assert(_widgetState != null);
assert(hasContent);
return keySubtree(build(new RouteArguments(
context: context,
previousPerformance: performance,
nextPerformance: nextRoute?.performance
)));
}
bool get canHaveHeroes => hasContent && modal && opaque;
Set<Key> get mostValuableKeys => null;
/// Return a party of heroes (one per tag) to animate. This is called by the
/// navigator when hasContent is true just after this route, the previous
/// route, or the next route, has been pushed or popped, to figure out which
/// heroes it should be trying to animate.
Map<Object, HeroHandle> getHeroesToAnimate([Set<Key> mostValuableKeys]) => const <Object, HeroHandle>{};
bool _hasActiveHeroes = false;
GlobalKey _subtreeKey;
/// Returns the BuildContext for the root of the subtree built for this route,
/// assuming that internalBuild used keySubtree to build that subtree.
/// This is only valid after a build phase.
BuildContext get subtreeContext => _subtreeKey.currentContext;
/// Wraps the given subtree in a route-specific GlobalKey.
Widget keySubtree(Widget child) {
return new KeyedSubtree(
key: _subtreeKey,
child: child
); );
push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings));
} }
/// Called by internalBuild. This is the method to override if you want to void push(Route route) {
/// change what subtree is built for this route. _popAllEphemeralRoutes();
Widget build(RouteArguments args); route.didPush(overlay, _currentOverlay);
_modal.add(route);
static Route of(BuildContext context) { route.didMakeCurrent();
Route result;
context.visitAncestorElements((Element element) {
if (element is StatefulComponentElement && element.state is _RouteWidgetState) {
result = element.widget.route;
return false;
}
return true;
});
return result;
}
_StorageEntryIdentifier _computeStorageIdentifier(BuildContext context) {
_StorageEntryIdentifier result = new _StorageEntryIdentifier();
result.clientType = context.widget.runtimeType;
Key lastKey = context.widget.key;
if (lastKey is! GlobalKey) {
context.visitAncestorElements((Element element) {
if (element.widget.key is GlobalKey) {
lastKey = element.widget.key;
return false;
} else if (element.widget is Navigator) {
// Not quite everyone who is in a Navigator actually is in a Route.
// For example, the modal barrier.
StatefulComponentElement statefulElement = element;
lastKey = new GlobalObjectKey(statefulElement.state);
return false;
} else if (element.widget.key != null) {
result.addKey(element.widget.key);
}
return true;
});
return result;
}
assert(lastKey is GlobalKey);
result.scopeKey = lastKey;
return result;
}
Map<_StorageEntryIdentifier, dynamic> _storage;
void writeState(BuildContext context, dynamic data) {
_storage ??= <_StorageEntryIdentifier, dynamic>{};
_storage[_computeStorageIdentifier(context)] = data;
}
dynamic readState(BuildContext context) => _storage != null ? _storage[_computeStorageIdentifier(context)] : null;
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $performance; key: $_subtreeKey)';
}
abstract class PerformanceRoute extends Route {
PerformanceRoute() {
_performance = createPerformance();
}
PerformanceView get performance => _performance?.view;
Performance _performance;
Performance createPerformance() {
Duration duration = transitionDuration;
assert(duration != null && duration >= Duration.ZERO);
return new Performance(duration: duration, debugLabel: debugLabel);
}
Duration get transitionDuration;
Widget _internalBuild(BuildContext context, Route nextRoute, { bool buildTargetHeroes: false }) {
assert(hasContent);
assert(transitionDuration > Duration.ZERO);
if (buildTargetHeroes && performance.progress != 1.0) {
Performance fakePerformance = createPerformance();
assert(fakePerformance != null);
fakePerformance.progress = 1.0;
return new OffStage(
child: keySubtree(
build(new RouteArguments(context: context, previousPerformance: fakePerformance))
)
);
}
return super._internalBuild(context, nextRoute, buildTargetHeroes: buildTargetHeroes);
} }
void didPush(NavigatorState navigator) { void pushEphemeral(Route route) {
super.didPush(navigator); route.didPush(overlay, _currentOverlay);
_performance?.forward(); _ephemeral.add(route);
route.didMakeCurrent();
} }
void didPop([dynamic result]) { void _popAllEphemeralRoutes() {
_performance?.reverse(); List<Route> localEphemeral = new List<Route>.from(_ephemeral);
super.didPop(result); _ephemeral.clear();
for (Route route in localEphemeral)
route.didPop(null);
assert(_ephemeral.isEmpty);
} }
}
const Duration _kTransitionDuration = const Duration(milliseconds: 150); void pop([dynamic result]) {
const Point _kTransitionStartPoint = const Point(0.0, 75.0); _removeCurrentRoute().didPop(result);
currentRoute.didMakeCurrent();
/// A route that represents a page in an application.
///
/// PageRoutes try to animate between themselves in a fashion that is aware of
/// any Heroes.
class PageRoute extends PerformanceRoute {
PageRoute(this._builder, {
this.name: '<anonymous>',
Set<Key> mostValuableKeys
}) : _mostValuableKeys = mostValuableKeys {
assert(_builder != null);
}
final RouteBuilder _builder;
final String name;
final Set<Key> _mostValuableKeys;
Set<Key> get mostValuableKeys => _mostValuableKeys;
bool get opaque => true;
Duration get transitionDuration => _kTransitionDuration;
Map<Object, HeroHandle> getHeroesToAnimate([Set<Key> mostValuableKeys]) {
return Hero.of(subtreeContext, mostValuableKeys);
} }
Widget build(RouteArguments args) { Widget build(BuildContext context) {
// TODO(jackson): Hit testing should ignore transform return new Overlay(
// TODO(jackson): Block input unless content is interactive key: _overlayKey,
// TODO(ianh): Support having different transitions, e.g. when heroes are around. initialEntries: _modal.first.overlayEntries
return new SlideTransition(
performance: args.previousPerformance,
position: new AnimatedValue<Point>(_kTransitionStartPoint, end: Point.origin, curve: Curves.easeOut),
child: new FadeTransition(
performance: args.previousPerformance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut),
child: invokeBuilder(args)
)
); );
} }
Widget invokeBuilder(RouteArguments args) {
Widget result = _builder(args);
assert(() {
if (result == null)
debugPrint('The builder for route \'$name\' returned null. RouteBuilders must never return null.');
assert(result != null && 'A RouteBuilder returned null. See the previous log message for details.' is String);
return true;
});
return result;
}
String get debugLabel => '${super.debugLabel}($name)';
}
class StateRoute extends Route {
StateRoute({ this.route, this.owner, this.onPop });
Route route;
State owner;
StateRouteCallback onPop;
bool get hasContent => false;
bool get modal => false;
bool get opaque => false;
void didPop([dynamic result]) {
assert(result == null);
if (onPop != null)
onPop(this);
super.didPop(result);
}
Widget build(RouteArguments args) => null;
} }
// 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 'framework.dart';
import 'overlay.dart';
abstract class Route {
List<Widget> createWidgets() => const <Widget>[];
OverlayEntry get topEntry => _entries.isNotEmpty ? _entries.last : null;
OverlayEntry get bottomEntry => _entries.isNotEmpty ? _entries.first : null;
final List<OverlayEntry> _entries = new List<OverlayEntry>();
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
List<Widget> widgets = createWidgets();
for (Widget widget in widgets) {
_entries.add(new OverlayEntry(child: widget));
overlay?.insert(_entries.last, above: insertionPoint);
insertionPoint = _entries.last;
}
}
void didMakeCurrent() { }
void didPop(dynamic result) {
for (OverlayEntry entry in _entries)
entry.remove();
}
}
class NamedRouteSettings {
const NamedRouteSettings({ this.name: '<anonymous>', this.mostValuableKeys });
final String name;
final Set<Key> mostValuableKeys;
}
typedef Route RouteFactory(NamedRouteSettings settings);
class Navigator extends StatefulComponent {
Navigator({
Key key,
this.onGenerateRoute,
this.onUnknownRoute
}) : super(key: key) {
assert(onGenerateRoute != null);
}
final RouteFactory onGenerateRoute;
final RouteFactory onUnknownRoute;
static const String defaultRouteName = '/';
static NavigatorState of(BuildContext context) {
NavigatorState result;
context.visitAncestorElements((Element element) {
if (element is StatefulComponentElement && element.state is NavigatorState) {
result = element.state;
return false;
}
return true;
});
return result;
}
NavigatorState createState() => new NavigatorState();
}
class NavigatorState extends State<Navigator> {
final GlobalKey<OverlayState> _overlayKey = new GlobalKey<OverlayState>();
final List<Route> _ephemeral = new List<Route>();
final List<Route> _modal = new List<Route>();
void initState() {
super.initState();
push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName)));
}
bool get hasPreviousRoute => _modal.length > 1;
OverlayState get overlay => _overlayKey.currentState;
OverlayEntry get _currentOverlay {
for (Route route in _ephemeral.reversed) {
if (route.topEntry != null)
return route.topEntry;
}
for (Route route in _modal.reversed) {
if (route.topEntry != null)
return route.topEntry;
}
return null;
}
Route get _currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.last;
Route _removeCurrentRoute() {
return _ephemeral.isNotEmpty ? _ephemeral.removeLast() : _modal.removeLast();
}
void pushNamed(String name, { Set<Key> mostValuableKeys }) {
NamedRouteSettings settings = new NamedRouteSettings(
name: name,
mostValuableKeys: mostValuableKeys
);
push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings));
}
void push(Route route) {
_popAllEphemeralRoutes();
route.didPush(overlay, _currentOverlay);
_modal.add(route);
route.didMakeCurrent();
}
void pushEphemeral(Route route) {
route.didPush(overlay, _currentOverlay);
_ephemeral.add(route);
route.didMakeCurrent();
}
void _popAllEphemeralRoutes() {
List<Route> localEphemeral = new List<Route>.from(_ephemeral);
_ephemeral.clear();
for (Route route in localEphemeral)
route.didPop(null);
assert(_ephemeral.isEmpty);
}
void pop([dynamic result]) {
_removeCurrentRoute().didPop(result);
_currentRoute.didMakeCurrent();
}
Widget build(BuildContext context) {
return new Overlay(
key: _overlayKey,
initialEntries: _modal.first._entries
);
}
}
...@@ -16,7 +16,7 @@ class OverlayEntry { ...@@ -16,7 +16,7 @@ class OverlayEntry {
bool get opaque => _opaque; bool get opaque => _opaque;
bool _opaque; bool _opaque;
void set opaque(bool value) { void set opaque(bool value) {
if (_opaque = value) if (_opaque == value)
return; return;
_opaque = value; _opaque = value;
_state?.setState(() {}); _state?.setState(() {});
......
...@@ -6,59 +6,11 @@ import 'package:flutter/animation.dart'; ...@@ -6,59 +6,11 @@ import 'package:flutter/animation.dart';
import 'basic.dart'; import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
import 'navigator2.dart'; import 'navigator.dart';
import 'overlay.dart';
import 'page_storage.dart'; import 'page_storage.dart';
import 'routes.dart';
import 'transitions.dart'; import 'transitions.dart';
// TODO(abarth): Should we add a type for the result?
abstract class TransitionRoute extends Route {
bool get opaque => true;
PerformanceView get performance => _performance?.view;
Performance _performance;
Duration get transitionDuration;
Performance createPerformance() {
Duration duration = transitionDuration;
assert(duration != null && duration >= Duration.ZERO);
return new Performance(duration: duration, debugLabel: debugLabel);
}
dynamic _result;
void _handleStatusChanged(PerformanceStatus status) {
switch (status) {
case PerformanceStatus.completed:
bottomEntry.opaque = opaque;
break;
case PerformanceStatus.forward:
case PerformanceStatus.reverse:
bottomEntry.opaque = false;
break;
case PerformanceStatus.dismissed:
super.didPop(_result);
break;
}
}
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
_performance = createPerformance()
..addStatusListener(_handleStatusChanged)
..forward();
super.didPush(overlay, insertionPoint);
}
void didPop(dynamic result) {
_result = result;
_performance.reverse();
}
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $_performance)';
}
class _Page extends StatefulComponent { class _Page extends StatefulComponent {
_Page({ _Page({
Key key, Key key,
...@@ -130,6 +82,8 @@ class PageRoute extends TransitionRoute { ...@@ -130,6 +82,8 @@ class PageRoute extends TransitionRoute {
final GlobalKey<_PageState> pageKey = new GlobalKey<_PageState>(); final GlobalKey<_PageState> pageKey = new GlobalKey<_PageState>();
bool get opaque => true;
String get name => settings.name; String get name => settings.name;
Duration get transitionDuration => const Duration(milliseconds: 150); Duration get transitionDuration => const Duration(milliseconds: 150);
......
// 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 'package:flutter/animation.dart';
import 'basic.dart';
import 'framework.dart';
import 'navigator.dart';
import 'overlay.dart';
class StateRoute extends Route {
StateRoute({ this.onPop });
final VoidCallback onPop;
List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
void didPush(OverlayState overlay, OverlayEntry insertionPoint) { }
void didMakeCurrent() { }
void didPop(dynamic result) {
if (onPop != null)
onPop();
}
}
class OverlayRoute extends Route {
List<Widget> createWidgets() => const <Widget>[];
List<OverlayEntry> get overlayEntries => _overlayEntries;
final List<OverlayEntry> _overlayEntries = new List<OverlayEntry>();
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
List<Widget> widgets = createWidgets();
for (Widget widget in widgets) {
_overlayEntries.add(new OverlayEntry(child: widget));
overlay?.insert(_overlayEntries.last, above: insertionPoint);
insertionPoint = _overlayEntries.last;
}
}
void didMakeCurrent() { }
void didPop(dynamic result) {
for (OverlayEntry entry in _overlayEntries)
entry.remove();
_overlayEntries.clear();
}
}
// TODO(abarth): Should we add a type for the result?
abstract class TransitionRoute extends OverlayRoute {
Duration get transitionDuration;
bool get opaque;
PerformanceView get performance => _performance?.view;
Performance _performance;
Performance createPerformance() {
Duration duration = transitionDuration;
assert(duration != null && duration >= Duration.ZERO);
return new Performance(duration: duration, debugLabel: debugLabel);
}
dynamic _result;
void _handleStatusChanged(PerformanceStatus status) {
switch (status) {
case PerformanceStatus.completed:
if (overlayEntries.isNotEmpty)
overlayEntries.first.opaque = opaque;
break;
case PerformanceStatus.forward:
case PerformanceStatus.reverse:
if (overlayEntries.isNotEmpty)
overlayEntries.first.opaque = false;
break;
case PerformanceStatus.dismissed:
super.didPop(_result);
break;
}
}
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
_performance = createPerformance()
..addStatusListener(_handleStatusChanged)
..forward();
super.didPush(overlay, insertionPoint);
}
void didPop(dynamic result) {
_result = result;
_performance.reverse();
}
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $_performance)';
}
...@@ -16,7 +16,7 @@ import 'framework.dart'; ...@@ -16,7 +16,7 @@ import 'framework.dart';
import 'gesture_detector.dart'; import 'gesture_detector.dart';
import 'homogeneous_viewport.dart'; import 'homogeneous_viewport.dart';
import 'mixed_viewport.dart'; import 'mixed_viewport.dart';
import 'navigator.dart'; import 'page_storage.dart';
// 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
const double _kMillisecondsPerSecond = 1000.0; const double _kMillisecondsPerSecond = 1000.0;
...@@ -56,7 +56,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -56,7 +56,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
void initState() { void initState() {
super.initState(); super.initState();
_animation = new SimulationStepper(_setScrollOffset); _animation = new SimulationStepper(_setScrollOffset);
_scrollOffset = Route.of(context)?.readState(context) ?? config.initialScrollOffset ?? 0.0; _scrollOffset = PageStorage.of(context)?.readState(context) ?? config.initialScrollOffset ?? 0.0;
} }
SimulationStepper _animation; SimulationStepper _animation;
...@@ -178,7 +178,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -178,7 +178,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
setState(() { setState(() {
_scrollOffset = newScrollOffset; _scrollOffset = newScrollOffset;
}); });
Route.of(context)?.writeState(context, _scrollOffset); PageStorage.of(context)?.writeState(context, _scrollOffset);
dispatchOnScroll(); dispatchOnScroll();
} }
......
...@@ -16,13 +16,18 @@ export 'src/widgets/focus.dart'; ...@@ -16,13 +16,18 @@ export 'src/widgets/focus.dart';
export 'src/widgets/framework.dart'; export 'src/widgets/framework.dart';
export 'src/widgets/gesture_detector.dart'; export 'src/widgets/gesture_detector.dart';
export 'src/widgets/gridpaper.dart'; export 'src/widgets/gridpaper.dart';
export 'src/widgets/hero_controller.dart';
export 'src/widgets/heroes.dart'; export 'src/widgets/heroes.dart';
export 'src/widgets/homogeneous_viewport.dart'; export 'src/widgets/homogeneous_viewport.dart';
export 'src/widgets/media_query.dart'; export 'src/widgets/media_query.dart';
export 'src/widgets/mimic.dart'; export 'src/widgets/mimic.dart';
export 'src/widgets/mixed_viewport.dart'; export 'src/widgets/mixed_viewport.dart';
export 'src/widgets/modal_barrier.dart';
export 'src/widgets/navigator.dart'; export 'src/widgets/navigator.dart';
export 'src/widgets/page_storage.dart';
export 'src/widgets/page.dart';
export 'src/widgets/placeholder.dart'; export 'src/widgets/placeholder.dart';
export 'src/widgets/routes.dart';
export 'src/widgets/scrollable.dart'; export 'src/widgets/scrollable.dart';
export 'src/widgets/statistics_overlay.dart'; export 'src/widgets/statistics_overlay.dart';
export 'src/widgets/transitions.dart'; export 'src/widgets/transitions.dart';
......
import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../engine/mock_events.dart'; import '../engine/mock_events.dart';
...@@ -11,7 +11,7 @@ void main() { ...@@ -11,7 +11,7 @@ void main() {
List accepted = []; List accepted = [];
tester.pumpWidget(new Navigator( tester.pumpWidget(new MaterialApp(
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) { return new Column(<Widget>[ '/': (RouteArguments args) { return new Column(<Widget>[
new Draggable( new Draggable(
......
import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'widget_tester.dart'; import 'widget_tester.dart';
...@@ -45,7 +45,7 @@ void main() { ...@@ -45,7 +45,7 @@ void main() {
'/second': (RouteArguments args) => new SecondComponent(), '/second': (RouteArguments args) => new SecondComponent(),
}; };
tester.pumpWidget(new Navigator(routes: routes)); tester.pumpWidget(new MaterialApp(routes: routes));
expect(tester.findText('X'), isNotNull); expect(tester.findText('X'), isNotNull);
expect(tester.findText('Y'), isNull); expect(tester.findText('Y'), isNull);
......
...@@ -34,9 +34,12 @@ void main() { ...@@ -34,9 +34,12 @@ void main() {
GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>(); GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
tester.pumpWidget(new Navigator( tester.pumpWidget(new Navigator(
key: navigatorKey, key: navigatorKey,
routes: <String, RouteBuilder>{ onGenerateRoute: (NamedRouteSettings settings) {
'/': (RouteArguments args) => new Container(child: new ThePositiveNumbers()), if (settings.name == '/')
'/second': (RouteArguments args) => new Container(child: new ThePositiveNumbers()), return new PageRoute(builder: (_) => new Container(child: new ThePositiveNumbers()));
else if (settings.name == '/second')
return new PageRoute(builder: (_) => new Container(child: new ThePositiveNumbers()));
return null;
} }
)); ));
...@@ -105,7 +108,7 @@ void main() { ...@@ -105,7 +108,7 @@ void main() {
expect(tester.findText('15'), isNotNull); expect(tester.findText('15'), isNotNull);
expect(tester.findText('16'), isNull); expect(tester.findText('16'), isNull);
expect(tester.findText('100'), isNull); expect(tester.findText('100'), isNull);
}); });
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment