| 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
|
| );
|
|
|