/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * JavaWorld Library, Copyright 2011 Bryan Chadwick * * * * FILE: ./world/World.java * * * * This file is part of JavaWorld. * * * * JavaWorld is free software: you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation, either version * * 3 of the License, or (at your option) any later version. * * * * JavaWorld is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with JavaWorld. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package world; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import image.Image; import image.Scene; /** * * * A Class representing a World and the related methods for drawing * the world and handling various events. In order to implement a * functioning World you must extend this class, and implement * an {@link world.World#onDraw onDraw} method. Other handler * methods ({@link world.World#tickRate tickRate}, {@link * world.World#onTick onTick}, {@link world.World#onMouse onMouse}, * {@link world.World#onKey onKey}, {@link world.World#onRelease * onRelease}, {@link world.World#stopWhen stopWhen}, and {@link * world.World#lastScene lastScene}) are optional, and can be * overridden to add new functionality. * *

See the individual methods for detailed documentation.

* *

*

Displaying Images/Scenes

*
* Simple and generated images can be displayed in a Window using the static * method {@link world.World#display(Image)}. * *
 *     import world.World;
 *     import image.*;
 * 
 *     public class DisplayTest{
 *         public static void main(String[] args){
 *             World.display(new EmptyScene(200, 200)
 *                                .placeImage(new Overlay(new Star(50, 30, 10, "outline", "black"),
 *                                                        new Star(50, 30, 10, "solid", "green")),
 *                                            100, 100));
 *         }
 *     }
 * 
* * When run, the program opens a window that looks something like this:

* * *
*

* *

*

Extending World

*
* Below is a simple example of a World that adds a new point at each mouse click. The world * contains a {@link image.Scene Scene} and a new {@link image.Circle Circle} is placed for each * "button-down" event received. * * *
   
 *        import image.*;
 *        import world.World;
 *        
 *        public class MousePointsWorld extends World{
 *            // Simple Main Program
 *            public static void main(String[] args){
 *                new MousePointsWorld( = new EmptyScene(200, 200))
 *                     .bigBang();
 *            }
 *            
 *            // The inner Scene
 *            Scene scene;
 *        
 *            // Create a new World
 *            MousePointsWorld(Scene scene){
 *                 this.scene = scene;
 *            }
 *            
 *            // Draw by returning the inner Scene
 *            public Scene onDraw(){ return this.scene; }
 *        
 *            // On a mouse click add a circle to the inner Scene
 *            public MouseWorld onMouse(int x, int y, String me){
 *                if(me.equals("button-down")){
 *                    return new MousePointsWorld(this.scene.placeImage(
 *                                     new Circle(20, "solid", "red")
 *                                           .overlay(new Circle(20, "outline", "black")), x, y));
 *                }
 *            }
 *        }
 * 
* * After a few mouse clicks, the window will look something like this:

* * *
*

*/ public abstract class World{ /** Default Tick rate for the world: ~33 frames per second */ public static double DEFAULT_TICK_RATE = BigBang.DEFAULT_TICK_RATE; /** Mouse down (button-down) event String */ public static String MOUSE_DOWN = BigBang.MOUSE_DOWN; /** Mouse up (button-up) event String */ public static String MOUSE_UP = BigBang.MOUSE_UP; /** Mouse window enter (enter) event String */ public static String MOUSE_ENTER = BigBang.MOUSE_ENTER; /** Mouse window leave (leave) event String */ public static String MOUSE_LEAVE = BigBang.MOUSE_LEAVE; /** Mouse motion (move) event String */ public static String MOUSE_MOVE = BigBang.MOUSE_MOVE; /** Mouse down & move (drag) event String */ public static String MOUSE_DRAG = BigBang.MOUSE_DRAG; /** Key arrow-up event String */ public static String KEY_ARROW_UP = BigBang.KEY_ARROW_UP; /** Key arrow-down event String */ public static String KEY_ARROW_DOWN = BigBang.KEY_ARROW_RIGHT; /** Key arrow-left event String */ public static String KEY_ARROW_LEFT = BigBang.KEY_ARROW_LEFT; /** Key arrow-right event String */ public static String KEY_ARROW_RIGHT = BigBang.KEY_ARROW_RIGHT; /** Return a visualization of this World as a {@link image.Scene Scene}. * See {@link image.EmptyScene}, {@link image.Scene#placeImage(Image, int, int)}, and * {@link image.Scene#addLine(int, int, int, int, String)} for documentation on * constructing Scenes */ public abstract Scene onDraw(); /** Return the tick rate for this World in seconds. For example, * 0.5 means two ticks per second. The * rate is only accessed when bigBang() is initially called and the * window is created. */ public double tickRate(){ return DEFAULT_TICK_RATE; } /** Produce a (possibly) new World based on the Tick of the clock. * This method is called to get the next world on each * clock tick.*/ public World onTick(){ return this; } /** Produce a (possibly) new World when a mouse event is * triggered. x and y are the location of the * event in the window, and event is a String * that describes what kind of event occurred. * *

* Possible Mouse Events * * * * * * * * * * * * * *
"button-down" : The user presses a mouse button in the World window
"button-up" : The user releases a mouse button in the World window
"move" : The user moves the mouse in the World window
"drag" : The user holds a mouse button and moves the mouse in the World window
"enter" : The user moves the mouse in-to the World window
"leave" : The user moves the mouse out-of the World window
*

*/ public World onMouse(int x, int y, String event){ return this; } /** Produce a (possibly) new World when a key is * pressed. The given event is a String that * describes what key was pressed. * *

* Special Keys * * * * * * * * * *
"up" : The user presses the up-arrow key
"down" : The user presses the down-arrow key
"left" : The user presses the left-arrow key
"right" : The user presses the right-arrow key
* * Other keys generate a single character String that * represents the key pressed. For example, Pressing the B * key on the keyboard generates "b" as an * event. If the shift key is held while pressing B then * "B" is generated.

*/ public World onKey(String event){ return this; } /** Produce a (possibly) new World when a key is released. The given event * is a String that describes which key was released. * *

* Special Keys * * * * * * * * * *
"up" : The user presses the up-arrow key
"down" : The user presses the down-arrow key
"left" : The user presses the left-arrow key
"right" : The user presses the right-arrow key
* * Other keys generate a single character String that * represents the key released. For example, Pressing then * releasing the B key on the keyboard generates "b" as an onKey event and again as an * onRelease event. If the shift key is held while * pressing/releasing B then "B" is * generated.

*/ public World onRelease(String event){ return this; } /** Determine if the World/interaction/animation should be * stopped. Returning a value of true * discontinues all events (mouse, key, ticks) and causes {@link * world.World#lastScene} to be used to draw the final * Scene. */ public boolean stopWhen(){ return false; } /** Returns the Scene that should be displayed when the * interaction/animation completes ({@link world.World#stopWhen} * returns true). */ public Scene lastScene(){ return this.onDraw(); } /** Kick off the interaction/animation. This method returns the final * state of the world after the user closes the World window. */ public World bigBang(){ return (World)new BigBang(this) .onDraw(new WorldDraw()) .onTick(new WorldTick(), tickRate()) .onMouse(new WorldMouse()) .onKey(new WorldKey()) .onRelease(new WorldRelease()) .stopWhen(new WorldStop()) .lastScene(new WorldLast()) .bigBang("World"); } /** Wrapper for OnDraw callback */ private static class WorldDraw{ @SuppressWarnings("unused") Scene apply(World w){ return w.onDraw(); } } /** Wrapper for OnTick callback */ private static class WorldTick{ @SuppressWarnings("unused") World apply(World w){ return w.onTick(); } } /** Wrapper for OnMouse callback */ private static class WorldMouse{ @SuppressWarnings("unused") World apply(World w, int x, int y, String me) { return w.onMouse(x,y,me); } } /** Wrapper for OnKey callback */ private static class WorldKey{ @SuppressWarnings("unused") World apply(World w, String ke){ return w.onKey(ke); } } /** Wrapper for OnRelease callback */ private static class WorldRelease{ @SuppressWarnings("unused") World apply(World w, String ke){ return w.onRelease(ke); } } /** Wrapper for StopWhen callback */ private static class WorldStop{ @SuppressWarnings("unused") boolean apply(World w){ return w.stopWhen(); } } /** Wrapper for LastScene callback */ private static class WorldLast{ @SuppressWarnings("unused") Scene apply(World w){ return w.lastScene(); } } /** Gap left around the border of the Window */ private static int SPACE = 5; /** Opens a new Window and displays the given {@link image.Image}/{@link image.Scene}, returns * true once the window is closed. */ public static boolean display(Image i){ try{ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }catch(Exception e){} JDialog f = new JDialog((JFrame)null, "Display", true); Scene scn = i.toScene(); f.setSize((int)(SPACE*2+Math.max(20, 14+scn.width())), (int)(Math.max(20, SPACE*2+31+scn.height()))); f.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); f.setResizable(false); f.getContentPane().add(new SceneComponent(scn)); f.setVisible(true); return true; } /** Component that displays and supports saving of an image */ private static class SceneComponent extends JComponent implements MouseListener{ private static final long serialVersionUID = 1L; Scene scn; public SceneComponent(Scene s){ this.scn = s; this.addMouseListener(this); } public void paint(Graphics g){ Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); g2.setColor(Color.white); g2.fillRect(0,0, this.getWidth(), this.getHeight()); g2.clipRect(SPACE, SPACE, (int)(this.scn.width()), (int)(this.scn.height())); this.scn.paint(g2,SPACE,SPACE); } /** Support saving screenshots... */ private JPopupMenu popup = new JPopupMenu("World Options"); { addItem(this.popup, "Save Image...", new ActionListener(){ public void actionPerformed(ActionEvent e){ doSaveAs(SceneComponent.this.scn); synchronized(SceneComponent.this.popup){ SceneComponent.this.popup.notify(); } } }); this.popup.addPopupMenuListener(new PopupMenuListener(){ public void popupMenuCanceled(PopupMenuEvent arg0){ synchronized(SceneComponent.this.popup){ SceneComponent.this.popup.notify(); } } public void popupMenuWillBecomeVisible(PopupMenuEvent arg0){} public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0){} }); } private static void addItem(JPopupMenu m, String s, ActionListener l){ JMenuItem item = new JMenuItem(s); if(l != null) item.addActionListener(l); m.add(item); } /** Ask the user for a file... then save the image */ private boolean doSaveAs(Scene scn){ try{ JFileChooser fc = new JFileChooser(); int result = fc.showDialog(this, "SaveAs"); // Should check for existence... if(result == JFileChooser.APPROVE_OPTION){ scn.toFile(fc.getSelectedFile().getAbsolutePath()); return true; } }catch(Exception e){ System.err.println(""); } return false; } /** Show the popup if it is "triggered" */ public void mousePressed(final MouseEvent e){ if(e.isPopupTrigger()){ final SceneComponent that = this; // Show the context Menu new Thread(){ public void run(){ SceneComponent.this.popup.show(that, e.getX(), e.getY()); try{ synchronized(SceneComponent.this.popup){ Thread.yield(); SceneComponent.this.popup.wait(); } }catch(Exception ee){} } }.start(); } } /* Mouse click/move/event Methods */ public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public void mouseReleased(MouseEvent e){} } }