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> {
String _searchQuery;
void _handleSearchBegin() {
Navigator.of(context).pushState(this, (_) {
setState(() {
_isSearching = false;
_searchQuery = null;
});
});
Navigator.of(context).push(new StateRoute(
onPop: () {
setState(() {
_isSearching = false;
_searchQuery = null;
});
}
));
setState(() {
_isSearching = true;
});
}
void _handleSearchEnd() {
assert(() {
final StateRoute currentRoute = Navigator.of(context).currentRoute;
return currentRoute.owner == this;
});
Navigator.of(context).pop();
}
......
......@@ -4,8 +4,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/navigator2.dart' as n2;
class Home extends StatelessComponent {
Widget build(BuildContext context) {
return new Container(
......@@ -15,11 +13,11 @@ class Home extends StatelessComponent {
new Text("You are at home"),
new RaisedButton(
child: new Text('GO SHOPPING'),
onPressed: () => n2.Navigator.of(context).pushNamed('/shopping')
onPressed: () => Navigator.of(context).pushNamed('/shopping')
),
new RaisedButton(
child: new Text('START ADVENTURE'),
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure')
onPressed: () => Navigator.of(context).pushNamed('/adventure')
)],
justifyContent: FlexJustifyContent.center
)
......@@ -36,11 +34,11 @@ class Shopping extends StatelessComponent {
new Text("Village Shop"),
new RaisedButton(
child: new Text('RETURN HOME'),
onPressed: () => n2.Navigator.of(context).pop()
onPressed: () => Navigator.of(context).pop()
),
new RaisedButton(
child: new Text('GO TO DUNGEON'),
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure')
onPressed: () => Navigator.of(context).pushNamed('/adventure')
)],
justifyContent: FlexJustifyContent.center
)
......@@ -57,7 +55,7 @@ class Adventure extends StatelessComponent {
new Text("Monster's Lair"),
new RaisedButton(
child: new Text('RUN!!!'),
onPressed: () => n2.Navigator.of(context).pop()
onPressed: () => Navigator.of(context).pop()
)],
justifyContent: FlexJustifyContent.center
)
......
......@@ -17,14 +17,9 @@ const double _kMinFlingVelocity = 700.0;
const double _kFlingVelocityScale = 1.0 / 300.0;
class _BottomSheet extends StatefulComponent {
_BottomSheet({
Key key,
this.child,
this.performance
}) : super(key: key);
_BottomSheet({ Key key, this.route }) : super(key: key);
final Widget child;
final Performance performance;
final _ModalBottomSheetRoute route;
_BottomSheetState createState() => new _BottomSheetState();
}
......@@ -54,47 +49,62 @@ class _BottomSheetState extends State<_BottomSheet> {
bool _dragEnabled = false;
void _handleDragStart(Point position) {
_dragEnabled = !config.performance.isAnimating;
_dragEnabled = !config.route._performance.isAnimating;
}
void _handleDragUpdate(double delta) {
if (!_dragEnabled)
return;
config.performance.progress -= delta / _layout.childTop.end;
config.route._performance.progress -= delta / _layout.childTop.end;
}
void _handleDragEnd(Offset velocity) {
if (!_dragEnabled)
return;
if (velocity.dy > _kMinFlingVelocity)
config.performance.fling(velocity: -velocity.dy * _kFlingVelocityScale);
config.route._performance.fling(velocity: -velocity.dy * _kFlingVelocityScale);
else
config.performance.forward();
config.route._performance.forward();
}
Widget build(BuildContext context) {
return new BuilderTransition(
performance: config.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.child)
)
return new Focus(
key: new GlobalObjectKey(config.route),
autofocus: true,
child: new GestureDetector(
onTap: () { Navigator.of(context).pop(); },
child: new Stack(<Widget>[
// mask
new ColorTransition(
performance: config.route._performance,
color: new AnimatedColorValue(Colors.transparent, end: Colors.black54),
child: new Container()
),
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 }) {
_performance = new Performance(duration: transitionDuration, debugLabel: 'ModalBottomSheet');
}
......@@ -102,53 +112,27 @@ class _ModalBottomSheetRoute extends Route {
final Completer completer;
final Widget child;
PerformanceView get performance => _performance?.view;
Performance _performance;
bool get ephemeral => true;
bool get modal => true;
bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration;
Widget build(RouteArguments args) {
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
)
])
)
);
}
Performance _performance;
void didPush(NavigatorState navigator) {
super.didPush(navigator);
_performance?.forward();
Performance createPerformance() {
_performance = super.createPerformance();
return _performance;
}
List<Widget> createWidgets() => [ new _BottomSheet(route: this) ];
void didPop([dynamic result]) {
completer.complete(result);
super.didPop(result);
if (_performance.status != PerformanceStatus.dismissed)
_performance?.reverse();
}
}
Future showModalBottomSheet({ BuildContext context, Widget child }) {
final Completer completer = new Completer();
Navigator.of(context).push(new _ModalBottomSheetRoute(
Navigator.of(context).pushEphemeral(new _ModalBottomSheetRoute(
completer: completer,
child: child
));
......
......@@ -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>[
new GestureDetector(
onTap: onDismiss,
......@@ -130,21 +131,27 @@ class Dialog extends StatelessComponent {
}
}
class _DialogRoute extends PerformanceRoute {
_DialogRoute({ this.completer, this.builder });
class _DialogRoute extends TransitionRoute {
_DialogRoute({ this.completer, this.child });
final Completer completer;
final RouteBuilder builder;
final Widget child;
bool get opaque => false;
Duration get transitionDuration => const Duration(milliseconds: 150);
Widget build(RouteArguments args) {
return new FadeTransition(
performance: args.previousPerformance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut),
child: builder(args)
);
List<Widget> createWidgets() {
return [
new Focus(
key: new GlobalObjectKey(this),
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]) {
......@@ -155,15 +162,6 @@ class _DialogRoute extends PerformanceRoute {
Future showDialog({ BuildContext context, Widget child }) {
Completer completer = new Completer();
Navigator.of(context).push(new _DialogRoute(
completer: completer,
builder: (RouteArguments args) {
return new Focus(
key: new GlobalObjectKey(completer),
autofocus: true,
child: child
);
}
));
Navigator.of(context).push(new _DialogRoute(completer: completer, child: child));
return completer.future;
}
......@@ -31,115 +31,90 @@ const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0);
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;
Widget build(BuildContext context) {
return new GestureDetector(
onHorizontalDragStart: (_) {
if (interactive)
route._takeControl();
},
onHorizontalDragUpdate: (double delta) {
if (interactive)
route._moveDrawer(delta);
},
onHorizontalDragEnd: (Offset velocity) {
if (interactive)
route._settle(velocity);
},
child: new Stack(<Widget>[
// mask
new GestureDetector(
onTap: () {
if (interactive)
route._close();
},
child: new ColorTransition(
performance: performance,
color: new AnimatedColorValue(Colors.transparent, end: Colors.black54),
child: new Container()
)
),
// drawer
new Positioned(
top: 0.0,
left: 0.0,
bottom: 0.0,
child: new SlideTransition(
performance: performance,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
child: new AnimatedContainer(
curve: Curves.ease,
duration: _kThemeChangeDuration,
decoration: new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
boxShadow: shadows[level]),
width: _kWidth,
child: child
return new Focus(
key: new GlobalObjectKey(route),
autofocus: true,
child: new GestureDetector(
onHorizontalDragStart: (_) {
if (route.interactive)
route._takeControl();
},
onHorizontalDragUpdate: (double delta) {
if (route.interactive)
route._moveDrawer(delta);
},
onHorizontalDragEnd: (Offset velocity) {
if (route.interactive)
route._settle(velocity);
},
child: new Stack(<Widget>[
// mask
new GestureDetector(
onTap: () {
if (route.interactive)
route._close();
},
child: new ColorTransition(
performance: route.performance,
color: new AnimatedColorValue(Colors.transparent, end: Colors.black54),
child: new Container()
)
),
new Positioned(
top: 0.0,
left: 0.0,
bottom: 0.0,
child: new SlideTransition(
performance: route.performance,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
child: new AnimatedContainer(
curve: Curves.ease,
duration: _kThemeChangeDuration,
decoration: new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
boxShadow: shadows[route.level]),
width: _kWidth,
child: route.child
)
)
)
)
])
])
)
);
}
}
class _DrawerRoute extends Route {
class _DrawerRoute extends TransitionRoute {
_DrawerRoute({ this.child, this.level });
final Widget child;
final int level;
PerformanceView get performance => _performance?.view;
Performance _performance = new Performance(duration: _kBaseSettleDuration, debugLabel: 'Drawer');
Duration get transitionDuration => _kBaseSettleDuration;
bool get opaque => false;
bool get interactive => _interactive;
bool _interactive = true;
Widget build(RouteArguments args) {
return new Focus(
key: new GlobalObjectKey(this),
autofocus: true,
child: new _Drawer(
child: child,
level: level,
performance: performance,
interactive: _interactive,
route: this
)
);
}
Performance _performance;
void didPush(NavigatorState navigator) {
super.didPush(navigator);
_performance.forward();
Performance createPerformance() {
_performance = super.createPerformance();
return _performance;
}
List<Widget> createWidgets() => [ new _Drawer(route: this) ];
void didPop([dynamic result]) {
assert(result == null); // because we don't do anything with it, so otherwise it'd be lost
super.didPop(result);
if (_performance.status != PerformanceStatus.dismissed)
_performance.reverse();
setState(() {
_interactive = false;
// TODO(ianh): https://github.com/flutter/engine/issues/1539
});
_interactive = false;
}
void _takeControl() {
......
......@@ -22,23 +22,9 @@ const double _kBaselineOffsetFromBottom = 20.0;
const Border _kDropdownUnderline = const Border(bottom: const BorderSide(color: const Color(0xFFBDBDBD), width: 2.0));
class _DropdownMenu extends StatelessComponent {
_DropdownMenu({
Key key,
this.items,
this.rect,
this.performance,
this.selectedIndex,
this.level: 4
}) : super(key: key) {
assert(items != null);
assert(performance != null);
}
_DropdownMenu({ Key key, this.route }) : super(key: key);
final List<DropdownMenuItem> items;
final Rect rect;
final PerformanceView performance;
final int selectedIndex;
final int level;
final _MenuRoute route;
Widget build(BuildContext context) {
// The menu is shown in three stages (unit timing in brackets):
......@@ -50,11 +36,11 @@ class _DropdownMenu extends StatelessComponent {
// When the menu is dismissed we just fade the entire thing out
// 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>[];
for (int itemIndex = 0; itemIndex < items.length; ++itemIndex) {
for (int itemIndex = 0; itemIndex < route.items.length; ++itemIndex) {
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));
} else {
final double start = (0.5 + (itemIndex + 1) * unit).clamp(0.0, 1.0);
......@@ -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));
}
children.add(new FadeTransition(
performance: performance,
performance: route.performance,
opacity: opacity,
child: new InkWell(
child: items[itemIndex],
child: route.items[itemIndex],
onTap: () {
Navigator.of(context).pop(items[itemIndex].value);
Navigator.of(context).pop(route.items[itemIndex].value);
}
)
));
......@@ -79,13 +65,13 @@ class _DropdownMenu extends StatelessComponent {
reverseCurve: new Interval(0.75, 1.0)
);
final AnimatedValue<double> menuTop = new AnimatedValue<double>(rect.top,
end: rect.top - selectedIndex * rect.height,
final AnimatedValue<double> menuTop = new AnimatedValue<double>(route.rect.top,
end: route.rect.top - route.selectedIndex * route.rect.height,
curve: new Interval(0.25, 0.5),
reverseCurve: const Interval(0.0, 0.001)
);
final AnimatedValue<double> menuBottom = new AnimatedValue<double>(rect.bottom,
end: menuTop.end + items.length * rect.height,
final AnimatedValue<double> menuBottom = new AnimatedValue<double>(route.rect.bottom,
end: menuTop.end + route.items.length * route.rect.height,
curve: new Interval(0.25, 0.5),
reverseCurve: const Interval(0.0, 0.001)
);
......@@ -93,32 +79,45 @@ class _DropdownMenu extends StatelessComponent {
final BoxPainter menuPainter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0,
boxShadow: shadows[level]
boxShadow: shadows[route.level]
));
return new FadeTransition(
performance: performance,
opacity: menuOpacity,
child: new BuilderTransition(
performance: 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));
final RenderBox renderBox = Navigator.of(context).context.findRenderObject();
final Size navigatorSize = renderBox.size;
final RelativeRect menuRect = new RelativeRect.fromSize(route.rect, navigatorSize);
return new Positioned(
top: menuRect.top - (route.selectedIndex * route.rect.height),
right: menuRect.right - _kMenuHorizontalPadding,
left: menuRect.left - _kMenuHorizontalPadding,
child: new Focus(
key: new GlobalObjectKey(route),
autofocus: true,
child: new FadeTransition(
performance: route.performance,
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({
this.completer,
this.items,
......@@ -133,33 +132,13 @@ class _MenuRoute extends PerformanceRoute {
final int level;
final int selectedIndex;
bool get ephemeral => true;
bool get modal => true;
bool get opaque => false;
Duration get transitionDuration => _kMenuDuration;
Widget build(RouteArguments args) {
final RenderBox renderBox = navigator.context.findRenderObject();
final Size navigatorSize = renderBox.size;
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
)
)
);
}
List<Widget> createWidgets() => [
new ModalBarrier(),
new _DropdownMenu(route: this)
];
void didPop([dynamic result]) {
completer.complete(result);
......@@ -210,7 +189,7 @@ class DropdownButton<T> extends StatelessComponent {
final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject();
final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size;
final Completer completer = new Completer();
Navigator.of(context).push(new _MenuRoute(
Navigator.of(context).pushEphemeral(new _MenuRoute(
completer: completer,
items: items,
selectedIndex: selectedIndex,
......
......@@ -8,9 +8,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.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 'title.dart';
......@@ -34,8 +31,6 @@ AssetBundle _initDefaultBundle() {
final AssetBundle _defaultBundle = _initDefaultBundle();
const bool _kUseNavigator2 = false;
class MaterialApp extends StatefulComponent {
MaterialApp({
Key key,
......@@ -87,10 +82,10 @@ class _MaterialAppState extends State<MaterialApp> {
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) {
return new n2.HeroPageRoute(
Route _generateRoute(NamedRouteSettings settings) {
return new HeroPageRoute(
builder: (BuildContext context) {
RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name);
return builder(new RouteArguments(context: context));
......@@ -101,19 +96,6 @@ class _MaterialAppState extends State<MaterialApp> {
}
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(
data: new MediaQueryData(size: _size),
child: new Theme(
......@@ -124,7 +106,10 @@ class _MaterialAppState extends State<MaterialApp> {
bundle: _defaultBundle,
child: new Title(
title: config.title,
child: navigator
child: new Navigator(
key: _navigator,
onGenerateRoute: _generateRoute
)
)
)
)
......
......@@ -22,80 +22,84 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuHorizontalPadding = 16.0;
const double _kMenuVerticalPadding = 8.0;
class PopupMenu extends StatelessComponent {
PopupMenu({
class _PopupMenu extends StatelessComponent {
_PopupMenu({
Key key,
this.items,
this.level: 4,
this.performance
}) : super(key: key) {
assert(items != null);
assert(performance != null);
}
this.route
}) : super(key: key);
final List<PopupMenuItem> items;
final int level;
final PerformanceView performance;
final _MenuRoute route;
Widget build(BuildContext context) {
final BoxPainter painter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
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>[];
for (int i = 0; i < items.length; ++i) {
for (int i = 0; i < route.items.length; ++i) {
double start = (i + 1) * unit;
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
children.add(new FadeTransition(
performance: performance,
performance: route.performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)),
child: new InkWell(
onTap: () { Navigator.of(context).pop(items[i].value); },
child: items[i]
onTap: () { Navigator.of(context).pop(route.items[i].value); },
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> height = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit * items.length));
return new FadeTransition(
performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)),
child: new BuilderTransition(
performance: performance,
variables: <AnimatedValue<double>>[width, height],
builder: (BuildContext context) {
return new CustomPaint(
onPaint: (ui.Canvas canvas, Size size) {
double widthValue = width.value * size.width;
double heightValue = height.value * size.height;
painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
},
child: new ConstrainedBox(
constraints: new BoxConstraints(
minWidth: _kMenuMinWidth,
maxWidth: _kMenuMaxWidth
),
child: new IntrinsicWidth(
stepWidth: _kMenuWidthStep,
child: new ScrollableViewport(
child: new Container(
padding: const EdgeDims.symmetric(
horizontal: _kMenuHorizontalPadding,
vertical: _kMenuVerticalPadding
),
child: new BlockBody(children)
final AnimatedValue<double> height = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit * route.items.length));
return new Positioned(
top: route.position?.top,
right: route.position?.right,
bottom: route.position?.bottom,
left: route.position?.left,
child: new Focus(
key: new GlobalObjectKey(route),
autofocus: true,
child: new FadeTransition(
performance: route.performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)),
child: new BuilderTransition(
performance: route.performance,
variables: <AnimatedValue<double>>[width, height],
builder: (BuildContext context) {
return new CustomPaint(
onPaint: (ui.Canvas canvas, Size size) {
double widthValue = width.value * size.width;
double heightValue = height.value * size.height;
painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
},
child: new ConstrainedBox(
constraints: new BoxConstraints(
minWidth: _kMenuMinWidth,
maxWidth: _kMenuMaxWidth
),
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 {
final double left;
}
class _MenuRoute extends PerformanceRoute {
class _MenuRoute extends TransitionRoute {
_MenuRoute({ this.completer, this.position, this.items, this.level });
final Completer completer;
......@@ -125,28 +129,13 @@ class _MenuRoute extends PerformanceRoute {
return result;
}
bool get ephemeral => true;
bool get modal => true;
bool get opaque => false;
Duration get transitionDuration => _kMenuDuration;
Widget build(RouteArguments args) {
return new Positioned(
top: position?.top,
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
)
)
);
}
List<Widget> createWidgets() => [
new ModalBarrier(),
new _PopupMenu(route: this),
];
void didPop([dynamic result]) {
completer.complete(result);
......@@ -156,7 +145,7 @@ class _MenuRoute extends PerformanceRoute {
Future showMenu({ BuildContext context, MenuPosition position, List<PopupMenuItem> items, int level: 4 }) {
Completer completer = new Completer();
Navigator.of(context).push(new _MenuRoute(
Navigator.of(context).pushEphemeral(new _MenuRoute(
completer: completer,
position: position,
items: items,
......
......@@ -34,19 +34,19 @@ class SnackBarAction extends StatelessComponent {
}
}
class SnackBar extends StatelessComponent {
SnackBar({
class _SnackBar extends StatelessComponent {
_SnackBar({
Key key,
this.content,
this.actions,
this.performance
this.route
}) : super(key: key) {
assert(content != null);
}
final Widget content;
final List<SnackBarAction> actions;
final PerformanceView performance;
final _SnackBarRoute route;
Widget build(BuildContext context) {
List<Widget> children = <Widget>[
......@@ -63,7 +63,7 @@ class SnackBar extends StatelessComponent {
if (actions != null)
children.addAll(actions);
return new SquashTransition(
performance: performance,
performance: route.performance,
height: new AnimatedValue<double>(
0.0,
end: kSnackBarHeight,
......@@ -91,27 +91,18 @@ class SnackBar extends StatelessComponent {
}
}
class _SnackBarRoute extends PerformanceRoute {
_SnackBarRoute({ this.content, this.actions });
final Widget content;
final List<SnackBarAction> actions;
bool get hasContent => false;
bool get ephemeral => true;
bool get modal => false;
class _SnackBarRoute extends TransitionRoute {
bool get opaque => false;
Duration get transitionDuration => const Duration(milliseconds: 200);
Widget build(RouteArguments args) => null;
}
void showSnackBar({ BuildContext context, GlobalKey<PlaceholderState> placeholderKey, Widget content, List<SnackBarAction> actions }) {
Route route = new _SnackBarRoute();
SnackBar snackBar = new SnackBar(
_SnackBarRoute route = new _SnackBarRoute();
_SnackBar snackBar = new _SnackBar(
route: route,
content: content,
actions: actions,
performance: route.performance
actions: actions
);
placeholderKey.currentState.child = snackBar;
Navigator.of(context).push(route);
Navigator.of(context).pushEphemeral(route);
}
......@@ -10,6 +10,7 @@ import 'basic.dart';
import 'binding.dart';
import 'framework.dart';
import 'navigator.dart';
import 'overlay.dart';
typedef bool DragTargetWillAccept<T>(T data);
typedef void DragTargetAccept<T>(T data);
......@@ -61,11 +62,11 @@ class Draggable extends StatefulComponent {
}
class _DraggableState extends State<Draggable> {
DragRoute _route;
_DragAvatar _avatar;
void _startDrag(PointerInputEvent event) {
if (_route != 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.
if (_avatar != null)
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);
Point dragStartPoint;
switch (config.dragAnchor) {
......@@ -78,39 +79,38 @@ class _DraggableState extends State<Draggable> {
break;
}
assert(dragStartPoint != null);
_route = new DragRoute(
_avatar = new _DragAvatar(
data: config.data,
dragStartPoint: dragStartPoint,
feedback: config.feedback,
feedbackOffset: config.feedbackOffset,
onDragFinished: () {
_route = null;
_avatar = null;
}
);
_route.update(point);
Navigator.of(context).push(_route);
_avatar.update(point);
_avatar.rebuild(context);
}
void _updateDrag(PointerInputEvent event) {
if (_route != null) {
Navigator.of(context).setState(() {
_route.update(new Point(event.x, event.y));
});
if (_avatar != null) {
_avatar.update(new Point(event.x, event.y));
_avatar.rebuild(context);
}
}
void _cancelDrag(PointerInputEvent event) {
if (_route != null) {
Navigator.of(context).popRoute(_route, DragEndKind.canceled);
assert(_route == null);
if (_avatar != null) {
_avatar.finish(_DragEndKind.canceled);
assert(_avatar == null);
}
}
void _drop(PointerInputEvent event) {
if (_route != null) {
_route.update(new Point(event.x, event.y));
Navigator.of(context).popRoute(_route, DragEndKind.dropped);
assert(_route == null);
if (_avatar != null) {
_avatar.update(new Point(event.x, event.y));
_avatar.finish(_DragEndKind.dropped);
assert(_avatar == null);
}
}
......@@ -187,10 +187,10 @@ class DragTargetState<T> extends State<DragTarget<T>> {
}
enum DragEndKind { dropped, canceled }
enum _DragEndKind { dropped, canceled }
class DragRoute extends Route {
DragRoute({
class _DragAvatar {
_DragAvatar({
this.data,
this.dragStartPoint: Point.origin,
this.feedback,
......@@ -209,6 +209,7 @@ class DragRoute extends Route {
DragTargetState _activeTarget;
bool _activeTargetWillAcceptDrop = false;
Offset _lastOffset;
OverlayEntry _entry;
void update(Point globalPosition) {
_lastOffset = globalPosition - dragStartPoint;
......@@ -222,6 +223,12 @@ class DragRoute extends Route {
_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) {
// TODO(abarth): Why do we reverse the path here?
for (HitTestEntry entry in path.reversed) {
......@@ -234,25 +241,21 @@ class DragRoute extends Route {
return null;
}
void didPop([DragEndKind endKind]) {
void finish(_DragEndKind endKind) {
if (_activeTarget != null) {
if (endKind == DragEndKind.dropped && _activeTargetWillAcceptDrop)
if (endKind == _DragEndKind.dropped && _activeTargetWillAcceptDrop)
_activeTarget.didDrop(data);
else
_activeTarget.didLeave(data);
}
_activeTarget = null;
_activeTargetWillAcceptDrop = false;
_entry.remove();
if (onDragFinished != null)
onDragFinished();
super.didPop(endKind);
}
bool get ephemeral => true;
bool get modal => false;
bool get opaque => false;
Widget build(RouteArguments args) {
Widget _build(BuildContext context) {
return new Positioned(
left: _lastOffset.dx,
top: _lastOffset.dy,
......
......@@ -8,7 +8,7 @@ import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'framework.dart';
import 'heroes.dart';
import 'navigator2.dart';
import 'navigator.dart';
import 'overlay.dart';
import 'page.dart';
......@@ -69,10 +69,9 @@ class HeroController {
}
void _addHeroesToOverlay(Iterable<Widget> heroes, OverlayState overlay) {
OverlayEntry insertionPoint = _to.topEntry;
for (Widget hero in heroes) {
OverlayEntry entry = new OverlayEntry(child: hero);
overlay.insert(entry, above: insertionPoint);
overlay.insert(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()
);
}
}
// 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 {
bool get opaque => _opaque;
bool _opaque;
void set opaque(bool value) {
if (_opaque = value)
if (_opaque == value)
return;
_opaque = value;
_state?.setState(() {});
......
......@@ -6,59 +6,11 @@ import 'package:flutter/animation.dart';
import 'basic.dart';
import 'framework.dart';
import 'navigator2.dart';
import 'overlay.dart';
import 'navigator.dart';
import 'page_storage.dart';
import 'routes.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 {
_Page({
Key key,
......@@ -130,6 +82,8 @@ class PageRoute extends TransitionRoute {
final GlobalKey<_PageState> pageKey = new GlobalKey<_PageState>();
bool get opaque => true;
String get name => settings.name;
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';
import 'gesture_detector.dart';
import 'homogeneous_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
const double _kMillisecondsPerSecond = 1000.0;
......@@ -56,7 +56,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
void initState() {
super.initState();
_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;
......@@ -178,7 +178,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
setState(() {
_scrollOffset = newScrollOffset;
});
Route.of(context)?.writeState(context, _scrollOffset);
PageStorage.of(context)?.writeState(context, _scrollOffset);
dispatchOnScroll();
}
......
......@@ -16,13 +16,18 @@ export 'src/widgets/focus.dart';
export 'src/widgets/framework.dart';
export 'src/widgets/gesture_detector.dart';
export 'src/widgets/gridpaper.dart';
export 'src/widgets/hero_controller.dart';
export 'src/widgets/heroes.dart';
export 'src/widgets/homogeneous_viewport.dart';
export 'src/widgets/media_query.dart';
export 'src/widgets/mimic.dart';
export 'src/widgets/mixed_viewport.dart';
export 'src/widgets/modal_barrier.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/routes.dart';
export 'src/widgets/scrollable.dart';
export 'src/widgets/statistics_overlay.dart';
export 'src/widgets/transitions.dart';
......
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:test/test.dart';
import '../engine/mock_events.dart';
......@@ -11,7 +11,7 @@ void main() {
List accepted = [];
tester.pumpWidget(new Navigator(
tester.pumpWidget(new MaterialApp(
routes: <String, RouteBuilder>{
'/': (RouteArguments args) { return new Column(<Widget>[
new Draggable(
......
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
......@@ -45,7 +45,7 @@ void main() {
'/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('Y'), isNull);
......
......@@ -34,9 +34,12 @@ void main() {
GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
tester.pumpWidget(new Navigator(
key: navigatorKey,
routes: <String, RouteBuilder>{
'/': (RouteArguments args) => new Container(child: new ThePositiveNumbers()),
'/second': (RouteArguments args) => new Container(child: new ThePositiveNumbers()),
onGenerateRoute: (NamedRouteSettings settings) {
if (settings.name == '/')
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() {
expect(tester.findText('15'), isNotNull);
expect(tester.findText('16'), 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