| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 import '../animation/animation.dart'; | 5 import '../animation/animated_value.dart'; |
| 6 import '../animation/curves.dart'; | 6 import '../animation/curves.dart'; |
| 7 import '../fn.dart'; | 7 import '../fn.dart'; |
| 8 import '../theme/colors.dart'; | 8 import '../theme/colors.dart'; |
| 9 import '../theme/shadows.dart'; | 9 import '../theme/shadows.dart'; |
| 10 import 'dart:async'; | 10 import 'dart:async'; |
| 11 import 'dart:math' as math; | 11 import 'dart:math' as math; |
| 12 import 'dart:sky' as sky; | 12 import 'dart:sky' as sky; |
| 13 import 'material.dart'; | 13 import 'material.dart'; |
| 14 | 14 |
| 15 const double _kWidth = 304.0; | 15 const double _kWidth = 304.0; |
| 16 const double _kMinFlingVelocity = 0.4; | 16 const double _kMinFlingVelocity = 0.4; |
| 17 const double _kBaseSettleDurationMS = 246.0; | 17 const double _kBaseSettleDurationMS = 246.0; |
| 18 const double _kMaxSettleDurationMS = 600.0; | 18 const double _kMaxSettleDurationMS = 600.0; |
| 19 const Curve _kAnimationCurve = parabolicRise; | 19 const Curve _kAnimationCurve = parabolicRise; |
| 20 | 20 |
| 21 class DrawerAnimation extends Animation { | 21 class DrawerController { |
| 22 Stream<double> get onPositionChanged => onValueChanged; | 22 final AnimatedValue position = new AnimatedValue(-_kWidth); |
| 23 | 23 |
| 24 bool get _isMostlyClosed => value <= -_kWidth / 2; | 24 bool get _isMostlyClosed => position.value <= -_kWidth / 2; |
| 25 | |
| 26 DrawerAnimation() { | |
| 27 value = -_kWidth; | |
| 28 } | |
| 29 | 25 |
| 30 void toggle(_) => _isMostlyClosed ? _open() : _close(); | 26 void toggle(_) => _isMostlyClosed ? _open() : _close(); |
| 31 | 27 |
| 32 void handleMaskTap(_) => _close(); | 28 void handleMaskTap(_) => _close(); |
| 33 | 29 |
| 34 void handlePointerDown(_) => stop(); | 30 void handlePointerDown(_) => position.stop(); |
| 35 | 31 |
| 36 void handlePointerMove(sky.PointerEvent event) { | 32 void handlePointerMove(sky.PointerEvent event) { |
| 37 if (isAnimating) | 33 if (position.isAnimating) |
| 38 return; | 34 return; |
| 39 value = math.min(0.0, math.max(value + event.dx, -_kWidth)); | 35 position.value = math.min(0.0, math.max(position.value + event.dx, -_kWidth)
); |
| 40 } | 36 } |
| 41 | 37 |
| 42 void handlePointerUp(_) { | 38 void handlePointerUp(_) { |
| 43 if (!isAnimating) | 39 if (!position.isAnimating) |
| 44 _settle(); | 40 _settle(); |
| 45 } | 41 } |
| 46 | 42 |
| 47 void handlePointerCancel(_) { | 43 void handlePointerCancel(_) { |
| 48 if (!isAnimating) | 44 if (!position.isAnimating) |
| 49 _settle(); | 45 _settle(); |
| 50 } | 46 } |
| 51 | 47 |
| 52 void _open() => _animateToPosition(0.0); | 48 void _open() => _animateToPosition(0.0); |
| 53 | 49 |
| 54 void _close() => _animateToPosition(-_kWidth); | 50 void _close() => _animateToPosition(-_kWidth); |
| 55 | 51 |
| 56 void _settle() => _isMostlyClosed ? _close() : _open(); | 52 void _settle() => _isMostlyClosed ? _close() : _open(); |
| 57 | 53 |
| 58 void _animateToPosition(double targetPosition) { | 54 void _animateToPosition(double targetPosition) { |
| 59 double distance = (targetPosition - value).abs(); | 55 double distance = (targetPosition - position.value).abs(); |
| 60 if (distance != 0) { | 56 if (distance != 0) { |
| 61 double targetDuration = distance / _kWidth * _kBaseSettleDurationMS; | 57 double targetDuration = distance / _kWidth * _kBaseSettleDurationMS; |
| 62 double duration = math.min(targetDuration, _kMaxSettleDurationMS); | 58 double duration = math.min(targetDuration, _kMaxSettleDurationMS); |
| 63 animateTo(targetPosition, duration, curve: _kAnimationCurve); | 59 position.animateTo(targetPosition, duration, curve: _kAnimationCurve); |
| 64 } | 60 } |
| 65 } | 61 } |
| 66 | 62 |
| 67 void handleFlingStart(event) { | 63 void handleFlingStart(event) { |
| 68 double direction = event.velocityX.sign; | 64 double direction = event.velocityX.sign; |
| 69 double velocityX = event.velocityX.abs() / 1000; | 65 double velocityX = event.velocityX.abs() / 1000; |
| 70 if (velocityX < _kMinFlingVelocity) | 66 if (velocityX < _kMinFlingVelocity) |
| 71 return; | 67 return; |
| 72 | 68 |
| 73 double targetPosition = direction < 0.0 ? -_kWidth : 0.0; | 69 double targetPosition = direction < 0.0 ? -_kWidth : 0.0; |
| 74 double distance = (targetPosition - value).abs(); | 70 double distance = (targetPosition - position.value).abs(); |
| 75 double duration = distance / velocityX; | 71 double duration = distance / velocityX; |
| 76 | 72 |
| 77 animateTo(targetPosition, duration, curve: linear); | 73 position.animateTo(targetPosition, duration, curve: linear); |
| 78 } | 74 } |
| 79 } | 75 } |
| 80 | 76 |
| 81 class Drawer extends Component { | 77 class Drawer extends Component { |
| 82 static final Style _style = new Style(''' | 78 static final Style _style = new Style(''' |
| 83 position: absolute; | 79 position: absolute; |
| 84 top: 0; | 80 top: 0; |
| 85 left: 0; | 81 left: 0; |
| 86 bottom: 0; | 82 bottom: 0; |
| 87 right: 0;''' | 83 right: 0;''' |
| 88 ); | 84 ); |
| 89 | 85 |
| 90 static final Style _maskStyle = new Style(''' | 86 static final Style _maskStyle = new Style(''' |
| 91 background-color: black; | 87 background-color: black; |
| 92 will-change: opacity; | 88 will-change: opacity; |
| 93 position: absolute; | 89 position: absolute; |
| 94 top: 0; | 90 top: 0; |
| 95 left: 0; | 91 left: 0; |
| 96 bottom: 0; | 92 bottom: 0; |
| 97 right: 0;''' | 93 right: 0;''' |
| 98 ); | 94 ); |
| 99 | 95 |
| 100 static final Style _contentStyle = new Style(''' | 96 static final Style _contentStyle = new Style(''' |
| 101 background-color: ${Grey[50]}; | 97 background-color: ${Grey[50]}; |
| 102 will-change: transform; | 98 will-change: transform; |
| 103 position: absolute; | 99 position: absolute; |
| 104 width: 304px; | 100 width: ${_kWidth}px; |
| 105 top: 0; | 101 top: 0; |
| 106 left: 0; | 102 left: 0; |
| 107 bottom: 0;''' | 103 bottom: 0;''' |
| 108 ); | 104 ); |
| 109 | 105 |
| 110 DrawerAnimation animation; | |
| 111 List<Node> children; | 106 List<Node> children; |
| 112 int level; | 107 int level; |
| 108 DrawerController controller; |
| 109 |
| 110 AnimatedValueListener _position; |
| 113 | 111 |
| 114 Drawer({ | 112 Drawer({ |
| 115 Object key, | 113 Object key, |
| 116 this.animation, | 114 this.controller, |
| 117 this.children, | 115 this.children, |
| 118 this.level: 0 | 116 this.level: 0 |
| 119 }) : super(key: key) { | 117 }) : super(key: key) { |
| 120 events.listen('pointerdown', animation.handlePointerDown); | 118 events.listen('pointerdown', controller.handlePointerDown); |
| 121 events.listen('pointermove', animation.handlePointerMove); | 119 events.listen('pointermove', controller.handlePointerMove); |
| 122 events.listen('pointerup', animation.handlePointerUp); | 120 events.listen('pointerup', controller.handlePointerUp); |
| 123 events.listen('pointercancel', animation.handlePointerCancel); | 121 events.listen('pointercancel', controller.handlePointerCancel); |
| 122 _position = new AnimatedValueListener(this, controller.position); |
| 124 } | 123 } |
| 125 | 124 |
| 126 double _position = -_kWidth; | 125 void didUnmount() { |
| 127 | 126 _position.stopListening(); |
| 128 bool _listening = false; | |
| 129 | |
| 130 void _ensureListening() { | |
| 131 if (_listening) | |
| 132 return; | |
| 133 | |
| 134 _listening = true; | |
| 135 animation.onPositionChanged.listen((position) { | |
| 136 setState(() { | |
| 137 _position = position; | |
| 138 }); | |
| 139 }); | |
| 140 } | 127 } |
| 141 | 128 |
| 142 Node build() { | 129 Node build() { |
| 143 _ensureListening(); | 130 _position.ensureListening(); |
| 144 | 131 |
| 145 bool isClosed = _position <= -_kWidth; | 132 bool isClosed = _position.value <= -_kWidth; |
| 146 String inlineStyle = 'display: ${isClosed ? 'none' : ''}'; | 133 String inlineStyle = 'display: ${isClosed ? 'none' : ''}'; |
| 147 String maskInlineStyle = 'opacity: ${(_position / _kWidth + 1) * 0.5}'; | 134 String maskInlineStyle = 'opacity: ${(_position.value / _kWidth + 1) * 0.5}'
; |
| 148 String contentInlineStyle = 'transform: translateX(${_position}px)'; | 135 String contentInlineStyle = 'transform: translateX(${_position.value}px)'; |
| 149 | 136 |
| 150 Container mask = new Container( | 137 Container mask = new Container( |
| 151 key: 'Mask', | 138 key: 'Mask', |
| 152 style: _maskStyle, | 139 style: _maskStyle, |
| 153 inlineStyle: maskInlineStyle | 140 inlineStyle: maskInlineStyle |
| 154 )..events.listen('gesturetap', animation.handleMaskTap) | 141 )..events.listen('gesturetap', controller.handleMaskTap) |
| 155 ..events.listen('gestureflingstart', animation.handleFlingStart); | 142 ..events.listen('gestureflingstart', controller.handleFlingStart); |
| 156 | 143 |
| 157 Material content = new Material( | 144 Material content = new Material( |
| 158 key: 'Content', | 145 key: 'Content', |
| 159 style: _contentStyle, | 146 style: _contentStyle, |
| 160 inlineStyle: contentInlineStyle, | 147 inlineStyle: contentInlineStyle, |
| 161 children: children, | 148 children: children, |
| 162 level: level | 149 level: level |
| 163 ); | 150 ); |
| 164 | 151 |
| 165 return new Container( | 152 return new Container( |
| 166 style: _style, | 153 style: _style, |
| 167 inlineStyle: inlineStyle, | 154 inlineStyle: inlineStyle, |
| 168 children: [ mask, content ] | 155 children: [ mask, content ] |
| 169 ); | 156 ); |
| 170 } | 157 } |
| 171 } | 158 } |
| OLD | NEW |