Index: sky/framework/components/popup_menu.dart |
diff --git a/sky/framework/components/popup_menu.dart b/sky/framework/components/popup_menu.dart |
index 9d9d7a8e513211c7545447e4f2646e5c857c7b5a..a7dbea68129a2ebd6971743688a6bc740f08457f 100644 |
--- a/sky/framework/components/popup_menu.dart |
+++ b/sky/framework/components/popup_menu.dart |
@@ -5,51 +5,106 @@ |
import '../animation/animated_value.dart'; |
import '../fn.dart'; |
import '../theme/colors.dart'; |
+import '../theme/view-configuration.dart'; |
+import 'dart:math' as math; |
+import 'dart:sky' as sky; |
import 'material.dart'; |
import 'popup_menu_item.dart'; |
-const double _kItemInitialOpacity = 0.0; |
-const double _kItemFinalOpacity = 1.0; |
-const double _kItemFadeDuration = 500.0; |
-const double _kItemFadeDelay = 200.0; |
+const double _kItemFadeDuration = 300.0; |
+const double _kItemFadeDelay = 100.0; |
+const double _kMenuExpandDuration = 300.0; |
+ |
+class PopupMenuController { |
+ bool isOpen = false; |
+ AnimatedValue position = new AnimatedValue(0.0); |
+ |
+ void open() { |
+ isOpen = true; |
+ position.animateTo(1.0, _kMenuExpandDuration); |
+ } |
+ |
+ void close() { |
+ position.animateTo(0.0, _kMenuExpandDuration); |
+ // TODO(abarth): We shouldn't mark the menu as closed until the animation |
+ // completes. |
+ isOpen = false; |
+ } |
+} |
class PopupMenu extends Component { |
static final Style _style = new Style(''' |
border-radius: 2px; |
padding: 8px 0; |
+ box-sizing: border-box; |
background-color: ${Grey[50]};''' |
); |
List<List<Node>> items; |
int level; |
+ PopupMenuController controller; |
+ |
+ AnimatedValueListener _position; |
List<AnimatedValue> _opacities; |
+ int _width; |
+ int _height; |
- PopupMenu({ Object key, this.items, this.level }) : super(key: key) { |
- _opacities = new List.from(items.map( |
- (item) => new AnimatedValue(_kItemInitialOpacity))); |
+ PopupMenu({ Object key, this.controller, this.items, this.level }) |
+ : super(key: key) { |
+ _position = new AnimatedValueListener(this, controller.position); |
} |
- // TODO(abarth): Rather than using didMount, we should have the parent |
- // component kick off these animations. |
- void didMount() { |
+ void _ensureItemAnimations() { |
+ if (_opacities != null && controller.isOpen) |
+ return; |
+ _opacities = new List.from(items.map((_) => new AnimatedValue(0.0))); |
int i = 0; |
_opacities.forEach((opacity) { |
- opacity.animateTo(_kItemFinalOpacity, _kItemFadeDuration, |
- initialDelay: _kItemFadeDelay * i++); |
+ opacity.animateTo(1.0, _kItemFadeDuration, |
+ initialDelay: _kItemFadeDelay * ++i); |
+ }); |
+ } |
+ |
+ String _inlineStyle() { |
+ double value = _position.value; |
+ if (value == null || value == 1.0 || _height == null || _width == null) |
+ return null; |
+ return ''' |
+ opacity: ${math.min(1.0, value * 1.5)}; |
+ width: ${math.min(_width, _width * value * 3.0)}px; |
+ height: ${_height * value}px;'''; |
+ } |
+ |
+ void didMount() { |
+ setState(() { |
+ var root = getRoot(); |
+ _width = root.clientWidth; |
+ _height = root.clientHeight; |
}); |
} |
+ void didUnmount() { |
+ _position.stopListening(); |
+ } |
+ |
Node build() { |
+ _position.ensureListening(); |
+ _ensureItemAnimations(); |
+ |
List<Node> children = []; |
- int i = 0; |
- items.forEach((List<Node> item) { |
- children.add( |
- new PopupMenuItem(key: i, children: item, opacity: _opacities[i])); |
- ++i; |
- }); |
+ |
+ if (controller.isOpen) { |
+ int i = 0; |
+ items.forEach((List<Node> item) { |
+ children.add( |
+ new PopupMenuItem(key: i, children: item, opacity: _opacities[i])); |
+ ++i; |
+ }); |
+ } |
return new Material( |
style: _style, |
+ inlineStyle: _inlineStyle(), |
children: children, |
level: level |
); |