Commit 09dc3929 authored by Matt Perry's avatar Matt Perry

Add an AnimationStatus to AnimationPerformance, and use that in Drawer,

SnackBar, and PopupMenu instead of custom statuses.
parent 01ac0095
......@@ -27,7 +27,7 @@ class StockHome extends StatefulComponent {
bool _isSearching = false;
String _searchQuery;
SnackBarStatus _snackBarStatus = SnackBarStatus.inactive;
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
bool _isSnackBarShowing = false;
void _handleSearchBegin() {
......@@ -59,28 +59,28 @@ class StockHome extends StatefulComponent {
}
bool _drawerShowing = false;
DrawerStatus _drawerStatus = DrawerStatus.inactive;
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
void _handleOpenDrawer() {
setState(() {
_drawerShowing = true;
_drawerStatus = DrawerStatus.active;
_drawerStatus = AnimationStatus.forward;
});
}
void _handleDrawerStatusChange(DrawerStatus status) {
void _handleDrawerStatusChange(AnimationStatus status) {
setState(() {
_drawerStatus = status;
});
}
bool _menuShowing = false;
PopupMenuStatus _menuStatus = PopupMenuStatus.inactive;
AnimationStatus _menuStatus = AnimationStatus.dismissed;
void _handleMenuShow() {
setState(() {
_menuShowing = true;
_menuStatus = PopupMenuStatus.active;
_menuStatus = AnimationStatus.forward;
});
}
......@@ -90,7 +90,7 @@ class StockHome extends StatefulComponent {
});
}
void _handleMenuStatusChanged(PopupMenuStatus status) {
void _handleMenuStatusChanged(AnimationStatus status) {
setState(() {
_menuStatus = status;
});
......@@ -112,7 +112,7 @@ class StockHome extends StatefulComponent {
}
Drawer buildDrawer() {
if (_drawerStatus == DrawerStatus.inactive)
if (_drawerStatus == AnimationStatus.dismissed)
return null;
assert(_drawerShowing); // TODO(mpcomplete): this is always true
return new Drawer(
......@@ -246,7 +246,7 @@ class StockHome extends StatefulComponent {
}
Widget buildSnackBar() {
if (_snackBarStatus == SnackBarStatus.inactive)
if (_snackBarStatus == AnimationStatus.dismissed)
return null;
return new SnackBar(
showing: _isSnackBarShowing,
......@@ -259,7 +259,7 @@ class StockHome extends StatefulComponent {
void _handleStockPurchased() {
setState(() {
_isSnackBarShowing = true;
_snackBarStatus = SnackBarStatus.active;
_snackBarStatus = AnimationStatus.forward;
});
}
......@@ -272,7 +272,7 @@ class StockHome extends StatefulComponent {
}
void addMenuToOverlays(List<Widget> overlays) {
if (_menuStatus == PopupMenuStatus.inactive)
if (_menuStatus == AnimationStatus.dismissed)
return;
overlays.add(new ModalOverlay(
children: [new StockMenu(
......
......@@ -8,6 +8,18 @@ import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/forces.dart';
import 'package:sky/animation/timeline.dart';
enum AnimationDirection {
forward,
reverse
}
enum AnimationStatus {
dismissed, // stoped at 0
forward, // animating from 0 => 1
reverse, // animating from 1 => 0
completed, // stopped at 1
}
// This class manages a "performance" - a collection of values that change
// based on a timeline. For example, a performance may handle an animation
// of a menu opening by sliding and fading in (changing Y value and opacity)
......@@ -16,30 +28,49 @@ import 'package:sky/animation/timeline.dart';
// manipulating |progress|, or |fling| the timeline causing a physics-based
// simulation to take over the progression.
class AnimationPerformance {
AnimationPerformance() {
AnimationPerformance({this.variable, this.duration}) {
_timeline = new Timeline(_tick);
}
AnimatedVariable variable;
// TODO(mpcomplete): duration should be on a director.
Duration duration;
// Advances from 0 to 1. On each tick, we'll update our variable's values.
Timeline _timeline;
Timeline get timeline => _timeline;
AnimationDirection _direction;
AnimationDirection get direction => _direction;
double get progress => timeline.value;
void set progress(double t) {
// TODO(mpcomplete): should this affect |direction|?
stop();
timeline.value = t.clamp(0.0, 1.0);
_checkStatusChanged();
}
bool get isDismissed => progress == 0.0;
bool get isCompleted => progress == 1.0;
bool get isDismissed => status == AnimationStatus.dismissed;
bool get isCompleted => status == AnimationStatus.completed;
bool get isAnimating => timeline.isAnimating;
Future play() => _animateTo(1.0);
Future reverse() => _animateTo(0.0);
AnimationStatus get status {
if (!isAnimating && progress == 1.0)
return AnimationStatus.completed;
if (!isAnimating && progress == 0.0)
return AnimationStatus.dismissed;
return direction == AnimationDirection.forward ?
AnimationStatus.forward :
AnimationStatus.reverse;
}
Future play([AnimationDirection direction = AnimationDirection.forward]) {
_direction = direction;
return resume();
}
Future forward() => play(AnimationDirection.forward);
Future reverse() => play(AnimationDirection.reverse);
Future resume() => _animateTo(direction == AnimationDirection.forward ? 1.0 : 0.0);
void stop() {
timeline.stop();
......@@ -51,6 +82,9 @@ class AnimationPerformance {
Future fling({double velocity: 1.0, Force force}) {
if (force == null)
force = kDefaultSpringForce;
// This is an approximation - the force may not necessarily result in
// animating the same direction as the initial velocity.
_direction = velocity >= 0.0 ? AnimationDirection.forward : AnimationDirection.reverse;
return timeline.fling(force.release(progress, velocity));
}
......@@ -70,6 +104,27 @@ class AnimationPerformance {
listener();
}
final List<Function> _statusListeners = new List<Function>();
void addStatusListener(Function listener) {
_statusListeners.add(listener);
}
void removeStatusListener(Function listener) {
_statusListeners.remove(listener);
}
AnimationStatus _lastStatus = AnimationStatus.dismissed;
void _checkStatusChanged() {
AnimationStatus currentStatus = status;
if (currentStatus != _lastStatus) {
List<Function> localListeners = new List<Function>.from(_statusListeners);
for (Function listener in localListeners)
listener(currentStatus);
}
_lastStatus = currentStatus;
}
Future _animateTo(double target) {
double remainingDistance = (target - timeline.value).abs();
timeline.stop();
......@@ -81,5 +136,6 @@ class AnimationPerformance {
void _tick(double t) {
variable.setProgress(t);
_notifyListeners();
_checkStatusChanged();
}
}
......@@ -15,6 +15,8 @@ import 'package:sky/widgets/scrollable_viewport.dart';
import 'package:sky/widgets/theme.dart';
import 'package:vector_math/vector_math.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
// TODO(eseidel): Draw width should vary based on device size:
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav
......@@ -35,14 +37,7 @@ const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0);
typedef void DrawerStatusChangeHandler (bool showing);
enum DrawerStatus {
active,
inactive,
}
typedef void DrawerStatusChangedCallback(DrawerStatus status);
typedef void DrawerStatusChangedCallback(AnimationStatus status);
class Drawer extends AnimatedComponent {
Drawer({
......@@ -70,7 +65,7 @@ class Drawer extends AnimatedComponent {
_performance = new AnimationPerformance()
..duration = _kBaseSettleDuration
..variable = new AnimatedList([_position, _maskColor])
..addListener(_checkForStateChanged);
..addStatusListener(_onStatusChanged);
watch(_performance);
if (showing)
_show();
......@@ -137,22 +132,16 @@ class Drawer extends AnimatedComponent {
double get xPosition => _position.value.x;
DrawerStatus _lastStatus;
void _checkForStateChanged() {
DrawerStatus status = _status;
if (_lastStatus != null && status != _lastStatus) {
if (status == DrawerStatus.inactive &&
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);
}
_lastStatus = status;
void _onStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed &&
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);
}
DrawerStatus get _status => _performance.isDismissed ? DrawerStatus.inactive : DrawerStatus.active;
bool get _isMostlyClosed => xPosition <= -_kWidth/2;
void _settle() => _fling(_isMostlyClosed ? -1.0 : 1.0);
......
......@@ -16,6 +16,8 @@ import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/popup_menu_item.dart';
import 'package:sky/widgets/scrollable_viewport.dart';
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
const Duration _kMenuDuration = const Duration(milliseconds: 300);
double _kMenuCloseIntervalEnd = 2.0 / 3.0;
const double _kMenuWidthStep = 56.0;
......@@ -25,12 +27,7 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuHorizontalPadding = 16.0;
const double _kMenuVerticalPadding = 8.0;
enum PopupMenuStatus {
active,
inactive,
}
typedef void PopupMenuStatusChangedCallback(PopupMenuStatus status);
typedef void PopupMenuStatusChangedCallback(AnimationStatus status);
class PopupMenu extends AnimatedComponent {
......@@ -59,7 +56,7 @@ class PopupMenu extends AnimatedComponent {
void initState() {
_performance = new AnimationPerformance()
..duration = _kMenuDuration
..addListener(_checkForStateChanged);
..addStatusListener(_onStatusChanged);
_updateAnimationVariables();
watch(_performance);
_updateBoxPainter();
......@@ -115,21 +112,14 @@ class PopupMenu extends AnimatedComponent {
boxShadow: shadows[level]));
}
PopupMenuStatus get _status => _opacity.value != 0.0 ? PopupMenuStatus.active : PopupMenuStatus.inactive;
PopupMenuStatus _lastStatus;
void _checkForStateChanged() {
PopupMenuStatus status = _status;
if (_lastStatus != null && status != _lastStatus) {
if (status == PopupMenuStatus.inactive &&
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);
}
_lastStatus = status;
void _onStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed &&
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);
}
......
......@@ -14,15 +14,11 @@ import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/default_text_style.dart';
import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/theme.dart';
import 'package:vector_math/vector_math.dart';
enum SnackBarStatus {
active,
inactive,
}
export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
typedef void SnackBarStatusChangedCallback(SnackBarStatus status);
typedef void SnackBarStatusChangedCallback(AnimationStatus status);
const Duration _kSlideInDuration = const Duration(milliseconds: 200);
......@@ -48,53 +44,35 @@ class SnackBarAction extends Component {
// TODO(mpcomplete): generalize this to a SlideIn class.
class SnackBarSlideInIntention extends AnimationIntention {
SnackBarSlideInIntention(this.duration, this.onStatusChanged);
SnackBarSlideInIntention(Duration duration) {
_position = new AnimatedValue<Point>(const Point(0.0, 50.0), end: Point.origin);
performance = new AnimationPerformance(duration: duration, variable: _position);
}
Duration duration;
SnackBarStatusChangedCallback onStatusChanged;
AnimatedValue<Point> _position;
AnimationPerformance _performance;
AnimationPerformance performance;
void initFields(AnimatedContainer container) {
_position = new AnimatedValue<Point>(new Point(0.0, 50.0), end: Point.origin);
_performance = new AnimationPerformance()
..duration = _kSlideInDuration
..variable = _position
..addListener(() { _updateProgress(container); });
_performance.progress = 0.0;
performance.addListener(() { _updateProgress(container); });
performance.progress = 0.0;
if (container.tag)
_show();
performance.play();
}
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) {
original.tag = updated.tag;
original.tag ? _show() : _hide();
performance.play(original.tag ? AnimationDirection.forward : AnimationDirection.reverse);
}
}
void _show() {
_performance.play();
}
void _hide() {
_performance.reverse();
}
SnackBarStatus _lastStatus;
void _updateProgress(AnimatedContainer container) {
container.setState(() {
container.transform = new Matrix4.identity()
..translate(_position.value.x, _position.value.y);
});
SnackBarStatus status = _status;
if (_lastStatus != null && status != _lastStatus && onStatusChanged != null)
scheduleMicrotask(() { onStatusChanged(status); });
_lastStatus = status;
}
SnackBarStatus get _status => _performance.isDismissed ? SnackBarStatus.inactive : SnackBarStatus.active;
}
class SnackBar extends StatefulComponent {
......@@ -117,7 +95,8 @@ class SnackBar extends StatefulComponent {
SnackBarSlideInIntention _intention;
void initState() {
_intention = new SnackBarSlideInIntention(_kSlideInDuration, onStatusChanged);
_intention = new SnackBarSlideInIntention(_kSlideInDuration);
_intention.performance.addStatusListener(_onStatusChanged);
}
void syncFields(SnackBar source) {
......@@ -127,6 +106,11 @@ class SnackBar extends StatefulComponent {
showing = source.showing;
}
void _onStatusChanged(AnimationStatus status) {
if (onStatusChanged != null)
scheduleMicrotask(() { onStatusChanged(status); });
}
Widget build() {
List<Widget> children = [
new Flexible(
......
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