001: package org.zilonis.tool.ext.aerith.animation;
002:
003: import java.awt.Component;
004: import java.awt.Container;
005: import java.awt.Graphics;
006: import java.awt.Graphics2D;
007: import java.awt.Image;
008: import java.awt.image.BufferedImage;
009:
010: import javax.swing.JComponent;
011:
012: import org.jdesktop.animation.timing.Animator;
013: import org.jdesktop.animation.timing.TimingTarget;
014: import org.zilonis.tool.ext.aerith.effects.ComponentState;
015:
016: /**
017: * This class is used to facilitate animated transitions in an application.
018: *
019: * ScreenTransition is given a container in a Swing application. When the
020: * application wishes to transition from one state of the application to
021: * another, the <code>startTransition</code> method is called, which calls
022: * back into the application to first reset the state of the application, then
023: * set up the following state of the application. Then ScreenTransition runs an
024: * animation from the previous state of the application to the new state.
025: *
026: * REMIND: There is some confusion in this and the effects package about the use
027: * of Component vs JComponent... If this framework is primarily intended for
028: * Swing, then maybe it would be good to standardize on JComponent?
029: *
030: * @author Chet Haase
031: */
032: public class ScreenTransition implements TimingTarget {
033:
034: /*
035: * Implementation detail: The key to making ScreenTransition work correctly
036: * is having two different views or layers of ScreenTransition under the
037: * covers. One layer is the "containerLayer", which is where the actual
038: * child components of ScreenTransition are placed. The other (more hidden)
039: * layer is the "animationLayer", which exists solely for displaying the
040: * animations which take place during any transition.
041: *
042: * The reason we cannot animate the transitions in the same container where
043: * the actual components live (at least not trivially) is that we need to
044: * layout the container for both states of a transition prior to actually
045: * running the animation. Moving the components around during these layout
046: * phases cannot happen onscreen (the net effect would be that the user
047: * would see the end result before the transition), so we do the layout on
048: * an invisible container instead. Then the animation happens to transition
049: * between the two states, in his separate animationLayer.
050: *
051: * Further detail: the "animationLayer" is set to be the glass pane of the
052: * application frame. Glass pane already has the functionality we need
053: * (specifically, it overlays the area that we need to render during the
054: * transition); the only trick is that we must position the rendering
055: * correctly since the glass pane typically covers the entire application
056: * frame.
057: */
058:
059: /**
060: * Handles the structure and rendering of the actual animation during the
061: * transitions.
062: */
063: private AnimationManager animationManager;
064:
065: /**
066: * The component where the transition animation occurs. This component
067: * (which is set to be the glass pane) is visible during the transition, but
068: * is otherwise invisible.
069: */
070: private AnimationLayer animationLayer;
071:
072: /**
073: * The component supplied at contruction time that holds the actual
074: * components added to ScreenTransition by the application. Keeping this
075: * container separate from ScreenTransition allows us to render the
076: * AnimationLayer during the transitions and separate the animation sequence
077: * from the actual container of the components.
078: */
079: private JComponent containerLayer;
080:
081: /**
082: * Image used to store the current state of the transition animation. This
083: * image will be rendered to during timingEvent() and then copied into the
084: * glass pane during the repaint cycle.
085: */
086: private BufferedImage transitionImage;
087:
088: /**
089: * Background that will be copied into the transitionImage on every frame.
090: * This represents the default (empty) state of the containerLayer; copying
091: * this into the transitionImage is like erasing to the background of the
092: * real container in the application.
093: */
094: private BufferedImage transitionImageBG;
095:
096: /**
097: * The user-defined code which ScreenTransition will call to reset and setup
098: * the previous/next states of the application during the transition setup
099: * process.
100: */
101: private TransitionTarget transitionTarget;
102:
103: /**
104: * If the application has already set their own custom glass pane, we save
105: * that component here before using the glass pane for our own purposes, and
106: * then we restore the original glass pane when the animation has completed.
107: */
108: private Component savedGlassPane;
109:
110: /**
111: * Timing engine for the transition animation.
112: */
113: private Animator timingController;
114:
115: /**
116: * Constructor for ScreenTransition. The application must supply the
117: * JComponent that they wish to transition and the TransitionTarget which
118: * supplies the callback methods called during the transition process.
119: *
120: * @param transitionComponent
121: * JComponent that the application wishes to run the transition
122: * on.
123: * @param transitionTarget
124: * Implementation of <code>TransitionTarget</code> interface
125: * which will be called during transition process.
126: */
127: public ScreenTransition(JComponent transitionComponent,
128: TransitionTarget transitionTarget) {
129: this .containerLayer = transitionComponent;
130: this .transitionTarget = transitionTarget;
131:
132: this .animationManager = new AnimationManager();
133: this .animationLayer = new AnimationLayer(this );
134: this .animationLayer.setVisible(false);
135: }
136:
137: /**
138: * Returns the content pane used in this ScreenTransition. Applications can
139: * add components directly to this container if they wish (although adding
140: * components to ScreenTransition will have the same effect).
141: */
142: public Container getContentPane() {
143: return containerLayer;
144: }
145:
146: /**
147: * Returns image used during timingEvent rendering. This is called by
148: * AnimationLayer to get the contents for the glass pane
149: */
150: Image getTransitionImage() {
151: return transitionImage;
152: }
153:
154: /**
155: * Returns true if the transition consists of completely opaque components.
156: */
157: boolean isOpaque() {
158: return containerLayer.isOpaque();
159: }
160:
161: /**
162: * Implementation of the <code>TimingTarget</code> interface. This method
163: * is called repeatedly during the transition animation. We change the
164: * animation fraction in the AnimationManager and then force a repaint,
165: * which will force the current transition state to be rendered.
166: */
167: public void timingEvent(float elapsedFraction) {
168: Graphics2D gImg = (Graphics2D) transitionImage.getGraphics();
169:
170: // copy background to transition image image
171: gImg.drawImage(transitionImageBG, 0, 0, null);
172:
173: // Render this frame of the animation
174: animationManager.paint(gImg, elapsedFraction);
175:
176: gImg.dispose();
177:
178: // Force transitionImage to be copied to the glass pane
179: animationLayer.repaint();
180: }
181:
182: /**
183: * Override of <code>TimingTarget.begin()</code>; nothing to do here.
184: */
185: public void begin() {
186: }
187:
188: /**
189: * Override of <code>TimingTarget.end()</code>; switch the visibility of
190: * the containerLayer and animationLayer and force repaint.
191: */
192: public void end() {
193: containerLayer.getRootPane().setGlassPane(savedGlassPane);
194: containerLayer.getRootPane().getGlassPane().setVisible(false);
195: animationLayer.setVisible(false);
196: containerLayer.setVisible(true);
197: containerLayer.repaint();
198: timingController = null;
199: transitionTarget.transitionComplete();
200: }
201:
202: /**
203: * Utility method to query whether a transition is currently taking place
204: */
205: public boolean isTransitioning() {
206: return animationLayer.isVisible();
207: }
208:
209: /**
210: * Begin the transition from the current application state to the next one.
211: * This method will call twice into the TransitionTarget specified in the
212: * ScreenTransition constructor: <code>resetCurrentScreen()</code> will be
213: * called to allow the application to clean up the current screen and
214: * <code>setupNextScreen()</code> will be called to allow the application
215: * to set up the state of the next screen. After these calls, the transition
216: * animation will begin.
217: *
218: * REMIND: should be called from EDT only?
219: *
220: * @param transitionTimeMS
221: * The length of this transition in milliseconds
222: */
223: public void startTransition(int transitionTimeMS) {
224: if (isTransitioning() && timingController != null) {
225: // REMIND: Might want something more robust here, such as
226: // putting the application into a state representative of the
227: // current transition, rather than just jumping to the end state
228: // of the transition
229: timingController.stop();
230: }
231:
232: // Reset the AnimationManager (this releases all previous transition
233: // data structures)
234: animationManager.reset();
235:
236: // Capture the current state of the application into the
237: // AnimationManager; this sets up the state we are transitioning from
238: for (Component child : containerLayer.getComponents()) {
239: if (child.isVisible() && (child instanceof JComponent)) {
240: animationManager.addStart((JComponent) child);
241: }
242: }
243:
244: // Ask the transitionTarget to reset the application state; this gives
245: // the application the chance to set up what will be the background
246: // for the transition
247: // REMIND: Might want to go back to original approach of simply removing
248: // all components from container instead of trusting the app to
249: // do this; otherwise, our use of transitioinImageBG might not work
250: transitionTarget.resetCurrentScreen();
251:
252: // Create the transition image
253: int cw = containerLayer.getWidth();
254: int ch = containerLayer.getHeight();
255: if (transitionImage == null || transitionImage.getWidth() != cw
256: || transitionImage.getHeight() != ch) {
257: // Recreate transition image and background for new dimensions
258: transitionImage = (BufferedImage) containerLayer
259: .createImage(cw, ch);
260: transitionImageBG = (BufferedImage) containerLayer
261: .createImage(cw, ch);
262: }
263: Graphics gImg = transitionImageBG.getGraphics();
264: ComponentState.paintComponentHierarchySingleBuffered(
265: containerLayer, gImg);
266: gImg.dispose();
267:
268: /**
269: * Debug tool; if the transitions are messed up, this allows us to see
270: * the individual transitionImageBG images we are creating. They display
271: * in their own JFrame on each transition.
272: */
273: // ImageViewer imageViewer = new ImageViewer(transitionImageBG);
274: // This records data in animationLayer used to copy the transition
275: // contents correctly into the glass pane
276: animationLayer.setupBackground(containerLayer);
277:
278: // Make the animationLayer visible and the contentPane invisible. This
279: // frees us to validate the application state for the next screen while
280: // keeping that new state invisible from the user; the animationLayer
281: // will only display contents appropriate to the transition (the
282: // previous
283: // state before the transition begins, the transitioning state during
284: // the transition).
285: savedGlassPane = containerLayer.getRootPane().getGlassPane();
286: containerLayer.getRootPane().setGlassPane(animationLayer);
287: containerLayer.getRootPane().getGlassPane().setVisible(true);
288: containerLayer.setVisible(false);
289:
290: // Now that the contentPane is invisible to the user, have the
291: // application setup the next screen. This will define the end state
292: // of the application for this transition.
293: transitionTarget.setupNextScreen();
294:
295: // Validating the container layer component ensures correct layout
296: // for the next screen of the application
297: containerLayer.validate();
298:
299: // Iterate through the visible components in the next application
300: // screen and add those end states to the AnimationManager
301: for (Component child : containerLayer.getComponents()) {
302: if (child.isVisible() && (child instanceof JComponent)) {
303: animationManager.addEnd((JComponent) child);
304: }
305: }
306:
307: // Init the AnimationManager; this sets up default or custom effects
308: // for each of the components involved in the transition
309: animationManager.init();
310:
311: // workaround: need glass pane to reflect initial contents when we
312: // exit this function to avoid flash of blank container
313: timingEvent(0);
314:
315: // Create the TimingController that will run the animation
316: timingController = new Animator(transitionTimeMS, this );
317: timingController.start();
318: }
319:
320: public void repeat() {
321:
322: }
323: }
|