drawer.dart 5.45 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
import 'dart:async';
6 7
import 'dart:sky' as sky;

8
import 'package:sky/animation/animated_value.dart';
9
import 'package:sky/animation/animation_performance.dart';
10
import 'package:sky/animation/forces.dart';
11
import 'package:sky/theme/colors.dart' as colors;
12
import 'package:sky/theme/shadows.dart';
13
import 'package:sky/widgets/animated_container.dart';
14
import 'package:sky/widgets/framework.dart';
15
import 'package:sky/widgets/basic.dart';
16
import 'package:sky/widgets/gesture_detector.dart';
17
import 'package:sky/widgets/navigator.dart';
18
import 'package:sky/widgets/scrollable.dart';
19
import 'package:sky/widgets/theme.dart';
20
import 'package:sky/widgets/transitions.dart';
21

22 23
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;

24 25 26 27 28 29 30 31 32 33 34 35 36 37
// TODO(eseidel): Draw width should vary based on device size:
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav

// Mobile:
// Width = Screen width − 56 dp
// Maximum width: 320dp
// Maximum width applies only when using a left nav. When using a right nav,
// the panel can cover the full width of the screen.

// Desktop/Tablet:
// Maximum width for a left nav is 400dp.
// The right nav can vary depending on content.

const double _kWidth = 304.0;
Matt Perry's avatar
Matt Perry committed
38
const double _kMinFlingVelocity = 365.0;
39
const double _kFlingVelocityScale = 1.0 / 300.0;
40
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
41
const Duration _kThemeChangeDuration = const Duration(milliseconds: 200);
42 43 44
const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0);

45
typedef void DrawerDismissedCallback();
46

47
class Drawer extends StatefulComponent {
48
  Drawer({
49
    Key key,
50 51 52
    this.children,
    this.showing: false,
    this.level: 0,
53
    this.onDismissed,
54 55 56 57 58 59
    this.navigator
  }) : super(key: key);

  List<Widget> children;
  bool showing;
  int level;
60
  DrawerDismissedCallback onDismissed;
61 62
  Navigator navigator;

63
  AnimationPerformance _performance;
64 65

  void initState() {
66
    _performance = new AnimationPerformance(duration: _kBaseSettleDuration);
67 68 69 70 71 72

    // Use a spring force for animating the drawer. We can't use curves for
    // this because we need a linear curve in order to track the user's finger
    // while dragging.
    _performance.attachedForce = kDefaultSpringForce;

Adam Barth's avatar
Adam Barth committed
73
    if (navigator != null) {
74 75 76
      scheduleMicrotask(() {
        navigator.pushState(this, (_) => _performance.reverse());
      });
Adam Barth's avatar
Adam Barth committed
77
    }
78 79
  }

80
  void syncConstructorArguments(Drawer source) {
81 82 83
    children = source.children;
    level = source.level;
    navigator = source.navigator;
84
    showing = source.showing;
85
    onDismissed = source.onDismissed;
86 87 88
  }

  Widget build() {
89
    var mask = new GestureDetector(
90 91 92
      child: new ColorTransition(
        performance: _performance,
        direction: showing ? Direction.forward : Direction.reverse,
Adam Barth's avatar
Adam Barth committed
93 94
        color: new AnimatedColorValue(colors.transparent, end: const Color(0x7F000000)),
        child: new Container()
95
      ),
96 97 98
      onTap: () {
        _performance.reverse();
      }
99 100
    );

101
    Widget content = new SlideTransition(
102 103 104 105 106
      performance: _performance,
      direction: showing ? Direction.forward : Direction.reverse,
      position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
      onDismissed: _onDismissed,
      child: new AnimatedContainer(
107
        behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
108 109 110 111
        decoration: new BoxDecoration(
          backgroundColor: Theme.of(this).canvasColor,
          boxShadow: shadows[level]),
        width: _kWidth,
112
        child: new Block(children)
113
      )
114
    );
115 116 117 118 119 120 121 122 123 124 125

    return new Listener(
      child: new Stack([ mask, content ]),
      onPointerDown: handlePointerDown,
      onPointerMove: handlePointerMove,
      onPointerUp: handlePointerUp,
      onPointerCancel: handlePointerCancel,
      onGestureFlingStart: handleFlingStart
    );
  }

126
  void _onDismissed() {
127 128 129 130 131 132
    if (navigator != null &&
        navigator.currentRoute is RouteState &&
        (navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
      navigator.pop();
    if (onDismissed != null)
      onDismissed();
133 134
  }

135
  bool get _isMostlyClosed => _performance.progress < 0.5;
136

137
  void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); }
138 139 140

  // TODO(mpcomplete): Figure out how to generalize these handlers on a
  // "PannableThingy" interface.
141 142 143 144
  EventDisposition handlePointerDown(_) {
    _performance.stop();
    return EventDisposition.processed;
 }
145

146
  EventDisposition handlePointerMove(sky.PointerEvent event) {
147
    if (_performance.isAnimating)
148
      return EventDisposition.ignored;
149
    _performance.progress += event.dx / _kWidth;
150
    return EventDisposition.processed;
151 152
  }

153 154
  EventDisposition handlePointerUp(_) {
    if (!_performance.isAnimating) {
155
      _settle();
156 157 158
      return EventDisposition.processed;
    }
    return EventDisposition.ignored;
159 160
  }

161 162
  EventDisposition handlePointerCancel(_) {
    if (!_performance.isAnimating) {
163
      _settle();
164 165 166
      return EventDisposition.processed;
    }
    return EventDisposition.ignored;
167 168
  }

169
  EventDisposition handleFlingStart(event) {
170
    if (event.velocityX.abs() >= _kMinFlingVelocity) {
171
      _performance.fling(velocity: event.velocityX * _kFlingVelocityScale);
172
      return EventDisposition.processed;
173
    }
174
    return EventDisposition.ignored;
175 176
  }
}