Effects Panel
Update
Fixed a bug on Windows that made the
effects demo, well, dog ugly! Also updated the
acknowledgments to use some of the new effects
(subtle but not un-purty). Also updated the src.zip,
web-start demo, and the CVS is of course up-to-date.
Quite often you want to overlay (or indeed...
underlay) effects over the top (or underneath!) of a
normal swing window. You've probably seen examples of
these before, from shadowing you can find in the
SwingX library (I think that's where I saw it) to
icon "blooms" as the mouse moves over a button
(something Krill's added to substance) to
transitions. They are all great, but I wanted a way
to build a re-usable library of effects so that they
could be easily added to any application without too
much re-jigging.
This is where the EffectsPanel comes in, it is a
Component that can be set as the glass pane (as it is
in my example) and you can add as many effects to it
as you like. It manages the timing and repainting of,
well Effects. The EffectsPanel class itself is really
not very exciting, create on, set it as the GlassPane
and then addEffects. Here's the code and then we'll
move onto the Effects themselves.
EffectsPanel effectsPanel = new EffectsPanel();
setGlassPane(effectsPanel);
effectsPanel.setVisible(true);
...
effectsPanel.addEffect(new SomeEffect());
That's it. What's much more interesting is the Effect
interface.
Special Effects
The Effect interface has three methods
of interest, let's start with the most simple.
public void paintEffect(Graphics2D graphics);
The implementation of this method is given a graphics
object and the effect paints itself on. That's it, of
course we would like our effects to change over time
so there is an update method that needs implementing
as well.
public long update();
That looks pretty simple too huh? Well it is, when
called by an EffectPanel it's a signal that any
animation, movement, pre-calcs should be done. The
return value is important as well, if it's a number
above zero it will be called again after that number
of intervals (how often update is polled is
controlled by the update panel). There are also a
couple of special values you can return specified in
the interface.
EFFECT_FINISHED
Return this, and the effect will be removed from the
EffectsPanel, effectively finishing it, and then
rather interestingly...
EFFECT_INACTIVE
Return this and the EffectsPanel will stop calling
update, which means that the effect will still be
painted, but no further calls to update will be made.
Now we can animate, paint our animations, really
that's all that is needed, except there is one more
method that you'll see used in a later example.
public boolean isLocalEffect();
Basically a local effect is supplied it's own
Graphics object meaning that you can go crazy with
transforms, composites etc, and the next effect along
will be un-affected, a non-local effect will impact
every other that is drawn.
So all you have to do to add an effect is create an
EffectsPanel and implement some effects. It's that
easy.
In the example I put in the web-start demo has three
effects, one is a does the Krill "Alpha Burst" thing
when a mouse moves over a component (using a
mouseListener to check when that happens). Here's the
class, you'll love how short it is!
public class AlphaBurst extends
AbstractComponentEffect{
protected int width = 0;
protected int height = 0;
protected float alpha = 1.0f;
/** Creates a new instance of AlphaBurst */
public AlphaBurst(Component c) {
super(c);
width = c.getWidth();
height = c.getHeight();
}
public void paintEffect(Graphics2D graphics) {
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,alpha));
graphics.drawImage(componentImage,centre.x-width/2,centre.y-height/2,width,height,null);
}
public long update() {
alpha=alpha-(alpha/8.0f);
width=(int) ((float)width*1.2f);
height=(int) ((float)height*1.2f);
if (alpha>0.1){
return 20;
} else {
return Effect.EFFECT_FINISHED;
}
}
}
That's it, it renders the component into an image,
and then increases the width and height it's drawn at
reducing the alpha. when the alpha goes below 10% it
ends the animation. That's it. Cute.
If you tick the "Mouse Trails" button it will also
add some particle effects, each time a mouse moved or
mouse dragged event is received it randomly adds a
particle. The particle effect is also pretty simple.
It's just a dot that updates (with a minor nod
towards physics) and bounces if it goes below the
bottom third of the screen. Here it is
public class ParticleEffect implements Effect{
protected BufferedImage particle = null;
protected double x = 0.0;
protected double y = 0.0;
protected double vx = 0.0;
protected double vy = 0.0;
protected int life = 0;
protected float alpha = 1.0f;
protected double floor = 1000000.0f;
/**
* Creates a new instance of ParticleEffect
*
*/
public ParticleEffect(BufferedImage particle, int x,
int y,int age, int floor) {
this.particle = particle;
this.x = (double) x;
this.y = (double) y;
this.vx = 4.0-Math.random()*8.0;
this.vy = 6.0-Math.random()*8.0;
this.life = age;
this.floor = (double) floor;
}
public boolean isLocalEffect() {
return true;
}
public void paintEffect(Graphics2D graphics) {
if (alpha<1.0f){
float finalAlpha = alpha;
if (graphics.getComposite() instanceof
AlphaComposite){
AlphaComposite alphaComp = (AlphaComposite)
graphics.getComposite();
finalAlpha = alphaComp.getAlpha() * alpha;
}
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,finalAlpha));
}
graphics.drawImage(particle,(int)x, (int)y,null);
}
public long update() {
life--;
if (life<=0){
return Effect.EFFECT_FINISHED;
}
if ((y>floor) && (vy>0)) {
vy=0.0-vy*0.5;
}
x+=vx;
y+=vy;
vy+=0.1;
if (life<10){
alpha = (float) life / 10.0f;
}
return 20;
}
}
Not rocket science as you can see. But we can do some
really cute things, what if we want our particles to
be reflected into that bottom third of the screen?
Well how about we create an Effect, that reflects
other Effects? Tick the Reflect check box to make it
happen, but here's the Reflect Effect class....
public class ReflectEffect implements Effect{
protected Effect reflectThis;
protected EffectsPanel thePanel;
/** Creates a new instance of ReflectEffect */
public ReflectEffect(Effect reflectThis, EffectsPanel
panel) {
this.reflectThis = reflectThis;
this.thePanel = panel;
}
public boolean isLocalEffect() {
return true;
}
public void paintEffect(Graphics2D graphics) {
reflectThis.paintEffect(graphics);
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.5f));
graphics.translate(0,thePanel.getHeight());
graphics.scale(1.0,-0.3);
reflectThis.paintEffect(graphics);
}
public long update() {
return reflectThis.update();
}
}
This effect takes another effect as a parameter,
passes on it's update calls to the sub-summed effect
and then in paint, does something clever. First it
asks the effect to paint itself normally before
applying a couple of transforms to the graphics
context, and calling it again making it paint itself
upside and squashed. Easy-peasy, why not have a play
with the web-start demo or check out (no pun) the
code from CVS in the JavaDEV project.
To see the new Effects run "Effects - Alpha Burst"