001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.math; 020 021import java.awt.event.ActionEvent; 022import java.awt.event.ActionListener; 023import java.util.ArrayList; 024 025import javax.swing.Timer; 026 027/** 028 * @author Stephane 029 */ 030public class SmoothMover implements ActionListener 031{ 032 public static interface SmoothMoverListener 033 { 034 public void moveStarted(SmoothMover source, double start, double end); 035 036 public void moveModified(SmoothMover source, double start, double end); 037 038 public void moveEnded(SmoothMover source, double value); 039 040 public void valueChanged(SmoothMover source, double newValue, int pourcent); 041 } 042 043 public static class SmoothMoverAdapter implements SmoothMoverListener 044 { 045 @Override 046 public void moveStarted(SmoothMover source, double start, double end) 047 { 048 } 049 050 @Override 051 public void moveModified(SmoothMover source, double start, double end) 052 { 053 } 054 055 @Override 056 public void moveEnded(SmoothMover source, double value) 057 { 058 } 059 060 @Override 061 public void valueChanged(SmoothMover source, double newValue, int pourcent) 062 { 063 } 064 } 065 066 public enum SmoothMoveType 067 { 068 NONE, LINEAR, LOG, EXP 069 }; 070 071 /** 072 * current value 073 */ 074 protected double currentValue; 075 /** 076 * smooth movement type 077 */ 078 protected SmoothMoveType type; 079 /** 080 * time to do move (in ms) 081 */ 082 protected int moveTime; 083 084 /** 085 * internals 086 */ 087 protected final Timer timer; 088 protected double destValue; 089 protected double[] stepValues; 090 // private int stepIndex; 091 protected long startTime; 092 protected final ArrayList<SmoothMoverListener> listeners; 093 094 public SmoothMover(double initValue, SmoothMoveType type) 095 { 096 super(); 097 098 currentValue = initValue; 099 destValue = initValue; 100 101 this.type = type; 102 // 60 updates per second by default 103 timer = new Timer(1000 / 60, this); 104 // no initial delay 105 timer.setInitialDelay(0); 106 timer.setRepeats(true); 107 // default : 1 second to reach destination 108 moveTime = 1000; 109 // default 110 stepValues = new double[0]; 111 112 listeners = new ArrayList<SmoothMoverListener>(); 113 } 114 115 public SmoothMover(double initValue) 116 { 117 this(initValue, SmoothMoveType.LINEAR); 118 } 119 120 /** 121 * Move to specified values v 122 */ 123 public void moveTo(double value) 124 { 125 if (destValue != value) 126 { 127 destValue = value; 128 // start movement 129 start(); 130 } 131 } 132 133 public boolean isMoving() 134 { 135 return timer.isRunning(); 136 } 137 138 protected void start() 139 { 140 // number of step to reach final value 141 final int size = Math.max(moveTime / timer.getDelay(), 1); 142 143 // calculate interpolation 144 switch (type) 145 { 146 case NONE: 147 stepValues = new double[2]; 148 stepValues[0] = currentValue; 149 stepValues[1] = destValue; 150 break; 151 152 case LINEAR: 153 stepValues = Interpolator.doLinearInterpolation(currentValue, destValue, size); 154 break; 155 156 case LOG: 157 stepValues = Interpolator.doLogInterpolation(currentValue, destValue, size); 158 break; 159 160 case EXP: 161 stepValues = Interpolator.doExpInterpolation(currentValue, destValue, size); 162 break; 163 } 164 165 // initialize index to 1 (ignore value 0 which is current value) 166 // stepIndex = 1; 167 168 if (!isMoving()) 169 { 170 // notify and start 171 moveStarted(); 172 timer.start(); 173 } 174 else 175 { 176 // update current value 177 // updateCurrentValue(); 178 // notify and restart 179 moveModified(); 180 // timer.restart(); 181 } 182 } 183 184 public void stop() 185 { 186 if (isMoving()) 187 { 188 // stop and notify 189 timer.stop(); 190 moveEnded(); 191 } 192 } 193 194 /** 195 * Shutdown the mover object (this actually stop internal timer and remove all listeners) 196 */ 197 public void shutDown() 198 { 199 timer.stop(); 200 timer.removeActionListener(this); 201 listeners.clear(); 202 } 203 204 /** 205 * @return the update delay (in ms) 206 */ 207 public int getUpdateDelay() 208 { 209 return timer.getDelay(); 210 } 211 212 /** 213 * @param updateDelay 214 * the update delay (in ms) to set 215 */ 216 public void setUpdateDelay(int updateDelay) 217 { 218 timer.setDelay(updateDelay); 219 } 220 221 /** 222 * @return the smooth type 223 */ 224 public SmoothMoveType getType() 225 { 226 return type; 227 } 228 229 /** 230 * @param type 231 * the smooth type to set 232 */ 233 public void setType(SmoothMoveType type) 234 { 235 this.type = type; 236 } 237 238 /** 239 * @return the moveTime 240 */ 241 public int getMoveTime() 242 { 243 return moveTime; 244 } 245 246 /** 247 * @param moveTime 248 * the moveTime to set 249 */ 250 public void setMoveTime(int moveTime) 251 { 252 // can't be < 1 253 this.moveTime = Math.max(moveTime, 1); 254 } 255 256 /** 257 * Immediately set the value 258 */ 259 public void setValue(double value) 260 { 261 // stop current movement 262 stop(); 263 // directly set value 264 destValue = value; 265 setCurrentValue(value, 100); 266 } 267 268 /** 269 * @return the value 270 */ 271 public double getValue() 272 { 273 return currentValue; 274 } 275 276 /** 277 * @return the destValue 278 */ 279 public double getDestValue() 280 { 281 return destValue; 282 } 283 284 public void addListener(SmoothMoverListener listener) 285 { 286 listeners.add(listener); 287 } 288 289 public void removeListener(SmoothMoverListener listener) 290 { 291 listeners.remove(listener); 292 } 293 294 /** 295 * Move started event 296 */ 297 protected void moveStarted() 298 { 299 startTime = System.currentTimeMillis(); 300 301 for (SmoothMoverListener listener : listeners) 302 listener.moveStarted(this, currentValue, destValue); 303 } 304 305 /** 306 * Move modified event 307 */ 308 protected void moveModified() 309 { 310 startTime = System.currentTimeMillis(); 311 312 for (SmoothMoverListener listener : listeners) 313 listener.moveModified(this, currentValue, destValue); 314 } 315 316 /** 317 * Move ended event 318 */ 319 protected void moveEnded() 320 { 321 for (SmoothMoverListener listener : listeners) 322 listener.moveEnded(this, currentValue); 323 } 324 325 /** 326 * update current value from elapsed time 327 */ 328 protected void updateCurrentValue() 329 { 330 final int elapsedMsTime = (int) (System.currentTimeMillis() - startTime); 331 332 // move completed ? 333 if ((type == SmoothMoveType.NONE) || (elapsedMsTime >= moveTime)) 334 { 335 setCurrentValue(destValue, 100); 336 // stop 337 stop(); 338 } 339 else 340 { 341 final int len = stepValues.length; 342 final int ind = Math.min((elapsedMsTime * len) / moveTime, len - 2); 343 // set value 344 setCurrentValue(stepValues[ind + 1], (elapsedMsTime * 100) / moveTime); 345 } 346 } 347 348 protected void setCurrentValue(double value, int pourcent) 349 { 350 if (currentValue != value) 351 { 352 currentValue = value; 353 // notify value changed 354 changed(value, pourcent); 355 } 356 } 357 358 /** 359 * Value changed event. 360 */ 361 protected void changed(double newValue, int pourcent) 362 { 363 for (SmoothMoverListener listener : listeners) 364 listener.valueChanged(this, newValue, pourcent); 365 } 366 367 @Override 368 public void actionPerformed(ActionEvent e) 369 { 370 // process only if timer running 371 if (isMoving()) 372 updateCurrentValue(); 373 } 374}