/**
* SlidingAnimator.java
* Copyright 2008 Zach Scrivena
* zachscrivena@gmail.com
* http://zs.freeshell.org/
*
* TERMS AND CONDITIONS:
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeshell.zs.common;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
/**
* Perform a sliding animation for a window.
*/
public final class SlidingAnimator
{
/** interval between animation refreshes, in milliseconds */
private static final int ANIMATION_REFRESH_INTERVAL_MILLISECONDS = 20;
/**
* Private constructor that should never be called.
*/
private SlidingAnimator()
{}
/**
* Perform a sliding animation for the specified window.
* This method blocks until the animation has finished; it should NOT be called on the EDT.
*
* @param sourceWindow
* source window to be animated
* @param refX
* x-coordinate of the window origin at the start/end reference position
* @param refY
* y-coordinate of the window origin at the start/end reference position
* @param dir
* direction of animation
* @param stepSize
* number of incremental pixels per animation step
*/
public static void animate(
final JWindow sourceWindow,
final int refX,
final int refY,
final Direction dir,
final int stepSize)
{
/****************************************
* (1) CREATE SWING TIMER FOR ANIMATION *
****************************************/
final Timer swingTimer = new Timer(ANIMATION_REFRESH_INTERVAL_MILLISECONDS, null);
swingTimer.start();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
/**************************************
* (2) DETERMINE ANIMATION PARAMETERS *
**************************************/
final int step = Math.max(stepSize, 1);
final int sourceWidth = sourceWindow.getWidth();
final int sourceHeight = sourceWindow.getHeight();
final int initialX;
final int initialY;
final int finalX;
final int finalY;
final int deltaX;
final int deltaY;
switch (dir)
{
case UP_IN:
initialX = refX;
initialY = refY + sourceHeight;
finalX = refX;
finalY = refY;
deltaX = 0;
deltaY = -step;
break;
case DOWN_IN:
initialX = refX;
initialY = refY - sourceHeight;
finalX = refX;
finalY = refY;
deltaX = 0;
deltaY = step;
break;
case LEFT_IN:
initialX = refX + sourceWidth;
initialY = refY;
finalX = refX;
finalY = refY;
deltaX = -step;
deltaY = 0;
break;
case RIGHT_IN:
initialX = refX - sourceWidth;
initialY = refY;
finalX = refX;
finalY = refY;
deltaX = step;
deltaY = 0;
break;
case UP_OUT:
initialX = refX;
initialY = refY;
finalX = refX;
finalY = refY - sourceHeight;
deltaX = 0;
deltaY = -step;
break;
case DOWN_OUT:
initialX = refX;
initialY = refY;
finalX = refX;
finalY = refY + sourceHeight;
deltaX = 0;
deltaY = step;
break;
case LEFT_OUT:
initialX = refX;
initialY = refY;
finalX = refX - sourceWidth;
finalY = refY;
deltaX = -step;
deltaY = 0;
break;
case RIGHT_OUT:
initialX = refX;
initialY = refY;
finalX = refX + sourceWidth;
finalY = refY;
deltaX = step;
deltaY = 0;
break;
default:
initialX = 0;
initialY = 0;
finalX = 0;
finalY = 0;
deltaX = 0;
deltaY = 0;
}
/********************************************
* (3) CREATE AN IMAGE OF THE SOURCE WINDOW *
********************************************/
final BufferedImage image = new BufferedImage(sourceWidth, sourceHeight, BufferedImage.TYPE_INT_RGB);
final Graphics2D g = image.createGraphics();
sourceWindow.setVisible(true);
sourceWindow.paintAll(g);
/* hide the source window if animation is "in-bound" */
if ((dir == Direction.UP_IN) ||
(dir == Direction.DOWN_IN) ||
(dir == Direction.LEFT_IN) ||
(dir == Direction.RIGHT_IN))
{
sourceWindow.setVisible(false);
}
/************************************************************
* (4) CREATE ACTUAL WINDOW USED IN RENDERING THE ANIMATION *
************************************************************/
final RenderedWindow window = new RenderedWindow(image, initialX, initialY);
/*******************************************************************
* (5) ADD ACTION LISTENER TO SWING TIMER FOR ANIMATING THE WINDOW *
*******************************************************************/
swingTimer.addActionListener(new ActionListener()
{
/** is this the first step in the animation? */
private boolean firstStep = true;
/**
* Perform a single step in the animation.
*/
public void actionPerformed(ActionEvent e)
{
/* update position of the virtual window */
final int nextX = window.virtualX + deltaX;
final int nextY = window.virtualY + deltaY;
/* check if the animation should be terminated */
boolean terminate = false;
switch (dir)
{
case UP_IN:
case UP_OUT:
if (nextY < finalY)
{
terminate = true;
}
break;
case DOWN_IN:
case DOWN_OUT:
if (nextY > finalY)
{
terminate = true;
}
break;
case LEFT_IN:
case LEFT_OUT:
if (nextX < finalX)
{
terminate = true;
}
break;
case RIGHT_IN:
case RIGHT_OUT:
if (nextX > finalX)
{
terminate = true;
}
break;
}
if (terminate)
{
/* terminate the animation */
swingTimer.stop();
if ((dir == Direction.UP_IN) ||
(dir == Direction.DOWN_IN) ||
(dir == Direction.LEFT_IN) ||
(dir == Direction.RIGHT_IN))
{
sourceWindow.setVisible(true);
}
else
{
sourceWindow.setVisible(false);
}
window.setVisible(false);
window.dispose();
return;
}
else
{
/* register updated position of the virtual window */
window.virtualX = nextX;
window.virtualY = nextY;
}
/* determine position and size of the rendered window */
final int windowX;
final int windowY;
final int windowWidth;
final int windowHeight;
if (window.virtualX >= refX)
{
windowX = window.virtualX;
windowWidth = sourceWidth - window.virtualX + refX;
}
else
{
windowX = refX;
windowWidth = window.virtualX + sourceWidth - refX;
}
if (window.virtualY >= refY)
{
windowY = window.virtualY;
windowHeight = sourceHeight - window.virtualY + refY;
}
else
{
windowY = refY;
windowHeight = window.virtualY + sourceHeight - refY;
}
window.setSize(windowWidth, windowHeight);
window.setLocation(windowX, windowY);
/* check if this is the first step in the animation */
if (firstStep)
{
window.setVisible(true);
firstStep = false;
if ((dir == Direction.UP_OUT) ||
(dir == Direction.DOWN_OUT) ||
(dir == Direction.LEFT_OUT) ||
(dir == Direction.RIGHT_OUT))
{
sourceWindow.setVisible(false);
}
}
}
});
}
});
/* wait for animation to finish */
while (swingTimer.isRunning())
{
Debug.sleep(ANIMATION_REFRESH_INTERVAL_MILLISECONDS);
}
}
/******************
* NESTED CLASSES *
******************/
/**
* Represent the actual window used in rendering an animation.
*/
private static class RenderedWindow
extends JWindow
{
/** image to be drawn on this window */
private final BufferedImage image;
/** current x-coordinate of the virtual window origin */
int virtualX;
/** current y-coordinate of the virtual window origin */
int virtualY;
/**
* Constructor.
*
* @param image
* image to be drawn on this window
* @param virtualX
* initial x-coordinate of the virtual window origin
* @param virtualY
* initial y-coordinate of the virtual window origin
*/
RenderedWindow(
final BufferedImage image,
final int virtualX,
final int virtualY)
{
super();
setAlwaysOnTop(true);
this.image = image;
this.virtualX = virtualX;
this.virtualY = virtualY;
}
@Override
public void update(Graphics g)
{
paint(g);
}
@Override
public void paint(Graphics g)
{
g.drawImage(image.getSubimage(
getX() - virtualX,
getY() - virtualY,
getWidth(),
getHeight()),
0,
0,
this);
}
}
/**
* Sliding animation direction.
*/
public static enum Direction
{
UP_IN,
UP_OUT,
DOWN_IN,
DOWN_OUT,
LEFT_IN,
LEFT_OUT,
RIGHT_IN,
RIGHT_OUT
}
}