/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.sun.lwuit.animations;
/**
* Abstracts the notion of physical motion over time from a numeric location to
* another. This class can be subclassed to implement any motion equation for
* appropriate physics effects.
* <p>This class relies on the System.currentTimeMillis() method to provide
* transitions between coordinates. The motion can be subclassed to provide every
* type of motion feel from parabolic motion to spline and linear motion. The default
* implementation provides a simple algorithm giving the feel of acceleration and
* deceleration.
*
* @author Shai Almog
*/
public class Motion {
// package protected for the resource editor
static final int LINEAR = 0;
static final int SPLINE = 1;
int motionType;
private static final int FRICTION = 2;
private int sourceValue;
private int destinationValue;
private int duration;
private long startTime;
private float initVelocity, friction;
private int lastReturnedValue;
private long currentMotionTime = -1;
/**
* Construct a point/destination motion
*
* @param sourceValue starting value
* @param destinationValue destination value
* @param duration motion duration
*/
protected Motion(int sourceValue, int destinationValue, int duration) {
this.sourceValue = sourceValue;
this.destinationValue = destinationValue;
this.duration = duration;
lastReturnedValue = sourceValue;
}
/**
* Construct a velocity motion
*
* @param sourceValue starting value
* @param initVelocity initial velocity
* @param friction degree of friction
*/
protected Motion(int sourceValue, float initVelocity, float friction) {
this.sourceValue = sourceValue;
this.initVelocity = initVelocity;
this.friction = friction;
duration = (int) ((Math.abs(initVelocity)) / friction);
}
/**
* Creates a linear motion starting from source value all the way to destination value
*
* @param sourceValue the number from which we are starting (usually indicating animation start position)
* @param destinationValue the number to which we are heading (usually indicating animation destination)
* @param duration the length in milliseconds of the motion (time it takes to get from sourceValue to
* destinationValue)
* @return new motion object
*/
public static Motion createLinearMotion(int sourceValue, int destinationValue, int duration) {
Motion l = new Motion(sourceValue, destinationValue, duration);
l.motionType = LINEAR;
return l;
}
/**
* Creates a spline motion starting from source value all the way to destination value
*
* @param sourceValue the number from which we are starting (usually indicating animation start position)
* @param destinationValue the number to which we are heading (usually indicating animation destination)
* @param duration the length in milliseconds of the motion (time it takes to get from sourceValue to
* destinationValue)
* @return new motion object
*/
public static Motion createSplineMotion(int sourceValue, int destinationValue, int duration) {
Motion spline = new Motion(sourceValue, destinationValue, duration);
spline.motionType = SPLINE;
return spline;
}
/**
* Creates a friction motion starting from source with initial speed and the friction
*
* @param sourceValue the number from which we are starting (usually indicating animation start position)
* @param maxValue the maximum value for the friction
* @param initVelocity the starting velocity
* @param friction the motion friction
* @return new motion object
*/
public static Motion createFrictionMotion(int sourceValue, int maxValue, float initVelocity, float friction) {
Motion frictionMotion = new Motion(sourceValue, initVelocity, friction);
frictionMotion.destinationValue = maxValue;
frictionMotion.motionType = FRICTION;
return frictionMotion;
}
/**
* Sets the start time to the current time
*/
public void start() {
startTime = System.currentTimeMillis();
}
/**
* Returns the current time within the motion relative to start time
*
* @return long value representing System.currentTimeMillis() - startTime
*/
public long getCurrentMotionTime() {
if(currentMotionTime < 0) {
return System.currentTimeMillis() - startTime;
}
return currentMotionTime;
}
/**
* Allows overriding the getCurrentMotionTime method value with a manual value
* to provide full developer control over animation speed/position.
*
* @param currentMotionTime the time in milliseconds for the motion.
*/
public void setCurrentMotionTime(long currentMotionTime) {
this.currentMotionTime = currentMotionTime;
// workaround allowing the motion to be restarted when manually setting the current time
if(lastReturnedValue == destinationValue) {
lastReturnedValue = sourceValue;
}
}
/**
* Sets the start time of the motion
*
* @param startTime the starting time
*/
public void setStartTime(long startTime) {
this.startTime = startTime;
}
/**
* Returns true if the motion has run its course and has finished meaning the current
* time is greater than startTime + duration.
*
* @return true if System.currentTimeMillis() > duration + startTime or the last returned value is the destination value
*/
public boolean isFinished() {
if(currentMotionTime < 0) {
return getCurrentMotionTime() > duration || destinationValue == lastReturnedValue;
}
return getCurrentMotionTime() > duration || destinationValue == lastReturnedValue;
}
private int getSplineValue() {
//make sure we reach the destination value.
if(isFinished()){
return destinationValue;
}
float totalTime = duration;
float currentTime = (int) getCurrentMotionTime();
if(currentMotionTime > -1) {
currentTime -= startTime;
totalTime -= startTime;
}
currentTime = Math.min(currentTime, totalTime);
int p = Math.abs(destinationValue - sourceValue);
float centerTime = totalTime / 2;
float l = p / (centerTime * centerTime);
int x;
if (sourceValue < destinationValue) {
if (currentTime > centerTime) {
x = sourceValue + (int) (l * (-centerTime * centerTime + 2 * centerTime * currentTime -
currentTime * currentTime / 2));
} else {
x = sourceValue + (int) (l * currentTime * currentTime / 2);
}
} else {
currentTime = totalTime - currentTime;
if (currentTime > centerTime) {
x = destinationValue + (int) (l * (-centerTime * centerTime + 2 * centerTime * currentTime -
currentTime * currentTime / 2));
} else {
x = destinationValue + (int) (l * currentTime * currentTime / 2);
}
}
return x;
}
/**
* Returns the value for the motion for the current clock time.
* The value is dependent on the Motion type.
*
* @return a value that is relative to the source value
*/
public int getValue() {
if(currentMotionTime > -1 && startTime > getCurrentMotionTime()) {
return sourceValue;
}
switch(motionType) {
case SPLINE:
lastReturnedValue = getSplineValue();
break;
case FRICTION:
lastReturnedValue = getFriction();
break;
default:
lastReturnedValue = getLinear();
break;
}
return lastReturnedValue;
}
private int getLinear() {
//make sure we reach the destination value.
if(isFinished()){
return destinationValue;
}
float totalTime = duration;
float currentTime = (int) getCurrentMotionTime();
if(currentMotionTime > -1) {
currentTime -= startTime;
totalTime -= startTime;
}
int dis = destinationValue - sourceValue;
int val = (int)(sourceValue + (currentTime / totalTime * dis));
if(destinationValue < sourceValue) {
return Math.max(destinationValue, val);
} else {
return Math.min(destinationValue, val);
}
}
private int getFriction() {
int time = (int) getCurrentMotionTime();
int retVal = 0;
retVal = (int)((Math.abs(initVelocity) * time) - (friction * (((float)time * time) / 2)));
if (initVelocity < 0) {
retVal *= -1;
}
retVal += (int) sourceValue;
if(destinationValue > sourceValue) {
return Math.min(retVal, destinationValue);
} else {
return Math.max(retVal, destinationValue);
}
}
/**
* The number from which we are starting (usually indicating animation start position)
*
* @return the source value
*/
public int getSourceValue() {
return sourceValue;
}
/**
* The number to which we will reach when the motion is finished
*
* @return the source value
*/
public int getDestinationValue() {
return destinationValue;
}
/**
* The number from which we are starting (usually indicating animation start position)
*
* @param sourceValue the source value
*/
public void setSourceValue(int sourceValue) {
this.sourceValue = sourceValue;
}
/**
* The value of System.currentTimemillis() when motion was started
*
* @return the start time
*/
protected long getStartTime() {
return startTime;
}
/**
* Returns the animation duration
*
* @return animation duration in milliseconds
*/
public int getDuration() {
return duration;
}
}