summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xsrc/demos/jrefract/JRefract.java15
-rwxr-xr-xsrc/demos/xtrans/InterpolatedFloat.java29
-rwxr-xr-xsrc/demos/xtrans/InterpolatedQuad2f.java45
-rwxr-xr-xsrc/demos/xtrans/InterpolatedQuad3f.java45
-rwxr-xr-xsrc/demos/xtrans/InterpolatedVec3f.java37
-rwxr-xr-xsrc/demos/xtrans/Main.java229
-rwxr-xr-xsrc/demos/xtrans/OffscreenComponentWrapper.java122
-rwxr-xr-xsrc/demos/xtrans/OffscreenDesktopManager.java784
-rwxr-xr-xsrc/demos/xtrans/OffscreenDesktopPane.java189
-rwxr-xr-xsrc/demos/xtrans/Quad2f.java74
-rwxr-xr-xsrc/demos/xtrans/Quad3f.java74
-rwxr-xr-xsrc/demos/xtrans/XTBasicTransition.java151
-rwxr-xr-xsrc/demos/xtrans/XTBasicTransitionManager.java344
-rwxr-xr-xsrc/demos/xtrans/XTDesktopManager.java166
-rwxr-xr-xsrc/demos/xtrans/XTDesktopPane.java446
-rwxr-xr-xsrc/demos/xtrans/XTTransition.java15
-rwxr-xr-xsrc/demos/xtrans/XTTransitionManager.java45
-rw-r--r--src/gleem/linalg/Vec2f.java4
18 files changed, 2811 insertions, 3 deletions
diff --git a/src/demos/jrefract/JRefract.java b/src/demos/jrefract/JRefract.java
index b3953b4..60361e0 100755
--- a/src/demos/jrefract/JRefract.java
+++ b/src/demos/jrefract/JRefract.java
@@ -53,6 +53,8 @@ import demos.vertexBufferObject.VertexBufferObject;
import demos.vertexProgRefract.VertexProgRefract;
import demos.vertexProgWarp.VertexProgWarp;
+import demos.xtrans.*;
+
/**
Wavelength-dependent refraction demo<br>
It's a chromatic aberration!<br>
@@ -210,13 +212,13 @@ public class JRefract {
});
inner.getContentPane().setLayout(new BorderLayout());
- if (which == REFRACT) {
+ /* if (which == REFRACT) {
// Testing scrolling
canvas.setSize(512, 512);
canvas.setPreferredSize(new Dimension(512, 512));
JScrollPane scroller = new JScrollPane(canvas);
inner.getContentPane().add(scroller);
- } else if (which == GEARS) {
+ } else */ if (which == GEARS) {
// Provide control over transparency of gears background
canvas.setOpaque(false);
JPanel gradientPanel = JGears.createGradientPanel();
@@ -244,7 +246,14 @@ public class JRefract {
public void run(String[] args) {
JFrame frame = new JFrame("JOGL and Swing Interoperability");
- desktop = new JDesktopPane();
+ if ((args.length > 0) && args[0].equals("-xt")) {
+ desktop = new XTDesktopPane();
+ // FIXME: this is a hack to get the repaint behavior to work correctly
+ ((XTDesktopPane) desktop).setAlwaysRedraw(true);
+ } else {
+ desktop = new JDesktopPane();
+ }
+
desktop.setSize(1024, 768);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(desktop, BorderLayout.CENTER);
diff --git a/src/demos/xtrans/InterpolatedFloat.java b/src/demos/xtrans/InterpolatedFloat.java
new file mode 100755
index 0000000..398e3b4
--- /dev/null
+++ b/src/demos/xtrans/InterpolatedFloat.java
@@ -0,0 +1,29 @@
+package demos.xtrans;
+
+import gleem.linalg.*;
+
+/** A floating-point value which interpolates between specified start
+ and end values. */
+
+public class InterpolatedFloat {
+ private float start;
+ private float end;
+
+ /** Returns the starting value for the interpolation. */
+ public float getStart() { return start; }
+
+ /** Sets the starting value for the interpolation. */
+ public void setStart(float val) { start = val; }
+
+ /** Returns the ending value for the interpolation. */
+ public float getEnd() { return end; }
+
+ /** Sets the ending value for the interpolation. */
+ public void setEnd(float val) { end = val; }
+
+ /** Gets the current interpolated value at the specified fraction of
+ interpolation (0.0 - 1.0). */
+ public float getCurrent(float fraction) {
+ return (start * (1.0f - fraction)) + (end * fraction);
+ }
+}
diff --git a/src/demos/xtrans/InterpolatedQuad2f.java b/src/demos/xtrans/InterpolatedQuad2f.java
new file mode 100755
index 0000000..b7262d7
--- /dev/null
+++ b/src/demos/xtrans/InterpolatedQuad2f.java
@@ -0,0 +1,45 @@
+package demos.xtrans;
+
+import gleem.linalg.*;
+
+/** A quadrilateral of two-dimensional floating-point values which
+ interpolates between specified start and end values. */
+
+public class InterpolatedQuad2f {
+ private Quad2f start;
+ private Quad2f end;
+
+ /** Constructs a new InterpolatedQuad2f. By default both the start
+ and end quadrilaterals have all of their points set to the
+ origin. */
+ public InterpolatedQuad2f() {
+ start = new Quad2f();
+ end = new Quad2f();
+ }
+
+ /** Returns the starting value for the interpolation. */
+ public Quad2f getStart() {
+ return start;
+ }
+
+ /** Sets the starting value for the interpolation. */
+ public void setStart(Quad2f quad) {
+ start.set(quad);
+ }
+
+ /** Returns the ending value for the interpolation. */
+ public Quad2f getEnd() {
+ return end;
+ }
+
+ /** Sets the ending value for the interpolation. */
+ public void setEnd(Quad2f quad) {
+ end.set(quad);
+ }
+
+ /** Gets the current interpolated value at the specified fraction of
+ interpolation (0.0 - 1.0). */
+ public Quad2f getCurrent(float fraction) {
+ return start.times(1.0f - fraction).plus(end.times(fraction));
+ }
+}
diff --git a/src/demos/xtrans/InterpolatedQuad3f.java b/src/demos/xtrans/InterpolatedQuad3f.java
new file mode 100755
index 0000000..895a458
--- /dev/null
+++ b/src/demos/xtrans/InterpolatedQuad3f.java
@@ -0,0 +1,45 @@
+package demos.xtrans;
+
+import gleem.linalg.*;
+
+/** A quadrilateral of three-dimensional floating-point values which
+ interpolates between specified start and end values. */
+
+public class InterpolatedQuad3f {
+ private Quad3f start;
+ private Quad3f end;
+
+ /** Constructs a new InterpolatedQuad3f. By default both the start
+ and end quadrilaterals have all of their points set to the
+ origin. */
+ public InterpolatedQuad3f() {
+ start = new Quad3f();
+ end = new Quad3f();
+ }
+
+ /** Returns the starting value for the interpolation. */
+ public Quad3f getStart() {
+ return start;
+ }
+
+ /** Sets the starting value for the interpolation. */
+ public void setStart(Quad3f quad) {
+ start.set(quad);
+ }
+
+ /** Returns the ending value for the interpolation. */
+ public Quad3f getEnd() {
+ return end;
+ }
+
+ /** Sets the ending value for the interpolation. */
+ public void setEnd(Quad3f quad) {
+ end.set(quad);
+ }
+
+ /** Gets the current interpolated value at the specified fraction of
+ interpolation (0.0 - 1.0). */
+ public Quad3f getCurrent(float fraction) {
+ return start.times(1.0f - fraction).plus(end.times(fraction));
+ }
+}
diff --git a/src/demos/xtrans/InterpolatedVec3f.java b/src/demos/xtrans/InterpolatedVec3f.java
new file mode 100755
index 0000000..28c3051
--- /dev/null
+++ b/src/demos/xtrans/InterpolatedVec3f.java
@@ -0,0 +1,37 @@
+package demos.xtrans;
+
+import gleem.linalg.*;
+
+/** A vector of three-dimensional floating-point values which
+ interpolates between specified start and end values. */
+
+public class InterpolatedVec3f {
+ private Vec3f start;
+ private Vec3f end;
+
+ /** Constructs a new InterpolatedQuad2f. By default both the start
+ and end quadrilaterals have all of their points set to the
+ origin. */
+ public InterpolatedVec3f() {
+ start = new Vec3f();
+ end = new Vec3f();
+ }
+
+ /** Returns the starting value for the interpolation. */
+ public Vec3f getStart() { return start; }
+
+ /** Sets the starting value for the interpolation. */
+ public void setStart(Vec3f vec) { start.set(vec); }
+
+ /** Returns the ending value for the interpolation. */
+ public Vec3f getEnd() { return end; }
+
+ /** Sets the ending value for the interpolation. */
+ public void setEnd(Vec3f vec) { end.set(vec); }
+
+ /** Gets the current interpolated value at the specified fraction of
+ interpolation (0.0 - 1.0). */
+ public Vec3f getCurrent(float fraction) {
+ return start.times(1.0f - fraction).plus(end.times(fraction));
+ }
+}
diff --git a/src/demos/xtrans/Main.java b/src/demos/xtrans/Main.java
new file mode 100755
index 0000000..07ee010
--- /dev/null
+++ b/src/demos/xtrans/Main.java
@@ -0,0 +1,229 @@
+package demos.xtrans;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.table.*;
+import javax.swing.tree.*;
+
+/** Demonstration showing off XTDesktopPane. */
+
+public class Main {
+ private XTDesktopPane desktop;
+ private XTBasicTransitionManager transManager;
+
+ private static final int TABLE = 1;
+ private static final int TREE = 2;
+
+ private Point loc = new Point();
+
+ private boolean scrollingEnabled = true;
+ private boolean rotationEnabled = true;
+ private boolean fadesEnabled = true;
+ private Random random;
+
+ private void chooseNextTransition() {
+ // Only choose one if the user's constraints force us to
+ if (scrollingEnabled && rotationEnabled && fadesEnabled) {
+ return;
+ }
+ if (random == null) {
+ random = new Random();
+ }
+ boolean fade = random.nextBoolean();
+ if (!fadesEnabled) {
+ fade = false;
+ }
+
+ XTBasicTransitionManager.Style style = XTBasicTransitionManager.STYLE_NO_MOTION;
+ if (scrollingEnabled) {
+ style = XTBasicTransitionManager.STYLE_SCROLL;
+ } else if (rotationEnabled) {
+ style = XTBasicTransitionManager.STYLE_ROTATE;
+ }
+ XTBasicTransitionManager.Direction direction = null;
+ switch (random.nextInt(4)) {
+ case 0: direction = XTBasicTransitionManager.DIR_LEFT; break;
+ case 1: direction = XTBasicTransitionManager.DIR_RIGHT; break;
+ case 2: direction = XTBasicTransitionManager.DIR_UP; break;
+ default: direction = XTBasicTransitionManager.DIR_DOWN; break;
+ }
+ transManager.setNextTransition(style, direction, fade);
+ }
+
+ private void addWindow(int which) {
+ JInternalFrame frame = new JInternalFrame();
+ frame.setResizable(true);
+ frame.setClosable(true);
+ frame.setVisible(true);
+
+ switch (which) {
+ case TABLE:
+ {
+ frame.setTitle("Table Example");
+ Object[][] data = produceTableData(3, 20);
+ DefaultTableModel model = new DefaultTableModel(data, new String[] { "A", "B", "C" });
+ JTable table = new JTable(model);
+ JScrollPane scrollPane = new JScrollPane(table);
+ frame.getContentPane().add(scrollPane);
+ break;
+ }
+
+ case TREE:
+ {
+ frame.setTitle("Tree Example");
+ DefaultMutableTreeNode root = new DefaultMutableTreeNode();
+ populateTree(root, 2);
+ JTree tree = new JTree(root);
+ tree.setRootVisible(false);
+ frame.getContentPane().add(tree);
+ break;
+ }
+
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ frame.setLocation(loc);
+ loc = new Point((loc.x + 20) % desktop.getWidth(), (loc.y + 20) % desktop.getHeight());
+ frame.addInternalFrameListener(new InternalFrameAdapter() {
+ public void internalFrameClosing(InternalFrameEvent e) {
+ chooseNextTransition();
+ }
+ });
+ frame.pack();
+ int sz = Math.min(desktop.getWidth() / 3, desktop.getHeight());
+ frame.setSize(sz, sz);
+ chooseNextTransition();
+ desktop.add(frame);
+ desktop.moveToFront(frame);
+ }
+
+ private Object[][] produceTableData(int cols, int rows) {
+ Object[][] res = new Object[rows][];
+
+ Random r = new Random();
+
+ for (int i = 0; i < rows; i++) {
+ Object[] row = new Object[cols];
+ for (int j = 0; j < cols; j++) {
+ row[j] = new Integer(r.nextInt(1000));
+ }
+ res[i] = row;
+ }
+
+ return res;
+ }
+
+ private void populateTree(DefaultMutableTreeNode node, int depth) {
+ node.add(new DefaultMutableTreeNode("A"));
+ node.add(new DefaultMutableTreeNode("B"));
+ node.add(new DefaultMutableTreeNode("C"));
+
+ if (depth > 0) {
+ for (Enumeration e = node.children(); e.hasMoreElements(); ) {
+ populateTree((DefaultMutableTreeNode) e.nextElement(), depth - 1);
+ }
+ }
+ }
+
+ private void run(String[] args) {
+ JFrame frame = new JFrame("Desktop Demo");
+
+ JMenu menu = new JMenu("Actions");
+ JMenuBar menuBar = new JMenuBar();
+ JMenuItem item;
+
+ item = new JMenuItem("Add Table");
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ addWindow(TABLE);
+ }
+ });
+ menu.add(item);
+
+ item = new JMenuItem("Add Tree");
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ addWindow(TREE);
+ }
+ });
+ menu.add(item);
+
+ item = new JMenuItem("Close all");
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ Component[] cs = desktop.getComponents();
+ for (int i = 0; i < cs.length; i++) {
+ chooseNextTransition();
+ desktop.remove(cs[i]);
+ }
+ }
+ });
+ menu.add(item);
+
+ item = new JMenuItem("Quit");
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ System.exit(0);
+ }
+ });
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_MASK));
+ menu.add(item);
+
+ menuBar.add(menu);
+
+ menu = new JMenu("Options");
+ JCheckBoxMenuItem ckitem = new JCheckBoxMenuItem("Enable Scrolling");
+ ckitem.setState(true);
+ ckitem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ scrollingEnabled = ((JCheckBoxMenuItem) e.getSource()).getState();
+ }
+ });
+ menu.add(ckitem);
+
+ ckitem = new JCheckBoxMenuItem("Enable Rotation");
+ ckitem.setState(true);
+ ckitem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ rotationEnabled = ((JCheckBoxMenuItem) e.getSource()).getState();
+ }
+ });
+ menu.add(ckitem);
+
+ ckitem = new JCheckBoxMenuItem("Enable Fades");
+ ckitem.setState(true);
+ ckitem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ fadesEnabled = ((JCheckBoxMenuItem) e.getSource()).getState();
+ }
+ });
+ menu.add(ckitem);
+
+ menuBar.add(menu);
+
+ frame.setJMenuBar(menuBar);
+
+ desktop = new XTDesktopPane();
+ transManager = (XTBasicTransitionManager) desktop.getTransitionManager();
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.getContentPane().add(desktop);
+
+ DisplayMode cur = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
+ int width = (int) (cur.getWidth() * 0.75f);
+ int height = (int) (width * 3.0f / 4.0f);
+ if (height >= 95.0f * cur.getHeight()) {
+ height = (int) (cur.getHeight() * 0.75f);
+ width = (int) (height * 4.0f / 3.0f);
+ }
+ frame.setSize(width, height);
+ frame.setVisible(true);
+ }
+
+ public static void main(String[] args) {
+ new Main().run(args);
+ }
+}
diff --git a/src/demos/xtrans/OffscreenComponentWrapper.java b/src/demos/xtrans/OffscreenComponentWrapper.java
new file mode 100755
index 0000000..760343d
--- /dev/null
+++ b/src/demos/xtrans/OffscreenComponentWrapper.java
@@ -0,0 +1,122 @@
+package demos.xtrans;
+
+import java.awt.*;
+import javax.swing.*;
+
+// Internal JOGL API references
+import com.sun.opengl.impl.Debug;
+// FIXME: debugging only
+import com.sun.opengl.impl.Java2D;
+
+/** Provides an interposition point where we can install a new
+ Graphics object in the rendering pipeline. Because lightweight
+ components always delegate up to their parents to fetch Graphics
+ objects, we can use this point to swap in the off-screen
+ VolatileImage we're using for rendering. Applications should not
+ need to construct instances of this class directly, though they
+ may encounter them when traversing the component hierarchy created
+ by the OffscreenDesktopPane, since they are automatically inserted
+ during add operations. */
+
+public class OffscreenComponentWrapper extends JComponent {
+ private static final boolean DEBUG = Debug.debug("OffscreenComponentWrapper");
+
+ /** Instantiates an OffscreenComponentWrapper. This should not be
+ called directly by applications. */
+ public OffscreenComponentWrapper(Component arg) {
+ if (arg == null) {
+ throw new RuntimeException("Null argument");
+ }
+ add(arg);
+ setOpaque(false);
+ }
+
+ protected void addImpl(Component c, Object constraints, int index) {
+ if (getComponentCount() != 0) {
+ throw new RuntimeException("May only add one child");
+ }
+ super.addImpl(c, constraints, index);
+ }
+
+ /** Returns the sole child component of this one. */
+ public Component getChild() {
+ if (getComponentCount() == 0) {
+ throw new RuntimeException("No child found");
+ }
+ return getComponent(0);
+ }
+
+ public void remove(int i) {
+ super.remove(i);
+ throw new RuntimeException("Should not call this");
+ }
+
+ public void remove(Component c) {
+ super.remove(c);
+ throw new RuntimeException("Should not call this");
+ }
+
+ /** Overrides the superclass's getGraphics() in order to provide a
+ correctly-translated Graphics object on the
+ OffscreenDesktopPane's back buffer. */
+ public Graphics getGraphics() {
+ Component parent = getParent();
+ if ((parent != null) && (parent instanceof OffscreenDesktopPane)) {
+ OffscreenDesktopPane desktop = (OffscreenDesktopPane) parent;
+ OffscreenDesktopManager manager = (OffscreenDesktopManager) desktop.getDesktopManager();
+ // Find out where the component we're rendering lives on the back buffer
+ Graphics g = manager.getOffscreenGraphics();
+ Rectangle bounds = manager.getBoundsOnBackBuffer(getChild());
+ if (bounds == null) {
+ if (DEBUG) {
+ System.err.println("No bounds for child");
+ }
+
+ // Not yet laid out on back buffer; however, avoid painting to
+ // screen
+ return g;
+ }
+ if (DEBUG) {
+ System.err.println("Graphics.translate(" + bounds.x + "," + bounds.y + ")");
+ System.err.println(" Surface identifier = " + Java2D.getOGLSurfaceIdentifier(g));
+ }
+
+ g.translate(bounds.x, bounds.y);
+ // Also take into account the translation of the child
+ Component c = getChild();
+ g.translate(-c.getX(), -c.getY());
+ // Make sure any changes the user made to the double-buffering
+ // of child components don't mess up the rendering results
+ OffscreenDesktopManager.switchDoubleBuffering(this, false);
+ // A child component will be performing drawing and therefore
+ // get the OpenGL copy of the back buffer out of sync; will need
+ // to repair it
+ // NOTE: in theory we should only need to set the copy back
+ // state on the OffscreenDesktopManager. However it seems that
+ // there are certain operations (like scrolling in a
+ // JScrollPane) that have unanticipated consequences, such as
+ // drawing into the Swing back buffer (which is heavily
+ // undesirable, because it can cause on-screen visual artifacts
+ // in our rendering paradigm) as well as potentially
+ // inadvertently copying regions of the back buffer from one
+ // place to another. Since we don't know the side effects of
+ // such operations we currently need to treat the entire back
+ // buffer as being dirty.
+ manager.setNeedsRedraw();
+ // NOTE: this is a bit of a hack but we need to indicate to the
+ // parent desktop that it is dirty as well since one of its
+ // children (being painted by alternate means) needs to be
+ // redrawn
+ desktop.repaint();
+ return g;
+ } else {
+ return super.getGraphics();
+ }
+ }
+
+ public void paintComponent(Graphics g) {
+ }
+
+ protected void paintChildren(Graphics g) {
+ }
+}
diff --git a/src/demos/xtrans/OffscreenDesktopManager.java b/src/demos/xtrans/OffscreenDesktopManager.java
new file mode 100755
index 0000000..656618f
--- /dev/null
+++ b/src/demos/xtrans/OffscreenDesktopManager.java
@@ -0,0 +1,784 @@
+package demos.xtrans;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+import java.beans.*;
+import java.nio.*;
+import java.util.*;
+import javax.swing.*;
+
+// Internal JOGL API references
+import com.sun.opengl.impl.Debug;
+// FIXME: debugging only
+import com.sun.opengl.impl.Java2D;
+
+// FIXME: we need a way to lock a portion of the off-screen back
+// buffer to be persistent for a while during component removals. It
+// turns out that the removal process of JInternalFrames "mostly"
+// works OK with temporarily preserving a region of the back buffer
+// but in the case of redraws of the target component occurring after
+// it has been removed (such as in the case of a GLJPanel being
+// animated) we need to preserve that region.
+
+/** A DesktopManager implementation supporting off-screen rendering
+ and management of components' images for later compositing. */
+
+public class OffscreenDesktopManager implements DesktopManager {
+ protected final static String HAS_BEEN_ICONIFIED_PROPERTY = "wasIconOnce";
+
+ protected VolatileImage offscreenBackBuffer;
+
+ // Verious dirty states.
+ //
+ // STATE_CLEAN indicates that the OpenGL texture is in sync with the
+ // VolatileImage back buffer and that no (relatively expensive) copy
+ // back operations need be performed.
+ //
+ // STATE_COPY_BACK indicates that the VolatileImage back buffer must
+ // be copied back e.g. into an OpenGL texture, but that the back
+ // buffer's contents are clean.
+ //
+ // STATE_REDRAW indicates that all components need to be repainted
+ // on the back buffer. STATE_REDRAW implies STATE_COPY_BACK.
+ //
+ // STATE_RELAYOUT is the most expensive state, in which all
+ // components are re-laid out on the back buffer and repainted
+ // completely. This implies STATE_REDRAW, which in turn implies
+ // STATE_COPY_BACK.
+ public static final int STATE_CLEAN = 0;
+ public static final int STATE_COPY_BACK = 1;
+ public static final int STATE_REDRAW = 2;
+ public static final int STATE_RELAYOUT = 3;
+
+ // We start off assuming that we need to lay out everything
+ protected int dirtyState = STATE_RELAYOUT;
+
+ protected Map/*<Component, Rectangle>*/ componentPositionsOnBackBuffer = new WeakHashMap();
+
+ // For debugging
+ private static final boolean DEBUG = Debug.debug("OffscreenDesktopManager");
+ private static final boolean VERBOSE = Debug.verbose();
+ private JFrame debuggingFrame;
+ private JPanel debuggingPanel;
+
+ public OffscreenDesktopManager() {
+ if (DEBUG) {
+ debuggingFrame = new JFrame("Debugging frame");
+ debuggingPanel = new JPanel() {
+ public void paintComponent(Graphics g) {
+ if (offscreenBackBuffer != null) {
+ g.drawImage(offscreenBackBuffer, 0, 0, null);
+ }
+ }
+ };
+ debuggingPanel.setDoubleBuffered(false);
+ debuggingFrame.getContentPane().add(debuggingPanel);
+ debuggingFrame.pack();
+ debuggingFrame.setLocation(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth() / 2, 0);
+ debuggingFrame.setSize(256, 256);
+ debuggingFrame.setVisible(true);
+ }
+ }
+
+ /** Sets the state bit in the desktop manager indicating that the
+ offscreen texture has changed and may need to be copied back
+ e.g. to an OpenGL texture. */
+ public void setNeedsCopyBack() {
+ dirtyState = Math.max(dirtyState, STATE_COPY_BACK);
+ }
+
+ /** Sets the state bit in the desktop manager indicating that the
+ child components need to be redrawn to the off-screen buffer. */
+ public void setNeedsRedraw() {
+ dirtyState = Math.max(dirtyState, STATE_REDRAW);
+ }
+
+ /** Sets the state bit in the desktop manager indicating that the
+ components need to be re-laid out on the off-screen back buffer.
+ This implies that all of these components need to be repainted
+ and also implies that the off-screen back buffer may need to be
+ copied back (needsCopyBack()). */
+ public void setNeedsReLayout() {
+ dirtyState = Math.max(dirtyState, STATE_RELAYOUT);
+ }
+
+ /** Returns the state bit in the desktop manager indicating that the
+ offscreen texture has changed and may need to be copied back
+ e.g. to an OpenGL texture. */
+ public boolean needsCopyBack() {
+ return (dirtyState >= STATE_COPY_BACK);
+ }
+
+ /** Returns the state bit in the desktop manager indicating that the
+ child components need to be redrawn to the off-screen buffer. */
+ public boolean needsRedraw() {
+ return (dirtyState >= STATE_REDRAW);
+ }
+
+ /** Returns the state bit in the desktop manager indicating that the
+ components need to be re-laid out on the off-screen back buffer.
+ This implies that all of these components need to be repainted
+ and also implies that the off-screen back buffer may need to be
+ copied back (needsCopyBack()). */
+ public boolean needsReLayout() {
+ return (dirtyState >= STATE_RELAYOUT);
+ }
+
+ /** Returns the default graphics configuration of the default screen
+ device for the local graphics environment. */
+ protected GraphicsConfiguration getDefaultConfiguration() {
+ return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
+ }
+
+ /** Fetches the Graphics object corresponding to the off-screen back
+ buffer. */
+ public Graphics getOffscreenGraphics() {
+ return offscreenBackBuffer.getGraphics();
+ }
+
+ /** Fetches the Image being used as the back buffer. */
+ public Image getOffscreenBackBuffer() {
+ return offscreenBackBuffer;
+ }
+
+ /** Returns the width of the off-screen back buffer at this point in
+ time, or -1 if it has not been created yet. */
+ public int getOffscreenBackBufferWidth() {
+ return offscreenBackBuffer.getWidth();
+ }
+
+ /** Returns the height of the off-screen back buffer at this point
+ in time, or -1 if it has not been created yet. */
+ public int getOffscreenBackBufferHeight() {
+ return offscreenBackBuffer.getHeight();
+ }
+
+ /** Fetches the Rectangle corresponding to the bounds of the given
+ child component on the off-screen back buffer. This portion of
+ the back buffer can be drawn manually by the end user to display
+ the given component. */
+ public Rectangle getBoundsOnBackBuffer(Component c) {
+ return (Rectangle) componentPositionsOnBackBuffer.get(c);
+ }
+
+ /** Updates the layouts of the components on the off-screen back
+ buffer without repainting the back buffer. This should always be
+ called after adding, removing or resizing child components. It
+ is called automatically by {@link #updateOffscreenBuffer
+ updateOffscreenBuffer}. */
+ public void layoutOffscreenBuffer(OffscreenDesktopPane parent) {
+ if (needsReLayout()) {
+ // Must do the following:
+ // 1. Lay out the desktop pane's children on the off-screen back
+ // buffer, keeping track of where they went
+ // 2. Draw the children to the off-screen buffer
+ // (Not done here: 3. Use JOGL to copy the off-screen buffer to a
+ // texture for later consumption by the XTDesktopPane)
+
+ //////////////////////////////////////////////////////////////////
+ // //
+ // FIXME: must use a more efficient packing algorithm than this //
+ // //
+ //////////////////////////////////////////////////////////////////
+
+ // NOTE: this is the rectangle packing problem, which is NP-hard.
+ // We could go to arbitrary lengths in order to improve the
+ // efficiency of this packing. Ideally we would like to minimize
+ // wasted space and (probably) shoot for a somewhat-square layout.
+ // Because currently we're just trying to get things working,
+ // we're going to do the simplest layout possible: just line
+ // things up left-to-right.
+ int maxHeight = -1;
+ int widthSum = 0;
+
+ // Two-pass algorithm, one getting maximum height and summing
+ // widths, and one laying things out
+ for (int i = 0; i < parent.getComponentCount(); i++) {
+ Component c = ((OffscreenComponentWrapper) parent.getComponent(i)).getChild();
+ int w = c.getWidth();
+ int h = c.getHeight();
+ maxHeight = Math.max(maxHeight, h);
+ widthSum += w;
+ }
+ int curX = 0;
+ for (int i = 0; i < parent.getComponentCount(); i++) {
+ Component c = ((OffscreenComponentWrapper) parent.getComponent(i)).getChild();
+ int w = c.getWidth();
+ int h = c.getHeight();
+ Rectangle r = new Rectangle(curX, 0, w, h);
+ componentPositionsOnBackBuffer.put(c, r);
+ curX += w;
+ }
+
+ // Re-create off-screen buffer if necessary
+ int offscreenWidth = nextPowerOf2(widthSum);
+ int offscreenHeight = nextPowerOf2(maxHeight);
+ if ((offscreenBackBuffer == null) ||
+ (offscreenWidth != offscreenBackBuffer.getWidth()) ||
+ (offscreenHeight != offscreenBackBuffer.getHeight())) {
+ if (offscreenBackBuffer != null) {
+ offscreenBackBuffer.flush();
+ }
+ offscreenBackBuffer =
+ getDefaultConfiguration().createCompatibleVolatileImage(offscreenWidth,
+ offscreenHeight);
+ }
+
+ if (DEBUG) {
+ debuggingPanel.setPreferredSize(new Dimension(offscreenWidth, offscreenHeight));
+ debuggingFrame.setSize(offscreenWidth + 10, offscreenHeight + 30);
+ }
+
+ dirtyState = STATE_REDRAW;
+ }
+ }
+
+ /** Updates the image on the off-screen back buffer. This should
+ always be called before attempting to draw the child components'
+ contents on the screen. If the child components' states are
+ clean, this method does nothing. Note that this changes the
+ state bits back to clean, so subclasses should capture the
+ current state before calling the superclass implementation. */
+ public void updateOffscreenBuffer(OffscreenDesktopPane parent) {
+ if (!needsCopyBack()) {
+ // Cleanest possible state
+ return;
+ }
+
+ layoutOffscreenBuffer(parent);
+
+ boolean validated = false;
+ boolean done = false;
+ while (!done) {
+ if (needsRedraw()) {
+ boolean redrawn = false;
+
+ do {
+ // Validate it
+ int res = offscreenBackBuffer.validate(getDefaultConfiguration());
+ if (!((res == VolatileImage.IMAGE_OK) ||
+ (res == VolatileImage.IMAGE_RESTORED))) {
+ // FIXME: fail more gracefully
+ throw new RuntimeException("Unable to validate VolatileImage");
+ }
+ validated = true;
+
+ // Lay out and render components
+ final Graphics g = offscreenBackBuffer.getGraphics();
+ int curX = 0;
+ for (int i = 0; i < parent.getComponentCount(); i++) {
+ Component c = ((OffscreenComponentWrapper) parent.getComponent(i)).getChild();
+
+ if (c.isVisible()) {
+ // Ensure this component and all children have double
+ // buffering disabled to prevent incorrect rendering results.
+ // Should try to make this more efficient, but doesn't look
+ // like there's any way to listen for setDoubleBuffered
+ // changes; could however listen for hierarchy change events,
+ // which are more likely to break things, but these are
+ // expensive to deliver as well
+ switchDoubleBuffering(c, false);
+
+ // NOTE: should probably be smarter about this and only
+ // paint components which really need it (consult
+ // RepaintManager?). However, experimentation has shown
+ // that at this point the RepaintManager is already in
+ // the process of painting the child components and its
+ // notion of the dirty regions has already been cleared.
+
+ Rectangle r = (Rectangle) componentPositionsOnBackBuffer.get(c);
+ if (r == null) {
+ // May be bug or race condition; for now just skip this one
+ continue;
+ }
+ Graphics g2 = g.create();
+ if (DEBUG && VERBOSE) {
+ System.err.println("Translating Graphics to (" + r.x + "," + r.y + ")");
+ System.err.println(" Surface identifier = " + Java2D.getOGLSurfaceIdentifier(g2));
+ }
+ g2.translate(r.x, r.y);
+ c.paint(g2);
+ g2.dispose();
+ }
+ }
+ g.dispose();
+ if (!offscreenBackBuffer.contentsLost()) {
+ redrawn = true;
+ done = true;
+ }
+ } while (!redrawn);
+ }
+
+ // If we didn't need to re-layout and draw the components, we
+ // might still need to copy back their results to an OpenGL
+ // texture. Subclasses should override this method to do
+ // additional work afterward.
+
+ if (!validated) {
+ int res = offscreenBackBuffer.validate(getDefaultConfiguration());
+ if (!((res == VolatileImage.IMAGE_OK) ||
+ (res == VolatileImage.IMAGE_RESTORED))) {
+ // FIXME: fail more gracefully
+ throw new RuntimeException("Unable to validate VolatileImage");
+ }
+ if (res == VolatileImage.IMAGE_RESTORED) {
+ // The contents were blown away since the time of the last
+ // render, so force a re-render
+ setNeedsRedraw();
+ } else {
+ done = true;
+ }
+ }
+ }
+
+ dirtyState = STATE_CLEAN;
+
+ // Subclasses would do the copy back here
+
+ if (DEBUG) {
+ debuggingPanel.repaint();
+ }
+ }
+
+ public void openFrame(JInternalFrame f) {
+ if(getDesktopPaneParent(f.getDesktopIcon()) != null) {
+ getDesktopPaneParent(f.getDesktopIcon()).add(f);
+ removeIconFor(f);
+ }
+
+ setNeedsReLayout();
+ }
+
+ public void closeFrame(JInternalFrame f) {
+ boolean findNext = f.isSelected();
+ JDesktopPane c = getDesktopPaneParent(f);
+ if (findNext)
+ try { f.setSelected(false); } catch (PropertyVetoException e2) { }
+ if(c != null) {
+ c.remove(f.getParent());
+ repaintPortionOfDesktop(c, f);
+ }
+ removeIconFor(f);
+ if(f.getNormalBounds() != null)
+ f.setNormalBounds(null);
+ if(wasIcon(f))
+ setWasIcon(f, null);
+ if (findNext) activateNextFrame(c);
+
+ setNeedsReLayout();
+ }
+
+ public void maximizeFrame(JInternalFrame f) {
+ if (f.isIcon()) {
+ try {
+ // In turn calls deiconifyFrame in the desktop manager.
+ // That method will handle the maximization of the frame.
+ f.setIcon(false);
+ } catch (PropertyVetoException e2) {
+ }
+ } else {
+ f.setNormalBounds(f.getBounds());
+ Rectangle desktopBounds = getDesktopPaneParent(f).getBounds();
+ setBoundsForFrame(f, 0, 0,
+ desktopBounds.width, desktopBounds.height);
+ }
+
+ // Set the maximized frame as selected.
+ try {
+ f.setSelected(true);
+ } catch (PropertyVetoException e2) {
+ }
+
+ setNeedsReLayout();
+ }
+
+ public void minimizeFrame(JInternalFrame f) {
+ // If the frame was an icon restore it back to an icon.
+ if (f.isIcon()) {
+ iconifyFrame(f);
+ return;
+ }
+
+ if ((f.getNormalBounds()) != null) {
+ Rectangle r = f.getNormalBounds();
+ f.setNormalBounds(null);
+ try { f.setSelected(true); } catch (PropertyVetoException e2) { }
+ setBoundsForFrame(f, r.x, r.y, r.width, r.height);
+ }
+
+ setNeedsReLayout();
+ }
+
+ public void iconifyFrame(JInternalFrame f) {
+ JInternalFrame.JDesktopIcon desktopIcon;
+ Container c = getDesktopPaneParent(f);
+ JDesktopPane d = f.getDesktopPane();
+ boolean findNext = f.isSelected();
+
+ desktopIcon = f.getDesktopIcon();
+ if(!wasIcon(f)) {
+ Rectangle r = getBoundsForIconOf(f);
+ desktopIcon.setBounds(r.x, r.y, r.width, r.height);
+ setWasIcon(f, Boolean.TRUE);
+ }
+
+ if (c == null) {
+ return;
+ }
+
+ if (c instanceof JLayeredPane) {
+ JLayeredPane lp = (JLayeredPane)c;
+ int layer = lp.getLayer(f);
+ lp.putLayer(desktopIcon, layer);
+ }
+
+ // If we are maximized we already have the normal bounds recorded
+ // don't try to re-record them, otherwise we incorrectly set the
+ // normal bounds to maximized state.
+ if (!f.isMaximum()) {
+ f.setNormalBounds(f.getBounds());
+ }
+ c.remove(f);
+ c.add(desktopIcon);
+ try {
+ f.setSelected(false);
+ } catch (PropertyVetoException e2) {
+ }
+
+ // Get topmost of the remaining frames
+ if (findNext) {
+ activateNextFrame(c);
+ }
+
+ setNeedsReLayout();
+ }
+
+ protected void activateNextFrame(Container c) {
+ int i;
+ JInternalFrame nextFrame = null;
+ if (c == null) return;
+ for (i = 0; i < c.getComponentCount(); i++) {
+ if (c.getComponent(i) instanceof JInternalFrame) {
+ nextFrame = (JInternalFrame) c.getComponent(i);
+ break;
+ }
+ }
+ if (nextFrame != null) {
+ try { nextFrame.setSelected(true); }
+ catch (PropertyVetoException e2) { }
+ moveToFront(nextFrame);
+ }
+ else {
+ c.requestFocus();
+ }
+
+ // This operation will change the graphic contents of the
+ // offscreen buffer but not the positions of any of the windows
+ setNeedsCopyBack();
+ }
+
+ public void deiconifyFrame(JInternalFrame f) {
+ JInternalFrame.JDesktopIcon desktopIcon = f.getDesktopIcon();
+ Container c = getDesktopPaneParent(desktopIcon);
+ if (c != null) {
+ c.add(f);
+ // If the frame is to be restored to a maximized state make
+ // sure it still fills the whole desktop.
+ if (f.isMaximum()) {
+ Rectangle desktopBounds = c.getBounds();
+ if (f.getWidth() != desktopBounds.width ||
+ f.getHeight() != desktopBounds.height) {
+ setBoundsForFrame(f, 0, 0,
+ desktopBounds.width, desktopBounds.height);
+ }
+ }
+ removeIconFor(f);
+ if (f.isSelected()) {
+ moveToFront(f);
+ } else {
+ try {
+ f.setSelected(true);
+ } catch (PropertyVetoException e2) {
+ }
+ }
+ }
+
+ setNeedsReLayout();
+ }
+
+ public void activateFrame(JInternalFrame f) {
+ Container p = getDesktopPaneParent(f);
+ Component[] c;
+ JDesktopPane d = f.getDesktopPane();
+ JInternalFrame currentlyActiveFrame =
+ (d == null) ? null : d.getSelectedFrame();
+ // fix for bug: 4162443
+ if(p == null) {
+ // If the frame is not in parent, its icon maybe, check it
+ p = getDesktopPaneParent(f.getDesktopIcon());
+ if(p == null)
+ return;
+ }
+ // we only need to keep track of the currentActive InternalFrame, if any
+ if (currentlyActiveFrame == null){
+ if (d != null) { d.setSelectedFrame(f);}
+ } else if (currentlyActiveFrame != f) {
+ // if not the same frame as the current active
+ // we deactivate the current
+ if (currentlyActiveFrame.isSelected()) {
+ try {
+ currentlyActiveFrame.setSelected(false);
+ }
+ catch(PropertyVetoException e2) {}
+ }
+ if (d != null) { d.setSelectedFrame(f);}
+ }
+ moveToFront(f);
+
+ // This operation will change the graphic contents of the
+ // offscreen buffer but not the positions of any of the windows
+ // setNeedsCopyBack();
+ setNeedsRedraw();
+
+ repaintPortionOfDesktop(f);
+ }
+
+ public void deactivateFrame(JInternalFrame f) {
+ JDesktopPane d = f.getDesktopPane();
+ JInternalFrame currentlyActiveFrame =
+ (d == null) ? null : d.getSelectedFrame();
+ if (currentlyActiveFrame == f)
+ d.setSelectedFrame(null);
+
+ // This operation will change the graphic contents of the
+ // offscreen buffer but not the positions of any of the windows
+ setNeedsRedraw();
+
+ repaintPortionOfDesktop(f);
+ }
+
+ public void beginDraggingFrame(JComponent f) {
+ // Nothing to do any more because the DesktopPane handles this by
+ // redrawing from the off-screen buffer
+ }
+
+ public void dragFrame(JComponent f, int newX, int newY) {
+ f.setLocation(newX, newY);
+ repaintPortionOfDesktop(f);
+ }
+
+ public void endDraggingFrame(JComponent f) {
+ // NOTE: nothing to do any more because OffscreenDesktopPane
+ // subclasses handle this
+ }
+
+ public void beginResizingFrame(JComponent f, int direction) {
+ }
+
+ public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
+ setBoundsForFrame(f, newX, newY, newWidth, newHeight);
+ repaintPortionOfDesktop(f);
+ }
+
+ public void endResizingFrame(JComponent f) {
+ repaintPortionOfDesktop(f);
+ }
+
+ public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
+ boolean didResize = (f.getWidth() != newWidth || f.getHeight() != newHeight);
+ f.setBounds(newX, newY, newWidth, newHeight);
+ if(didResize) {
+ f.validate();
+ }
+ setNeedsReLayout();
+ }
+
+ protected void removeIconFor(JInternalFrame f) {
+ JInternalFrame.JDesktopIcon di = f.getDesktopIcon();
+ JDesktopPane c = getDesktopPaneParent(di);
+ if(c != null) {
+ c.remove(di);
+ repaintPortionOfDesktop(c, di);
+ }
+ }
+
+ protected Rectangle getBoundsForIconOf(JInternalFrame f) {
+ //
+ // Get the icon for this internal frame and its preferred size
+ //
+
+ JInternalFrame.JDesktopIcon icon = f.getDesktopIcon();
+ Dimension prefSize = icon.getPreferredSize();
+ //
+ // Get the parent bounds and child components.
+ //
+
+ Container c = getDesktopPaneParent(f);
+ if (c == null) {
+ c = getDesktopPaneParent(f.getDesktopIcon());
+ }
+
+ if (c == null) {
+ /* the frame has not yet been added to the parent; how about (0,0) ?*/
+ return new Rectangle(0, 0, prefSize.width, prefSize.height);
+ }
+
+ Rectangle parentBounds = c.getBounds();
+ Component [] components = c.getComponents();
+
+
+ //
+ // Iterate through valid default icon locations and return the
+ // first one that does not intersect any other icons.
+ //
+
+ Rectangle availableRectangle = null;
+ JInternalFrame.JDesktopIcon currentIcon = null;
+
+ int x = 0;
+ int y = parentBounds.height - prefSize.height;
+ int w = prefSize.width;
+ int h = prefSize.height;
+
+ boolean found = false;
+
+ while (!found) {
+
+ availableRectangle = new Rectangle(x,y,w,h);
+
+ found = true;
+
+ for ( int i=0; i<components.length; i++ ) {
+
+ //
+ // Get the icon for this component
+ //
+
+ if ( components[i] instanceof JInternalFrame ) {
+ currentIcon = ((JInternalFrame)components[i]).getDesktopIcon();
+ }
+ else if ( components[i] instanceof JInternalFrame.JDesktopIcon ){
+ currentIcon = (JInternalFrame.JDesktopIcon)components[i];
+ } else
+ /* found a child that's neither an internal frame nor
+ an icon. I don't believe this should happen, but at
+ present it does and causes a null pointer exception.
+ Even when that gets fixed, this code protects against
+ the npe. hania */
+ continue;
+
+ //
+ // If this icon intersects the current location, get next location.
+ //
+
+ if ( !currentIcon.equals(icon) ) {
+ if ( availableRectangle.intersects(currentIcon.getBounds()) ) {
+ found = false;
+ break;
+ }
+ }
+ }
+
+ if (currentIcon == null)
+ /* didn't find any useful children above. This probably shouldn't
+ happen, but this check protects against an npe if it ever does
+ (and it's happening now) */
+ return availableRectangle;
+
+ x += currentIcon.getBounds().width;
+
+ if ( x + w > parentBounds.width ) {
+ x = 0;
+ y -= h;
+ }
+ }
+
+ return(availableRectangle);
+ }
+
+ protected void setWasIcon(JInternalFrame f, Boolean value) {
+ if (value != null) {
+ f.putClientProperty(HAS_BEEN_ICONIFIED_PROPERTY, value);
+ }
+ }
+
+ protected boolean wasIcon(JInternalFrame f) {
+ return (f.getClientProperty(HAS_BEEN_ICONIFIED_PROPERTY) == Boolean.TRUE);
+ }
+
+ protected JDesktopPane getDesktopPaneParent(Component f) {
+ Container c = f.getParent();
+ if (c == null) {
+ return null;
+ }
+ if (!(c instanceof OffscreenComponentWrapper)) {
+ throw new RuntimeException("Illegal component structure");
+ }
+ Container parent = c.getParent();
+ if (parent == null) {
+ return null;
+ }
+ if (!(parent instanceof JDesktopPane)) {
+ throw new RuntimeException("Illegal component structure");
+ }
+ return (JDesktopPane) parent;
+ }
+
+ private void moveToFront(Component f) {
+ Container c = getDesktopPaneParent(f);
+ if (c instanceof JDesktopPane) {
+ ((JDesktopPane) c).moveToFront(f.getParent());
+ }
+ }
+
+ private static int nextPowerOf2(int number) {
+ // Workaround for problems where 0 width or height are transiently
+ // seen during layout
+ if (number == 0) {
+ return 2;
+ }
+
+ if (((number-1) & number) == 0) {
+ //ex: 8 -> 0b1000; 8-1=7 -> 0b0111; 0b1000&0b0111 == 0
+ return number;
+ }
+ int power = 0;
+ while (number > 0) {
+ number = number>>1;
+ power++;
+ }
+ return (1<<power);
+ }
+
+ /** Repaints the portion of the desktop pane parent corresponding to
+ the given component. */
+ protected void repaintPortionOfDesktop(Component comp) {
+ repaintPortionOfDesktop(getDesktopPaneParent(comp), comp);
+ }
+
+ /** Repaints the portion of the passed desktop pane corresponding to
+ the given component. */
+ protected void repaintPortionOfDesktop(JDesktopPane desktop, Component comp) {
+ // Indicate to the desktop pane that a certain portion of the
+ // on-screen representation is dirty. The desktop can map these
+ // coordinates to the positions of the windows if they are
+ // different.
+ Rectangle r = comp.getBounds();
+ desktop.repaint(r.x, r.y, r.width, r.height);
+ }
+
+ /** Helper function to force the double-buffering of an entire
+ component hierarchy to the on or off state. */
+ public static void switchDoubleBuffering(Component root, boolean val) {
+ if (root instanceof JComponent) {
+ ((JComponent) root).setDoubleBuffered(val);
+ }
+ if (root instanceof Container) {
+ Container container = (Container) root;
+ for (int i = 0; i < container.getComponentCount(); i++) {
+ switchDoubleBuffering(container.getComponent(i), val);
+ }
+ }
+ }
+}
diff --git a/src/demos/xtrans/OffscreenDesktopPane.java b/src/demos/xtrans/OffscreenDesktopPane.java
new file mode 100755
index 0000000..7800666
--- /dev/null
+++ b/src/demos/xtrans/OffscreenDesktopPane.java
@@ -0,0 +1,189 @@
+package demos.xtrans;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+import javax.swing.*;
+
+// Internal JOGL API references
+import com.sun.opengl.impl.Debug;
+
+/** A subclass of JDesktopPane which performs all of the rendering of
+ its child components into an off-screen buffer. Provides access to
+ this back buffer so subclasses can determine how to draw the
+ contents of the child windows to the screen. */
+
+public class OffscreenDesktopPane extends JDesktopPane {
+ //
+ // Debugging functionality only
+ //
+ private static final boolean DEBUG = Debug.debug("OffscreenDesktopPane");
+ private static final Color[] colors = {
+ Color.LIGHT_GRAY,
+ Color.CYAN,
+ Color.PINK,
+ Color.GRAY,
+ Color.MAGENTA,
+ Color.BLUE,
+ Color.ORANGE,
+ Color.DARK_GRAY,
+ Color.RED,
+ Color.YELLOW
+ };
+ private int colorIdx;
+
+ private Color getNextColor() {
+ Color c = colors[colorIdx];
+ colorIdx = (colorIdx + 1) % colors.length;
+ return c;
+ }
+
+ private Map/*<Component, Color>*/ componentColorMap;
+
+ /** Constructs a new OffscreenDesktopPane. */
+ public OffscreenDesktopPane() {
+ super();
+ if (DEBUG) {
+ componentColorMap = new WeakHashMap();
+ }
+ // Only use an OffscreenDesktopManager instance directly if we
+ // have not been subclassed
+ if (getClass() == OffscreenDesktopPane.class) {
+ setDesktopManager(new OffscreenDesktopManager());
+ }
+ }
+
+ /** Overrides superclass's addImpl to insert an
+ OffscreenComponentWrapper between the added component and this
+ one. The wrapper component produces Graphics objects used when
+ children repaint themselves directly. */
+ protected void addImpl(Component c, Object constraints, int index) {
+ if (c instanceof OffscreenComponentWrapper) {
+ throw new RuntimeException("Should not add OffscreenComponentWrappers directly");
+ }
+ OffscreenComponentWrapper wrapper = new OffscreenComponentWrapper(c);
+ // Note: this is essential in order to keep mouse events
+ // propagating correctly down the hierarchy
+ wrapper.setBounds(getBounds());
+ OffscreenDesktopManager.switchDoubleBuffering(wrapper, false);
+ super.addImpl(wrapper, constraints, index);
+ if (DEBUG) {
+ componentColorMap.put(c, getNextColor());
+ }
+ getOffscreenDesktopManager().setNeedsReLayout();
+ repaint();
+ }
+
+ // In order to hide the presence of the OffscreenComponentWrapper a
+ // little more, we override remove to make it look like we can
+ // simply pass in the JInternalFrames inside the
+ // OffscreenComponentWrappers. There are some situations where we
+ // can't hide the presence of the OffscreenComponentWrapper (such as
+ // when calling getParent() of the JInternalFrame) so to avoid
+ // incorrect behavior of the rest of the toolkit we don't override
+ // getComponent() to skip the OffscreenComponentWrappers.
+
+ /** Removes the component at the given index. */
+ public void remove(int index) {
+ Component c = getComponent(index);
+ super.remove(index);
+ OffscreenDesktopManager.switchDoubleBuffering(c, true);
+ }
+
+ /** Removes the specified component from this
+ OffscreenDesktopPane. This method accepts either the components
+ added by the application (which are not direct children of this
+ one) or the OffscreenComponentWrappers added implicitly by the
+ add() method. */
+ public void remove(Component comp) {
+ comp = getWrapper(comp);
+ if (comp == null) {
+ // This component must not be one of our children
+ return;
+ }
+ super.remove(comp);
+ OffscreenDesktopManager.switchDoubleBuffering(comp, true);
+ }
+
+ public void reshape(int x, int y, int w, int h) {
+ super.reshape(x, y, w, h);
+ Rectangle rect = new Rectangle(x, y, w, h);
+ Component[] cs = getComponents();
+ for (int i = 0; i < cs.length; i++) {
+ // Note: this is essential in order to keep mouse events
+ // propagating correctly down the hierarchy
+ ((OffscreenComponentWrapper) cs[i]).setBounds(rect);
+ }
+ }
+
+ /** Overridden from JLayeredPane for convenience when manipulating
+ child components. Accepts either the component added by the
+ application or the OffscreenComponentWrapper added implicitly by
+ the add() method. */
+ public void setPosition(Component c, int position) {
+ super.setPosition(getWrapper(c), position);
+ }
+
+ /** Paints all children of this OffscreenDesktopPane to the internal
+ off-screen buffer. Does no painting to the passed Graphics
+ object; this is the responsibility of subclasses. */
+ protected void paintChildren(Graphics g) {
+ // Update desktop manager's offscreen buffer if necessary
+ getOffscreenDesktopManager().updateOffscreenBuffer(this);
+
+ if (DEBUG) {
+ // Subclasses will need to override this behavior anyway, so
+ // only enable an on-screen representation if debugging is
+ // enabled. For now, simply paint colored rectangles indicating
+ // the on-screen locations of the windows.
+ final Component[] components = getRealChildComponents();
+ int compCount = components.length;
+ for (int i = compCount - 1; i >= 0; i--) {
+ Component c = components[i];
+ Rectangle r = c.getBounds();
+ Color col = (Color) componentColorMap.get(c);
+ g.setColor(col);
+ g.fillRect(r.x, r.y, r.width, r.height);
+ }
+ }
+ }
+
+ /** Fetches the real child components of this OffscreenDesktopPane,
+ skipping all OffscreenComponentWrappers implicitly added. */
+ protected Component[] getRealChildComponents() {
+ Component[] cs = getComponents();
+ for (int i = 0; i < cs.length; i++) {
+ cs[i] = ((OffscreenComponentWrapper) cs[i]).getChild();
+ }
+ return cs;
+ }
+
+ /** Returns the OffscreenDesktopManager associated with this
+ pane. */
+ public OffscreenDesktopManager getOffscreenDesktopManager() {
+ return (OffscreenDesktopManager) getDesktopManager();
+ }
+
+ /** Returns the real child component of this OffscreenDesktopPane,
+ skipping the OffscreenComponentWrapper implicitly added. */
+ protected static Component getRealComponent(Component c) {
+ if (c instanceof OffscreenComponentWrapper) {
+ return ((OffscreenComponentWrapper) c).getChild();
+ }
+ return c;
+ }
+
+ /** Returns the OffscreenComponentWrapper corresponding to the given
+ child component, or the passed component if it is already the
+ wrapper. */
+ protected static Component getWrapper(Component c) {
+ if (c instanceof OffscreenComponentWrapper) {
+ return c;
+ }
+ Component parent = c.getParent();
+ if (parent instanceof OffscreenComponentWrapper) {
+ return parent;
+ }
+ return null;
+ }
+}
diff --git a/src/demos/xtrans/Quad2f.java b/src/demos/xtrans/Quad2f.java
new file mode 100755
index 0000000..008c3df
--- /dev/null
+++ b/src/demos/xtrans/Quad2f.java
@@ -0,0 +1,74 @@
+package demos.xtrans;
+
+import gleem.linalg.*;
+
+/** A quadrilateral in which the vertices are two-dimensional
+ floating-point values. */
+
+public class Quad2f {
+ private Vec2f[] vecs;
+
+ public static final int UPPER_LEFT = 0;
+ public static final int LOWER_LEFT = 1;
+ public static final int LOWER_RIGHT = 2;
+ public static final int UPPER_RIGHT = 2;
+
+ private static final int NUM_VECS = 4;
+
+ /** Constructs a Quad2f in which all the vertices are set to the
+ origin. */
+ public Quad2f() {
+ vecs = new Vec2f[NUM_VECS];
+ for (int i = 0; i < NUM_VECS; i++) {
+ vecs[i] = new Vec2f();
+ }
+ }
+
+ /** Constructs a Quad2f in which the vertices are set to the
+ specified values. */
+ public Quad2f(Vec2f upperLeft,
+ Vec2f lowerLeft,
+ Vec2f lowerRight,
+ Vec2f upperRight) {
+ this();
+ setVec(0, upperLeft);
+ setVec(1, lowerLeft);
+ setVec(2, lowerRight);
+ setVec(3, upperRight);
+ }
+
+ /** Sets the specified vertex to the specified value. */
+ public void setVec(int which, Vec2f val) {
+ vecs[which].set(val);
+ }
+
+ /** Returns the specified vertex. */
+ public Vec2f getVec(int which) {
+ return vecs[which];
+ }
+
+ /** Sets all four points of this quadrilateral. */
+ public void set(Quad2f quad) {
+ for (int i = 0; i < NUM_VECS; i++) {
+ setVec(i, quad.getVec(i));
+ }
+ }
+
+ /** Returns a newly-constructed Quad2f in which all vertices have
+ been multiplied in scalar fashion by the passed value. */
+ public Quad2f times(float val) {
+ return new Quad2f(getVec(0).times(val),
+ getVec(1).times(val),
+ getVec(2).times(val),
+ getVec(3).times(val));
+ }
+
+ /** Returns a newly-constructed Quad2f in which the vertices are the
+ component-wise sums of this quad and the passed quad. */
+ public Quad2f plus(Quad2f val) {
+ return new Quad2f(getVec(0).plus(val.getVec(0)),
+ getVec(1).plus(val.getVec(1)),
+ getVec(2).plus(val.getVec(2)),
+ getVec(3).plus(val.getVec(3)));
+ }
+}
diff --git a/src/demos/xtrans/Quad3f.java b/src/demos/xtrans/Quad3f.java
new file mode 100755
index 0000000..a9f0dd7
--- /dev/null
+++ b/src/demos/xtrans/Quad3f.java
@@ -0,0 +1,74 @@
+package demos.xtrans;
+
+import gleem.linalg.*;
+
+/** A quadrilateral in which the vertices are three-dimensional
+ floating-point values. */
+
+public class Quad3f {
+ private Vec3f[] vecs;
+
+ public static final int UPPER_LEFT = 0;
+ public static final int LOWER_LEFT = 1;
+ public static final int LOWER_RIGHT = 2;
+ public static final int UPPER_RIGHT = 3;
+
+ private static final int NUM_VECS = 4;
+
+ /** Constructs a Quad3f in which all the vertices are set to the
+ origin. */
+ public Quad3f() {
+ vecs = new Vec3f[NUM_VECS];
+ for (int i = 0; i < NUM_VECS; i++) {
+ vecs[i] = new Vec3f();
+ }
+ }
+
+ /** Constructs a Quad3f in which the vertices are set to the
+ specified values. */
+ public Quad3f(Vec3f upperLeft,
+ Vec3f lowerLeft,
+ Vec3f lowerRight,
+ Vec3f upperRight) {
+ this();
+ setVec(0, upperLeft);
+ setVec(1, lowerLeft);
+ setVec(2, lowerRight);
+ setVec(3, upperRight);
+ }
+
+ /** Sets the specified vertex to the specified value. */
+ public void setVec(int which, Vec3f val) {
+ vecs[which].set(val);
+ }
+
+ /** Returns the specified vertex. */
+ public Vec3f getVec(int which) {
+ return vecs[which];
+ }
+
+ /** Sets all four points of this quadrilateral. */
+ public void set(Quad3f quad) {
+ for (int i = 0; i < NUM_VECS; i++) {
+ setVec(i, quad.getVec(i));
+ }
+ }
+
+ /** Returns a newly-constructed Quad2f in which all vertices have
+ been multiplied in scalar fashion by the passed value. */
+ public Quad3f times(float val) {
+ return new Quad3f(getVec(0).times(val),
+ getVec(1).times(val),
+ getVec(2).times(val),
+ getVec(3).times(val));
+ }
+
+ /** Returns a newly-constructed Quad2f in which the vertices are the
+ component-wise sums of this quad and the passed quad. */
+ public Quad3f plus(Quad3f val) {
+ return new Quad3f(getVec(0).plus(val.getVec(0)),
+ getVec(1).plus(val.getVec(1)),
+ getVec(2).plus(val.getVec(2)),
+ getVec(3).plus(val.getVec(3)));
+ }
+}
diff --git a/src/demos/xtrans/XTBasicTransition.java b/src/demos/xtrans/XTBasicTransition.java
new file mode 100755
index 0000000..a6bfd0f
--- /dev/null
+++ b/src/demos/xtrans/XTBasicTransition.java
@@ -0,0 +1,151 @@
+package demos.xtrans;
+
+import javax.media.opengl.*;
+import gleem.linalg.*;
+
+/** A basic transition supporting animated translation, rotation about
+ a pivot point, scrolling and fading effects. */
+
+public class XTBasicTransition implements XTTransition {
+ protected Vec3f pivot = new Vec3f();
+ protected Vec3f axis = new Vec3f();
+ protected InterpolatedFloat angle;
+ protected InterpolatedVec3f translation;
+ protected InterpolatedQuad3f vertices;
+ protected InterpolatedQuad2f texcoords;
+ protected InterpolatedFloat alpha;
+ protected float percentage;
+
+ /** Constructs a new transition object with all of its initial state
+ set to zero. */
+ public XTBasicTransition() {
+ }
+
+ public void update(float percentage) {
+ this.percentage = percentage;
+ }
+
+ public void draw(GL gl) {
+ float percent = percentage;
+ Quad3f vts = vertices.getCurrent(percent);
+ Quad2f tex = texcoords.getCurrent(percent);
+
+ if (translation != null) {
+ Vec3f trans = translation.getCurrent(percent);
+ gl.glTranslatef(trans.x(), trans.y(), trans.z());
+ }
+ // Rotate about pivot point
+ gl.glTranslatef(pivot.x(), pivot.y(), pivot.z());
+ if (angle != null) {
+ gl.glRotatef(angle.getCurrent(percent), axis.x(), axis.y(), axis.z());
+ }
+ gl.glTranslatef(-pivot.x(), -pivot.y(), -pivot.z());
+
+ gl.glBegin(GL.GL_TRIANGLES);
+ float a = 1.0f;
+ if (alpha != null) {
+ a = alpha.getCurrent(percent);
+ }
+ gl.glColor4f(1, 1, 1, a);
+ // Triangle 1
+ gl.glTexCoord2f(tex.getVec(0).x(), tex.getVec(0).y());
+ gl.glVertex3f (vts.getVec(0).x(), vts.getVec(0).y(), vts.getVec(0).z());
+ gl.glTexCoord2f(tex.getVec(1).x(), tex.getVec(1).y());
+ gl.glVertex3f (vts.getVec(1).x(), vts.getVec(1).y(), vts.getVec(1).z());
+ gl.glTexCoord2f(tex.getVec(3).x(), tex.getVec(3).y());
+ gl.glVertex3f (vts.getVec(3).x(), vts.getVec(3).y(), vts.getVec(3).z());
+ // Triangle 2
+ gl.glTexCoord2f(tex.getVec(3).x(), tex.getVec(3).y());
+ gl.glVertex3f (vts.getVec(3).x(), vts.getVec(3).y(), vts.getVec(3).z());
+ gl.glTexCoord2f(tex.getVec(1).x(), tex.getVec(1).y());
+ gl.glVertex3f (vts.getVec(1).x(), vts.getVec(1).y(), vts.getVec(1).z());
+ gl.glTexCoord2f(tex.getVec(2).x(), tex.getVec(2).y());
+ gl.glVertex3f (vts.getVec(2).x(), vts.getVec(2).y(), vts.getVec(2).z());
+ gl.glEnd();
+ }
+
+ /** Returns the rotation axis for this transition. By default this
+ axis is not set, so if an interpolator is specified for the
+ rotation angle the axis of rotation must be specified as
+ well. */
+ public Vec3f getRotationAxis() { return axis; }
+
+ /** Sets the rotation axis for this transition. By default this axis
+ is not set, so if an interpolator is specified for the rotation
+ angle the axis of rotation must be specified as well. */
+ public void setRotationAxis(Vec3f axis) { this.axis.set(axis); }
+
+ /** Returns the interpolator for the rotation angle for this
+ transition. By default this interpolator is null, meaning that
+ the transition does not perform any rotation. */
+ public InterpolatedFloat getRotationAngle() { return angle; }
+
+ /** Sets the interpolator for the rotation angle for this
+ transition. By default this interpolator is null, meaning that
+ the transition does not perform any rotation. */
+ public void setRotationAngle(InterpolatedFloat angle) { this.angle = angle; }
+
+ /** Returns the pivot point for this transition's rotation. By
+ default this is set to the origin, which corresponds to the
+ upper-left corner of the component being animated. */
+ public Vec3f getPivotPoint() { return pivot; }
+
+ /** Sets the pivot point for this transition's rotation. By default
+ this is set to the origin, which corresponds to the upper-left
+ corner of the component being animated. */
+ public void setPivotPoint(Vec3f point) { pivot.set(point); }
+
+ /** Returns the interpolator for this transition's translation. By
+ default the translation is a fixed vector from the origin to the
+ upper left corner of the component being animated; the vertices
+ are specified relative to that point in the +x and -y
+ directions. */
+ public InterpolatedVec3f getTranslation() { return translation; }
+
+ /** Sets the interpolator for this transition's translation. By
+ default the translation is a fixed vector from the origin to the
+ upper left corner of the component being animated; the vertices
+ are specified relative to that point in the +x and -y
+ directions. */
+ public void setTranslation(InterpolatedVec3f interp) { translation = interp; }
+
+ /** Returns the interpolator for the vertices of this transition's
+ component when drawn on screen. By default the vertices are
+ specified with the upper-left corner of the component at the
+ origin and the component extending in the +x (right) and -y
+ (down) directions; the units of the vertices correspond to the
+ on-screen size of the component in pixels. The component's
+ location is specified using the translation interpolator. */
+ public InterpolatedQuad3f getVertices() { return vertices; }
+
+ /** Sets the interpolator for the vertices of this transition's
+ component when drawn on screen. By default the vertices are
+ specified with the upper-left corner of the component at the
+ origin and the component extending in the +x (right) and -y
+ (down) directions; the units of the vertices correspond to the
+ on-screen size of the component in pixels. The component's
+ location is specified using the translation interpolator. */
+ public void setVertices(InterpolatedQuad3f interp) { vertices = interp; }
+
+ /** Returns the interpolator for the texture coordinates of this
+ transition's component when drawn on screen. By default these
+ are set so that at either the start or end point (depending on
+ whether this is an "in" or "out" transition) the texture
+ coordinates specify rendering of the component's entire
+ contents. */
+ public InterpolatedQuad2f getTexCoords() { return texcoords; }
+
+ /** Sets the interpolator for the texture coordinates of this
+ transition's component when drawn on screen. By default these
+ are set so that at either the start or end point (depending on
+ whether this is an "in" or "out" transition) the texture
+ coordinates specify rendering of the component's entire
+ contents. */
+ public void setTexCoords(InterpolatedQuad2f interp) { texcoords = interp; }
+
+ /** Returns the interpolator for this component's alpha value. */
+ public InterpolatedFloat getAlpha() { return alpha; }
+
+ /** Sets the interpolator for this component's alpha value. */
+ public void setAlpha(InterpolatedFloat interp) { alpha = interp; }
+}
diff --git a/src/demos/xtrans/XTBasicTransitionManager.java b/src/demos/xtrans/XTBasicTransitionManager.java
new file mode 100755
index 0000000..85a94fb
--- /dev/null
+++ b/src/demos/xtrans/XTBasicTransitionManager.java
@@ -0,0 +1,344 @@
+package demos.xtrans;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+import gleem.linalg.*;
+
+/** A basic transition manager supporting animated scrolling, rotating
+ and fading of components. */
+
+public class XTBasicTransitionManager implements XTTransitionManager {
+ /** Indicates the style of the transition (either no motion,
+ scrolling, or rotating). */
+ public static class Style {
+ private Style() {}
+ }
+
+ /** Indicates the component has no motion (scrolling or rotation) in
+ its animation. */
+ public static Style STYLE_NO_MOTION = new Style();
+
+ /** Indicates the component is to be scrolled in to or out of
+ place. */
+ public static Style STYLE_SCROLL = new Style();
+
+ /** Indicates the component is to be rotated in to or out of
+ place. */
+ public static Style STYLE_ROTATE = new Style();
+
+ /** Indicates the direction of the transition if it contains any
+ motion (either up, down, left, or right). */
+ public static class Direction {
+ private Direction() {}
+ }
+
+ /** Indicates the component's animation is from or toward the left,
+ depending on whether the transition is an "in" or "out"
+ transition. */
+ public static Direction DIR_LEFT = new Direction();
+
+ /** Indicates the component's animation is from or toward the right,
+ depending on whether the transition is an "in" or "out"
+ transition. */
+ public static Direction DIR_RIGHT = new Direction();
+
+ /** Indicates the component's animation is in the upward
+ direction. */
+ public static Direction DIR_UP = new Direction();
+
+ /** Indicates the component's animation is in the downward
+ direction. */
+ public static Direction DIR_DOWN = new Direction();
+
+ private Style nextTransitionStyle;
+ private Direction nextTransitionDirection;
+ private boolean nextTransitionFade;
+
+ private Random random;
+
+ /** Sets the next transition to be used by this transition manager
+ for either an "in" or an "out" transition. By default the
+ transition manager selects random transitions from those
+ available. */
+ public void setNextTransition(Style style,
+ Direction direction,
+ boolean fade) {
+ if (style == null) {
+ throw new IllegalArgumentException("Must supply a style");
+ }
+ nextTransitionStyle = style;
+ nextTransitionDirection = direction;
+ nextTransitionFade = fade;
+ }
+
+ /** Creates an XTBasicTransition for the given component. By default
+ this transition manager chooses a random transition from those
+ available if one is not specified via {@link #setNextTransition
+ setNextTransition}. */
+ public XTTransition createTransitionForComponent(Component c,
+ boolean isAddition,
+ Rectangle oglViewportOfDesktop,
+ Point viewportOffsetFromOrigin,
+ Rectangle2D oglTexCoordsOnBackBuffer) {
+ if (nextTransitionStyle == null) {
+ chooseRandomTransition();
+ }
+
+ // Figure out the final positions of everything
+ // Keep in mind that the Java2D origin is at the upper left and
+ // the OpenGL origin is at the lower left
+ Rectangle bounds = c.getBounds();
+ int x = bounds.x;
+ int y = bounds.y;
+ int w = bounds.width;
+ int h = bounds.height;
+ float tx = (float) oglTexCoordsOnBackBuffer.getX();
+ float ty = (float) oglTexCoordsOnBackBuffer.getY();
+ float tw = (float) oglTexCoordsOnBackBuffer.getWidth();
+ float th = (float) oglTexCoordsOnBackBuffer.getHeight();
+ float vx = oglViewportOfDesktop.x;
+ float vy = oglViewportOfDesktop.y;
+ float vw = oglViewportOfDesktop.width;
+ float vh = oglViewportOfDesktop.height;
+ Quad3f verts = new Quad3f(new Vec3f(0, 0, 0),
+ new Vec3f(0, -h, 0),
+ new Vec3f(w, -h, 0),
+ new Vec3f(w, 0, 0));
+ Quad2f texcoords = new Quad2f(new Vec2f(tx, ty + th),
+ new Vec2f(tx, ty),
+ new Vec2f(tx + tw, ty),
+ new Vec2f(tx + tw, ty + th));
+
+ XTBasicTransition trans = new XTBasicTransition();
+
+ Vec3f translation = new Vec3f(x - viewportOffsetFromOrigin.x,
+ vh - y - viewportOffsetFromOrigin.y,
+ 0);
+ InterpolatedVec3f transInterp = new InterpolatedVec3f();
+ transInterp.setStart(translation);
+ transInterp.setEnd(translation);
+
+ InterpolatedQuad3f quadInterp = new InterpolatedQuad3f();
+ quadInterp.setStart(verts);
+ quadInterp.setEnd(verts);
+
+ InterpolatedQuad2f texInterp = new InterpolatedQuad2f();
+ texInterp.setStart(texcoords);
+ texInterp.setEnd(texcoords);
+
+ trans.setTranslation(transInterp);
+ trans.setVertices(quadInterp);
+ trans.setTexCoords(texInterp);
+
+ // Now decide how we are going to handle this transition
+ Style transitionStyle = nextTransitionStyle;
+ Direction transitionDirection = nextTransitionDirection;
+ boolean fade = nextTransitionFade;
+ nextTransitionStyle = null;
+ nextTransitionDirection = null;
+ nextTransitionFade = false;
+
+ int[] vtIdxs = null;
+ int[] ttIdxs = null;
+ Vec3f rotAxis = null;
+ Vec3f pivot = null;
+ float startAngle = 0;
+ float endAngle = 0;
+
+ if (fade) {
+ InterpolatedFloat alpha = new InterpolatedFloat();
+ float start = (isAddition ? 0.0f : 1.0f);
+ float end = (isAddition ? 1.0f : 0.0f);
+ alpha.setStart(start);
+ alpha.setEnd(end);
+ trans.setAlpha(alpha);
+ }
+
+ if (transitionDirection != null) {
+ if (transitionStyle == STYLE_SCROLL) {
+ if (transitionDirection == DIR_LEFT) {
+ vtIdxs = new int[] { 3, 2, 2, 3 };
+ ttIdxs = new int[] { 0, 1, 1, 0 };
+ } else if (transitionDirection == DIR_RIGHT) {
+ vtIdxs = new int[] { 0, 1, 1, 0 };
+ ttIdxs = new int[] { 3, 2, 2, 3 };
+ } else if (transitionDirection == DIR_UP) {
+ vtIdxs = new int[] { 1, 1, 2, 2 };
+ ttIdxs = new int[] { 0, 0, 3, 3 };
+ } else {
+ // DIR_DOWN
+ vtIdxs = new int[] { 0, 0, 3, 3 };
+ ttIdxs = new int[] { 1, 1, 2, 2 };
+ }
+ } else if (transitionStyle == STYLE_ROTATE) {
+ if (transitionDirection == DIR_LEFT) {
+ rotAxis = new Vec3f(0, 1, 0);
+ pivot = new Vec3f();
+ startAngle = -90;
+ endAngle = 0;
+ } else if (transitionDirection == DIR_RIGHT) {
+ rotAxis = new Vec3f(0, 1, 0);
+ pivot = new Vec3f(w, 0, 0);
+ startAngle = 90;
+ endAngle = 0;
+ } else if (transitionDirection == DIR_UP) {
+ rotAxis = new Vec3f(1, 0, 0);
+ pivot = new Vec3f(0, -h, 0);
+ startAngle = 90;
+ endAngle = 0;
+ } else {
+ // DIR_DOWN
+ rotAxis = new Vec3f(1, 0, 0);
+ pivot = new Vec3f();
+ startAngle = -90;
+ endAngle = 0;
+ }
+ }
+ }
+
+
+ /*
+ switch (transitionType) {
+ case FADE:
+ {
+ InterpolatedFloat alpha = new InterpolatedFloat();
+ float start = (isAddition ? 0.0f : 1.0f);
+ float end = (isAddition ? 1.0f : 0.0f);
+ alpha.setStart(start);
+ alpha.setEnd(end);
+ trans.setAlpha(alpha);
+ break;
+ }
+ case SCROLL_LEFT:
+ {
+ vtIdxs = new int[] { 3, 2, 2, 3 };
+ ttIdxs = new int[] { 0, 1, 1, 0 };
+ break;
+ }
+ case SCROLL_RIGHT:
+ {
+ vtIdxs = new int[] { 0, 1, 1, 0 };
+ ttIdxs = new int[] { 3, 2, 2, 3 };
+ break;
+ }
+ case SCROLL_UP:
+ {
+ vtIdxs = new int[] { 1, 1, 2, 2 };
+ ttIdxs = new int[] { 0, 0, 3, 3 };
+ break;
+ }
+ case SCROLL_DOWN:
+ {
+ vtIdxs = new int[] { 0, 0, 3, 3 };
+ ttIdxs = new int[] { 1, 1, 2, 2 };
+ break;
+ }
+ case ROTATE_LEFT:
+ {
+ rotAxis = new Vec3f(0, 1, 0);
+ pivot = new Vec3f();
+ startAngle = -90;
+ endAngle = 0;
+ break;
+ }
+ case ROTATE_RIGHT:
+ {
+ rotAxis = new Vec3f(0, 1, 0);
+ // pivot = translation.plus(new Vec3f(w, 0, 0));
+ pivot = new Vec3f(w, 0, 0);
+ startAngle = 90;
+ endAngle = 0;
+ break;
+ }
+ case ROTATE_UP:
+ {
+ rotAxis = new Vec3f(1, 0, 0);
+ // pivot = translation.plus(new Vec3f(0, -h, 0));
+ pivot = new Vec3f(0, -h, 0);
+ startAngle = 90;
+ endAngle = 0;
+ break;
+ }
+ case ROTATE_DOWN:
+ {
+ rotAxis = new Vec3f(1, 0, 0);
+ pivot = new Vec3f();
+ startAngle = -90;
+ endAngle = 0;
+ break;
+ }
+ }
+
+ */
+
+ if (vtIdxs != null) {
+ if (isAddition) {
+ quadInterp.setStart(new Quad3f(verts.getVec(vtIdxs[0]),
+ verts.getVec(vtIdxs[1]),
+ verts.getVec(vtIdxs[2]),
+ verts.getVec(vtIdxs[3])));
+ texInterp.setStart(new Quad2f(texcoords.getVec(ttIdxs[0]),
+ texcoords.getVec(ttIdxs[1]),
+ texcoords.getVec(ttIdxs[2]),
+ texcoords.getVec(ttIdxs[3])));
+ } else {
+ // Note: swapping the vertex and texture indices happens to
+ // have the correct effect
+ int[] tmp = vtIdxs;
+ vtIdxs = ttIdxs;
+ ttIdxs = tmp;
+
+ quadInterp.setEnd(new Quad3f(verts.getVec(vtIdxs[0]),
+ verts.getVec(vtIdxs[1]),
+ verts.getVec(vtIdxs[2]),
+ verts.getVec(vtIdxs[3])));
+ texInterp.setEnd(new Quad2f(texcoords.getVec(ttIdxs[0]),
+ texcoords.getVec(ttIdxs[1]),
+ texcoords.getVec(ttIdxs[2]),
+ texcoords.getVec(ttIdxs[3])));
+ }
+ } else if (rotAxis != null) {
+ if (!isAddition) {
+ float tmp = endAngle;
+ endAngle = -startAngle;
+ startAngle = tmp;
+ }
+
+ trans.setPivotPoint(pivot);
+ trans.setRotationAxis(rotAxis);
+ InterpolatedFloat rotInterp = new InterpolatedFloat();
+ rotInterp.setStart(startAngle);
+ rotInterp.setEnd(endAngle);
+ trans.setRotationAngle(rotInterp);
+ }
+
+ return trans;
+ }
+
+ /** Chooses a random transition from those available. */
+ protected void chooseRandomTransition() {
+ if (random == null) {
+ random = new Random();
+ }
+ nextTransitionFade = random.nextBoolean();
+ nextTransitionStyle = null;
+ do {
+ int style = random.nextInt(3);
+ switch (style) {
+ // Make no-motion transitions always use fades for effect
+ // without biasing transitions toward no-motion transitions
+ case 0: if (nextTransitionFade) nextTransitionStyle = STYLE_NO_MOTION; break;
+ case 1: nextTransitionStyle = STYLE_SCROLL; break;
+ default: nextTransitionStyle = STYLE_ROTATE; break;
+ }
+ } while (nextTransitionStyle == null);
+ int dir = random.nextInt(4);
+ switch (dir) {
+ case 0: nextTransitionDirection = DIR_LEFT; break;
+ case 1: nextTransitionDirection = DIR_RIGHT; break;
+ case 2: nextTransitionDirection = DIR_UP; break;
+ default: nextTransitionDirection = DIR_DOWN; break;
+ }
+ }
+}
diff --git a/src/demos/xtrans/XTDesktopManager.java b/src/demos/xtrans/XTDesktopManager.java
new file mode 100755
index 0000000..4b911ee
--- /dev/null
+++ b/src/demos/xtrans/XTDesktopManager.java
@@ -0,0 +1,166 @@
+package demos.xtrans;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+import java.beans.*;
+import java.nio.*;
+import java.util.*;
+import javax.swing.*;
+import javax.media.opengl.*;
+import javax.media.opengl.glu.*;
+import com.sun.opengl.impl.*;
+
+import com.sun.opengl.impl.windows.*;
+
+/** A desktop manager implementation supporting accelerated
+ transitions of the components on the desktop via OpenGL. This
+ class does not need to be instantiated by end users; it is
+ installed automatically when an XTDesktopPane is constructed. */
+public class XTDesktopManager extends OffscreenDesktopManager {
+ private GLContext j2dContext;
+ private Object j2dContextSurfaceIdentifier;
+ private int oglTextureId;
+ private int prevBackBufferWidth;
+ private int prevBackBufferHeight;
+
+ private int textureTarget = GL.GL_TEXTURE_2D;
+
+ /** Returns the OpenGL texture object ID associated with the
+ off-screen back buffer for all of the components on the
+ desktop. */
+ public int getOpenGLTextureObject() {
+ return oglTextureId;
+ }
+
+ /** Returns a rectangle specifying the OpenGL texture coordinates of
+ the passed component in the texture object. The x and y
+ coordinates of the returned rectangle specify the lower left
+ corner of the component's image. */
+ public Rectangle2D getOpenGLTextureCoords(Component c) {
+ Rectangle rect = getBoundsOnBackBuffer(c);
+ if (rect == null) {
+ throw new RuntimeException("Unknown component " + c);
+ }
+ double offscreenWidth = getOffscreenBackBufferWidth();
+ double offscreenHeight = getOffscreenBackBufferHeight();
+ return new Rectangle2D.Double(rect.x / offscreenWidth,
+ (offscreenHeight - rect.y - rect.height) / offscreenHeight,
+ rect.width / offscreenWidth,
+ rect.height / offscreenHeight);
+ }
+
+ /** Updates the off-screen buffer of this desktop manager and makes
+ the rendering results available to OpenGL in the form of a
+ texture object. */
+ public void updateOffscreenBuffer(OffscreenDesktopPane parent) {
+ boolean needsCopy = needsCopyBack();
+ boolean hadPrevBackBuffer = false;
+ super.updateOffscreenBuffer(parent);
+ Image img = getOffscreenBackBuffer();
+ final boolean mustResizeOGLTexture = ((oglTextureId == 0) ||
+ (img == null) ||
+ (prevBackBufferWidth != img.getWidth(null)) ||
+ (prevBackBufferHeight != img.getHeight(null)));
+ if (needsCopy) {
+ final Graphics g = getOffscreenGraphics();
+ // Capture off-screen buffer contents into OpenGL texture
+ 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();
+
+ if (oglTextureId == 0) {
+ // Set up and initialize texture
+ int[] tmp = new int[1];
+
+ gl.glGenTextures(1, tmp, 0);
+ oglTextureId = tmp[0];
+ if (oglTextureId == 0) {
+ throw new RuntimeException("Error generating OpenGL back buffer texture");
+ }
+ assert mustResizeOGLTexture : "Must know we need to resize";
+ }
+
+ gl.glBindTexture(textureTarget, oglTextureId);
+
+ int offscreenWidth = getOffscreenBackBufferWidth();
+ int offscreenHeight = getOffscreenBackBufferHeight();
+
+ if (mustResizeOGLTexture) {
+ prevBackBufferWidth = offscreenWidth;
+ prevBackBufferHeight = offscreenHeight;
+
+ gl.glTexImage2D(textureTarget,
+ 0,
+ GL.GL_RGBA8,
+ offscreenWidth,
+ offscreenHeight,
+ 0,
+ GL.GL_RGBA,
+ GL.GL_UNSIGNED_BYTE,
+ null);
+ }
+
+ // Copy texture from offscreen buffer
+ // NOTE: assumes read buffer is set up
+ // FIXME: could be more efficient by copying only bounding rectangle
+
+ gl.glPixelStorei(GL.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE);
+ gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, GL.GL_FALSE);
+ gl.glPixelStorei(GL.GL_UNPACK_LSB_FIRST, GL.GL_FALSE);
+ gl.glPixelStorei(GL.GL_PACK_LSB_FIRST, GL.GL_FALSE);
+ gl.glPixelStorei(GL.GL_UNPACK_ROW_LENGTH, 0);
+ gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, 0);
+ gl.glPixelStorei(GL.GL_UNPACK_SKIP_ROWS, 0);
+ gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, 0);
+ gl.glPixelStorei(GL.GL_UNPACK_SKIP_PIXELS, 0);
+ gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, 0);
+ gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
+ gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1);
+ gl.glPixelTransferf(GL.GL_RED_SCALE, 1);
+ gl.glPixelTransferf(GL.GL_GREEN_SCALE, 1);
+ gl.glPixelTransferf(GL.GL_BLUE_SCALE, 1);
+ gl.glPixelTransferf(GL.GL_ALPHA_SCALE, 1);
+ gl.glPixelTransferf(GL.GL_RED_BIAS, 0);
+ gl.glPixelTransferf(GL.GL_GREEN_BIAS, 0);
+ gl.glPixelTransferf(GL.GL_BLUE_BIAS, 0);
+ gl.glPixelTransferf(GL.GL_ALPHA_BIAS, 0);
+
+ // long start = System.currentTimeMillis();
+ gl.glCopyTexSubImage2D(textureTarget,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ offscreenWidth,
+ offscreenHeight);
+ // long end = System.currentTimeMillis();
+ // System.err.println("glCopyTexSubImage2D " + offscreenWidth + "x" + offscreenHeight + " took " + (end - start) + " ms");
+
+ } finally {
+ j2dContext.release();
+ }
+ }
+ });
+ }
+ }
+
+ // Ideally we would force a repaint only of the 2D bounds of the 3D
+ // component projected onto the desktop. However for expedience
+ // we'll currently just repaint the entire desktop to get correct
+ // results.
+ protected void repaintPortionOfDesktop(JDesktopPane desktop, Component comp) {
+ desktop.repaint();
+ }
+}
diff --git a/src/demos/xtrans/XTDesktopPane.java b/src/demos/xtrans/XTDesktopPane.java
new file mode 100755
index 0000000..8582e71
--- /dev/null
+++ b/src/demos/xtrans/XTDesktopPane.java
@@ -0,0 +1,446 @@
+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.*;
+
+import com.sun.opengl.impl.windows.*;
+
+/** A JDesktopPane subclass supporting Accelerated Transitions (XT) of
+ the components contained within. */
+
+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/*<TransitionInfo>*/ 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);
+ }
+}
diff --git a/src/demos/xtrans/XTTransition.java b/src/demos/xtrans/XTTransition.java
new file mode 100755
index 0000000..1c706fa
--- /dev/null
+++ b/src/demos/xtrans/XTTransition.java
@@ -0,0 +1,15 @@
+package demos.xtrans;
+
+import javax.media.opengl.*;
+
+/** Specifies the interface by which a transition is updated and drawn
+ by the XTDesktopPane. */
+
+public interface XTTransition {
+ /** Updates this transition's state to the given fraction in its
+ animation cycle (0.0 - 1.0). */
+ public void update(float fraction);
+
+ /** Draws this transition using the passed OpenGL object. */
+ public void draw(GL gl);
+}
diff --git a/src/demos/xtrans/XTTransitionManager.java b/src/demos/xtrans/XTTransitionManager.java
new file mode 100755
index 0000000..18e9da0
--- /dev/null
+++ b/src/demos/xtrans/XTTransitionManager.java
@@ -0,0 +1,45 @@
+package demos.xtrans;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+/** Specifies how the XTDesktopPane creates new transitions. */
+
+public interface XTTransitionManager {
+ /** Create a new transition for the given component.
+ <ul>
+
+ <li> The passed component's bounds indicate the location of the
+ component on the desktop. The (x,y) of the bounds correspond to
+ the upper left of the component; note that this differs from the
+ OpenGL coordinate system.
+
+ <li> The <code>isAddition</code> parameter indicates whether
+ the component is being added to or removed from the desktop.
+
+ <li> The <code>oglViewportOfDesktop</code> specifies the
+ rectangle corresponding to the entire desktop pane in the
+ default OpenGL coordinate system with the (x, y) origin of the
+ rectangle at the lower left. This rectangle should be used in
+ conjunction with the component's bounds and the
+ <code>viewportOffsetFromOrigin</code> to determine where to draw
+ the vertices for the component.
+
+ <li> The <code>viewportOffsetFromOrigin</code> specifies the
+ current OpenGL viewport's offset from the origin of the OpenGL
+ coordinate system. The XTDesktopPane re-centers the OpenGL
+ viewport around each component so any perspective effects appear
+ symmetric. This offset should be subtracted from the translation
+ of the component or its vertex locations.
+
+ <li> The <code>oglTexCoordsOnBackBuffer</code> specifies the
+ texture coordinates of the passed component on the back
+ buffer. The (x,y) of this rectangle specifies the lower-left
+ corner of the image corresponding to the component. </ul>
+ */
+ public XTTransition createTransitionForComponent(Component c,
+ boolean isAddition,
+ Rectangle oglViewportOfDesktop,
+ Point viewportOffsetFromOrigin,
+ Rectangle2D oglTexCoordsOnBackBuffer);
+}
diff --git a/src/gleem/linalg/Vec2f.java b/src/gleem/linalg/Vec2f.java
index 2e02701..4c02b61 100644
--- a/src/gleem/linalg/Vec2f.java
+++ b/src/gleem/linalg/Vec2f.java
@@ -59,6 +59,10 @@ public class Vec2f {
return new Vec2f(this);
}
+ public void set(Vec2f arg) {
+ set(arg.x, arg.y);
+ }
+
public void set(float x, float y) {
this.x = x;
this.y = y;