| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 import '../animation/animated_value.dart'; | |
| 6 import '../animation/curves.dart'; | |
| 7 import '../fn2.dart'; | |
| 8 import '../theme2/colors.dart'; | |
| 9 import '../theme2/shadows.dart'; | |
| 10 import 'animated_component.dart'; | |
| 11 import 'dart:math' as math; | |
| 12 import 'dart:sky' as sky; | |
| 13 import 'material.dart'; | |
| 14 import 'package:vector_math/vector_math.dart'; | |
| 15 | |
| 16 // TODO(eseidel): Draw width should vary based on device size: | |
| 17 // http://www.google.com/design/spec/layout/structure.html#structure-side-nav | |
| 18 | |
| 19 // Mobile: | |
| 20 // Width = Screen width − 56 dp | |
| 21 // Maximum width: 320dp | |
| 22 // Maximum width applies only when using a left nav. When using a right nav, | |
| 23 // the panel can cover the full width of the screen. | |
| 24 | |
| 25 // Desktop/Tablet: | |
| 26 // Maximum width for a left nav is 400dp. | |
| 27 // The right nav can vary depending on content. | |
| 28 | |
| 29 const double _kWidth = 304.0; | |
| 30 const double _kMinFlingVelocity = 0.4; | |
| 31 const double _kBaseSettleDurationMS = 246.0; | |
| 32 const double _kMaxSettleDurationMS = 600.0; | |
| 33 const Curve _kAnimationCurve = parabolicRise; | |
| 34 | |
| 35 typedef void DrawerStatusChangeHandler (bool showing); | |
| 36 | |
| 37 class DrawerController { | |
| 38 | |
| 39 DrawerController(this.onStatusChange) { | |
| 40 position = new AnimatedValue(-_kWidth, onChange: _checkValue); | |
| 41 } | |
| 42 final DrawerStatusChangeHandler onStatusChange; | |
| 43 AnimatedValue position; | |
| 44 | |
| 45 bool _oldClosedState = true; | |
| 46 void _checkValue() { | |
| 47 var newClosedState = isClosed; | |
| 48 if (onStatusChange != null && _oldClosedState != newClosedState) { | |
| 49 onStatusChange(!newClosedState); | |
| 50 _oldClosedState = newClosedState; | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 bool get isClosed => position.value == -_kWidth; | |
| 55 bool get _isMostlyClosed => position.value <= -_kWidth / 2; | |
| 56 void toggle() => _isMostlyClosed ? _open() : _close(); | |
| 57 | |
| 58 void handleMaskTap(_) => _close(); | |
| 59 void handlePointerDown(_) => position.stop(); | |
| 60 | |
| 61 void handlePointerMove(sky.PointerEvent event) { | |
| 62 if (position.isAnimating) | |
| 63 return; | |
| 64 position.value = math.min(0.0, math.max(position.value + event.dx, -_kWidth)
); | |
| 65 } | |
| 66 | |
| 67 void handlePointerUp(_) { | |
| 68 if (!position.isAnimating) | |
| 69 _settle(); | |
| 70 } | |
| 71 | |
| 72 void handlePointerCancel(_) { | |
| 73 if (!position.isAnimating) | |
| 74 _settle(); | |
| 75 } | |
| 76 | |
| 77 void _open() => _animateToPosition(0.0); | |
| 78 | |
| 79 void _close() => _animateToPosition(-_kWidth); | |
| 80 | |
| 81 void _settle() => _isMostlyClosed ? _close() : _open(); | |
| 82 | |
| 83 void _animateToPosition(double targetPosition) { | |
| 84 double distance = (targetPosition - position.value).abs(); | |
| 85 if (distance != 0) { | |
| 86 double targetDuration = distance / _kWidth * _kBaseSettleDurationMS; | |
| 87 double duration = math.min(targetDuration, _kMaxSettleDurationMS); | |
| 88 position.animateTo(targetPosition, duration, curve: _kAnimationCurve); | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 void handleFlingStart(event) { | |
| 93 double direction = event.velocityX.sign; | |
| 94 double velocityX = event.velocityX.abs() / 1000; | |
| 95 if (velocityX < _kMinFlingVelocity) | |
| 96 return; | |
| 97 | |
| 98 double targetPosition = direction < 0.0 ? -_kWidth : 0.0; | |
| 99 double distance = (targetPosition - position.value).abs(); | |
| 100 double duration = distance / velocityX; | |
| 101 | |
| 102 if (distance > 0) | |
| 103 position.animateTo(targetPosition, duration, curve: linear); | |
| 104 } | |
| 105 | |
| 106 } | |
| 107 | |
| 108 class Drawer extends AnimatedComponent { | |
| 109 | |
| 110 Drawer({ | |
| 111 Object key, | |
| 112 this.controller, | |
| 113 this.children, | |
| 114 this.level: 0 | |
| 115 }) : super(key: key) { | |
| 116 animate(controller.position, (double value) { | |
| 117 _position = value; | |
| 118 }); | |
| 119 } | |
| 120 | |
| 121 List<UINode> children; | |
| 122 int level; | |
| 123 DrawerController controller; | |
| 124 | |
| 125 void syncFields(Drawer source) { | |
| 126 children = source.children; | |
| 127 level = source.level; | |
| 128 controller = source.controller; | |
| 129 super.syncFields(source); | |
| 130 } | |
| 131 | |
| 132 double _position; | |
| 133 | |
| 134 UINode build() { | |
| 135 Matrix4 transform = new Matrix4.identity(); | |
| 136 transform.translate(_position); | |
| 137 | |
| 138 double scaler = _position / _kWidth + 1; | |
| 139 Color maskColor = new Color.fromARGB((0x7F * scaler).floor(), 0, 0, 0); | |
| 140 | |
| 141 var mask = new EventListenerNode( | |
| 142 new Container(decoration: new BoxDecoration(backgroundColor: maskColor)), | |
| 143 onGestureTap: controller.handleMaskTap, | |
| 144 onGestureFlingStart: controller.handleFlingStart | |
| 145 ); | |
| 146 | |
| 147 Container content = new Container( | |
| 148 decoration: new BoxDecoration( | |
| 149 backgroundColor: Grey[50], | |
| 150 boxShadow: Shadow[level]), | |
| 151 width: _kWidth, | |
| 152 transform: transform, | |
| 153 child: new Block(children) | |
| 154 ); | |
| 155 | |
| 156 return new EventListenerNode( | |
| 157 new Stack([ mask, content ]), | |
| 158 onPointerDown: controller.handlePointerDown, | |
| 159 onPointerMove: controller.handlePointerMove, | |
| 160 onPointerUp: controller.handlePointerUp, | |
| 161 onPointerCancel: controller.handlePointerCancel | |
| 162 ); | |
| 163 } | |
| 164 | |
| 165 } | |
| OLD | NEW |