/* * 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.jphone; import com.sun.scenario.animation.Animation; import com.sun.scenario.animation.BeanProperty; import java.awt.Color; import java.awt.Cursor; import java.awt.Font; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.net.URL; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.SwingUtilities; import com.sun.scenario.animation.Clip; import com.sun.scenario.animation.Composer; import com.sun.scenario.animation.KeyFrames; import com.sun.scenario.animation.Timeline; import com.sun.scenario.animation.TimingTargetAdapter; import com.sun.scenario.scenegraph.SGComposite; 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.event.SGMouseAdapter; import com.sun.scenario.scenegraph.event.SGMouseListener; import demo.util.StandaloneDemo; import java.awt.AWTEvent; import java.awt.Component; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.AWTEventListener; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; /** * Demo application to mimic a phone interface, where one of several * icons on the main screen may be pressed to activate various applications. * Clicking an icon on the main screen will cause all icons to animate off * screen and an application screen to fade/scale into place. Clicking on * that application screen will cause the reverse animations; the application * screen will fade/scale out and the main screen icons will move back into * place. * * To use the non-default hand cursor, hit "h" on the keyboard, which toggles * its visibility. * * @author Chet */ public class JPhone extends StandaloneDemo { JFrame f; SGGroup rootNode = new SGGroup(); HandCursor handNode; Clip menuAnim, appAnim; // Animations that happen when main screen icon is clicked Timeline menuOutTimeline = new Timeline(); // Animations that happen when application screen goes away Timeline menuInTimeline = new Timeline(); // All icons and app screen images are hard-coded along with their captions String mainMenuIcons[] = { "SMS.png", "cal.png", "wallpaper.png", "photo.png", "browser.png", "graph.png", "map.gif", "games.png", "clock.png", "calc.png", "notes.png", "tools.png"}; String iconCaptions[] = { "Text", "Calendar", "Photos", "Camera", "Browser", "Stocks", "Maps", "Games", "Clock", "Calculator", "Notes", "Settings"}; String trayIcons[] = { "phone.png", "mail.png", "browser.png", "music.png"}; String trayCaptions[] = { "Phone", "Mail", "Browser", "Music"}; // For now, all "application screens" use the same dummy image... String apps[] = { "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png", "CalculatorApp.png"}; SGNode appIconNodes[] = new SGNode[apps.length]; SGNode appPhotoNodes[] = new SGNode[apps.length]; private static final int PAD_H = 10; private static final int PAD_V = 20; private static final int ICON_SIZE = 64; private static final int MENU_OUT_TIME = 3000; private static final int MENU_IN_TIME = 300; private Font captionFont = new Font("SansSerif", Font.PLAIN, 11); private static final int SCREEN_W = 320; private static final int SCREEN_H = 480; private Cursor invisibleCursor = null; static { Composer.register(AffineTransform.class, TransformComposer.class); } SGMouseListener appStartListener = new SGMouseAdapter() { @Override public void mouseClicked(MouseEvent e, SGNode n) { Animation a = (Animation) n.getAttribute("startAnim"); a.start(); } }; SGMouseListener appStopListener = new SGMouseAdapter() { @Override public void mouseClicked(MouseEvent e, SGNode n) { Animation a = (Animation) n.getAttribute("stopAnim"); a.start(); } }; private URL getImageURL(String filename) { return getClass().getResource("images/" + filename); } /** * Create actual window frame */ private void createFrame() { f = new JFrame("jPhone"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setBackground(Color.BLACK); } /** * Create main panel and root node */ private void initJSGPanel() { setOpaque(true); setBackground(Color.BLACK); setPreferredSize(new Dimension(SCREEN_W, SCREEN_H)); setScene(rootNode); handNode = new HandCursor(rootNode); addMouseMotionListener(handNode); // Create empty cursor to use when hand is showing BufferedImage emptyImage = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB); invisibleCursor = Toolkit.getDefaultToolkit(). createCustomCursor(emptyImage, new Point(0, 0), "empty"); } /** * Make frame visible and add key listener to it to handle hand cursor * toggling */ private void showFrame() { final JFrame frame = f; frame.getToolkit().addAWTEventListener( new AWTEventListener() { boolean handShowing = false; @Override public void eventDispatched(AWTEvent event) { Component target = (Component) event.getSource(); if (SwingUtilities.isDescendingFrom(target, frame)) { if (event instanceof KeyEvent && event.getID() == KeyEvent.KEY_TYPED) { KeyEvent ke = (KeyEvent)event; if (ke.getKeyChar() == 'h') { if (handShowing) { setCursor(null); handNode.setVisible(false); handShowing = false; } else { setCursor(invisibleCursor); handNode.setVisible(true); handShowing = true; } frame.repaint(); } } } } }, AWTEvent.KEY_EVENT_MASK); f.add(this); f.invalidate(); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); } /** * Creates tray of icons at the bottom of the screen. There is currently * no input or functionality from these icons - they're all show. * But the tray reacts to the other actions, fading out when the menu * icons move out of the way and fading back in when the menu icons * return. */ private void createTray() { final SGGroup trayGroup = new SGGroup(); // Set up transform/opacity filters for the tray SGComposite opacityNode = new SGComposite(); opacityNode.setOpacity(1f); SGTransform trayTransform = SGTransform.createTranslation(0, SCREEN_H - (1.5 * ICON_SIZE), trayGroup); opacityNode.setChild(trayTransform); rootNode.add(opacityNode); // Set up the basic tray background SGShape trayBackground = new SGShape(); trayBackground.setShape(new Rectangle(SCREEN_W, (int)(ICON_SIZE * 1.5))); trayBackground.setMode(SGShape.Mode.FILL); trayBackground.setFillPaint(new GradientPaint(0f, 0f, Color.DARK_GRAY, 0f, (float)(ICON_SIZE * 1.5), Color.LIGHT_GRAY)); trayGroup.add(trayBackground); // Set up the darker background for the captions SGShape captionBackground = new SGShape(); captionBackground.setShape(new Rectangle(SCREEN_W, 20)); captionBackground.setMode(SGShape.Mode.FILL); captionBackground.setFillPaint(new GradientPaint(0f, 0f, Color.DARK_GRAY, 0f, 10f, Color.GRAY)); SGTransform captionBGTransform = SGTransform.createTranslation(0, (1.5 * ICON_SIZE) - 22, captionBackground); trayGroup.add(captionBGTransform); // Set up tray icons with captions for (int i = 0; i < trayIcons.length; ++i) { int xOffset = PAD_H + (i * (PAD_H + ICON_SIZE)); SGGroup imageAndCaption = new SGGroup(); SGTransform transform = SGTransform.createTranslation(xOffset, 8, imageAndCaption); trayGroup.add(transform); try { BufferedImage originalImage = ImageIO.read(getImageURL(trayIcons[i])); // Scale to the size required BufferedImage iconImage = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB); Graphics2D gImage = iconImage.createGraphics(); gImage.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); gImage.drawImage(originalImage, 0, 0, ICON_SIZE, ICON_SIZE, null); gImage.dispose(); SGImage icon = new SGImage(); imageAndCaption.add(icon); icon.setImage(iconImage); } catch (Exception e) { System.out.println("Problem loading tray icon: " + e); } // caption SGText textNode = new SGText(); textNode.setText(trayCaptions[i]); textNode.setFont(captionFont); textNode.setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON); textNode.setLocation(new Point2D.Double(20, ICON_SIZE + 5)); textNode.setFillPaint(Color.WHITE); Rectangle2D rect = textNode.getBounds(); textNode.setLocation(new Point2D.Double( (ICON_SIZE - rect.getWidth())/2, ICON_SIZE + 16)); imageAndCaption.add(textNode); } // Fade tray out as menu icons move out Clip fader = Clip.create((int)MENU_OUT_TIME, opacityNode, "opacity", 1f, 0f); fader.addTarget(new TimingTargetAdapter() { public void end() { trayGroup.setVisible(false); } }); menuOutTimeline.schedule(fader); // Fade tray in as menu icons move back in fader = Clip.create((int)MENU_IN_TIME, opacityNode, "opacity", 0f, 1f); fader.addTarget(new TimingTargetAdapter() { public void begin() { trayGroup.setVisible(true); } }); menuInTimeline.schedule(fader); } /** * Creates the "main menu" of icons in the main screen. This method * loads the images for the icons, positions the icons appropriately, * and sets up the required animations. Clicking on an icon will * trigger both the animation of moving all buttons off the screen * and the fading/scaling in of the application screen associated with * that icon. */ private void createMenu() { int iconIndex = 0; // Location that menu icons will move away from during transition int xCenter = ((5 * PAD_H) + (4 * ICON_SIZE)) / 2; int yCenter = 70; for (int row = 0; row < 3; ++row) { double xOffset = 0; double yOffset = PAD_V + (row * (PAD_V + ICON_SIZE)); for (int col = 0; col < 4; ++col, ++iconIndex) { // Set up node to hold both image and caption SGGroup imageAndCaption = new SGGroup(); // image SGImage icon = new SGImage(); try { BufferedImage originalImage = ImageIO.read(getImageURL(mainMenuIcons[iconIndex])); // Scale to the size required BufferedImage iconImage = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB); Graphics2D gImage = iconImage.createGraphics(); gImage.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); gImage.drawImage(originalImage, 0, 0, ICON_SIZE, ICON_SIZE, null); gImage.dispose(); icon.setImage(iconImage); imageAndCaption.add(icon); } catch (Exception e) { System.out.println("Error loading image: " + e); } // caption SGText textNode = new SGText(); textNode.setText(iconCaptions[iconIndex]); textNode.setFont(captionFont); textNode.setFillPaint(Color.LIGHT_GRAY); textNode.setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON); Rectangle2D rect = textNode.getBounds(); textNode.setLocation(new Point2D.Double( (ICON_SIZE - rect.getWidth())/2, ICON_SIZE + 10)); imageAndCaption.add(textNode); // position the icon in the grid xOffset = PAD_H + (col * (PAD_H + ICON_SIZE)); SGTransform.Translate transformNode = SGTransform.createTranslation(xOffset, yOffset, imageAndCaption); rootNode.add(transformNode); // Create animation that sends this icon offscreen in the // direction of (center, iconLocation) double xDir = xOffset - xCenter; double yDir = yOffset - yCenter; double xOffscreen, yOffscreen; if (Math.abs(xDir) > Math.abs(yDir)) { // x movement larger if (xDir < 0) { xOffscreen = -ICON_SIZE + xDir; double t = (float) xOffscreen / xDir; yOffscreen = (int) (yOffset + t * yDir); } else { xOffscreen = 320 + xDir; double t = (float) xOffscreen / xDir; yOffscreen = (int) (yOffset + t * yDir); } } else { if (yDir < 0) { yOffscreen = -ICON_SIZE + yDir; double t = (float) yOffscreen / yDir; xOffscreen = (int) (xOffset + t * xDir); } else { yOffscreen = 480 + yDir; double t = (float) yOffscreen / yDir; xOffscreen = (int) (xOffset + t * xDir); } } // animate icon to offscreen location during transtion Clip iconClip = Clip.create(MENU_OUT_TIME, transformNode, "translateX", xOffscreen); iconClip.addTarget(KeyFrames.create( new BeanProperty(transformNode, "translateY"), yOffscreen)); menuOutTimeline.schedule(iconClip); // animate icon back onto the screen iconClip = Clip.create(MENU_IN_TIME, transformNode, "translateX", xOffset); iconClip.addTarget(KeyFrames.create( new BeanProperty(transformNode, "translateY"), yOffset)); menuInTimeline.schedule(iconClip); icon.putAttribute("homelocation", new Point2D.Double(xOffset, yOffset)); // Create animation for app screen associated with this icon createApp(icon, iconIndex); } } } /** * Create application screen associated with a particular icon on the * main screen. * * @param appIcon The SGNode for the icon that corresponds * to this application screen * @param appIndex The index in the list of applications that should * be used */ private void createApp(SGNode appIcon, int appIndex) { // Create the SGImage node that holds the app screen picture final SGImage photo = new SGImage(); try { BufferedImage originalImage = ImageIO.read(getImageURL(apps[appIndex])); // Scale to the size we need BufferedImage scaledImage = new BufferedImage( SCREEN_W, SCREEN_H, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = scaledImage.createGraphics(); g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.drawImage(originalImage, 0, 0, scaledImage.getWidth(), scaledImage.getHeight(), null); photo.setImage(scaledImage); } catch (Exception e) { System.out.println("Problem loading application screen image: " + e); } // App screen is just an image, scaled/faded in when it becomes active photo.setVisible(false); SGComposite opacityNode = new SGComposite(); opacityNode.setOpacity(0f); opacityNode.setChild(photo); AffineTransform fullScale = new AffineTransform(); AffineTransform smallScale = AffineTransform.getTranslateInstance( SCREEN_W/2, SCREEN_H/2); smallScale.scale(.1, .1); smallScale.translate(-SCREEN_W/2, -SCREEN_H/2); SGTransform scaleNode = SGTransform.createAffine(smallScale, opacityNode); fullScale = new AffineTransform(); rootNode.add(scaleNode); // Create animation to scale the picture in // First, fade it in final Timeline appAnims = new Timeline(); Clip fader = Clip.create((int)MENU_OUT_TIME, opacityNode, "opacity", .1f, 1f); Clip scaler = Clip.create((int)MENU_OUT_TIME, scaleNode, "affine", smallScale, fullScale); appAnims.schedule(fader); appAnims.schedule(scaler); fader.addTarget(new TimingTargetAdapter() { public void begin() { photo.setVisible(true); } }); fader.addBeginAnimation(menuOutTimeline); appIcon.putAttribute("startAnim", appAnims); appIcon.addMouseListener(appStartListener); // Now, create animation to move app out and bring menu back in final Timeline appAnimOut = new Timeline(); fader = Clip.create((int)MENU_IN_TIME, opacityNode, "opacity", 1f, 0f); scaler = Clip.create((int)MENU_IN_TIME, scaleNode, "affine", fullScale, smallScale); fader.addTarget(new TimingTargetAdapter() { public void end() { photo.setVisible(false); } }); appAnimOut.schedule(fader); appAnimOut.schedule(scaler); fader.addBeginAnimation(menuInTimeline); photo.putAttribute("stopAnim", appAnimOut); photo.addMouseListener(appStopListener); appIconNodes[appIndex] = appIcon; appPhotoNodes[appIndex] = photo; } /** Creates a new instance of JPhone */ public JPhone() { createFrame(); createMenu(); createTray(); initJSGPanel(); } public String getDemoName() { return "jPhone"; } public void resetForDemoSequence() { for (SGNode n : appPhotoNodes) { n.setVisible(false); } for (SGNode n : appIconNodes) { Point2D p = (Point2D) n.getAttribute("homelocation"); SGTransform.Translate t = (SGTransform.Translate) n.getParent().getParent(); t.setTranslateX(p.getX()); t.setTranslateY(p.getY()); } } public void scheduleDemoSequence(Timeline tl) { int t = 0; for (int i = 0; i < appIconNodes.length; i++) { Animation a; a = (Animation) appIconNodes[i].getAttribute("startAnim"); tl.schedule(a, t); t += MENU_IN_TIME + 250; a = (Animation) appPhotoNodes[i].getAttribute("stopAnim"); tl.schedule(a, t); t += MENU_OUT_TIME + 100; } } /** * Create the application and GUI on the Swing Event Dispatch Trhead * @param args the command line arguments */ public static void main(String[] args) { Runnable doCreateAndShowGUI = new Runnable() { public void run() { new JPhone().showFrame(); } }; SwingUtilities.invokeLater(doCreateAndShowGUI); } } /** * Utility class for handling the creation and display of the hand cursor. * Visibility is controlled by toggling the "h" key. */ class HandCursor implements MouseMotionListener { BufferedImage handImage = null; int mouseX = -1; int mouseY = -1; SGTransform.Translate handTransform; SGImage handNode; HandCursor(SGGroup rootNode) { try { handImage = ImageIO.read(getImageURL("HandLarge.png")); } catch (Exception e) { System.out.println("problem reading cursor image: " + e); } // Make the hand image larger int newW = (int)(handImage.getWidth() * 1.4); int newH = (int)(handImage.getHeight() * 1.4); BufferedImage largerHand = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = largerHand.createGraphics(); g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.drawImage(handImage, 0, 0, newW, newH, null); handNode = new SGImage(); handNode.setImage(largerHand); handNode.setVisible(false); handTransform = SGTransform.createTranslation(0, 0, handNode); rootNode.add(handTransform); } public void setVisible(boolean visible) { handNode.setVisible(visible); } private URL getImageURL(String filename) { return getClass().getResource("images/" + filename); } /** * Track mouse motion and set the position of the hand cursor appropriately * * @param me MouseEvent which holds the position that we will use to * set the location of the hand cursor */ public void mouseMoved(MouseEvent me) { mouseX = me.getX(); mouseY = me.getY(); handTransform.setTranslation(mouseX - 160, mouseY - 5); } public void mouseDragged(MouseEvent me) { } }