Added support for animations to the animation manager. Moved animation

classes into their own package.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@849 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Walter Korman
2002-01-11 16:17:34 +00:00
parent c677d08c50
commit aa574a8182
19 changed files with 869 additions and 140 deletions
@@ -0,0 +1,138 @@
//
// $Id: AnimatedPanel.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.image.BufferStrategy;
import java.util.List;
import com.threerings.media.Log;
/**
* The animated panel provides a useful extensible implementation of a
* {@link Canvas} that implements the {@link AnimatedView} interface.
* Sub-classes should override {@link #render} to draw their
* panel-specific contents, and may choose to override {@link
* #invalidateRects} and {@link #invalidateRect} to optimize their
* internal rendering.
*/
public class AnimatedPanel extends Canvas implements AnimatedView
{
/**
* Constructs an animated panel.
*/
public AnimatedPanel ()
{
// set our attributes for optimal display performance
// setIgnoreRepaint(true);
}
// documentation inherited
public void paint (Graphics g)
{
// Log.info("AnimatedPanel paint");
update(g);
}
// documentation inherited
public void update (Graphics g)
{
// Log.info("AnimatedPanel update");
paintImmediately();
}
/**
* Renders the panel to the given graphics object. Sub-classes
* should override this method to paint their panel-specific
* contents.
*/
protected void render (Graphics g)
{
// nothing for now
}
// documentation inherited
public void invalidateRects (List rects)
{
// nothing for now
}
// documentation inherited
public void invalidateRect (Rectangle rect)
{
// nothing for now
}
// documentation inherited
public void paintImmediately ()
{
if (!isValid() || !isShowing()) {
Log.warning("Attempt to paint unprepared panel " +
"[valid=" + isValid() +
", showing=" + isShowing() + "].");
return;
}
if (_strategy == null) {
// create and obtain a reference to the buffer strategy
createBufferStrategy(BUFFER_COUNT);
_strategy = getBufferStrategy();
Log.info("Created buffer strategy [strategy=" + _strategy + "].");
}
// render the panel
Graphics g = null;
try {
g = _strategy.getDrawGraphics();
render(g);
} finally {
if (g != null) {
g.dispose();
}
}
_strategy.show();
}
// documentation inherited
public void createBufferStrategy (int numBuffers)
{
// explicitly avoid trying to create a page-flipping strategy, as
// page-flipping seems to result in artifacts in certain
// conditions, and the buffer strategy's volatile images are
// irretrievably lost when the panel is hidden.
// try an accelerated blitting strategy
BufferCapabilities bufferCaps = new BufferCapabilities(
new ImageCapabilities(true), new ImageCapabilities(true), null);
try {
createBufferStrategy(numBuffers, bufferCaps);
Log.info("Created accelerated blitting strategy.");
return;
} catch (AWTException e) {
// failed, fall through to the next potential strategy
}
// try an un-accelerated blitting strategy
bufferCaps = new BufferCapabilities(
new ImageCapabilities(false), new ImageCapabilities(false), null);
try {
createBufferStrategy(numBuffers, bufferCaps);
} catch (AWTException e) {
throw new InternalError("Could not create a buffer strategy");
}
}
/** The number of buffers to use when rendering. */
protected static final int BUFFER_COUNT = 2;
/** The buffer strategy used for optimal animation rendering. */
protected BufferStrategy _strategy;
}
@@ -0,0 +1,40 @@
//
// $Id: AnimatedView.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
import java.awt.Rectangle;
import java.util.List;
/**
* A view that wishes to interact with the animation manager needs to
* implement this interface to give the animation manager a means by which
* to communicate the regions of the view that need to be repainted
* because of the process of animating on top of the view.
*/
public interface AnimatedView
{
/**
* Invalidate a list of rectangles in screen pixel coordinates in the
* scene view for later repainting.
*
* @param rects the list of {@link java.awt.Rectangle} objects.
*/
public void invalidateRects (List rects);
/**
* Invalidates a rectangle in screen pixel coordinates in the scene
* view for later repainting.
*
* @param rect the {@link java.awt.Rectangle} to dirty.
*/
public void invalidateRect (Rectangle rect);
/**
* Requests that the animated view paint itself immediately (that it
* complete the painting process before returning from this function).
* This will only be called on the AWT thread and when it is safe to
* paint.
*/
public void paintImmediately ();
}
@@ -0,0 +1,136 @@
//
// $Id: Animation.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import com.threerings.media.Log;
/**
* The animation class is an abstract class that should be extended to
* provide animation functionality for use with the {@link
* AnimationManager} and an {@link AnimatedView}.
*/
public abstract class Animation
{
/**
* Constructs an animation.
*
* @param bounds the animation rendering bounds.
*/
public Animation (Rectangle bounds)
{
_bounds = bounds;
}
/**
* Called by the {@link AnimationManager} to allow the animation to
* render itself to the given graphics context.
*/
public abstract void paint (Graphics2D gfx);
/**
* Called periodically by the {@link AnimationManager} to give the
* animation a chance to do its thing.
*/
public abstract void tick (long timestamp);
/**
* Invalidates the animation's bounds for later re-rendering by the
* {@link AnimationManager}.
*/
public void invalidate ()
{
_animmgr.addDirtyRect(_bounds);
}
/**
* Returns whether the animation has finished all of its business.
*/
public boolean isFinished ()
{
return _finished;
}
/**
* Adds an animation observer to this animation's list of observers.
*/
public void addAnimationObserver (AnimationObserver obs)
{
// create the observer list if it doesn't yet exist
if (_observers == null) {
_observers = new ArrayList();
}
// make sure each observer observes only once
if (_observers.contains(obs)) {
Log.info("Attempt to observe animation already observing " +
"[anim=" + this + ", obs=" + obs + "].");
return;
}
// add the observer
_observers.add(obs);
}
/**
* Notifies any animation observers that the given animation is
* finished.
*/
public void notifyObservers (AnimationEvent e)
{
int size = (_observers == null) ? 0 : _observers.size();
for (int ii = 0; ii < size; ii++) {
((AnimationObserver)_observers.get(ii)).handleEvent(e);
}
}
/**
* Return a string representation of the animation.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer();
buf.append("[");
toString(buf);
return buf.append("]").toString();
}
/**
* This should be overridden by derived classes (which should be sure
* to call <code>super.toString()</code>) to append the derived class
* specific animation information to the string buffer.
*/
protected void toString (StringBuffer buf)
{
buf.append("bounds=").append(_bounds);
}
/**
* Called by the animation manager when an animation is added to said
* manager for management.
*
* @param animmgr the animation manager.
* @param view the animated view.
*/
protected void setAnimationManager (AnimationManager animmgr)
{
_animmgr = animmgr;
}
/** Whether the animation is finished. */
protected boolean _finished = false;
/** The animation bounds. */
protected Rectangle _bounds;
/** The list of animation observers. */
protected ArrayList _observers;
/** The animation manager. */
protected AnimationManager _animmgr;
}
@@ -0,0 +1,16 @@
//
// $Id: AnimationCompletedEvent.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
/**
* An animation completed event is dispatched when an animation has
* completed all of its business.
*/
public class AnimationCompletedEvent extends AnimationEvent
{
public AnimationCompletedEvent (Animation anim)
{
super(anim);
}
}
@@ -0,0 +1,32 @@
//
// $Id: AnimationEvent.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
/**
* An animation event is sent to all of an animation's observers whenever
* one of the various possible animation events takes place.
*/
public class AnimationEvent
{
/**
* Create an animation event.
*
* @param animation the involved animation.
*/
public AnimationEvent (Animation anim)
{
_anim = anim;
}
/**
* Returns the animation involved in the event.
*/
public Animation getAnimation ()
{
return _anim;
}
/** The animation associated with this event. */
protected Animation _anim;
}
@@ -0,0 +1,341 @@
//
// $Id: AnimationManager.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.event.AncestorEvent;
import com.samskivert.swing.event.AncestorAdapter;
import com.samskivert.util.Interval;
import com.samskivert.util.IntervalManager;
import com.threerings.media.Log;
import com.threerings.media.sprite.SpriteManager;
import com.threerings.media.util.PerformanceMonitor;
import com.threerings.media.util.PerformanceObserver;
/**
* The animation manager handles the regular refreshing of the scene view
* to allow for animation. It also may someday manage special scene-wide
* animations, such as rain, fog, or earthquakes.
*/
public class AnimationManager
implements Interval, PerformanceObserver
{
/**
* Construct and initialize the animation manager with a sprite
* manager and the view in which the animations will take place. The
* animation manager will automatically start itself up, but must be
* explicitly shutdown when the animated view is no longer in
* operation via a call to {@link #stop}.
*/
public AnimationManager (SpriteManager spritemgr, AnimatedView view)
{
// save off references to the objects we care about
_spritemgr = spritemgr;
_view = view;
// register to monitor the refresh action
PerformanceMonitor.register(this, "refresh", 1000);
// start ourselves up
start();
}
/**
* Starts the animation manager to doing its business.
*/
public synchronized void start ()
{
if (_ticker == null) {
// create ticker for queueing up tick requests on AWT thread
_ticker = new Runnable() {
public void run () {
tick();
}
};
// register the refresh interval
_iid = IntervalManager.register(this, REFRESH_INTERVAL, null, true);
}
}
/**
* Instructs the animation manager to stop doing its business.
*/
public synchronized void stop ()
{
if (_ticker != null) {
_ticker = null;
// un-register the refresh interval since we're now hidden
IntervalManager.remove(_iid);
_iid = -1;
}
}
/**
* Adds a rectangle to the dirty rectangle list. Note that the
* rectangle may be destructively modified by the animation manager at
* some later date.
*
* @param rect the rectangle to add.
*/
public void addDirtyRect (Rectangle rect)
{
_dirty.add(rect);
}
/**
* Registers the given {@link Animation} with the animation manager
* for ticking and painting.
*/
public void registerAnimation (Animation anim)
{
if (_anims.contains(anim)) {
Log.warning("Attempt to register animation more than once " +
"[anim=" + anim + "].");
return;
}
anim.setAnimationManager(this);
_anims.add(anim);
// Log.info("Registered animation [anim=" + anim + "].");
}
/**
* Un-registers the given {@link Animation} from the animation
* manager.
*/
public void unregisterAnimation (Animation anim)
{
// un-register the animation
if (!_anims.remove(anim)) {
Log.warning("Attempt to un-register animation that isn't " +
"registered [anim=" + anim + "].");
}
// dirty the animation bounds
anim.invalidate();
// Log.info("Un-registered animation [anim=" + anim + "].");
}
/**
* Renders all registered animations to the given graphics context.
*/
public void renderAnimations (Graphics2D gfx)
{
int size = _anims.size();
for (int ii = 0; ii < size; ii++) {
Animation anim = (Animation)_anims.get(ii);
anim.paint(gfx);
}
}
/**
* Called by our interval when we'd like to begin a tick. Returns
* whether we're already ticking, and notes that we've requested
* another tick.
*/
protected synchronized boolean requestTick ()
{
return !(_ticking++ > 0);
}
/**
* Called by the tick task when it's finished with a tick.
* Returns whether a new tick task should be created immediately.
*/
protected synchronized boolean finishedTick ()
{
if (--_ticking > 0) {
_ticking = 1;
return true;
}
return false;
}
/**
* The <code>IntervalManager</code> calls this method as often as
* we've requested to obtain our desired frame rate. Since we'd
* like to avoid messy thread-synchronization between the AWT
* thread and other threads, here we just add the tick task to the
* AWT thread for later execution.
*/
public void intervalExpired (int id, Object arg)
{
if (requestTick()) {
// throw the tick task on the AWT thread task queue
queueTick();
}
}
/**
* The <code>tick</code> method handles updating sprites and
* repainting the target display.
*/
protected void tick ()
{
synchronized (this) {
// see if we were shutdown since we were last queued up
if (_ticker == null) {
return;
}
}
// every tick should have a timestamp associated with it
long now = System.currentTimeMillis();
// call tick on all sprites
_spritemgr.tick(now, _dirty);
// call tick on all animations
tickAnimations(now);
// perform a single pass merging overlapping rectangles. note
// that this will also clear out the contents of our internal
// dirty rectangle list.
List rects = mergeDirtyRects(_dirty);
// invalidate screen-rects dirtied by sprites and/or animations
if (rects.size() > 0) {
// pass the dirty-rects on to the scene view
_view.invalidateRects(rects);
// refresh the display
_view.paintImmediately();
}
// remove any finished animations
removeFinishedAnimations();
// update refresh-rate information
PerformanceMonitor.tick(AnimationManager.this, "refresh");
if (finishedTick()) {
// finishedTick returning true means there's been a
// request for at least one more tick since we started
// this tick, so we want to queue up another tick
// immediately
queueTick();
}
}
/**
* Calls tick on all animations currently registered with the
* animation manager.
*/
protected void tickAnimations (long timestamp)
{
int size = _anims.size();
for (int ii = 0; ii < size; ii++) {
((Animation)_anims.get(ii)).tick(timestamp);
// if (anim.isFinished()) {
// anim.notifyObservers(new AnimationWillCompleteEvent(anim));
// }
}
}
/**
* Removes any finished animations from the list of animations and
* notifies their respective animation observers, if any.
*/
protected void removeFinishedAnimations ()
{
int size = _anims.size();
for (int ii = size - 1; ii >= 0; ii--) {
Animation anim = (Animation)_anims.get(ii);
if (anim.isFinished()) {
// let any animation observers know that we're done
anim.notifyObservers(new AnimationCompletedEvent(anim));
// un-register the animation
unregisterAnimation(anim);
Log.info("Removed finished animation [anim=" + anim + "].");
}
}
}
/**
* Queues up a tick on the AWT event handler thread, iff we are still
* operating.
*/
protected synchronized void queueTick ()
{
if (_ticker != null) {
SwingUtilities.invokeLater(_ticker);
}
}
/**
* Returns a new list of dirty rectangles representing the given list
* with any intersecting rectangles merged. The given list is
* destructively modified and cleared of all contents.
*
* @return the list of merged dirty rects.
*/
protected List mergeDirtyRects (List rects)
{
ArrayList merged = new ArrayList();
while (rects.size() > 0) {
// pop the next rectangle from the dirty list
Rectangle mr = (Rectangle)rects.remove(0);
// merge in any overlapping rectangles
for (int ii = 0; ii < rects.size(); ii++) {
Rectangle r = (Rectangle)rects.get(ii);
if (mr.intersects(r)) {
// remove the overlapping rectangle from the list
rects.remove(ii--);
// grow the merged dirty rectangle
mr.add(r);
}
}
// add the merged rectangle to the list
merged.add(mr);
}
return merged;
}
// documentation inherited
public void checkpoint (String name, int ticks)
{
Log.info(name + " [ticks=" + ticks + "].");
}
/** The list of animations. */
protected ArrayList _anims = new ArrayList();
/** The dirty rectangles. */
protected ArrayList _dirty = new ArrayList();
/** The ticker runnable that we put on the AWT thread periodically. */
protected Runnable _ticker;
/** The number of outstanding tick requests. */
protected int _ticking = 0;
/** The refresh interval id. */
protected int _iid = -1;
/** The sprite manager. */
protected SpriteManager _spritemgr;
/** The view on which we are animating. */
protected AnimatedView _view;
/** The desired number of refresh operations per second. */
protected static final int FRAME_RATE = 70;
/** The milliseconds to sleep to obtain desired frame rate. */
protected static final long REFRESH_INTERVAL = 1000 / FRAME_RATE;
}
@@ -0,0 +1,19 @@
//
// $Id: AnimationObserver.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
/**
* An interface to be implemented by classes that would like to observe an
* {@link Animation} and be notified of interesting events relating to it.
*/
public interface AnimationObserver
{
/**
* This method is called by the {@link AnimationManager} when
* something interesting involving the animation happens.
*
* @param event the animation event.
*/
public void handleEvent (AnimationEvent event);
}
@@ -0,0 +1,97 @@
//
// $Id: BobbleAnimation.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import com.threerings.media.Log;
import com.threerings.media.util.RandomUtil;
/**
* An animation that bobbles an image around within a specific horizontal
* and vertical pixel range for a given period of time.
*/
public class BobbleAnimation extends Animation
{
/**
* Constructs a bobble animation.
*
* @param image the image to animate.
* @param sx the starting x-position.
* @param sy the starting y-position.
* @param rx the horizontal bobble range.
* @param ry the vertical bobble range.
* @param length the time to animate in milliseconds.
*/
public BobbleAnimation (
Image image, int sx, int sy, int rx, int ry, int length)
{
super(new Rectangle(sx - rx, sy - ry, sx + image.getWidth(null) + rx,
sy + image.getHeight(null) + ry));
// save things off
_image = image;
_sx = sx;
_sy = sy;
_rx = rx;
_ry = ry;
// calculate animation ending time
_end = System.currentTimeMillis() + length;
// calculate the dirty rectangle bounds
int wid = image.getWidth(null), hei = image.getHeight(null);
// _bounds = new Rectangle(sx - rx, sy - ry, sx + wid + rx, sy + hei + ry);
}
// documentation inherited
public void tick (long timestamp)
{
_finished = (timestamp >= _end);
invalidate();
// calculate the latest position
int dx = RandomUtil.getInt(_rx);
int dy = RandomUtil.getInt(_ry);
_x = (_sx + dx) * ((RandomUtil.getInt(2) == 0) ? -1 : 1);
_y = (_sy + dy) * ((RandomUtil.getInt(2) == 0) ? -1 : 1);
}
// documentation inherited
public void paint (Graphics2D gfx)
{
gfx.drawImage(_image, _x, _y, null);
}
// documentation inherited
protected void toString (StringBuffer buf)
{
super.toString(buf);
buf.append(", x=").append(_x);
buf.append(", y=").append(_y);
buf.append(", rx=").append(_rx);
buf.append(", ry=").append(_ry);
buf.append(", sx=").append(_sx);
buf.append(", sy=").append(_sy);
}
/** The current position of the image. */
protected int _x, _y;
/** The horizontal and vertical bobbling range. */
protected int _rx, _ry;
/** The starting position. */
protected int _sx, _sy;
/** The image to animate. */
protected Image _image;
/** The ending animation time. */
protected long _end;
}
@@ -0,0 +1,165 @@
//
// $Id: ExplodeAnimation.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import com.threerings.media.Log;
/**
* An animation that displays an image exploding into chunks. The
* animation ends when all image chunks have exited the animation bounds.
*/
public class ExplodeAnimation extends Animation
{
/**
* Constructs an explode animation.
*
* @param bounds the bounds within which to animate.
* @param image the image to animate.
* @param sx the starting x-position.
* @param sy the starting y-position.
* @param vel the initial chunk velocity.
*/
public ExplodeAnimation (
Rectangle bounds, Image image, int sx, int sy, float vel)
{
super(bounds);
// save things off
_image = image;
_sx = sx;
_sy = sy;
_vel = vel;
// save the animation starting time
_start = System.currentTimeMillis();
// determine chunk dimensions
_cwid = image.getWidth(null) / X_CHUNK_COUNT;
_chei = image.getHeight(null) / Y_CHUNK_COUNT;
// initialize the chunk positions
int cx = sx + (image.getWidth(null) / 2);
int cy = sy + (image.getHeight(null) / 2);
int cpos = (cx << 16 | cy);
for (int ii = 0; ii < NUM_CHUNKS; ii++) {
int xpos = ii % X_CHUNK_COUNT;
int ypos = ii / X_CHUNK_COUNT;
_cpos[ii] = ((sx + xpos) << 16 | (sy + ypos));
}
}
// documentation inherited
public void tick (long timestamp)
{
// figure out the distance the chunks have travelled
long msecs = timestamp - _start;
int travpix = (int)(msecs * _vel);
// move all chunks and check whether any remain to be animated
int inside = 0;
for (int ii = 0; ii < NUM_CHUNKS; ii++) {
// retrieve the current chunk position
int x = _cpos[ii] >> 16;
int y = _cpos[ii] & 0xFFFF;
// determine the chunk movement direction
int xpos = ii % X_CHUNK_COUNT;
int ypos = ii / X_CHUNK_COUNT;
int mx, my;
switch (xpos) {
case 0: mx = -1; break;
case X_CHUNK_COUNT - 1: mx = 1; break;
default: mx = 0; break;
}
switch (ypos) {
case 0: my = -1; break;
case Y_CHUNK_COUNT - 1: my = 1; break;
default: my = 0;
}
// deal with the center point for odd dimensions
if (mx == 0 && my == 0) {
my = 1;
}
// update the chunk position
x = _sx + (travpix * mx);
y = _sy + (travpix * my);
_cpos[ii] = (x << 16 | y);
// note whether this chunk is still within our bounds
if (_bounds.contains(x, y)) {
inside++;
}
}
_finished = (inside == 0);
invalidate();
}
// documentation inherited
public void paint (Graphics2D gfx)
{
for (int ii = 0; ii < NUM_CHUNKS; ii++) {
// get the chunk location on-screen
int x = _cpos[ii] >> 16;
int y = _cpos[ii] & 0xFFFF;
// get the chunk position within the image
int xpos = ii % X_CHUNK_COUNT;
int ypos = ii / X_CHUNK_COUNT;
// calculate image chunk offset
int xoff = -(xpos * _cwid);
int yoff = -(ypos * _chei);
// draw the chunk
Shape oclip = gfx.getClip();
gfx.clipRect(x, y, _cwid, _chei);
gfx.drawImage(_image, x + xoff, y + yoff, null);
gfx.setClip(oclip);
}
}
// documentation inherited
protected void toString (StringBuffer buf)
{
super.toString(buf);
buf.append(", sx=").append(_sx);
buf.append(", sy=").append(_sy);
}
/** The number of horizontal chunks from the image to animate. */
protected static final int X_CHUNK_COUNT = 3;
/** The number of vertical chunks from the image to animate. */
protected static final int Y_CHUNK_COUNT = 3;
/** The total number of chunks. */
protected static final int NUM_CHUNKS = X_CHUNK_COUNT * Y_CHUNK_COUNT;
/** The current position of each chunk. */
protected int[] _cpos = new int[NUM_CHUNKS];
/** The individual chunk dimensions in pixels. */
protected int _cwid, _chei;
/** The chunk velocity. */
protected float _vel;
/** The starting position. */
protected int _sx, _sy;
/** The image to animate. */
protected Image _image;
/** The starting animation time. */
protected long _start;
}
@@ -0,0 +1,117 @@
//
// $Id: FadeAnimation.java,v 1.1 2002/01/11 16:17:33 shaper Exp $
package com.threerings.media.animation;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import com.threerings.media.Log;
/**
* An animation that displays an image fading from invisibility to full
* opacity, or vice-versa. The animation is finished when the target
* opacity is reached.
*/
public class FadeAnimation extends Animation
{
/**
* Constructs a fade animation.
*
* @param image the image to animate.
* @param x the image x-position.
* @param y the image y-position.
* @param alpha the starting alpha.
* @param step the alpha amount to step by each millisecond.
* @param target the target alpha level.
*/
public FadeAnimation (
Image image, int x, int y, float alpha, float step, float target)
{
super(new Rectangle(x, y, image.getWidth(null), image.getHeight(null)));
// save things off
_image = image;
_x = x;
_y = y;
_startAlpha = _alpha = alpha;
_step = step;
_target = target;
// create the initial composite
_comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, _alpha);
// save the animation starting time
_start = System.currentTimeMillis();
}
// documentation inherited
public void tick (long timestamp)
{
// figure out the current alpha
long msecs = timestamp - _start;
_alpha = Math.min(_startAlpha + (msecs * _step), 1.0f);
_comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, _alpha);
// check whether we're done and dirty ourselves
_finished = ((_startAlpha < _target) ? (_alpha >= _target) :
(_alpha <= _target));
invalidate();
// Log.info("Ticked fade [anim=" + this + "].");
}
// documentation inherited
public void paint (Graphics2D gfx)
{
Composite ocomp = gfx.getComposite();
if (_comp == null) {
Log.warning("Fade anim has null composite [anim=" + this + "].");
} else {
gfx.setComposite(_comp);
}
gfx.drawImage(_image, _x, _y, null);
gfx.setComposite(ocomp);
}
// documentation inherited
protected void toString (StringBuffer buf)
{
super.toString(buf);
buf.append(", x=").append(_x);
buf.append(", y=").append(_y);
buf.append(", alpha=").append(_alpha);
buf.append(", startAlpha=").append(_startAlpha);
buf.append(", step=").append(_step);
buf.append(", target=").append(_target);
}
/** The composite object used to render the image with desired opacity. */
protected Composite _comp;
/** The current alpha of the image. */
protected float _alpha;
/** The target alpha. */
protected float _target;
/** The alpha step per millisecond. */
protected float _step;
/** The starting alpha. */
protected float _startAlpha;
/** The image position. */
protected int _x, _y;
/** The image to animate. */
protected Image _image;
/** The starting animation time. */
protected long _start;
}