diff options
-rwxr-xr-x | src/demos/jrefract/JRefract.java | 15 | ||||
-rwxr-xr-x | src/demos/xtrans/InterpolatedFloat.java | 29 | ||||
-rwxr-xr-x | src/demos/xtrans/InterpolatedQuad2f.java | 45 | ||||
-rwxr-xr-x | src/demos/xtrans/InterpolatedQuad3f.java | 45 | ||||
-rwxr-xr-x | src/demos/xtrans/InterpolatedVec3f.java | 37 | ||||
-rwxr-xr-x | src/demos/xtrans/Main.java | 229 | ||||
-rwxr-xr-x | src/demos/xtrans/OffscreenComponentWrapper.java | 122 | ||||
-rwxr-xr-x | src/demos/xtrans/OffscreenDesktopManager.java | 784 | ||||
-rwxr-xr-x | src/demos/xtrans/OffscreenDesktopPane.java | 189 | ||||
-rwxr-xr-x | src/demos/xtrans/Quad2f.java | 74 | ||||
-rwxr-xr-x | src/demos/xtrans/Quad3f.java | 74 | ||||
-rwxr-xr-x | src/demos/xtrans/XTBasicTransition.java | 151 | ||||
-rwxr-xr-x | src/demos/xtrans/XTBasicTransitionManager.java | 344 | ||||
-rwxr-xr-x | src/demos/xtrans/XTDesktopManager.java | 166 | ||||
-rwxr-xr-x | src/demos/xtrans/XTDesktopPane.java | 446 | ||||
-rwxr-xr-x | src/demos/xtrans/XTTransition.java | 15 | ||||
-rwxr-xr-x | src/demos/xtrans/XTTransitionManager.java | 45 | ||||
-rw-r--r-- | src/gleem/linalg/Vec2f.java | 4 |
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; |