Commit d89fb146 authored by Matt Perry's avatar Matt Perry

Push PopupMenu's animation code into its build function and use transitions.

Also add intervals and curves to AnimationPerformance, which affect all
variables used by the performance.
parent 870376da
......@@ -15,15 +15,15 @@ abstract class AnimatedVariable {
String toString();
}
abstract class CurvedVariable implements AnimatedVariable {
CurvedVariable({this.interval, this.reverseInterval, this.curve, this.reverseCurve});
class AnimationTiming {
AnimationTiming({this.interval, this.reverseInterval, this.curve, this.reverseCurve});
Interval interval;
Interval reverseInterval;
Curve curve;
Curve reverseCurve;
double _transform(double t, Direction direction) {
double transform(double t, Direction direction) {
Interval interval = _getInterval(direction);
if (interval != null)
t = interval.transform(t);
......@@ -48,7 +48,7 @@ abstract class CurvedVariable implements AnimatedVariable {
}
}
class AnimatedValue<T extends dynamic> extends CurvedVariable {
class AnimatedValue<T extends dynamic> extends AnimationTiming implements AnimatedVariable {
AnimatedValue(this.begin, { this.end, Interval interval, Curve curve, Curve reverseCurve })
: super(interval: interval, curve: curve, reverseCurve: reverseCurve) {
value = begin;
......@@ -62,7 +62,7 @@ class AnimatedValue<T extends dynamic> extends CurvedVariable {
void setProgress(double t, Direction direction) {
if (end != null) {
t = _transform(t, direction);
t = transform(t, direction);
value = (t == 1.0) ? end : lerp(t);
}
}
......@@ -70,14 +70,14 @@ class AnimatedValue<T extends dynamic> extends CurvedVariable {
String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)';
}
class AnimatedList extends CurvedVariable {
class AnimatedList extends AnimationTiming implements AnimatedVariable {
List<AnimatedVariable> variables;
AnimatedList(this.variables, { Interval interval, Curve curve, Curve reverseCurve })
: super(interval: interval, curve: curve, reverseCurve: reverseCurve);
void setProgress(double t, Direction direction) {
double adjustedTime = _transform(t, direction);
double adjustedTime = transform(t, direction);
for (AnimatedVariable variable in variables)
variable.setProgress(adjustedTime, direction);
}
......
......@@ -46,6 +46,8 @@ class AnimationPerformance {
Direction _curveDirection;
Direction get curveDirection => _curveDirection;
AnimationTiming timing;
// If non-null, animate with this force instead of a tween animation.
Force attachedForce;
......@@ -67,6 +69,10 @@ class AnimationPerformance {
_checkStatusChanged();
}
double get curvedProgress {
return timing != null ? timing.transform(progress, curveDirection) : progress;
}
bool get isDismissed => status == AnimationStatus.dismissed;
bool get isCompleted => status == AnimationStatus.completed;
bool get isAnimating => timeline.isAnimating;
......@@ -82,7 +88,7 @@ class AnimationPerformance {
}
void updateVariable(AnimatedVariable variable) {
variable.setProgress(progress, curveDirection);
variable.setProgress(curvedProgress, curveDirection);
}
Future play([Direction direction = Direction.forward]) {
......@@ -165,7 +171,7 @@ class AnimationPerformance {
void _tick(double t) {
_updateCurveDirection();
if (variable != null)
variable.setProgress(t, curveDirection);
variable.setProgress(curvedProgress, curveDirection);
_notifyListeners();
_checkStatusChanged();
}
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:async';
import 'dart:sky' as sky;
import 'package:sky/animation/animated_value.dart';
......@@ -10,11 +10,11 @@ import 'package:sky/animation/animation_performance.dart';
import 'package:sky/painting/box_painter.dart';
import 'package:sky/theme/colors.dart';
import 'package:sky/theme/shadows.dart';
import 'package:sky/widgets/animated_component.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/popup_menu_item.dart';
import 'package:sky/widgets/scrollable.dart';
import 'package:sky/widgets/transitions.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
......@@ -29,7 +29,7 @@ const double _kMenuVerticalPadding = 8.0;
typedef void PopupMenuStatusChangedCallback(AnimationStatus status);
class PopupMenu extends AnimatedComponent {
class PopupMenu extends StatefulComponent {
PopupMenu({
Key key,
......@@ -46,63 +46,42 @@ class PopupMenu extends AnimatedComponent {
int level;
Navigator navigator;
AnimatedValue<double> _opacity;
AnimatedValue<double> _width;
AnimatedValue<double> _height;
List<AnimatedValue<double>> _itemOpacities;
AnimationPerformance _performance;
void initState() {
_performance = new AnimationPerformance()
..duration = _kMenuDuration
..addStatusListener(_onStatusChanged);
_updateAnimationVariables();
watch(_performance);
..duration = _kMenuDuration;
_performance.timing = new AnimationTiming()
..reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd);
_updateBoxPainter();
if (showing)
_open();
}
void syncFields(PopupMenu source) {
if (showing != source.showing) {
showing = source.showing;
if (showing)
_open();
else
_close();
}
if (!showing && source.showing)
_open();
showing = source.showing;
onStatusChanged = source.onStatusChanged;
if (level != source.level) {
level = source.level;
_updateBoxPainter();
}
if (items.length != source.items.length)
_updateAnimationVariables();
items = source.items;
navigator = source.navigator;
super.syncFields(source);
}
void _updateAnimationVariables() {
double unit = 1.0 / (items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
_opacity = new AnimatedValue<double>(0.0, end: 1.0);
_width = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit));
_height = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit * items.length));
_itemOpacities = new List<AnimatedValue<double>>();
for (int i = 0; i < items.length; ++i) {
double start = (i + 1) * unit;
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
_itemOpacities.add(new AnimatedValue<double>(
0.0, end: 1.0, interval: new Interval(start, end)));
void _open() {
if (navigator != null) {
scheduleMicrotask(() {
navigator.pushState(this, (_) => _close());
});
}
List<AnimatedVariable> variables = new List<AnimatedVariable>()
..add(_opacity)
..add(_width)
..add(_height)
..addAll(_itemOpacities);
AnimatedList list = new AnimatedList(variables)
..reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd);
_performance.variable = list;
}
void _close() {
_performance.reverse();
}
void _updateBoxPainter() {
......@@ -112,63 +91,70 @@ class PopupMenu extends AnimatedComponent {
boxShadow: shadows[level]));
}
void _onStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed &&
navigator != null &&
void _onDismissed() {
if (navigator != null &&
navigator.currentRoute is RouteState &&
(navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
navigator.pop();
if (onStatusChanged != null)
onStatusChanged(status);
}
void _open() {
_performance.play();
if (navigator != null)
navigator.pushState(this, (_) => _close());
}
void _close() {
_performance.reverse();
onStatusChanged(AnimationStatus.dismissed);
}
BoxPainter _painter;
Widget build() {
int i = 0;
List<Widget> children = new List.from(items.map((Widget item) {
return new Opacity(opacity: _itemOpacities[i++].value, child: item);
}));
double unit = 1.0 / (items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
List<Widget> children = [];
for (int i = 0; i < items.length; ++i) {
double start = (i + 1) * unit;
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
children.add(new FadeTransition(
direction: showing ? Direction.forward : Direction.reverse,
performance: _performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)),
child: items[i]));
}
return new Opacity(
opacity: math.min(1.0, _opacity.value * 3.0),
final width = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit));
final height = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit * items.length));
return new FadeTransition(
direction: showing ? Direction.forward : Direction.reverse,
performance: _performance,
onDismissed: _onDismissed,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)),
child: new Container(
margin: new EdgeDims.all(_kMenuMargin),
child: new CustomPaint(
callback: (sky.Canvas canvas, Size size) {
double width = _width.value * size.width;
double height = _height.value * size.height;
_painter.paint(canvas, new Rect.fromLTWH(size.width - width, 0.0, width, height));
},
child: new ConstrainedBox(
constraints: new BoxConstraints(
minWidth: _kMenuMinWidth,
maxWidth: _kMenuMaxWidth
),
child: new ShrinkWrapWidth(
stepWidth: _kMenuWidthStep,
child: new ScrollableViewport(
child: new Container(
padding: const EdgeDims.symmetric(
horizontal: _kMenuHorizontalPadding,
vertical: _kMenuVerticalPadding
),
child: new Block(children)
child: new BuilderTransition(
direction: showing ? Direction.forward : Direction.reverse,
performance: _performance,
variables: [width, height],
builder: () {
return new CustomPaint(
callback: (sky.Canvas canvas, Size size) {
double widthValue = width.value * size.width;
double heightValue = height.value * size.height;
_painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
},
child: new ConstrainedBox(
constraints: new BoxConstraints(
minWidth: _kMenuMinWidth,
maxWidth: _kMenuMaxWidth
),
child: new ShrinkWrapWidth(
stepWidth: _kMenuWidthStep,
child: new ScrollableViewport(
child: new Container(
padding: const EdgeDims.symmetric(
horizontal: _kMenuHorizontalPadding,
vertical: _kMenuVerticalPadding
),
child: new Block(children)
)
)
)
)
)
)
);
}
)
)
);
......
......@@ -92,9 +92,9 @@ class SlideTransition extends TransitionBase {
AnimatedValue<Point> position;
void syncFields(SlideTransition updated) {
position = updated.position;
super.syncFields(updated);
void syncFields(SlideTransition source) {
position = source.position;
super.syncFields(source);
}
Widget build() {
......@@ -125,9 +125,9 @@ class FadeTransition extends TransitionBase {
AnimatedValue<double> opacity;
void syncFields(FadeTransition updated) {
opacity = updated.opacity;
super.syncFields(updated);
void syncFields(FadeTransition source) {
opacity = source.opacity;
super.syncFields(source);
}
Widget build() {
......@@ -156,9 +156,9 @@ class ColorTransition extends TransitionBase {
AnimatedColorValue color;
void syncFields(ColorTransition updated) {
color = updated.color;
super.syncFields(updated);
void syncFields(ColorTransition source) {
color = source.color;
super.syncFields(source);
}
Widget build() {
......@@ -192,10 +192,10 @@ class SquashTransition extends TransitionBase {
AnimatedValue<double> width;
AnimatedValue<double> height;
void syncFields(SquashTransition updated) {
width = updated.width;
height = updated.height;
super.syncFields(updated);
void syncFields(SquashTransition source) {
width = source.width;
height = source.height;
super.syncFields(source);
}
Widget build() {
......@@ -206,3 +206,40 @@ class SquashTransition extends TransitionBase {
return new SizedBox(width: _maybe(width), height: _maybe(height), child: child);
}
}
typedef Widget BuilderFunction();
class BuilderTransition extends TransitionBase {
BuilderTransition({
Key key,
this.variables,
this.builder,
Duration duration,
AnimationPerformance performance,
Direction direction,
Function onDismissed,
Function onCompleted,
Widget child
}) : super(key: key,
duration: duration,
performance: performance,
direction: direction,
onDismissed: onDismissed,
onCompleted: onCompleted,
child: child);
List<AnimatedValue> variables;
BuilderFunction builder;
void syncFields(BuilderTransition source) {
variables = source.variables;
builder = source.builder;
super.syncFields(source);
}
Widget build() {
for (int i = 0; i < variables.length; ++i)
performance.updateVariable(variables[i]);
return builder();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment