Something for the weekend
Introducing... the EffectsPanel class!
Now that I've documented and fixed up what was there, I've started moving a few of the new things I've been building into the library, and I wanted to start with the EffectPanel.


EffectsPanel
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"


webstart-small2

|