Chromium Code Reviews| 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 'dart:sky' as sky; | 5 import 'dart:sky' as sky; |
| 6 | 6 |
| 7 import '../animation/animation_performance.dart'; | 7 import '../animation/animation_performance.dart'; |
| 8 import '../animation/curves.dart'; | 8 import '../animation/curves.dart'; |
| 9 import '../theme/shadows.dart'; | 9 import '../theme/shadows.dart'; |
| 10 import 'animated_component.dart'; | 10 import 'animated_component.dart'; |
| 11 import 'animation_builder.dart'; | 11 import 'animation_builder.dart'; |
| 12 import 'basic.dart'; | 12 import 'basic.dart'; |
| 13 import 'scrollable_viewport.dart'; | 13 import 'scrollable_viewport.dart'; |
| 14 import 'navigator.dart'; | |
| 14 import 'theme.dart'; | 15 import 'theme.dart'; |
| 15 | 16 |
| 16 // TODO(eseidel): Draw width should vary based on device size: | 17 // TODO(eseidel): Draw width should vary based on device size: |
| 17 // http://www.google.com/design/spec/layout/structure.html#structure-side-nav | 18 // http://www.google.com/design/spec/layout/structure.html#structure-side-nav |
| 18 | 19 |
| 19 // Mobile: | 20 // Mobile: |
| 20 // Width = Screen width − 56 dp | 21 // Width = Screen width − 56 dp |
| 21 // Maximum width: 320dp | 22 // Maximum width: 320dp |
| 22 // Maximum width applies only when using a left nav. When using a right nav, | 23 // 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 // the panel can cover the full width of the screen. |
| 24 | 25 |
| 25 // Desktop/Tablet: | 26 // Desktop/Tablet: |
| 26 // Maximum width for a left nav is 400dp. | 27 // Maximum width for a left nav is 400dp. |
| 27 // The right nav can vary depending on content. | 28 // The right nav can vary depending on content. |
| 28 | 29 |
| 29 const double _kWidth = 304.0; | 30 const double _kWidth = 304.0; |
| 30 const double _kMinFlingVelocity = 0.4; | 31 const double _kMinFlingVelocity = 0.4; |
| 31 const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); | 32 const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); |
| 32 // TODO(mpcomplete): The curve must be linear if we want the drawer to track | 33 // TODO(mpcomplete): The curve must be linear if we want the drawer to track |
| 33 // the user's finger. Odeon remedies this by attaching spring forces to the | 34 // the user's finger. Odeon remedies this by attaching spring forces to the |
| 34 // initial timeline when animating (so it doesn't look linear). | 35 // initial timeline when animating (so it doesn't look linear). |
| 35 const Curve _kAnimationCurve = linear; | 36 const Curve _kAnimationCurve = linear; |
| 36 | 37 |
| 37 typedef void DrawerStatusChangeHandler (bool showing); | 38 typedef void DrawerStatusChangeHandler (bool showing); |
| 38 | 39 |
| 39 class DrawerController { | 40 class Drawer extends AnimatedComponent { |
| 40 DrawerController(this.onStatusChange) { | 41 Drawer({ |
| 41 builder = new AnimationBuilder() | 42 String key, |
| 43 this.children, | |
| 44 this.showing: false, | |
| 45 this.level: 0, | |
| 46 this.onStatusChange, | |
| 47 this.navigator | |
| 48 }) : super(key: key); | |
| 49 | |
| 50 List<Widget> children; | |
| 51 bool showing; | |
| 52 int level; | |
| 53 DrawerStatusChangeHandler onStatusChange; | |
| 54 Navigator navigator; | |
| 55 | |
| 56 AnimationPerformance _performance; | |
| 57 AnimationBuilder _builder; | |
| 58 | |
| 59 void initState() { | |
| 60 _builder = new AnimationBuilder() | |
| 42 ..position = new AnimatedType<Point>( | 61 ..position = new AnimatedType<Point>( |
| 43 new Point(-_kWidth, 0.0), end: Point.origin, curve: _kAnimationCurve); | 62 new Point(-_kWidth, 0.0), end: Point.origin, curve: _kAnimationCurve); |
| 44 performance = builder.createPerformance([builder.position], | 63 _performance = _builder.createPerformance([_builder.position], |
| 45 duration: _kBaseSettleDuration) | 64 duration: _kBaseSettleDuration) |
| 46 ..addListener(_checkValue); | 65 ..addListener(_checkValue); |
| 66 watchPerformance(_performance); | |
| 47 } | 67 } |
| 48 final DrawerStatusChangeHandler onStatusChange; | |
| 49 | 68 |
| 50 AnimationPerformance performance; | 69 void syncFields(Drawer source) { |
| 51 AnimationBuilder builder; | 70 const String kDrawerRouteName = "[open drawer]"; |
|
abarth-chromium
2015/07/11 02:00:54
I wonder if the navigation should let you use opaq
jackson
2015/07/14 18:25:50
I agree, I'll do this in a new CL.
| |
| 71 children = source.children; | |
| 72 level = source.level; | |
| 73 navigator = source.navigator; | |
| 74 if (showing != source.showing) { | |
| 75 showing = source.showing; | |
| 76 if (showing) { | |
| 77 if (navigator != null) { | |
| 78 navigator.pushState(kDrawerRouteName, (_) { | |
| 79 onStatusChange(false); | |
| 80 }); | |
| 81 } | |
| 82 _performance.play(); | |
| 83 } else { | |
| 84 if (navigator != null && navigator.currentRoute.name == kDrawerRouteName ) | |
| 85 navigator.pop(); | |
| 86 _performance.reverse(); | |
| 87 } | |
| 88 } | |
| 89 super.syncFields(source); | |
| 90 } | |
| 52 | 91 |
| 53 double get xPosition => builder.position.value.x; | 92 // TODO(mpcomplete): the animation system should handle building, maybe? Or |
| 93 // at least setting the transform. Figure out how this could work for things | |
| 94 // like fades, slides, rotates, pinch, etc. | |
| 95 Widget build() { | |
| 96 if (isClosed) { | |
| 97 return new Container(width: 0.0, height: 0.0); | |
| 98 } | |
|
abarth-chromium
2015/07/11 02:00:54
We should make an |Empty| widget so that this can
jackson
2015/07/14 18:25:49
This isn't needed now that we aren't building when
| |
| 99 | |
| 100 // TODO(mpcomplete): animate as a fade-in. | |
| 101 double scaler = _performance.progress; | |
| 102 Color maskColor = new Color.fromARGB((0x7F * scaler).floor(), 0, 0, 0); | |
| 103 | |
| 104 var mask = new Listener( | |
| 105 child: new Container(decoration: new BoxDecoration(backgroundColor: maskCo lor)), | |
| 106 onGestureTap: handleMaskTap | |
| 107 ); | |
| 108 | |
| 109 Widget content = _builder.build( | |
| 110 new Container( | |
| 111 decoration: new BoxDecoration( | |
| 112 backgroundColor: Theme.of(this).canvasColor, | |
| 113 boxShadow: shadows[level]), | |
| 114 width: _kWidth, | |
| 115 child: new ScrollableBlock(children) | |
| 116 )); | |
| 117 | |
| 118 return new Listener( | |
| 119 child: new Stack([ mask, content ]), | |
| 120 onPointerDown: handlePointerDown, | |
| 121 onPointerMove: handlePointerMove, | |
| 122 onPointerUp: handlePointerUp, | |
| 123 onPointerCancel: handlePointerCancel, | |
| 124 onGestureFlingStart: handleFlingStart | |
| 125 ); | |
| 126 } | |
| 127 | |
| 128 double get xPosition => _builder.position.value.x; | |
| 54 | 129 |
| 55 bool _oldClosedState = true; | 130 bool _oldClosedState = true; |
| 56 void _checkValue() { | 131 void _checkValue() { |
| 57 var newClosedState = isClosed; | 132 bool newClosedState = isClosed; |
| 58 if (onStatusChange != null && _oldClosedState != newClosedState) { | 133 if (onStatusChange != null && _oldClosedState != newClosedState) { |
| 59 onStatusChange(!newClosedState); | 134 onStatusChange(!newClosedState); |
| 60 _oldClosedState = newClosedState; | |
| 61 } | 135 } |
| 136 _oldClosedState = newClosedState; | |
| 62 } | 137 } |
| 63 | 138 |
| 64 bool get isClosed => performance.isDismissed; | 139 bool get isClosed => _performance.isDismissed; |
| 65 bool get _isMostlyClosed => xPosition <= -_kWidth/2; | 140 bool get _isMostlyClosed => xPosition <= -_kWidth/2; |
| 66 | 141 |
| 67 void open() => performance.play(); | 142 void open() => _performance.play(); |
| 68 | 143 |
| 69 void close() => performance.reverse(); | 144 void close() => _performance.reverse(); |
|
abarth-chromium
2015/07/11 02:00:54
We can delete most of these functions. They exist
jackson
2015/07/14 18:25:50
Done.
| |
| 70 | 145 |
| 71 void _settle() => _isMostlyClosed ? close() : open(); | 146 void _settle() => _isMostlyClosed ? close() : open(); |
| 72 | 147 |
| 73 void handleMaskTap(_) => close(); | 148 void handleMaskTap(_) => close(); |
|
abarth-chromium
2015/07/11 02:00:54
e.g., this function can just call _performance.rev
jackson
2015/07/14 18:25:49
Done.
| |
| 74 | 149 |
| 75 // TODO(mpcomplete): Figure out how to generalize these handlers on a | 150 // TODO(mpcomplete): Figure out how to generalize these handlers on a |
| 76 // "PannableThingy" interface. | 151 // "PannableThingy" interface. |
| 77 void handlePointerDown(_) => performance.stop(); | 152 void handlePointerDown(_) => _performance.stop(); |
| 78 | 153 |
| 79 void handlePointerMove(sky.PointerEvent event) { | 154 void handlePointerMove(sky.PointerEvent event) { |
| 80 if (performance.isAnimating) | 155 if (_performance.isAnimating) |
| 81 return; | 156 return; |
| 82 performance.progress += event.dx / _kWidth; | 157 _performance.progress += event.dx / _kWidth; |
| 83 } | 158 } |
| 84 | 159 |
| 85 void handlePointerUp(_) { | 160 void handlePointerUp(_) { |
| 86 if (!performance.isAnimating) | 161 if (!_performance.isAnimating) |
| 87 _settle(); | 162 _settle(); |
| 88 } | 163 } |
| 89 | 164 |
| 90 void handlePointerCancel(_) { | 165 void handlePointerCancel(_) { |
| 91 if (!performance.isAnimating) | 166 if (!_performance.isAnimating) |
| 92 _settle(); | 167 _settle(); |
| 93 } | 168 } |
| 94 | 169 |
| 95 void handleFlingStart(event) { | 170 void handleFlingStart(event) { |
| 96 double velocityX = event.velocityX / 1000; | 171 double velocityX = event.velocityX / 1000; |
| 97 if (velocityX.abs() >= _kMinFlingVelocity) | 172 if (velocityX.abs() >= _kMinFlingVelocity) |
| 98 performance.fling(velocity: velocityX / _kWidth); | 173 _performance.fling(velocity: velocityX / _kWidth); |
| 99 } | 174 } |
| 100 } | 175 } |
| 101 | |
| 102 class Drawer extends AnimatedComponent { | |
| 103 Drawer({ | |
| 104 String key, | |
| 105 this.controller, | |
| 106 this.children, | |
| 107 this.level: 0 | |
| 108 }) : super(key: key) { | |
| 109 watchPerformance(controller.performance); | |
| 110 } | |
| 111 | |
| 112 List<Widget> children; | |
| 113 int level; | |
| 114 DrawerController controller; | |
| 115 | |
| 116 void syncFields(Drawer source) { | |
| 117 children = source.children; | |
| 118 level = source.level; | |
| 119 controller = source.controller; | |
| 120 super.syncFields(source); | |
| 121 } | |
| 122 | |
| 123 // TODO(mpcomplete): the animation system should handle building, maybe? Or | |
| 124 // at least setting the transform. Figure out how this could work for things | |
| 125 // like fades, slides, rotates, pinch, etc. | |
| 126 Widget build() { | |
| 127 // TODO(mpcomplete): animate as a fade-in. | |
| 128 double scaler = controller.performance.progress; | |
| 129 Color maskColor = new Color.fromARGB((0x7F * scaler).floor(), 0, 0, 0); | |
| 130 | |
| 131 var mask = new Listener( | |
| 132 child: new Container(decoration: new BoxDecoration(backgroundColor: maskCo lor)), | |
| 133 onGestureTap: controller.handleMaskTap | |
| 134 ); | |
| 135 | |
| 136 Widget content = controller.builder.build( | |
| 137 new Container( | |
| 138 decoration: new BoxDecoration( | |
| 139 backgroundColor: Theme.of(this).canvasColor, | |
| 140 boxShadow: shadows[level]), | |
| 141 width: _kWidth, | |
| 142 child: new ScrollableBlock(children) | |
| 143 )); | |
| 144 | |
| 145 return new Listener( | |
| 146 child: new Stack([ mask, content ]), | |
| 147 onPointerDown: controller.handlePointerDown, | |
| 148 onPointerMove: controller.handlePointerMove, | |
| 149 onPointerUp: controller.handlePointerUp, | |
| 150 onPointerCancel: controller.handlePointerCancel, | |
| 151 onGestureFlingStart: controller.handleFlingStart | |
| 152 ); | |
| 153 } | |
| 154 | |
| 155 } | |
| OLD | NEW |