/* * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. * * Sun gratefully acknowledges that this software was originally authored * and developed by Kenneth Bradley Russell and Christopher John Kline. */ package demos.xtrans; import java.awt.*; import java.awt.geom.*; import java.util.*; import javax.swing.*; import javax.media.opengl.*; import javax.media.opengl.glu.*; import com.sun.opengl.impl.*; /** A JDesktopPane subclass supporting Accelerated Transitions (XT) of * the components contained within. * * @author Kenneth Russell */ public class XTDesktopPane extends OffscreenDesktopPane { private GLContext j2dContext; private Object j2dContextSurfaceIdentifier; private Rectangle oglViewport; private XTTransitionManager transitionManager = new XTBasicTransitionManager(); private boolean reallyRemove; private boolean alwaysRedraw; static class TransitionInfo { boolean isIn; Component target; long startTime; XTTransition trans; TransitionInfo(boolean isIn, Component target, long startTime, XTTransition trans) { this.isIn = isIn; this.target = target; this.startTime = startTime; this.trans = trans; } } private java.util.List/**/ transitions = new ArrayList(); private float TRANSITION_DURATION = 300.0f; private int textureTarget = GL.GL_TEXTURE_2D; private GLU glu = new GLU(); /** Creates a new accelerated transition desktop pane. */ public XTDesktopPane() { super(); if (!Java2D.isOGLPipelineActive()) { throw new RuntimeException("XTDesktopPane requires new Java2D/JOGL support in Java SE 6 and -Dsun.java2d.opengl=true"); } setDesktopManager(new XTDesktopManager()); } /** Overridden to use a transition to display the given component. */ protected void addImpl(Component c, Object constraints, int index) { super.addImpl(c, constraints, index); getOffscreenDesktopManager().layoutOffscreenBuffer(this); // When animating the component's transition, center the // perspective projection around the center of the newly-added // component so that the perspective effects appear symmetric. // This amounts to moving the viewport so the component is in the // center. addTransition(true, c, transitionManager.createTransitionForComponent(c, true, getOGLViewport(), computeViewportOffsetToCenterComponent(c, getOGLViewport()), getXTDesktopManager().getOpenGLTextureCoords(c))); } /** Overridden to use an animated transition to remove the passed component. */ public void remove(int index) { if (reallyRemove) { super.remove(index); } else { addRemoveTransition(getRealComponent(getComponent(index))); } } /** Overridden to use an animated transition to remove the passed component. */ public void remove(Component c) { if (reallyRemove) { super.remove(c); } else { addRemoveTransition(getRealComponent(c)); } } /** Causes the given component to really be removed from this desktop pane. Called when the removal transition is complete. */ protected void removeImpl(Component c) { reallyRemove = true; try { remove(c); } finally { reallyRemove = false; } } /** Overridden to draw the child components, including any animated transitions, using OpenGL. */ protected void paintChildren(final Graphics g) { // FIXME: this is a hack to get repainting behavior to work // properly when we specify that optimized drawing is disabled (so // that childrens' repaint requests will trickle up to us via the // Animator) but need to descend to repaint our children -- // currently don't know how to distinguish between repaint events // propagated up to us and those initiated by the children (which // typically go through the OffscreenComponentWrapper's // getGraphics() method and implicitly cause a redraw of all child // components as well as the desktop) if (alwaysRedraw) { getOffscreenDesktopManager().setNeedsRedraw(); } // Update desktop manager's offscreen buffer if necessary getOffscreenDesktopManager().updateOffscreenBuffer(this); // Draw textured quads using JOGL over current contents of back // buffer final Component[] components = getRealChildComponents(); final ArrayList expiredTransitions = new ArrayList(); Java2D.invokeWithOGLContextCurrent(g, new Runnable() { public void run() { // Get valid Java2D context if (j2dContext == null || j2dContextSurfaceIdentifier != Java2D.getOGLSurfaceIdentifier(g)) { j2dContext = GLDrawableFactory.getFactory().createExternalGLContext(); j2dContext.setGL(new DebugGL(j2dContext.getGL())); j2dContextSurfaceIdentifier = Java2D.getOGLSurfaceIdentifier(g); } j2dContext.makeCurrent(); // No-op try { GL gl = j2dContext.getGL(); // Figure out where JDesktopPane is on the Swing back buffer Rectangle oglRect = Java2D.getOGLViewport(g, getWidth(), getHeight()); // Cache this value for adding transitions later oglViewport = new Rectangle(oglRect); // Set up perspective projection so we can do some subtle // 3D effects. We set up the view volume so that at z=0 // the lower-left coordinates of the desktop are (0, 0) // and the upper right coordinates are // (oglRect.getWidth(), oglRect.getHeight()). The key here // is to decide on the field of view and then figure out // how far back we have to put the eye point in order for // this to occur. double fovy = 30.0; // degrees double w = oglRect.getWidth(); double h = oglRect.getHeight(); // d is the distance from the eye point to the image plane // (z=0) double d = (h / 2) / Math.tan(Math.toRadians(fovy) / 2); double near = d - (h / 2); double far = d + (h / 2); gl.glViewport(oglRect.x, oglRect.y, oglRect.width, oglRect.height); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPushMatrix(); gl.glLoadIdentity(); glu.gluPerspective(fovy, (w / h), near, far); gl.glMatrixMode(GL.GL_TEXTURE); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); gl.glLoadIdentity(); double eyeX = w / 2; double eyeY = h / 2; // Object x and y are the same as eye x and y since we're // looking in the -z direction glu.gluLookAt(eyeX, eyeY, d, eyeX, eyeY, 0, 0, 1, 0); // Set up a scissor box so we don't blow away other // components if we shift around the viewport to get the // animated transitions' perspective effects to be // centered gl.glEnable(GL.GL_SCISSOR_TEST); Rectangle r = Java2D.getOGLScissorBox(g); if (r != null) { gl.glScissor(r.x, r.y, r.width, r.height); } /* // Orthographic projection for debugging gl.glViewport(oglRect.x, oglRect.y, oglRect.width, oglRect.height); // Set up coordinate system for easy access gl.glMatrixMode(GL.GL_PROJECTION); // System.err.println("oglRect x = " + oglRect.getX()); // System.err.println("oglRect y = " + oglRect.getY()); // System.err.println("oglRect w = " + oglRect.getWidth()); // System.err.println("oglRect h = " + oglRect.getHeight()); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glOrtho(oglRect.getX(), oglRect.getX() + oglRect.getWidth(), oglRect.getY(), oglRect.getY() + oglRect.getHeight(), -1, 1); gl.glMatrixMode(GL.GL_TEXTURE); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); gl.glLoadIdentity(); */ // Enable and bind texture corresponding to internal frames' back buffer gl.glBindTexture(textureTarget, getXTDesktopManager().getOpenGLTextureObject()); gl.glEnable(textureTarget); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); gl.glEnable(GL.GL_BLEND); gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); // Iterate down children in z order bottom-to-top int compCount = components.length; long curTime = currentTimeMillis(); for (int i = compCount - 1; i >= 0; i--) { Component c = components[i]; // Find transition for this component TransitionInfo info = transitionForComponent(c); if (info != null) { gl.glPushMatrix(); // When animating the component's transition, center the // perspective projection around the center of the newly-added // component so that the perspective effects appear symmetric. // This amounts to moving the viewport so the component is in the // center. Point viewportOffset = computeViewportOffsetToCenterComponent(c, getOGLViewport()); gl.glViewport(oglRect.x + viewportOffset.x, oglRect.y + viewportOffset.y, oglRect.width, oglRect.height); // Update it float percent = clamp((curTime - info.startTime) / TRANSITION_DURATION, 0.0f, 1.0f); XTTransition trans = info.trans; trans.update(percent); trans.draw(gl); // See whether the transition has expired if (percent == 1.0f) { transitions.remove(info); expiredTransitions.add(info); } gl.glPopMatrix(); // Put the viewport back where it was gl.glViewport(oglRect.x, oglRect.y, oglRect.width, oglRect.height); } else { // For each one, get the OpenGL texture coordinates on the offscreen OpenGL texture Rectangle2D oglTexCoords = getXTDesktopManager().getOpenGLTextureCoords(c); Rectangle bounds = c.getBounds(); int cx = bounds.x; int cy = bounds.y; int cw = bounds.width; int ch = bounds.height; float tx = (float) oglTexCoords.getX(); float ty = (float) oglTexCoords.getY(); float tw = (float) oglTexCoords.getWidth(); float th = (float) oglTexCoords.getHeight(); float vx = oglRect.x; float vy = oglRect.y; float vw = oglRect.width; float vh = oglRect.height; // Draw a quad per component gl.glBegin(GL.GL_TRIANGLES); gl.glColor4f(1, 1, 1, 1); // Triangle 1 gl.glTexCoord2f(tx, ty + th); gl.glVertex3f (cx, vh - cy, 0); gl.glTexCoord2f(tx, ty); gl.glVertex3f (cx, vh - cy - ch, 0); gl.glTexCoord2f(tx + tw, ty + th); gl.glVertex3f (cx + cw, vh - cy, 0); // Triangle 2 gl.glTexCoord2f(tx + tw, ty + th); gl.glVertex3f (cx + cw, vh - cy, 0); gl.glTexCoord2f(tx, ty); gl.glVertex3f (cx, vh - cy - ch, 0); gl.glTexCoord2f(tx + tw, ty); gl.glVertex3f (cx + cw, vh - cy - ch, 0); gl.glEnd(); } } gl.glFlush(); gl.glDisable(textureTarget); gl.glDisable(GL.GL_BLEND); gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_TEXTURE); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPopMatrix(); gl.glFinish(); } finally { j2dContext.release(); } } }); for (Iterator iter = expiredTransitions.iterator(); iter.hasNext(); ) { TransitionInfo info = (TransitionInfo) iter.next(); if (!info.isIn) { removeImpl(info.target); repaint(); } } if (!transitions.isEmpty()) { repaint(); } } /** Overridden from parent to disable optimized drawing so that we get correct rendering results with embedded GLJPanels */ public boolean isOptimizedDrawingEnabled() { return false; } /** Returns the XTDesktopManager for this desktop pane. */ public XTDesktopManager getXTDesktopManager() { return (XTDesktopManager) getDesktopManager(); } /** Returns the transition manager for this desktop pane. By default this is an XTBasicTransitionManager. */ public XTTransitionManager getTransitionManager() { return transitionManager; } /** Sets the transition manager for this desktop pane. By default this is an XTBasicTransitionManager. */ public void setTransitionManager(XTTransitionManager manager) { transitionManager = manager; } /** Workaround to get painting behavior to work properly in some situations. */ public void setAlwaysRedraw(boolean onOrOff) { alwaysRedraw = onOrOff; } /** Workaround to get painting behavior to work properly in some situations. */ public boolean getAlwaysRedraw() { return alwaysRedraw; } /** Returns the transition corresponding to the passed Component, or null if no transition is currently active for this component. */ private TransitionInfo transitionForComponent(Component c) { for (Iterator iter = transitions.iterator(); iter.hasNext(); ) { TransitionInfo info = (TransitionInfo) iter.next(); if (info.target == c) { return info; } } return null; } /** Adds a transition for the specified component. An "out" transition will automatically cause the component to be removed after it has completed running. */ protected void addTransition(boolean isIn, Component target, XTTransition trans) { TransitionInfo info = new TransitionInfo(isIn, target, currentTimeMillis(), trans); transitions.add(info); } /** Adds a removal transition for the given component. */ protected void addRemoveTransition(Component target) { addTransition(false, target, transitionManager.createTransitionForComponent(target, false, getOGLViewport(), computeViewportOffsetToCenterComponent(target, getOGLViewport()), getXTDesktopManager().getOpenGLTextureCoords(target))); } /** Computes the offset applied to the OpenGL viewport to center the given component in the viewport. This is used to make the perspective effects appear symmetric about the component. */ protected Point computeViewportOffsetToCenterComponent(Component c, Rectangle oglViewport) { Rectangle bounds = c.getBounds(); return new Point(bounds.x + ((bounds.width - oglViewport.width) / 2), -bounds.y + ((oglViewport.height - bounds.height) / 2)); } /** Clamps the given value between the specified minimum and maximum. */ protected static float clamp(float val, float min, float max) { return Math.min(max, Math.max(min, val)); } /** Returns the current time in milliseconds. */ protected static long currentTimeMillis() { // Avoid 1.5 compilation dependencies since no perceived // improvement by changing this // return System.nanoTime() / 1000000; return System.currentTimeMillis(); } /** Returns the OpenGL viewport corresponding to this desktop pane. */ protected Rectangle getOGLViewport() { if (oglViewport != null) { return oglViewport; } Rectangle b = getBounds(); return new Rectangle(0, 0, b.width, b.height); } }