// 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 'package:flutter/widgets.dart'; import 'colors.dart'; import 'material.dart'; // 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 _kEdgeDragWidth = 20.0; const double _kMinFlingVelocity = 365.0; const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); class Drawer extends StatelessComponent { Drawer({ Key key, this.elevation: 16, this.child }) : super(key: key); final int elevation; final Widget child; Widget build(BuildContext context) { return new ConstrainedBox( constraints: const BoxConstraints.expand(width: _kWidth), child: new Material( elevation: elevation, child: child ) ); } } class DrawerController extends StatefulComponent { DrawerController({ GlobalKey key, this.child }) : super(key: key); final Widget child; DrawerControllerState createState() => new DrawerControllerState(); } class DrawerControllerState extends State<DrawerController> { void initState() { super.initState(); _performance = new Performance(duration: _kBaseSettleDuration) ..addListener(_performanceChanged) ..addStatusListener(_performanceStatusChanged); } void dispose() { _performance ..removeListener(_performanceChanged) ..removeStatusListener(_performanceStatusChanged) ..stop(); super.dispose(); } void _performanceChanged() { setState(() { // The performance's state is our build state, and it changed already. }); } LocalHistoryEntry _historyEntry; void _ensureHistoryEntry() { if (_historyEntry == null) { ModalRoute route = ModalRoute.of(context); if (route != null) { _historyEntry = new LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved); route.addLocalHistoryEntry(_historyEntry); } } } void _performanceStatusChanged(PerformanceStatus status) { switch (status) { case PerformanceStatus.forward: _ensureHistoryEntry(); break; case PerformanceStatus.reverse: _historyEntry?.remove(); _historyEntry = null; break; case PerformanceStatus.dismissed: break; case PerformanceStatus.completed: break; } } void _handleHistoryEntryRemoved() { _historyEntry = null; close(); } Performance _performance; double _width = _kEdgeDragWidth; void _handleSizeChanged(Size newSize) { setState(() { _width = newSize.width; }); } void _handlePointerDown(_) { _performance.stop(); _ensureHistoryEntry(); } void _move(double delta) { _performance.progress += delta / _width; } void _settle(Offset velocity) { if (_performance.isDismissed) return; if (velocity.dx.abs() >= _kMinFlingVelocity) { _performance.fling(velocity: velocity.dx / _width); } else if (_performance.progress < 0.5) { close(); } else { open(); } } void open() { _performance.fling(velocity: 1.0); } void close() { _performance.fling(velocity: -1.0); } final AnimatedColorValue _color = new AnimatedColorValue(Colors.transparent, end: Colors.black54); final GlobalKey _gestureDetectorKey = new GlobalKey(); Widget build(BuildContext context) { if (_performance.status == PerformanceStatus.dismissed) { return new Align( alignment: const FractionalOffset(0.0, 0.5), child: new GestureDetector( key: _gestureDetectorKey, onHorizontalDragUpdate: _move, onHorizontalDragEnd: _settle, behavior: HitTestBehavior.translucent, child: new Container(width: _kEdgeDragWidth) ) ); } else { _performance.updateVariable(_color); return new GestureDetector( key: _gestureDetectorKey, onHorizontalDragUpdate: _move, onHorizontalDragEnd: _settle, child: new RepaintBoundary( child: new Stack( children: <Widget>[ new GestureDetector( onTap: close, child: new DecoratedBox( decoration: new BoxDecoration( backgroundColor: _color.value ), child: new Container() ) ), new Align( alignment: const FractionalOffset(0.0, 0.5), child: new Listener( onPointerDown: _handlePointerDown, child: new Align( alignment: const FractionalOffset(1.0, 0.5), widthFactor: _performance.progress, child: new SizeObserver( onSizeChanged: _handleSizeChanged, child: new RepaintBoundary( child: new Focus( key: new GlobalObjectKey(config.key), child: config.child ) ) ) ) ) ) ] ) ) ); } } }