| 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:async'; | |
| 6 import 'dart:math' as math; | 5 import 'dart:math' as math; |
| 7 import 'dart:sky' as sky; | 6 import 'dart:sky' as sky; |
| 8 | 7 |
| 9 import 'package:sky/animation/animation_performance.dart'; | 8 import 'package:sky/animation/animation_performance.dart'; |
| 10 import 'package:sky/painting/box_painter.dart'; | 9 import 'package:sky/painting/box_painter.dart'; |
| 11 import 'package:sky/theme/colors.dart'; | 10 import 'package:sky/theme/colors.dart'; |
| 12 import 'package:sky/theme/shadows.dart'; | 11 import 'package:sky/theme/shadows.dart'; |
| 13 import 'package:sky/widgets/animated_component.dart'; | 12 import 'package:sky/widgets/animated_component.dart'; |
| 14 import 'package:sky/widgets/basic.dart'; | 13 import 'package:sky/widgets/basic.dart'; |
| 15 import 'package:sky/widgets/popup_menu_item.dart'; | 14 import 'package:sky/widgets/popup_menu_item.dart'; |
| 16 import 'package:sky/widgets/scrollable_viewport.dart'; | 15 import 'package:sky/widgets/scrollable_viewport.dart'; |
| 17 | 16 |
| 18 const Duration _kMenuOpenDuration = const Duration(milliseconds: 300); | 17 const Duration _kMenuOpenDuration = const Duration(milliseconds: 300); |
| 19 const Duration _kMenuCloseDuration = const Duration(milliseconds: 200); | 18 const Duration _kMenuCloseDuration = const Duration(milliseconds: 200); |
| 20 const Duration _kMenuCloseDelay = const Duration(milliseconds: 100); | 19 const Duration _kMenuCloseDelay = const Duration(milliseconds: 100); |
| 21 const double _kMenuWidthStep = 56.0; | 20 const double _kMenuWidthStep = 56.0; |
| 22 const double _kMenuMargin = 16.0; // 24.0 on tablet | 21 const double _kMenuMargin = 16.0; // 24.0 on tablet |
| 23 const double _kMenuMinWidth = 2.0 * _kMenuWidthStep; | 22 const double _kMenuMinWidth = 2.0 * _kMenuWidthStep; |
| 24 const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep; | 23 const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep; |
| 25 const double _kMenuHorizontalPadding = 16.0; | 24 const double _kMenuHorizontalPadding = 16.0; |
| 26 const double _kMenuVerticalPadding = 8.0; | 25 const double _kMenuVerticalPadding = 8.0; |
| 27 | 26 |
| 28 enum MenuState { closed, opening, open, closing } | 27 enum PopupMenuStatus { |
| 28 active, |
| 29 inactive, |
| 30 } |
| 29 | 31 |
| 30 class PopupMenuController { | 32 typedef void PopupMenuStatusChangedCallback(PopupMenuStatus status); |
| 31 | |
| 32 PopupMenuController() { | |
| 33 position = new AnimatedType<double>(0.0, end: 1.0); | |
| 34 performance = new AnimationPerformance() | |
| 35 ..variable = position | |
| 36 ..addListener(_updateState); | |
| 37 } | |
| 38 | |
| 39 AnimatedType<double> position; | |
| 40 AnimationPerformance performance; | |
| 41 | |
| 42 MenuState _state = MenuState.closed; | |
| 43 MenuState get state => _state; | |
| 44 | |
| 45 bool get canReact => (_state == MenuState.opening) || (_state == MenuState.ope
n); | |
| 46 | |
| 47 void _updateState() { | |
| 48 if (position.value == 0.0) { | |
| 49 _state = MenuState.closed; | |
| 50 if (_closeCompleter != null) | |
| 51 _closeCompleter.complete(); | |
| 52 return; | |
| 53 } | |
| 54 | |
| 55 if (position.value == 1.0) | |
| 56 _state = MenuState.open; | |
| 57 } | |
| 58 | |
| 59 Completer _closeCompleter; | |
| 60 Timer _closeTimer; | |
| 61 | |
| 62 void open() { | |
| 63 if (_state != MenuState.closed) | |
| 64 return; | |
| 65 if (_closeTimer != null) { | |
| 66 _closeTimer.cancel(); | |
| 67 _closeTimer = null; | |
| 68 } | |
| 69 _closeCompleter = null; | |
| 70 _state = MenuState.opening; | |
| 71 performance..duration = _kMenuOpenDuration | |
| 72 ..play(); | |
| 73 } | |
| 74 | |
| 75 Future close() { | |
| 76 if (_state == MenuState.closing || _state == MenuState.closed) | |
| 77 return _closeCompleter.future; | |
| 78 | |
| 79 _state = MenuState.closing; | |
| 80 assert(_closeCompleter == null); | |
| 81 _closeCompleter = new Completer(); | |
| 82 performance.duration = _kMenuCloseDuration; | |
| 83 | |
| 84 assert(_closeTimer == null); | |
| 85 _closeTimer = new Timer(_kMenuCloseDelay, performance.reverse); | |
| 86 | |
| 87 return _closeCompleter.future; | |
| 88 } | |
| 89 } | |
| 90 | 33 |
| 91 class PopupMenu extends AnimatedComponent { | 34 class PopupMenu extends AnimatedComponent { |
| 92 | 35 |
| 93 PopupMenu({ String key, this.controller, this.items, this.level }) | 36 PopupMenu({ |
| 94 : super(key: key); | 37 String key, |
| 38 this.showing, |
| 39 this.onStatusChanged, |
| 40 this.items, |
| 41 this.level |
| 42 }) : super(key: key); |
| 95 | 43 |
| 96 PopupMenuController controller; | 44 bool showing; |
| 45 PopupMenuStatusChangedCallback onStatusChanged; |
| 97 List<PopupMenuItem> items; | 46 List<PopupMenuItem> items; |
| 98 int level; | 47 int level; |
| 99 | 48 |
| 49 AnimatedType<double> _position; |
| 50 AnimationPerformance _performance; |
| 51 |
| 100 void initState() { | 52 void initState() { |
| 53 _position = new AnimatedType<double>(0.0, end: 1.0); |
| 54 _performance = new AnimationPerformance() |
| 55 ..variable = _position |
| 56 ..addListener(_checkForStateChanged); |
| 57 watch(_performance); |
| 58 _updateBoxPainter(); |
| 59 if (showing) |
| 60 _open(); |
| 61 } |
| 62 |
| 63 void syncFields(PopupMenu source) { |
| 64 if (showing != source.showing) { |
| 65 showing = source.showing; |
| 66 if (showing) |
| 67 _open(); |
| 68 else |
| 69 _close(); |
| 70 } |
| 71 onStatusChanged = source.onStatusChanged; |
| 72 if (level != source.level) { |
| 73 level = source.level; |
| 74 _updateBoxPainter(); |
| 75 } |
| 76 items = source.items; |
| 77 super.syncFields(source); |
| 78 } |
| 79 |
| 80 void _updateBoxPainter() { |
| 101 _painter = new BoxPainter(new BoxDecoration( | 81 _painter = new BoxPainter(new BoxDecoration( |
| 102 backgroundColor: Grey[50], | 82 backgroundColor: Grey[50], |
| 103 borderRadius: 2.0, | 83 borderRadius: 2.0, |
| 104 boxShadow: shadows[level])); | 84 boxShadow: shadows[level])); |
| 105 watch(controller.performance); | |
| 106 } | 85 } |
| 107 | 86 |
| 108 void syncFields(PopupMenu source) { | 87 PopupMenuStatus get _status => _position.value != 0.0 ? PopupMenuStatus.active
: PopupMenuStatus.inactive; |
| 109 controller = source.controller; | 88 |
| 110 items = source.items; | 89 PopupMenuStatus _lastStatus; |
| 111 level = source.level; | 90 void _checkForStateChanged() { |
| 112 super.syncFields(source); | 91 PopupMenuStatus status = _status; |
| 92 if (_lastStatus != null && status != _lastStatus && onStatusChanged != null) |
| 93 onStatusChanged(status); |
| 94 _lastStatus = status; |
| 95 } |
| 96 |
| 97 void _open() { |
| 98 _performance |
| 99 ..duration = _kMenuOpenDuration |
| 100 ..play(); |
| 101 } |
| 102 |
| 103 void _close() { |
| 104 _performance |
| 105 ..duration = _kMenuCloseDuration |
| 106 ..reverse(); |
| 113 } | 107 } |
| 114 | 108 |
| 115 BoxPainter _painter; | 109 BoxPainter _painter; |
| 116 | 110 |
| 117 double _opacityFor(int i) { | 111 double _opacityFor(int i) { |
| 118 assert(controller.position.value != null); | 112 assert(_position.value != null); |
| 119 if (controller.position.value == null || controller.position.value == 1.0) | 113 if (_position.value == null || _position.value == 1.0) |
| 120 return 1.0; | 114 return 1.0; |
| 121 double unit = 1.0 / items.length; | 115 double unit = 1.0 / items.length; |
| 122 double duration = 1.5 * unit; | 116 double duration = 1.5 * unit; |
| 123 double start = i * unit; | 117 double start = i * unit; |
| 124 return math.max(0.0, math.min(1.0, (controller.position.value - start) / dur
ation)); | 118 return math.max(0.0, math.min(1.0, (_position.value - start) / duration)); |
| 125 } | 119 } |
| 126 | 120 |
| 127 Widget build() { | 121 Widget build() { |
| 128 int i = 0; | 122 int i = 0; |
| 129 List<Widget> children = new List.from(items.map((Widget item) { | 123 List<Widget> children = new List.from(items.map((Widget item) { |
| 130 double opacity = _opacityFor(i); | 124 double opacity = _opacityFor(i); |
| 131 return new PopupMenuItem(child: item, opacity: opacity); | 125 return new PopupMenuItem(child: item, opacity: opacity); |
| 132 })); | 126 })); |
| 133 | 127 |
| 134 return new Opacity( | 128 return new Opacity( |
| 135 opacity: math.min(1.0, controller.position.value * 3.0), | 129 opacity: math.min(1.0, _position.value * 3.0), |
| 136 child: new Container( | 130 child: new Container( |
| 137 margin: new EdgeDims.all(_kMenuMargin), | 131 margin: new EdgeDims.all(_kMenuMargin), |
| 138 child: new CustomPaint( | 132 child: new CustomPaint( |
| 139 callback: (sky.Canvas canvas, Size size) { | 133 callback: (sky.Canvas canvas, Size size) { |
| 140 double width = math.min(size.width, size.width * (0.5 + controller.p
osition.value * 2.0)); | 134 double width = math.min(size.width, size.width * (0.5 + _position.va
lue * 2.0)); |
| 141 double height = math.min(size.height, size.height * controller.posit
ion.value * 1.5); | 135 double height = math.min(size.height, size.height * _position.value
* 1.5); |
| 142 _painter.paint(canvas, new Rect.fromLTRB(size.width - width, 0.0, wi
dth, height)); | 136 _painter.paint(canvas, new Rect.fromLTRB(size.width - width, 0.0, wi
dth, height)); |
| 143 }, | 137 }, |
| 144 child: new ConstrainedBox( | 138 child: new ConstrainedBox( |
| 145 constraints: new BoxConstraints( | 139 constraints: new BoxConstraints( |
| 146 minWidth: _kMenuMinWidth, | 140 minWidth: _kMenuMinWidth, |
| 147 maxWidth: _kMenuMaxWidth | 141 maxWidth: _kMenuMaxWidth |
| 148 ), | 142 ), |
| 149 child: new ShrinkWrapWidth( | 143 child: new ShrinkWrapWidth( |
| 150 stepWidth: _kMenuWidthStep, | 144 stepWidth: _kMenuWidthStep, |
| 151 child: new ScrollableViewport( | 145 child: new ScrollableViewport( |
| 152 child: new Container( | 146 child: new Container( |
| 153 padding: const EdgeDims.symmetric( | 147 padding: const EdgeDims.symmetric( |
| 154 horizontal: _kMenuHorizontalPadding, | 148 horizontal: _kMenuHorizontalPadding, |
| 155 vertical: _kMenuVerticalPadding | 149 vertical: _kMenuVerticalPadding |
| 156 ), | 150 ), |
| 157 child: new Block(children) | 151 child: new Block(children) |
| 158 ) | 152 ) |
| 159 ) | 153 ) |
| 160 ) | 154 ) |
| 161 ) | 155 ) |
| 162 ) | 156 ) |
| 163 ) | 157 ) |
| 164 ); | 158 ); |
| 165 } | 159 } |
| 166 | 160 |
| 167 } | 161 } |
| OLD | NEW |