2015-08-20 11:25:59 -07:00

178 lines
5.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 'dart:sky' as sky;
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/forces.dart';
import 'package:sky/theme/shadows.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scrollable.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/transitions.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
// 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;
const double _kMinFlingVelocity = 365.0;
const double _kFlingVelocityScale = 1.0 / 300.0;
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
const Duration _kThemeChangeDuration = const Duration(milliseconds: 200);
const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0);
typedef void DrawerDismissedCallback();
class Drawer extends StatefulComponent {
Drawer({
Key key,
this.children,
this.showing: false,
this.level: 0,
this.onDismissed,
this.navigator
}) : super(key: key);
List<Widget> children;
bool showing;
int level;
DrawerDismissedCallback onDismissed;
Navigator navigator;
AnimationPerformance _performance;
void initState() {
_performance = new AnimationPerformance(duration: _kBaseSettleDuration);
// 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;
if (navigator != null) {
scheduleMicrotask(() {
navigator.pushState(this, (_) => _performance.reverse());
});
}
}
void syncFields(Drawer source) {
children = source.children;
level = source.level;
navigator = source.navigator;
showing = source.showing;
onDismissed = source.onDismissed;
}
Widget build() {
var mask = new Listener(
child: new ColorTransition(
performance: _performance,
direction: showing ? Direction.forward : Direction.reverse,
color: new AnimatedColorValue(colors.transparent, end: const Color(0x7F000000)),
child: new Container()
),
onGestureTap: handleMaskTap
);
Widget content = new SlideTransition(
performance: _performance,
direction: showing ? Direction.forward : Direction.reverse,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
onDismissed: _onDismissed,
child: new AnimatedContainer(
behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
decoration: new BoxDecoration(
backgroundColor: Theme.of(this).canvasColor,
boxShadow: shadows[level]),
width: _kWidth,
child: new ScrollableBlock(children)
)
);
return new Listener(
child: new Stack([ mask, content ]),
onPointerDown: handlePointerDown,
onPointerMove: handlePointerMove,
onPointerUp: handlePointerUp,
onPointerCancel: handlePointerCancel,
onGestureFlingStart: handleFlingStart
);
}
void _onDismissed() {
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();
}
bool get _isMostlyClosed => _performance.progress < 0.5;
void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); }
EventDisposition handleMaskTap(_) {
_performance.reverse();
return EventDisposition.consumed;
}
// TODO(mpcomplete): Figure out how to generalize these handlers on a
// "PannableThingy" interface.
EventDisposition handlePointerDown(_) {
_performance.stop();
return EventDisposition.processed;
}
EventDisposition handlePointerMove(sky.PointerEvent event) {
if (_performance.isAnimating)
return EventDisposition.ignored;
_performance.progress += event.dx / _kWidth;
return EventDisposition.processed;
}
EventDisposition handlePointerUp(_) {
if (!_performance.isAnimating) {
_settle();
return EventDisposition.processed;
}
return EventDisposition.ignored;
}
EventDisposition handlePointerCancel(_) {
if (!_performance.isAnimating) {
_settle();
return EventDisposition.processed;
}
return EventDisposition.ignored;
}
EventDisposition handleFlingStart(event) {
if (event.velocityX.abs() >= _kMinFlingVelocity) {
_performance.fling(velocity: event.velocityX * _kFlingVelocityScale);
return EventDisposition.processed;
}
return EventDisposition.ignored;
}
}