diff --git a/examples/src/main/java/org/piccolo2d/examples/pswing/PSwingExample1.java b/examples/src/main/java/org/piccolo2d/examples/pswing/PSwingExample1.java index d21918d..a26c0a4 100644 --- a/examples/src/main/java/org/piccolo2d/examples/pswing/PSwingExample1.java +++ b/examples/src/main/java/org/piccolo2d/examples/pswing/PSwingExample1.java @@ -28,16 +28,18 @@ */ package org.piccolo2d.examples.pswing; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import org.piccolo2d.PNode; +import org.piccolo2d.event.PZoomEventHandler; +import org.piccolo2d.extras.pswing.PComboBox; +import org.piccolo2d.extras.pswing.PSwing; +import org.piccolo2d.extras.pswing.PSwingCanvas; +import org.piccolo2d.nodes.PText; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JColorChooser; +import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -48,13 +50,11 @@ import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; - -import org.piccolo2d.PNode; -import org.piccolo2d.event.PZoomEventHandler; -import org.piccolo2d.extras.pswing.PComboBox; -import org.piccolo2d.extras.pswing.PSwing; -import org.piccolo2d.extras.pswing.PSwingCanvas; -import org.piccolo2d.nodes.PText; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; /** @@ -63,6 +63,14 @@ public class PSwingExample1 { public static void main(final String[] args) { + new PSwingExample1().run(); + } + + protected PSwing createPSwing(JComponent component) { + return new PSwing(component); + } + + protected void run() { final PSwingCanvas pCanvas = new PSwingCanvas(); final PText pText = new PText("PText"); pCanvas.getLayer().addChild(pText); @@ -87,13 +95,13 @@ System.out.println("TestZSwing.actionPerformed!!!!!!!!!!!!!!*********************"); } }); - final PSwing pSwing = new PSwing(jButton); + final PSwing pSwing = createPSwing(jButton); pCanvas.getLayer().addChild(pSwing); pSwing.repaint(); final JSpinner jSpinner = new JSpinner(); jSpinner.setPreferredSize(new Dimension(100, jSpinner.getPreferredSize().height)); - final PSwing pSpinner = new PSwing(jSpinner); + final PSwing pSpinner = createPSwing(jSpinner); pCanvas.getLayer().addChild(pSpinner); pSpinner.translate(0, 150); @@ -108,20 +116,20 @@ System.out.println("TestPSwing.JChekbox.stateChanged@" + System.currentTimeMillis()); } }); - final PSwing pCheckBox = new PSwing(jcb); + final PSwing pCheckBox = createPSwing(jcb); pCanvas.getLayer().addChild(pCheckBox); pCheckBox.translate(100, 0); // Growable JTextArea final JTextArea textArea = new JTextArea("This is a growable TextArea.\nTry it out!"); textArea.setBorder(new LineBorder(Color.blue, 3)); - PSwing swing = new PSwing(textArea); + PSwing swing = createPSwing(textArea); swing.translate(150, 150); pCanvas.getLayer().addChild(swing); // A Slider final JSlider slider = new JSlider(); - final PSwing pSlider = new PSwing(slider); + final PSwing pSlider = createPSwing(slider); pSlider.translate(200, 200); pCanvas.getLayer().addChild(pSlider); @@ -130,26 +138,26 @@ tree.setEditable(true); final JScrollPane p = new JScrollPane(tree); p.setPreferredSize(new Dimension(150, 150)); - final PSwing pTree = new PSwing(p); + final PSwing pTree = createPSwing(p); pCanvas.getLayer().addChild(pTree); pTree.translate(0, 250); // A JColorChooser - also demonstrates JTabbedPane final JColorChooser chooser = new JColorChooser(); - final PSwing pChooser = new PSwing(chooser); + final PSwing pChooser = createPSwing(chooser); pCanvas.getLayer().addChild(pChooser); pChooser.translate(100, 300); final JPanel myPanel = new JPanel(); myPanel.setBorder(BorderFactory.createTitledBorder("Titled Border")); myPanel.add(new JCheckBox("CheckBox")); - final PSwing panelSwing = new PSwing(myPanel); + final PSwing panelSwing = createPSwing(myPanel); pCanvas.getLayer().addChild(panelSwing); panelSwing.translate(400, 50); // A Slider final JSlider slider2 = new JSlider(); - final PSwing pSlider2 = new PSwing(slider2); + final PSwing pSlider2 = createPSwing(slider2); pSlider2.translate(200, 200); final PNode root = new PNode(); root.addChild(pSlider2); @@ -164,7 +172,7 @@ final String[] listItems = { "Summer Teeth", "Mermaid Avenue", "Being There", "A.M." }; final PComboBox box = new PComboBox(listItems); comboPanel.add(box); - swing = new PSwing(comboPanel); + swing = createPSwing(comboPanel); swing.translate(200, 230); pCanvas.getLayer().addChild(swing); box.setEnvironment(swing, pCanvas);// has to be done manually at present diff --git a/examples/src/main/java/org/piccolo2d/examples/pswing/PSwingExample4.java b/examples/src/main/java/org/piccolo2d/examples/pswing/PSwingExample4.java new file mode 100644 index 0000000..ec849d7 --- /dev/null +++ b/examples/src/main/java/org/piccolo2d/examples/pswing/PSwingExample4.java @@ -0,0 +1,23 @@ +package org.piccolo2d.examples.pswing; + +import org.piccolo2d.extras.pswing.PSwing; + +import javax.swing.JComponent; + +/** + * Extends {@link PSwingExample1} but uses {@link org.piccolo2d.extras.pswing.PSwing#setUseBufferedPainting(boolean)} + * for {@link org.piccolo2d.extras.pswing.PSwing}s. + */ +public class PSwingExample4 extends PSwingExample1 { + + public static void main(final String[] args) { + new PSwingExample4().run(); + } + + protected PSwing createPSwing(JComponent component) { + PSwing pSwing = new PSwing(component); + pSwing.setUseBufferedPainting(true); + return pSwing; + } + +} diff --git a/extras/src/main/java/org/piccolo2d/extras/pswing/PSwing.java b/extras/src/main/java/org/piccolo2d/extras/pswing/PSwing.java index 76368ba..41bbe9b 100644 --- a/extras/src/main/java/org/piccolo2d/extras/pswing/PSwing.java +++ b/extras/src/main/java/org/piccolo2d/extras/pswing/PSwing.java @@ -28,6 +28,14 @@ */ package org.piccolo2d.extras.pswing; +import org.piccolo2d.PCamera; +import org.piccolo2d.PLayer; +import org.piccolo2d.PNode; +import org.piccolo2d.util.PBounds; +import org.piccolo2d.util.PPaintContext; + +import javax.swing.JComponent; +import javax.swing.RepaintManager; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; @@ -40,6 +48,8 @@ import java.awt.event.ContainerAdapter; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; @@ -48,15 +58,6 @@ import java.util.ArrayList; import java.util.Arrays; -import javax.swing.JComponent; -import javax.swing.RepaintManager; - -import org.piccolo2d.PCamera; -import org.piccolo2d.PLayer; -import org.piccolo2d.PNode; -import org.piccolo2d.util.PBounds; -import org.piccolo2d.util.PPaintContext; - /* This message was sent to Sun on August 27, 1999 @@ -203,6 +204,11 @@ /** Temporary repaint bounds. */ private static final PBounds TEMP_REPAINT_BOUNDS2 = new PBounds(); + /** For use when buffered painting is enabled. */ + private static final Color BUFFER_BACKGROUND_COLOR = new Color(0, 0, 0, 0); + + private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); + /** Default Greek threshold, 0.3d. */ private static final double DEFAULT_GREEK_THRESHOLD = 0.3d; @@ -212,6 +218,15 @@ /** Swing component for this Swing node. */ private JComponent component = null; + /** + * Whether or not to use buffered painting. + * @see {@link #paint(java.awt.Graphics2D)} + */ + private boolean useBufferedPainting = false; + + /** Used when buffered painting is enabled. */ + private BufferedImage buffer; + /** Minimum font size. */ private double minFontSize = Double.MAX_VALUE; @@ -326,6 +341,22 @@ } /** + * If true {@link PSwing} will paint the {@link JComponent} to a buffer with no graphics + * transformations applied and then paint the buffer to the target transformed + * graphics context. On some platforms (such as Mac OS X) rendering {@link JComponent}s to + * a transformed context is slow. Enabling buffered painting gives a significant performance + * boost on these platforms; however, at the expense of a lower-quality drawing result at larger + * scales. + */ + public void setUseBufferedPainting(final boolean useBufferedPainting) { + this.useBufferedPainting = useBufferedPainting; + } + + public boolean isUseBufferedPainting() { + return this.useBufferedPainting; + } + + /** * Ensures the bounds of the underlying component are accurate, and sets the * bounds of this PNode. */ @@ -471,14 +502,49 @@ final RenderingHints oldHints = g2.getRenderingHints(); - g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); - component.paint(g2); + if (useBufferedPainting) { + Graphics2D bufferedGraphics = getBufferedGraphics(g2); + component.paint(bufferedGraphics); + g2.drawRenderedImage(buffer, IDENTITY_TRANSFORM); + } else { + g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); + component.paint(g2); + } g2.setRenderingHints(oldHints); manager.unlockRepaint(component); } + private Graphics2D getBufferedGraphics(Graphics2D source) { + final Graphics2D bufferedGraphics; + if(!isBufferValid()) { + // Get the graphics context associated with a new buffered image. + // Use TYPE_INT_ARGB_PRE so that transparent components look good on Windows. + buffer = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); + bufferedGraphics = buffer.createGraphics(); + } + else { + // Use the graphics context associated with the existing buffered image + bufferedGraphics = buffer.createGraphics(); + // Clear the buffered image to prevent artifacts on Macintosh + bufferedGraphics.setBackground(BUFFER_BACKGROUND_COLOR); + bufferedGraphics.clearRect(0, 0, component.getWidth(), component.getHeight()); + } + bufferedGraphics.setRenderingHints(source.getRenderingHints()); + return bufferedGraphics; + } + + /** + * Tells whether the buffer for the image of the Swing components + * is currently valid. + * + * @return true if the buffer is currently valid + */ + private boolean isBufferValid() { + return !(buffer == null || buffer.getWidth() != component.getWidth() || buffer.getHeight() != component.getHeight()); + } + /** * Repaints the specified portion of this visual component. Note that the * input parameter may be modified as a result of this call.