/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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){}
}
}