navigator.dart 7.3 KB
Newer Older
1 2 3 4
// 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.

5 6
import 'dart:async';

7
import 'package:sky/animation/animated_value.dart';
8 9 10
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/widgets/animated_component.dart';
11
import 'package:sky/widgets/basic.dart';
12
import 'package:vector_math/vector_math.dart';
13

H
Hixie 已提交
14
typedef Widget RouteBuilder(Navigator navigator, RouteBase route);
15 16

abstract class RouteBase {
17
  Widget build(Navigator navigator, RouteBase route);
H
Hixie 已提交
18
  bool get isOpaque;
19
  void popState([dynamic result]) { assert(result == null); }
20 21 22
}

class Route extends RouteBase {
23
  Route({ this.name, this.builder });
H
Hixie 已提交
24

25
  final String name;
H
Hixie 已提交
26 27
  final RouteBuilder builder;

28
  Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
H
Hixie 已提交
29
  bool get isOpaque => true;
30 31
}

H
Hixie 已提交
32
class DialogRoute extends RouteBase {
33
  DialogRoute({ this.completer, this.builder });
H
Hixie 已提交
34

35
  final Completer completer;
H
Hixie 已提交
36 37 38 39
  final RouteBuilder builder;

  Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
  bool get isOpaque => false;
40

41 42
  void popState([dynamic result]) {
    completer.complete(result);
H
Hixie 已提交
43 44 45 46
  }
}

class RouteState extends RouteBase {
47
  RouteState({ this.callback, this.route, this.owner });
48 49

  Function callback;
50 51
  RouteBase route;
  StatefulComponent owner;
52

53
  Widget build(Navigator navigator, RouteBase route) => null;
H
Hixie 已提交
54
  bool get isOpaque => false;
55

56 57
  void popState([dynamic result]) {
    assert(result == null);
58 59 60
    if (callback != null)
      callback(this);
  }
61 62
}

63 64
// TODO(jackson): Refactor this into its own file
// and support multiple transition types
65 66
const Duration _kTransitionDuration = const Duration(milliseconds: 150);
const Point _kTransitionStartPoint = const Point(0.0, 75.0);
67 68
enum TransitionDirection { forward, reverse }
class Transition extends AnimatedComponent {
69
  Transition({
H
Hixie 已提交
70
    Key key,
71 72 73
    this.content,
    this.direction,
    this.onDismissed,
74
    this.onCompleted,
75
    this.interactive
H
Hixie 已提交
76
  }): super(key: key);
77 78 79 80
  Widget content;
  TransitionDirection direction;
  bool interactive;
  Function onDismissed;
81
  Function onCompleted;
82

83 84
  AnimatedValue<Point> _position;
  AnimatedValue<double> _opacity;
85 86 87
  AnimationPerformance _performance;

  void initState() {
88
    _position = new AnimatedValue<Point>(
C
Collin Jackson 已提交
89 90 91 92
      _kTransitionStartPoint,
      end: Point.origin,
      curve: easeOut
    );
93
    _opacity = new AnimatedValue<double>(0.0, end: 1.0)
94 95 96 97
      ..curve = easeOut;
    _performance = new AnimationPerformance()
      ..duration = _kTransitionDuration
      ..variable = new AnimatedList([_position, _opacity])
98 99
      ..addListener(_checkDismissed)
      ..addListener(_checkCompleted);
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
    if (direction == TransitionDirection.reverse)
      _performance.progress = 1.0;
    watch(_performance);
    _start();
  }

  void _start() {
    _dismissed = false;
    switch (direction) {
      case TransitionDirection.forward:
      _performance.play();
      break;
      case TransitionDirection.reverse:
      _performance.reverse();
      break;
    }
  }

  void syncFields(Transition source) {
    content = source.content;
    if (direction != source.direction) {
      direction = source.direction;
      _start();
    }
C
Collin Jackson 已提交
124
    interactive = source.interactive;
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    onDismissed = source.onDismissed;
    super.syncFields(source);
  }

  bool _dismissed = false;
  void _checkDismissed() {
    if (!_dismissed &&
        direction == TransitionDirection.reverse &&
        _performance.isDismissed) {
      if (onDismissed != null)
        onDismissed();
      _dismissed = true;
    }
  }

140 141 142 143 144 145 146 147 148 149 150
  bool _completed = false;
  void _checkCompleted() {
    if (!_completed &&
        direction == TransitionDirection.forward &&
        _performance.isCompleted) {
      if (onCompleted != null)
        onCompleted();
      _completed = true;
    }
  }

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
  Widget build() {
    Matrix4 transform = new Matrix4.identity()
      ..translate(_position.value.x, _position.value.y);
    // TODO(jackson): Hit testing should ignore transform
    // TODO(jackson): Block input unless content is interactive
    return new Transform(
      transform: transform,
      child: new Opacity(
        opacity: _opacity.value,
        child: content
      )
    );
  }
}

