/* * 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(); } }