Commit b991b7d6 authored by Adam Barth's avatar Adam Barth

Add an initial implementation of Navigator2

This navigator can handle simple page navigation. I'll add more features in
subsequent CLs.
parent cbf9eab8
......@@ -4,51 +4,71 @@
import 'package:flutter/material.dart';
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (RouteArguments args) => new Container(
import 'package:flutter/src/widgets/navigator2.dart' as n2;
class Home extends StatelessComponent {
Widget build(BuildContext context) {
return new Container(
padding: const EdgeDims.all(30.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)),
child: new Column(<Widget>[
new Text("You are at home"),
new RaisedButton(
child: new Text('GO SHOPPING'),
onPressed: () => Navigator.of(args.context).pushNamed('/shopping')
onPressed: () => n2.Navigator.of(context).pushNamed('/shopping')
),
new RaisedButton(
child: new Text('START ADVENTURE'),
onPressed: () => Navigator.of(args.context).pushNamed('/adventure')
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure')
)],
justifyContent: FlexJustifyContent.center
)
),
'/shopping': (RouteArguments args) => new Container(
);
}
}
class Shopping extends StatelessComponent {
Widget build(BuildContext context) {
return new Container(
padding: const EdgeDims.all(20.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)),
child: new Column(<Widget>[
new Text("Village Shop"),
new RaisedButton(
child: new Text('RETURN HOME'),
onPressed: () => Navigator.of(args.context).pop()
onPressed: () => n2.Navigator.of(context).pop()
),
new RaisedButton(
child: new Text('GO TO DUNGEON'),
onPressed: () => Navigator.of(args.context).pushNamed('/adventure')
onPressed: () => n2.Navigator.of(context).pushNamed('/adventure')
)],
justifyContent: FlexJustifyContent.center
)
),
'/adventure': (RouteArguments args) => new Container(
);
}
}
class Adventure extends StatelessComponent {
Widget build(BuildContext context) {
return new Container(
padding: const EdgeDims.all(20.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)),
child: new Column(<Widget>[
new Text("Monster's Lair"),
new RaisedButton(
child: new Text('RUN!!!'),
onPressed: () => Navigator.of(args.context).pop()
onPressed: () => n2.Navigator.of(context).pop()
)],
justifyContent: FlexJustifyContent.center
)
)
);
}
}
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (_) => new Home(),
'/shopping': (_) => new Shopping(),
'/adventure': (_) => new Adventure(),
};
final ThemeData theme = new ThemeData(
......
......@@ -8,6 +8,8 @@ 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 'theme.dart';
import 'title.dart';
......@@ -31,6 +33,8 @@ AssetBundle _initDefaultBundle() {
final AssetBundle _defaultBundle = _initDefaultBundle();
const bool _kUseNavigator2 = false;
class MaterialApp extends StatefulComponent {
MaterialApp({
Key key,
......@@ -83,6 +87,19 @@ class _MaterialAppState extends State<MaterialApp> {
void _metricHandler(Size size) => setState(() { _size = size; });
Widget build(BuildContext context) {
Widget navigator;
if (_kUseNavigator2) {
navigator = new n2.Navigator(
key: _navigator,
routes: config.routes
);
} else {
navigator = new Navigator(
key: _navigator,
routes: config.routes,
onGenerateRoute: config.onGenerateRoute
);
}
return new MediaQuery(
data: new MediaQueryData(size: _size),
child: new Theme(
......@@ -93,11 +110,7 @@ class _MaterialAppState extends State<MaterialApp> {
bundle: _defaultBundle,
child: new Title(
title: config.title,
child: new Navigator(
key: _navigator,
routes: config.routes,
onGenerateRoute: config.onGenerateRoute
)
child: navigator
)
)
)
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/animation.dart';
import 'basic.dart';
import 'framework.dart';
import 'overlay.dart';
import 'transitions.dart';
abstract class Route {
/// Override this function to return the widget that this route should display.
Widget createWidget();
Widget _child;
OverlayEntry _entry;
void willPush() {
_child = createWidget();
}
void didPop(dynamic result) {
_entry.remove();
}
}
typedef Widget RouteBuilder(args);
typedef RouteBuilder RouteGenerator(String name);
const String _kDefaultPageName = '/';
class Navigator extends StatefulComponent {
Navigator({
Key key,
this.routes,
this.onGeneratePage,
this.onUnknownPage
}) : super(key: key) {
// To use a navigator, you must at a minimum define the route with the name '/'.
assert(routes != null);
assert(routes.containsKey(_kDefaultPageName));
}
final Map<String, RouteBuilder> routes;
/// you need to implement this if you pushNamed() to names that might not be in routes.
final RouteGenerator onGeneratePage;
/// 404 generator. You only need to implement this if you have a way to navigate to arbitrary names.
final RouteBuilder onUnknownPage;
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> {
GlobalKey<OverlayState> _overlay = new GlobalKey<OverlayState>();
List<Route> _history = new List<Route>();
void initState() {
super.initState();
_addRouteToHistory(new PageRoute(
builder: config.routes[_kDefaultPageName],
name: _kDefaultPageName
));
}
RouteBuilder _generatePage(String name) {
assert(config.onGeneratePage != null);
return config.onGeneratePage(name);
}
bool get hasPreviousRoute => _history.length > 1;
void pushNamed(String name, { Set<Key> mostValuableKeys }) {
final RouteBuilder builder = config.routes[name] ?? _generatePage(name) ?? config.onUnknownPage;
assert(builder != null); // 404 getting your 404!
push(new PageRoute(
builder: builder,
name: name,
mostValuableKeys: mostValuableKeys
));
}
void _addRouteToHistory(Route route) {
route.willPush();
route._entry = new OverlayEntry(child: route._child);
_history.add(route);
}
void push(Route route) {
OverlayEntry reference = _history.last._entry;
_addRouteToHistory(route);
_overlay.currentState.insert(route._entry, above: reference);
}
void pop([dynamic result]) {
_history.removeLast().didPop(result);
}
Widget build(BuildContext context) {
return new Overlay(
key: _overlay,
initialEntries: <OverlayEntry>[ _history.first._entry ]
);
}
}
abstract class TransitionRoute extends Route {
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);
}
void willPush() {
_performance = createPerformance();
_performance.forward();
super.willPush();
}
Future didPop(dynamic result) async {
await _performance.reverse();
super.didPop(result);
}
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $_performance)';
}
class _Page extends StatefulComponent {
_Page({ Key key, this.route }) : super(key: key);
final PageRoute route;
_PageState createState() => new _PageState();
}
class _PageState extends State<_Page> {
final AnimatedValue<Point> _position =
new AnimatedValue<Point>(const Point(0.0, 75.0), end: Point.origin, curve: Curves.easeOut);
final AnimatedValue<double> _opacity =
new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut);
Widget build(BuildContext context) {
return new SlideTransition(
performance: config.route.performance,
position: _position,
child: new FadeTransition(
performance: config.route.performance,
opacity: _opacity,
child: _invokeBuilder()
)
);
}
Widget _invokeBuilder() {
Widget result = config.route.builder(null);
assert(() {
if (result == null)
debugPrint('The builder for route \'${config.route.name}\' returned null. Route builders must never return null.');
assert(result != null && 'A route builder returned null. See the previous log message for details.' is String);
return true;
});
return result;
}
}
class PageRoute extends TransitionRoute {
PageRoute({
this.builder,
this.name: '<anonymous>',
this.mostValuableKeys
}) {
assert(builder != null);
}
final RouteBuilder builder;
final String name;
final Set<Key> mostValuableKeys;
Duration get transitionDuration => const Duration(milliseconds: 150);
Widget createWidget() => new _Page(route: this);
String get debugLabel => '${super.debugLabel}($name)';
}
// 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';
class OverlayEntry {
OverlayEntry({
this.child,
bool opaque: false
}) : _opaque = opaque;
final Widget child;
bool get opaque => _opaque;
bool _opaque;
void set opaque(bool value) {
_opaque = value;
_state?.setState(() {});
}
OverlayState _state;
/// Remove the entry from the overlay.
void remove() {
_state?._remove(this);
_state = null;
}
}
class Overlay extends StatefulComponent {
Overlay({
Key key,
this.initialEntries
}) : super(key: key);
final List<OverlayEntry> initialEntries;
OverlayState createState() => new OverlayState();
}
class OverlayState extends State<Overlay> {
final List<OverlayEntry> _entries = new List<OverlayEntry>();
void initState() {
super.initState();
for (OverlayEntry entry in config.initialEntries)
insert(entry);
}
void insert(OverlayEntry entry, { OverlayEntry above }) {
assert(entry._state == null);
if (above != null) {
print('above._state ${above._state} --- ${above._state == this}');
print('_entries.contains ${_entries.contains(above)}');
}
assert(above == null || (above._state == this && _entries.contains(above)));
entry._state = this;
setState(() {
int index = above == null ? _entries.length : _entries.indexOf(above) + 1;
_entries.insert(index, entry);
});
}
void _remove(OverlayEntry entry) {
setState(() {
_entries.remove(entry);
});
}
Widget build(BuildContext context) {
List<Widget> backwardsChildren = <Widget>[];
for (int i = _entries.length - 1; i >= 0; --i) {
OverlayEntry entry = _entries[i];
backwardsChildren.add(new KeyedSubtree(
key: new ObjectKey(entry),
child: entry.child
));
if (entry.opaque)
break;
}
return new Stack(backwardsChildren.reversed.toList(growable: false));
}
}
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