/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Icy 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.math;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.Timer;
/**
* @author Stephane
*/
public class SmoothMover implements ActionListener
{
public static interface SmoothMoverListener
{
public void moveStarted(SmoothMover source, double start, double end);
public void moveModified(SmoothMover source, double start, double end);
public void moveEnded(SmoothMover source, double value);
public void valueChanged(SmoothMover source, double newValue, int pourcent);
}
public static class SmoothMoverAdapter implements SmoothMoverListener
{
@Override
public void moveStarted(SmoothMover source, double start, double end)
{
}
@Override
public void moveModified(SmoothMover source, double start, double end)
{
}
@Override
public void moveEnded(SmoothMover source, double value)
{
}
@Override
public void valueChanged(SmoothMover source, double newValue, int pourcent)
{
}
}
public enum SmoothMoveType
{
NONE, LINEAR, LOG, EXP
};
/**
* current value
*/
protected double currentValue;
/**
* smooth movement type
*/
protected SmoothMoveType type;
/**
* time to do move (in ms)
*/
protected int moveTime;
/**
* internals
*/
protected final Timer timer;
protected double destValue;
protected double[] stepValues;
// private int stepIndex;
protected long startTime;
protected final ArrayList<SmoothMoverListener> listeners;
public SmoothMover(double initValue, SmoothMoveType type)
{
super();
currentValue = initValue;
destValue = initValue;
this.type = type;
// 60 updates per second by default
timer = new Timer(1000 / 60, this);
// no initial delay
timer.setInitialDelay(0);
timer.setRepeats(true);
// default : 1 second to reach destination
moveTime = 1000;
// default
stepValues = new double[0];
listeners = new ArrayList<SmoothMoverListener>();
}
public SmoothMover(double initValue)
{
this(initValue, SmoothMoveType.LINEAR);
}
/**
* Move to specified values v
*/
public void moveTo(double value)
{
if (destValue != value)
{
destValue = value;
// start movement
start();
}
}
public boolean isMoving()
{
return timer.isRunning();
}
protected void start()
{
// number of step to reach final value
final int size = Math.max(moveTime / timer.getDelay(), 1);
// calculate interpolation
switch (type)
{
case NONE:
stepValues = new double[2];
stepValues[0] = currentValue;
stepValues[1] = destValue;
break;
case LINEAR:
stepValues = Interpolator.doLinearInterpolation(currentValue, destValue, size);
break;
case LOG:
stepValues = Interpolator.doLogInterpolation(currentValue, destValue, size);
break;
case EXP:
stepValues = Interpolator.doExpInterpolation(currentValue, destValue, size);
break;
}
// initialize index to 1 (ignore value 0 which is current value)
// stepIndex = 1;
if (!isMoving())
{
// notify and start
moveStarted();
timer.start();
}
else
{
// update current value
// updateCurrentValue();
// notify and restart
moveModified();
// timer.restart();
}
}
public void stop()
{
if (isMoving())
{
// stop and notify
timer.stop();
moveEnded();
}
}
/**
* Shutdown the mover object (this actually stop internal timer and remove all listeners)
*/
public void shutDown()
{
timer.stop();
timer.removeActionListener(this);
listeners.clear();
}
/**
* @return the update delay (in ms)
*/
public int getUpdateDelay()
{
return timer.getDelay();
}
/**
* @param updateDelay
* the update delay (in ms) to set
*/
public void setUpdateDelay(int updateDelay)
{
timer.setDelay(updateDelay);
}
/**
* @return the smooth type
*/
public SmoothMoveType getType()
{
return type;
}
/**
* @param type
* the smooth type to set
*/
public void setType(SmoothMoveType type)
{
this.type = type;
}
/**
* @return the moveTime
*/
public int getMoveTime()
{
return moveTime;
}
/**
* @param moveTime
* the moveTime to set
*/
public void setMoveTime(int moveTime)
{
// can't be < 1
this.moveTime = Math.max(moveTime, 1);
}
/**
* Immediately set the value
*/
public void setValue(double value)
{
// stop current movement
stop();
// directly set value
destValue = value;
setCurrentValue(value, 100);
}
/**
* @return the value
*/
public double getValue()
{
return currentValue;
}
/**
* @return the destValue
*/
public double getDestValue()
{
return destValue;
}
public void addListener(SmoothMoverListener listener)
{
listeners.add(listener);
}
public void removeListener(SmoothMoverListener listener)
{
listeners.remove(listener);
}
/**
* Move started event
*/
protected void moveStarted()
{
startTime = System.currentTimeMillis();
for (SmoothMoverListener listener : listeners)
listener.moveStarted(this, currentValue, destValue);
}
/**
* Move modified event
*/
protected void moveModified()
{
startTime = System.currentTimeMillis();
for (SmoothMoverListener listener : listeners)
listener.moveModified(this, currentValue, destValue);
}
/**
* Move ended event
*/
protected void moveEnded()
{
for (SmoothMoverListener listener : listeners)
listener.moveEnded(this, currentValue);
}
/**
* update current value from elapsed time
*/
protected void updateCurrentValue()
{
final int elapsedMsTime = (int) (System.currentTimeMillis() - startTime);
// move completed ?
if ((type == SmoothMoveType.NONE) || (elapsedMsTime >= moveTime))
{
setCurrentValue(destValue, 100);
// stop
stop();
}
else
{
final int len = stepValues.length;
final int ind = Math.min((elapsedMsTime * len) / moveTime, len - 2);
// set value
setCurrentValue(stepValues[ind + 1], (elapsedMsTime * 100) / moveTime);
}
}
protected void setCurrentValue(double value, int pourcent)
{
if (currentValue != value)
{
currentValue = value;
// notify value changed
changed(value, pourcent);
}
}
/**
* Value changed event.
*/
protected void changed(double newValue, int pourcent)
{
for (SmoothMoverListener listener : listeners)
listener.valueChanged(this, newValue, pourcent);
}
@Override
public void actionPerformed(ActionEvent e)
{
// process only if timer running
if (isMoving())
updateCurrentValue();
}
}