001: /*
002: * EffectEngine.java
003: *
004: * Created on March 19, 2007, 10:10 PM
005: *
006: * Copyright 2006-2007 Nigel Hughes
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
009: * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
010: * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
012: * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
013: * governing permissions and limitations under the License.
014: */
015:
016: package com.blogofbug.swing.components.effects;
017:
018: import com.blogofbug.swing.SwingBugUtilities;
019: import com.jhlabs.composite.AddComposite;
020: import java.awt.Graphics;
021: import java.awt.Graphics2D;
022: import java.awt.event.ActionEvent;
023: import java.awt.event.ActionListener;
024: import java.awt.event.ComponentEvent;
025: import java.awt.event.ComponentListener;
026: import java.util.LinkedList;
027: import javax.swing.JComponent;
028: import javax.swing.Timer;
029:
030: /**
031: * Deligate implementation of an effects engine. All a component needs to do is create an instance and
032: * ensure that paint() method is called by their paintComponent method.
033: *
034: * @author nigel
035: */
036: public class EffectEngine implements EffectContainer, ActionListener {
037: /**
038: * The id for the main timer event
039: */
040: protected static final int TIMER_IDENTIFIER = 1;
041: /**
042: * The delay used for the timer, sub-classes that want to provide a higher resolution can
043: * override this
044: */
045: protected int delay = 20;
046: /**
047: * A list of the currently running effects
048: */
049: protected LinkedList<EffectState> effects = new LinkedList<EffectState>();
050: /**
051: * A temporary list of effects that are finsihed use for clean-up after the rest as has been
052: * used.
053: */
054: protected LinkedList<Effect> finishedEffects = new LinkedList<Effect>();
055:
056: /**
057: * The timer used to trigger effects
058: */
059: protected Timer timer = new Timer(TIMER_IDENTIFIER, this );
060:
061: /**
062: * The component the engine is attached to
063: */
064: protected JComponent component = null;
065:
066: /**
067: * Counts the number of timers created
068: */
069: private static int timerCount = 0;
070:
071: /**
072: * Remembers if this is a paint only engine or not
073: */
074: private boolean paintOnly = true;
075:
076: /**
077: * Creates a new instance of EffectEngine that can be tied to a component.
078: * @param component The component to be tied to.
079: * @param paintOnly setting this to true will create a "paint only" effects
080: * layer where effects are not updated by a timer, but relys on external
081: * updates and repaints. paintOnly effect engines are preferable if possible,
082: * but please note there a defects with Swing timer that cause it to be slow
083: * if you have many of them, so it is better to use the timer in the effect
084: * engine if you have to
085: */
086: public EffectEngine(JComponent component, boolean paintOnly) {
087: this .component = component;
088: this .paintOnly = paintOnly;
089: if (!paintOnly) {
090: //Start this timer if it's visible
091: if (component.isVisible()) {
092: startTimer();
093: }
094: //Controls when the timer is started and stopped
095: component.addComponentListener(new ComponentListener() {
096: public void componentHidden(
097: ComponentEvent componentEvent) {
098: stopTimer();
099: }
100:
101: public void componentMoved(ComponentEvent componentEvent) {
102: }
103:
104: public void componentResized(
105: ComponentEvent componentEvent) {
106: }
107:
108: public void componentShown(ComponentEvent componentEvent) {
109: startTimer();
110: }
111: });
112: }
113: }
114:
115: /**
116: * Adds an effect to the effects engine
117: * @param effect The effect to add
118: */
119: public void addEffect(Effect effect) {
120: effects.addLast(new EffectState(effect, System
121: .currentTimeMillis()));
122: }
123:
124: /**
125: * Called when the comonent needs paintin'
126: *
127: * @param graphics The graphics context
128: */
129: public void paintEffects(Graphics graphics) {
130: Graphics2D g2d = (Graphics2D) graphics;
131:
132: for (EffectState effectState : effects) {
133: Effect effect = effectState.getTheEffect();
134:
135: Graphics2D effectContext = g2d;
136: if (effect.isLocalEffect()) {
137: effectContext = (Graphics2D) g2d.create();
138: }
139:
140: effect.paintEffect(effectContext);
141: }
142: }
143:
144: /**
145: * Starts the timer
146: */
147: private void startTimer() {
148: if (!paintOnly) {
149: SwingBugUtilities.addTimerListener(this );
150: }
151: }
152:
153: /**
154: * stops the timer
155: */
156: private void stopTimer() {
157: if (!paintOnly) {
158: SwingBugUtilities.removeTimerListener(this );
159: }
160: }
161:
162: /**
163: * Called when the timer fires. It updates any effects that need updating,
164: * removes any that have finished, and then if at leat one has updated
165: * stimulates a repaint.
166: *
167: * @param actionEvent The action event
168: */
169: public void actionPerformed(ActionEvent actionEvent) {
170: //Stop the timer if it's no longer valid
171: if (!component.isValid()) {
172: stopTimer();
173: }
174: if (update()) {
175: component.repaint();
176: }
177: }
178:
179: /**
180: * Called when the timer fires. It updates any effects that need updating,
181: * removes any that have finished, and then if at leat one has updated
182: * stimulates a repaint.
183: *
184: * @return A boolean, if true, then a repaint should occur
185: *
186: */
187: public boolean update() {
188: long time = System.currentTimeMillis();
189: finishedEffects.clear();
190: boolean oneUpdated = false;
191:
192: //Update any effects that need updating
193: for (EffectState effectState : effects) {
194: long fireNext = effectState.getFireNext();
195: if (!((fireNext == Effect.EFFECT_INACTIVE) || (fireNext == Effect.EFFECT_FINISHED))) {
196: if (effectState.getFireNext() < time) {
197: fireNext = effectState.getTheEffect().update();
198: oneUpdated = true;
199: if (fireNext == Effect.EFFECT_FINISHED) {
200: finishedEffects.add(effectState.getTheEffect());
201: } else if (fireNext == Effect.EFFECT_INACTIVE) {
202: effectState.setFireNext(fireNext);
203: } else {
204: effectState.setFireNext(time + fireNext);
205: }
206: }
207: }
208: }
209:
210: //Remove any finished effects
211: for (Effect deadEffect : finishedEffects) {
212: for (EffectState effectState : effects) {
213: if (effectState.getTheEffect() == deadEffect) {
214: effects.remove(effectState);
215: break;
216: }
217: }
218: }
219:
220: //If we updated any of them, we need to fire a repaint
221: if (oneUpdated) {
222: return true;
223: }
224:
225: return false;
226: }
227:
228: /**
229: * Determines the width of the container
230: *
231: * @return The width in pixels of the container
232: */
233: public int getWidth() {
234: return component.getWidth();
235: }
236:
237: /**
238: * Determines the height of the container
239: *
240: * @return The height in pixels of the container
241: */
242: public int getHeight() {
243: return component.getHeight();
244: }
245:
246: /**
247: * Wakes an inactive effect
248: *
249: * @param effect The effect to wake
250: */
251: public void wakeEffect(Effect effect) {
252: for (EffectState effectState : effects) {
253: if (effectState.getTheEffect() == effect) {
254: effectState.setFireNext(0);
255: return;
256: }
257: }
258: }
259:
260: /**
261: * Removes the effect
262: *
263: * @param effect The effect to remove
264: */
265: public void removeEffect(Effect effect) {
266: for (EffectState effectState : effects) {
267: if (effectState.getTheEffect() == effect) {
268: effects.remove(effectState);
269: return;
270: }
271: }
272: }
273:
274: /**
275: * Utility class to hold the effects, and their state.
276: *
277: */
278: protected class EffectState {
279: /**
280: * The effect itself
281: */
282: protected Effect theEffect;
283:
284: /**
285: * When the effect should fire next
286: */
287: protected long fireNext;
288:
289: /**
290: * Constructs a new instance of the effect
291: * @param effect The effect
292: * @param fireNext The next time it should be updated
293: */
294: protected EffectState(Effect effect, long fireNext) {
295: theEffect = effect;
296: this .fireNext = fireNext;
297: }
298:
299: /**
300: * Gets the time the effect should next fire.
301: * @return The next fire time
302: */
303: public long getFireNext() {
304: return fireNext;
305: }
306:
307: /**
308: * Gets the effect
309: * @return The effect object
310: */
311: public Effect getTheEffect() {
312: return theEffect;
313: }
314:
315: /**
316: * Sets the next fire time
317: * @param fireNext The next time it should fire
318: */
319: public void setFireNext(long fireNext) {
320: this .fireNext = fireNext;
321: }
322:
323: /**
324: * Sets the effect
325: * @param theEffect The new effect
326: */
327: public void setTheEffect(Effect theEffect) {
328: this.theEffect = theEffect;
329: }
330: }
331:
332: }
|