class HistoryEntry {
167
  HistoryEntry({ this.route });
168
  final RouteBase route;
H
Hixie 已提交
169
  bool fullyOpaque = false;
170 171 172
  // TODO(jackson): Keep track of the requested transition
}

173 174 175 176 177 178
class NavigationState {

  NavigationState(List<Route> routes) {
    for (Route route in routes) {
      if (route.name != null)
        namedRoutes[route.name] = route;
179
    }
180
    history.add(new HistoryEntry(route: routes[0]));
181 182
  }

183
  List<HistoryEntry> history = new List<HistoryEntry>();
184
  int historyIndex = 0;
185
  Map<String, RouteBase> namedRoutes = new Map<String, RouteBase>();
186

187
  RouteBase get currentRoute => history[historyIndex].route;
188
  bool hasPrevious() => historyIndex > 0;
189 190 191 192 193 194 195

  void pushNamed(String name) {
    Route route = namedRoutes[name];
    assert(route != null);
    push(route);
  }

196
  void push(RouteBase route) {
197
    HistoryEntry historyEntry = new HistoryEntry(route: route);
198 199
    history.insert(historyIndex + 1, historyEntry);
    historyIndex++;
200 201
  }

202
  void pop([dynamic result]) {
203
    if (historyIndex > 0) {
204
      HistoryEntry entry = history[historyIndex];
205
      entry.route.popState(result);
H
Hixie 已提交
206
      entry.fullyOpaque = false;
207 208 209 210 211
      historyIndex--;
    }
  }
}

212
class Navigator extends StatefulComponent {
213

H
Hixie 已提交
214
  Navigator(this.state, { Key key }) : super(key: key);
215 216 217

  NavigationState state;

A
Adam Barth 已提交
218 219 220 221
  void syncFields(Navigator source) {
    state = source.state;
  }

222 223
  RouteBase get currentRoute => state.currentRoute;

224
  void pushState(StatefulComponent owner, Function callback) {
225
    RouteBase route = new RouteState(
226
      owner: owner,
227 228 229 230 231 232
      callback: callback,
      route: state.currentRoute
    );
    push(route);
  }

233 234 235 236 237 238
  void pushNamed(String name) {
    setState(() {
      state.pushNamed(name);
    });
  }

239 240
  void push(RouteBase route) {
    setState(() {
241
      state.push(route);
242 243 244
    });
  }

245
  void pop([dynamic result]) {
246
    setState(() {
247
      state.pop(result);
248
    });
249 250
  }

H
Hixie 已提交
251
  Widget build() {
252 253
    List<Widget> visibleRoutes = new List<Widget>();
    for (int i = 0; i < state.history.length; i++) {
254
      // Avoid building routes that are not visible
H
Hixie 已提交
255
      if (i + 1 < state.history.length && state.history[i + 1].fullyOpaque)
256
        continue;
257 258 259 260 261 262 263 264
      HistoryEntry historyEntry = state.history[i];
      Widget content = historyEntry.route.build(this, historyEntry.route);
      if (i == 0) {
        visibleRoutes.add(content);
        continue;
      }
      if (content == null)
        continue;
C
Collin Jackson 已提交
265
      Transition transition = new Transition(
H
Hixie 已提交
266
        key: new Key.fromObjectIdentity(historyEntry),
C
Collin Jackson 已提交
267 268 269 270 271 272 273
        content: content,
        direction: (i <= state.historyIndex) ? TransitionDirection.forward : TransitionDirection.reverse,
        interactive: (i == state.historyIndex),
        onDismissed: () {
          setState(() {
            state.history.remove(historyEntry);
          });
274 275 276
        },
        onCompleted: () {
          setState(() {
H
Hixie 已提交
277
            historyEntry.fullyOpaque = historyEntry.route.isOpaque;
278
          });
C
Collin Jackson 已提交
279 280
        }
      );
281 282 283
      visibleRoutes.add(transition);
    }
    return new Stack(visibleRoutes);
284
  }
H
Hixie 已提交
285
}