Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Unified Diff: client/touch/Momentum.dart

Issue 9382027: Move client/{base, observable, layout, touch, util, view} to samples/ui_lib . (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: '' Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « client/touch/Math.dart ('k') | client/touch/ScrollWatcher.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: client/touch/Momentum.dart
===================================================================
--- client/touch/Momentum.dart (revision 4144)
+++ client/touch/Momentum.dart (working copy)
@@ -1,554 +0,0 @@
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-/**
- * Implementations can be used to simulate the deceleration of an element within
- * a certain region. To use this behavior you need to provide an initial
- * velocity that is meant to represent the gesture that is initiating this
- * deceleration. You also provide the bounds of the region that the element
- * exists in, and the current offset of the element within that region. The
- * transitions will have the element decelerate to rest, or stretch past the
- * offset boundaries and then come to rest.
- *
- * This is primarily designed to solve the problem of slow scrolling in mobile
- * safari. You can use this along with the [Scroller] behavior to make a
- * scrollable area scroll the same way it would in a native application.
- *
- * Implementations of this interface do not maintain any references to HTML
- * elements, and therefore cannot do any redrawing of elements. They only
- * calculates where the element should be on an interval. It is the delegate's
- * responsibility to redraw the element when the onDecelerate callback is
- * invoked. It is recommended that you move the element with a hardware
- * accelerated method such as using 'translate3d' on the element's
- * -webkit-transform style property.
- */
-interface Momentum default TimeoutMomentum {
-
- Momentum(MomentumDelegate delegate, [num defaultDecelerationFactor]);
-
- bool get decelerating();
-
- num get decelerationFactor();
-
- /**
- * Transition end handler. This function must be invoked after any transition
- * that occurred as a result of a call to the delegate's onDecelerate callback.
- */
- void onTransitionEnd();
-
- /**
- * Start decelerating.
- * The [velocity] passed should be in terms of number of pixels / millisecond.
- * [minCoord] and [maxCoord] specify the content's scrollable boundary.
- * The current offset of the element within its boundaries is specified by
- * [initialOffset].
- * Returns true if deceleration has been initiated.
- */
- bool start(Coordinate velocity, Coordinate minCoord, Coordinate maxCoord,
- Coordinate initialOffset, [num decelerationFactor]);
-
- /**
- * Calculate the velocity required to transition between coordinates [start]
- * and [target] optionally specifying a custom [decelerationFactor].
- */
- Coordinate calculateVelocity(Coordinate start, Coordinate target,
- [num decelerationFactor]);
-
- /** Stop decelerating and return the current velocity. */
- Coordinate stop();
-
- /** Aborts decelerating without dispatching any notification events. */
- void abort();
-
- /** null if no transition is in progress. */
- Coordinate get destination();
-}
-
-/**
- * Momentum Delegate interface.
- * You are required to implement this interface in order to use the
- * Momentum behavior.
- */
-interface MomentumDelegate {
- /**
- * Callback for a deceleration step. The delegate is responsible for redrawing
- * the element in its new position specified in px.
- */
- void onDecelerate(num x, num y);
-
- /**
- * Callback for end of deceleration.
- */
- void onDecelerationEnd();
-}
-
-class BouncingState {
- static final NOT_BOUNCING = 0;
- static final BOUNCING_AWAY = 1;
- static final BOUNCING_BACK = 2;
-}
-
-class _Move {
- final num x;
- final num y;
- final num vx;
- final num vy;
- final num time;
-
- _Move(this.x, this.y, this.vx, this.vy, this.time);
-}
-
-/**
- * Secant method root solver helper class.
- * We use http://en.wikipedia.org/wiki/Secant_method
- * falling back to the http://en.wikipedia.org/wiki/Bisection_method
- * if it doesn't appear we are converging properlty.
- * TODO(jacobr): simplify the code so we don't have to use this solver
- * class at all.
- */
-class Solver {
-
- static num solve(num fn(num), num targetY, num startX,
- [int maxIterations = 50]) {
- num lastX = 0;
- num lastY = fn(lastX);
- num deltaX;
- num deltaY;
- num minX = null;
- num maxX = null;
- num x = startX;
- num delta = startX;
- for (int i = 0; i < maxIterations; i++) {
- num y = fn(x);
- if (y.round() == targetY.round()) {
- return x;
- }
- if (y > targetY) {
- maxX = x;
- } else {
- minX = x;
- }
-
- num errorY = targetY - y;
- deltaX = x - lastX;
- deltaY = y - lastY;
- lastX = x;
- lastY = y;
- // Avoid divide by zero and as a hack just repeat the previous delta.
- // Obviously this is a little dangerous and we might not converge.
- if (deltaY != 0) {
- delta = errorY * deltaX / deltaY;
- }
- x += delta;
- if (minX != null && maxX != null
- && (x > minX || x < maxX)) {
- // Fall back to binary search.
- x = (minX + maxX) / 2;
- }
- }
- window.console.warn('''Could not find an exact solution. LastY=${lastY},
- targetY=${targetY} lastX=$lastX delta=$delta deltaX=$deltaX
- deltaY=$deltaY''');
- return x;
- }
-}
-
-/**
- * Helper class modeling the physics of a throwable scrollable area along a
- * single dimension.
- */
-class SingleDimensionPhysics {
- /** The number of frames per second the animation should run at. */
- static final _FRAMES_PER_SECOND = 60;
-
- /**
- * The spring coefficient for when the element has passed a boundary and is
- * decelerating to change direction and bounce back. Each frame, the velocity
- * will be changed by x times this coefficient, where x is the current stretch
- * value of the element from its boundary. This will end when velocity reaches
- * zero.
- */
- static final _PRE_BOUNCE_COEFFICIENT = 7.0 / _FRAMES_PER_SECOND;
-
- /**
- * The spring coefficient for when the element is bouncing back from a
- * stretched offset to a min or max position. Each frame, the velocity will
- * be changed to x times this coefficient, where x is the current stretch
- * value of the element from its boundary. This will end when the stretch
- * value reaches 0.
- */
- static final _POST_BOUNCE_COEFFICIENT = 7.0 / _FRAMES_PER_SECOND;
-
- /**
- * The number of milliseconds per animation frame.
- */
- static final _MS_PER_FRAME = 1000.0 / _FRAMES_PER_SECOND;
-
- /**
- * The constant factor applied to velocity at each frame to simulate
- * deceleration.
- */
- static final _DECELERATION_FACTOR = 0.97;
-
-
- static final _MAX_VELOCITY_STATIC_FRICTION = 0.08 * _MS_PER_FRAME;
- static final _DECELERATION_FACTOR_STATIC_FRICTION = 0.92;
-
- /**
- * Minimum velocity required to start or continue deceleration, in
- * pixels/frame. This is equivalent to 0.25 px/ms.
- */
- static final _MIN_VELOCITY = 0.25 * _MS_PER_FRAME;
-
- /**
- * Minimum velocity during a step, in pixels/frame. This is equivalent to 0.01
- * px/ms.
- */
- static final _MIN_STEP_VELOCITY = 0.01 * _MS_PER_FRAME;
-
- /**
- * Boost the initial velocity by a certain factor before applying momentum.
- * This just gives the momentum a better feel.
- */
- static final _INITIAL_VELOCITY_BOOST_FACTOR = 1.25;
-
- /**
- * Additional deceleration factor to apply for the current move only. This
- * is helpful for cases such as scroll wheel scrolling where the default
- * amount of deceleration is inadequate.
- */
- num customDecelerationFactor = 1;
- num _minCoord;
- num _maxCoord;
-
- /** The bouncing state. */
- int _bouncingState;
-
-
- num velocity;
- num _currentOffset;
-
- /**
- * constant used when guessing at the velocity required to throw to a specific
- * location. Chosen arbitrarily. All that really matters is that the velocity
- * is large enough that a throw gesture will occur.
- */
- static final _VELOCITY_GUESS = 20;
-
- SingleDimensionPhysics() : _bouncingState = BouncingState.NOT_BOUNCING {
- }
-
- void configure(num minCoord, num maxCoord,
- num initialOffset, num customDecelerationFactor_,
- num velocity_) {
- _bouncingState = BouncingState.NOT_BOUNCING;
- _minCoord = minCoord;
- _maxCoord = maxCoord;
- _currentOffset = initialOffset;
- this.customDecelerationFactor = customDecelerationFactor_;
- _adjustInitialVelocityAndBouncingState(velocity_);
- }
-
- num solve(num initialOffset, num targetOffset,
- num customDecelerationFactor_) {
- initialOffset = initialOffset.round();
- targetOffset = targetOffset.round();
- if (initialOffset == targetOffset) {
- return 0;
- }
- return Solver.solve((num velocity_) {
- // Don't specify min and max coordinates as we don't need to bother
- // with the simulating bouncing off the edges.
- configure(null, null, initialOffset.round(),
- customDecelerationFactor_, velocity_);
- stepAll();
- return _currentOffset;
- },
- targetOffset,
- targetOffset > initialOffset ? _VELOCITY_GUESS : -_VELOCITY_GUESS);
- }
-
- /**
- * Helper method to calculate initial velocity.
- * The [velocity] passed here should be in terms of number of
- * pixels / millisecond. Returns the adjusted x and y velocities.
- */
- void _adjustInitialVelocityAndBouncingState(num v) {
- velocity = v * _MS_PER_FRAME * _INITIAL_VELOCITY_BOOST_FACTOR;
-
- if (velocity.abs() < _MIN_VELOCITY) {
- if (_minCoord !== null && _currentOffset < _minCoord) {
- velocity = (_minCoord - _currentOffset) * _POST_BOUNCE_COEFFICIENT;
- velocity = Math.max(velocity, _MIN_STEP_VELOCITY);
- _bouncingState = BouncingState.BOUNCING_BACK;
- } else if (_maxCoord !== null && _currentOffset > _maxCoord) {
- velocity = (_currentOffset - _maxCoord) * _POST_BOUNCE_COEFFICIENT;
- velocity = -Math.max(velocity, _MIN_STEP_VELOCITY);
- _bouncingState = BouncingState.BOUNCING_BACK;
- }
- }
- }
-
- /**
- * Apply deceleration.
- */
- void _adjustVelocity() {
- num speed = velocity.abs();
- velocity *= _DECELERATION_FACTOR;
- if (customDecelerationFactor != null) {
- velocity *= customDecelerationFactor;
- }
- // This isn't really how static friction works but it is a plausible
- // approximation.
- if (speed < _MAX_VELOCITY_STATIC_FRICTION) {
- velocity *= _DECELERATION_FACTOR_STATIC_FRICTION;
- }
-
- num stretchDistance;
- if (_minCoord !== null && _currentOffset < _minCoord) {
- stretchDistance = _minCoord - _currentOffset;
- } else {
- if (_maxCoord !== null && _currentOffset > _maxCoord) {
- stretchDistance = _maxCoord - _currentOffset;
- }
- }
- if (stretchDistance != null) {
- if (stretchDistance * velocity < 0) {
- _bouncingState = _bouncingState == BouncingState.BOUNCING_BACK ?
- BouncingState.NOT_BOUNCING : BouncingState.BOUNCING_AWAY;
- velocity += stretchDistance * _PRE_BOUNCE_COEFFICIENT;
- } else {
- _bouncingState = BouncingState.BOUNCING_BACK;
- velocity = stretchDistance > 0 ?
- Math.max(stretchDistance * _POST_BOUNCE_COEFFICIENT,
- _MIN_STEP_VELOCITY) :
- Math.min(stretchDistance * _POST_BOUNCE_COEFFICIENT,
- -_MIN_STEP_VELOCITY);
- }
- } else {
- _bouncingState = BouncingState.NOT_BOUNCING;
- }
- }
-
- void step() {
- // It is common for scrolling to be disabled so in these cases we want to
- // avoid needless calculations.
- if (velocity !== null) {
- _currentOffset += velocity;
- _adjustVelocity();
- }
- }
-
- void stepAll() {
- while(!isDone()) {
- step();
- }
- }
-
- /**
- * Whether or not the current velocity is above the threshold required to
- * continue decelerating.
- */
- bool isVelocityAboveThreshold(num threshold) {
- return velocity.abs() >= threshold;
- }
-
- bool isDone() {
- return _bouncingState == BouncingState.NOT_BOUNCING &&
- !isVelocityAboveThreshold(_MIN_STEP_VELOCITY);
- }
-}
-
-/**
- * Implementation of a momentum strategy using webkit-transforms
- * and timeouts.
- */
-class TimeoutMomentum implements Momentum {
-
- SingleDimensionPhysics physicsX;
- SingleDimensionPhysics physicsY;
- Coordinate _previousOffset;
- Queue<_Move> _moves;
- num _stepTimeout;
- bool _decelerating;
- MomentumDelegate _delegate;
- int _nextY;
- int _nextX;
- Coordinate _minCoord;
- Coordinate _maxCoord;
- num _customDecelerationFactor;
- num _defaultDecelerationFactor;
-
- TimeoutMomentum(this._delegate, [num defaultDecelerationFactor = 1])
- : _defaultDecelerationFactor = defaultDecelerationFactor,
- _decelerating = false,
- _moves = new Queue<_Move>(),
- physicsX = new SingleDimensionPhysics(),
- physicsY = new SingleDimensionPhysics();
-
- /**
- * Calculate and return the moves for the deceleration motion.
- */
- void _calculateMoves() {
- _moves.clear();
- num time = TimeUtil.now();
- while (!physicsX.isDone() || !physicsY.isDone()) {
- _stepWithoutAnimation();
- time += SingleDimensionPhysics._MS_PER_FRAME;
- if (_isStepNecessary()) {
- _moves.add(new _Move(_nextX, _nextY,
- physicsX.velocity,
- physicsY.velocity, time));
- _previousOffset.y = _nextY;
- _previousOffset.x = _nextX;
- }
- }
- }
-
- bool get decelerating() => _decelerating;
- num get decelerationFactor() => _customDecelerationFactor;
-
- /**
- * Checks whether or not an animation step is necessary or not. Animations
- * steps are not necessary when the velocity gets so low that in several
- * frames the offset is the same.
- * Returns true if there is movement to be done in the next frame.
- */
- bool _isStepNecessary() {
- return _nextY != _previousOffset.y || _nextX != _previousOffset.x;
- }
-
- /**
- * The [TouchHandler] requires this function but we don't need to do
- * anything here.
- */
- void onTransitionEnd() {
- }
-
- Coordinate calculateVelocity(Coordinate start_, Coordinate target,
- [num decelerationFactor = null]) {
- return new Coordinate(
- physicsX.solve(start_.x, target.x, decelerationFactor),
- physicsY.solve(start_.y, target.y, decelerationFactor));
- }
-
- bool start(Coordinate velocity, Coordinate minCoord, Coordinate maxCoord,
- Coordinate initialOffset, [num decelerationFactor = null]) {
- _customDecelerationFactor = _defaultDecelerationFactor;
- if (decelerationFactor !== null) {
- _customDecelerationFactor = decelerationFactor;
- }
-
- if (_stepTimeout !== null) {
- Env.cancelRequestAnimationFrame(_stepTimeout);
- _stepTimeout = null;
- }
-
- assert (_stepTimeout === null);
- assert(minCoord.x <= maxCoord.x);
- assert(minCoord.y <= maxCoord.y);
- _previousOffset = initialOffset.clone();
- physicsX.configure(minCoord.x, maxCoord.x, initialOffset.x,
- _customDecelerationFactor, velocity.x);
- physicsY.configure(minCoord.y, maxCoord.y, initialOffset.y,
- _customDecelerationFactor, velocity.y);
- if (!physicsX.isDone() || !physicsY.isDone()) {
- _calculateMoves();
- if (!_moves.isEmpty()) {
- num firstTime = _moves.first().time;
- _stepTimeout = Env.requestAnimationFrame(
- _step, null, firstTime);
- _decelerating = true;
- return true;
- }
- }
- _decelerating = false;
- return false;
- }
-
- /**
- * Update the x, y values of the element offset without actually moving the
- * element. This is done because we store decimal values for x, y for
- * precision, but moving is only required when the offset is changed by at
- * least a whole integer.
- */
- void _stepWithoutAnimation() {
- physicsX.step();
- physicsY.step();
- // TODO(jacobr): double.round() should return an int, see b/5121907
- _nextX = physicsX._currentOffset.round().toInt();
- _nextY = physicsY._currentOffset.round().toInt();
- }
-
- /**
- * Calculate the next offset of the element and animate it to that position.
- */
- void _step(num timestamp) {
- _stepTimeout = null;
-
- // Prune moves that are more than 1 frame behind when we have more
- // available moves.
- num lastEpoch = timestamp - SingleDimensionPhysics._MS_PER_FRAME;
- while (!_moves.isEmpty() && _moves.first() !== _moves.last()
- && _moves.first().time < lastEpoch) {
- _moves.removeFirst();
- }
-
- if (!_moves.isEmpty()) {
- final move = _moves.removeFirst();
- _delegate.onDecelerate(move.x, move.y);
- if (!_moves.isEmpty()) {
- num nextTime = _moves.first().time;
- assert(_stepTimeout === null);
- _stepTimeout = Env.requestAnimationFrame(_step, null, nextTime);
- } else {
- stop();
- }
- }
- }
-
- void abort() {
- _decelerating = false;
- _moves.clear();
- if (_stepTimeout !== null) {
- Env.cancelRequestAnimationFrame(_stepTimeout);
- _stepTimeout = null;
- }
- }
-
- Coordinate stop() {
- final wasDecelerating = _decelerating;
- _decelerating = false;
- Coordinate velocity;
- if (!_moves.isEmpty()) {
- final move = _moves.first();
- // This is a workaround for the ugly hacks that get applied when a user
- // passed a velocity in to this Momentum implementation.
- num velocityScale = SingleDimensionPhysics._MS_PER_FRAME *
- SingleDimensionPhysics._INITIAL_VELOCITY_BOOST_FACTOR;
- velocity = new Coordinate(
- move.vx / velocityScale, move.vy / velocityScale);
- } else {
- velocity = new Coordinate(0, 0);
- }
- _moves.clear();
- if (_stepTimeout !== null) {
- Env.cancelRequestAnimationFrame(_stepTimeout);
- _stepTimeout = null;
- }
- if (wasDecelerating) {
- _delegate.onDecelerationEnd();
- }
- return velocity;
- }
-
- Coordinate get destination() {
- if (!_moves.isEmpty()) {
- final lastMove = _moves.last();
- return new Coordinate(lastMove.x, lastMove.y);
- } else {
- return null;
- }
- }
-}
« no previous file with comments | « client/touch/Math.dart ('k') | client/touch/ScrollWatcher.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698