/* * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Sun Microsystems nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package demo.intro; import com.sun.scenario.scenegraph.JSGPanel; import com.sun.scenario.scenegraph.SGGroup; import com.sun.scenario.scenegraph.SGImage; import com.sun.scenario.scenegraph.SGNode; import com.sun.scenario.scenegraph.SGShape; import com.sun.scenario.scenegraph.SGText; import com.sun.scenario.scenegraph.SGTransform; import com.sun.scenario.scenegraph.SGTransform; import com.sun.scenario.scenegraph.SGTransform; import com.sun.scenario.scenegraph.SGTransform; import com.sun.scenario.scenegraph.event.SGMouseAdapter; import com.sun.scenario.scenegraph.event.SGMouseListener; import demo.util.SceneTreeView; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Arc2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JTabbedPane; import javax.swing.KeyStroke; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.jdesktop.application.SingleFrameApplication; import static java.lang.Math.cos; import static java.lang.Math.sin; import static java.lang.Math.sqrt; /** * */ public class Intro extends SingleFrameApplication { private static Logger logger = Logger.getLogger(Intro.class.getName()); private JTabbedPane tabbedPane = null; /* Create a black filled antialiased SGShape. */ private SGShape createFill(Shape shape2D) { SGShape shape = new SGShape(); shape.setShape(shape2D); shape.setFillPaint(Color.black); shape.setMode(SGShape.Mode.FILL); shape.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON); return shape; } /* Create a black stroked antialiased SGShape with the specified * stroke width and round (line) end caps. */ private SGShape createStroke(Shape shape2D, float width) { SGShape shape = new SGShape(); shape.setShape(shape2D); shape.setDrawStroke(new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)); shape.setDrawPaint(Color.black); shape.setMode(SGShape.Mode.STROKE); shape.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON); return shape; } /* Returns a "smiley face" scene graph based on the SVG code here: * http://en.wikipedia.org/wiki/Image:Smiley.svg */ private SGNode createSmiley() { SGShape circle = new SGShape(); circle.setShape(new Ellipse2D.Float(-20f, -20f, 40f, 40f)); circle.setFillPaint(Color.YELLOW); circle.setMode(SGShape.Mode.STROKE_FILL); circle.setDrawStroke(new BasicStroke(0.15f)); circle.setDrawPaint(Color.black); circle.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON); SGShape leftEye = createFill(new Ellipse2D.Float(-8.5f, -11f, 5f, 8f)); SGShape leftDimple = createStroke(createArc(4f, P(-14.6f, 5.7f), P(-10.6f, 2.7f)), 0.5f); SGShape rightEye = createFill(new Ellipse2D.Float(3.5f, -11f, 5f, 8f)); SGShape rightDimple = createStroke(createArc(4f, P(10.6f, 2.7f), P(14.6f, 5.7f)), 0.5f); SGShape smile1 = createStroke(createArc(13.5f, P(-12f, 5f), P(12, 5f)), 0.75f); SGShape smile2 = createStroke(createArc(13f, P(-12f, 5f), P(12, 5f)), 0.75f); SGGroup group = new SGGroup(); group.add(circle); group.add(leftEye); group.add(leftDimple); group.add(rightEye); group.add(rightDimple); group.add(smile1); group.add(smile2); return group; } /* Creates a JSGPanel that contains a scene with a smiley face. */ private JComponent createSmileyPanel() { JSGPanel panel = new ScalingSGPanel(); panel.setBorder(new EmptyBorder(4, 4, 4, 4)); panel.setBackground(Color.white); panel.setScene(createSmiley()); panel.setPreferredSize(new Dimension(640, 480)); return panel; } /* Creates a JSGPanel with "Hello World" followed by an image of the earth. * Dragging the earth rotates the scene. */ private JComponent createHelloWorldMousePanel() { SGNode earth = createEarth(); SGNode row = createRow(createHelloWorldText(), earth); SGTransform scene = createRotation(createBorder(row)); JSGPanel panel = new ScalingSGPanel(); final SGTransform.Rotate rotate = (SGTransform.Rotate)scene.getChild(); SGMouseListener changeRotation = new SGMouseAdapter() { @Override public void mouseDragged(MouseEvent e, SGNode node) { Rectangle2D r = e.getComponent().getBounds(); double x = e.getX() - r.getCenterX(); double y = e.getY() - r.getCenterY(); rotate.setRotation(Math.atan2(y, x)); } }; earth.addMouseListener(changeRotation); panel.setBackground(Color.BLACK); panel.setScene(scene); panel.setPreferredSize(new Dimension(640, 480)); return panel; } /* Arrange the specified nodes in a row. The nodes are centered vertically * and separated by a gap horizontally. */ private SGNode createRow(SGNode... nodes) { double rowHeight = 0.0; for(SGNode node : nodes) { rowHeight = Math.max(rowHeight, node.getBounds().getHeight()); } SGGroup row = new SGGroup(); double x = 0.0; double gap = 8.0; for(SGNode node : nodes) { Rectangle2D nodeR = node.getBounds(); double y = (rowHeight - nodeR.getHeight()) / 2.0; double dx = x - nodeR.getX(); double dy = y - nodeR.getY(); SGTransform xlate = SGTransform.createTranslation(dx, dy, node); row.add(xlate); x += nodeR.getWidth() + gap; } return row; } /* Return an SGImage node with a picture of the earth. */ private SGNode createEarth() { BufferedImage image = null; String imagePath = "resources/earth.png"; try { URL url = getClass().getResource(imagePath); if (url != null) { image = ImageIO.read(url); } } catch (IOException e) { } if (image == null) { logger.warning("can't load " + imagePath); } SGImage sgImage = new SGImage(); sgImage.setImage(image); return sgImage; } /* Creates a JSGPanel with "Hello World" followed by an image of the earth. */ private JComponent createHelloWorldImagePanel() { SGNode row = createRow(createHelloWorldText(), createEarth()); SGNode scene = createBorder(row); JSGPanel panel = new ScalingSGPanel(); panel.setBackground(Color.BLACK); panel.setScene(scene); panel.setPreferredSize(new Dimension(640, 480)); return panel; } /* Creates a chain of three SGTransform nodes above * the specified node, that rotate it about its center. * The SGTransform.Rotate node is the child of the node * this method returns. */ private SGTransform createRotation(SGNode node) { Rectangle2D nodeR = node.getBounds(); double cx = nodeR.getCenterX(); double cy = nodeR.getCenterY(); SGTransform toOriginT = SGTransform.createTranslation(-cx, -cy, node); SGTransform.Rotate rotateT = SGTransform.createRotation(0.0, toOriginT); SGTransform fromOriginT = SGTransform.createTranslation(cx, cy, rotateT); return fromOriginT; } /* Creates a JPanel with a slider above a JSGPanel. The slider * rotates the scene. */ private JComponent createRotatingHelloWorldPanel() { SGTransform scene = createRotation(createBorder(createHelloWorldText())); final SGTransform.Rotate rotate = (SGTransform.Rotate)scene.getChild(); JSGPanel scenePanel = new ScalingSGPanel(); scenePanel.setBackground(Color.BLACK); scenePanel.setScene(scene); scenePanel.setPreferredSize(new Dimension(640, 480)); JPanel controlPanel = new JPanel(); final JSlider slider = new JSlider(0, 100, 0); ChangeListener changeRotation = new ChangeListener() { public void stateChanged(ChangeEvent e) { double a = slider.getValue() * (Math.PI / 50.0); rotate.setRotation(a); } }; slider.addChangeListener(changeRotation); controlPanel.add(new JLabel("Rotation: ")); controlPanel.add(slider); JPanel panel = new JPanel(new BorderLayout()); panel.add(controlPanel, BorderLayout.NORTH); panel.add(scenePanel, BorderLayout.CENTER); return panel; } /* Returns an SGGroup that contains the node on top of a * (borderWidth) bigger yellow-bordered red round rectangle. */ private SGNode createBorder(SGNode node) { Rectangle2D nodeR = node.getBounds(); double borderWidth = 10; double x = nodeR.getX() - borderWidth; double y = nodeR.getY() - borderWidth; double w = nodeR.getWidth() + (2 * borderWidth); double h = nodeR.getHeight() + (2 * borderWidth); double a = 1.5 * borderWidth; SGShape border = new SGShape(); border.setShape(new RoundRectangle2D.Double(x, y, w, h, a, a)); border.setFillPaint(new Color(0x660000)); border.setDrawPaint(new Color(0xFFFF33)); border.setDrawStroke(new BasicStroke((float)(borderWidth / 2))); border.setMode(SGShape.Mode.STROKE_FILL); border.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON); SGGroup borderedNode = new SGGroup(); borderedNode.add(border); borderedNode.add(node); return borderedNode; } /* Creates a JSGPanel that contains a scene with a SGGroup * that contains "Hello World" and a garish border. */ private JComponent createBorderedHelloWorldPanel() { JSGPanel panel = new ScalingSGPanel(); panel.setBackground(Color.BLACK); panel.setScene(createBorder(createHelloWorldText())); panel.setPreferredSize(new Dimension(640, 480)); return panel; } /* Returns a "Hello World" SGText node. Big font, white text. */ private SGText createHelloWorldText() { SGText text = new SGText(); text.setText("Hello World"); text.setFont(new Font("SansSerif", Font.PLAIN, 36)); text.setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON); text.setFillPaint(Color.WHITE); return text; } /* Creates a JSGPanel that contains a scene with just one SGText * scene graph node. */ private JComponent createHelloWorldPanel() { JSGPanel panel = new ScalingSGPanel(); panel.setBackground(Color.BLACK); panel.setScene(createHelloWorldText()); panel.setPreferredSize(new Dimension(640, 480)); return panel; } private JComponent createMainPanel() { tabbedPane = new JTabbedPane(); tabbedPane.setName("introTabbedPane"); JSGPanel panel = new ScalingSGPanel(); panel.setBorder(new EmptyBorder(4, 4, 4, 4)); panel.setBackground(Color.white); panel.setScene(createSmiley()); tabbedPane.add("Hello", createHelloWorldPanel()); tabbedPane.add("[Hello]", createBorderedHelloWorldPanel()); tabbedPane.add("Rotate", createRotatingHelloWorldPanel()); tabbedPane.add("Image", createHelloWorldImagePanel()); tabbedPane.add("Mouse", createHelloWorldMousePanel()); tabbedPane.add("Smiley", createSmileyPanel()); return tabbedPane; } @Override protected void startup() { show(createMainPanel()); } /* * A version of JSGPanel that centers and scales its scene * to fill the panel. The scene's aspect ratio is preserved. * We also map control-T to a interactive popup that shows the * structure of the scene graph (SceneTreeView.installHotkeyDisplay). */ private static class ScalingSGPanel extends JSGPanel { private SGNode root = null; private SGTransform.Translate originT = null; private SGTransform.Scale scaleT = null; private SGTransform.Translate centerT = null; /* We request focus when mouse events (that aren't explicitly * consumed) land on the JSGPanel. This enables the control-T * hot key which brings up the scene graph structure viewer. */ ScalingSGPanel() { MouseListener requestFocus = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { e.getComponent().requestFocusInWindow(); } }; addMouseListener(requestFocus); } /* Makes the original scene a child of the 3 transform nodes. */ @Override public void setScene(SGNode scene) { root = scene; SceneTreeView.installHotkeyDisplay(this, root, 0); originT = SGTransform.createTranslation(0f, 0f, scene); scaleT = SGTransform.createScale(1f, 1f, originT); centerT = SGTransform.createTranslation(0f, 0f, scaleT); super.setScene(centerT); } /* configure the transforms so that originT translates the * original scene (root) so that its upper left hand corner * is the origin, scaleT scales the normalized bounds of the * scene to fill the panel and preserve the original scene's * aspect ratio, and centerT centers the result. */ @Override public void doLayout() { if (root != null) { Rectangle2D sceneR = root.getBounds(); originT.setTranslateX(-sceneR.getX()); originT.setTranslateY(-sceneR.getY()); Insets insets = getInsets(); double iw = insets.left + insets.right; double ih = insets.top + insets.bottom; double size = Math.min(getWidth() - iw, getHeight() - ih); double scale = size / Math.max(sceneR.getWidth(), sceneR.getHeight()); scaleT.setScale(scale, scale); double ssw = scale * sceneR.getWidth(); double ssh = scale * sceneR.getHeight(); centerT.setTranslateX(insets.left + ((getWidth() - ssw - iw) / 2.0)); centerT.setTranslateY(insets.top + ((getHeight() - ssh - ih) / 2.0)); } } } /* Although it would be more consistent to have called this * "createPoint", P is shorter. */ private Point2D.Float P(float x, float y) { return new Point2D.Float(x, y); } /* Given two points on a circle, and the circle's radius, create * an Arc2D that represents the arc from p1 to p2. See #arcCenter. */ private Shape createArc(float r, Point2D p1, Point2D p2) { Point2D.Float c = arcCenter(r, p1, p2); double x1 = p1.getX() - c.getX(); // p1 translated to the center double y1 = p1.getY() - c.getY(); double a1 = 360.0 - Math.toDegrees(Math.atan2(y1, x1)); double x2 = p2.getX() - c.getX(); // p2 translated to the center double y2 = p2.getY() - c.getY(); double a2 = 360.0 - Math.toDegrees(Math.atan2(y2, x2)); float x = (float)(c.getX() - r); // origin of the arc's bounding float y = (float)(c.getY() - r); // rectangle float s = 2f * r; // diameter of the circle that contains the arc float a = (float)a1; // arc start angle float e = (float)(a2 - a1); // arc angle's "extent" return new Arc2D.Float(x, y, s, s, a, e, Arc2D.OPEN); } /* Given two points on a circle, and the circle's radius, compute * the center of the circle. SVG circular arcs are specified this way, * see http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands */ private Point2D.Float arcCenter(float r, Point2D p1, Point2D p2) { // d is the distance between p1,p2 and d2 is the distance // to the midpoint of that line. double d = p1.distance(p2); double d2 = d / 2.0; // max possible value for d2 is r, when p1,p2 bisect the circle if (d2 > r) { throw new IllegalArgumentException("invalid parameters"); } // dc is the distance from center of p1,p2 to the circle's // center, since the circle's radius makes a right trangle with // dc and d2. double dc = sqrt((r*r) - (d2*d2)); // x3,y3 are the coordinates of the midpoint of p1,p2 and // ux,uy is a unit vector perpindicular to p1,p2 double x3 = (p1.getX() + p2.getX()) / 2.0; double y3 = (p1.getY() + p2.getY()) / 2.0; double ux = (p2.getY() - p1.getY()) / d; double uy = (p1.getX() - p2.getX()) / d; // The circle's center is just x3,y3 plus the ux,uy // scaled by dc (the distance from x3,y3 to the center) double cx = x3 + (ux * dc); double cy = y3 + (uy * dc); return new Point2D.Float((float)cx, (float)cy); } public static void main(String[] args) { launch(Intro.class, args); } }