/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * AndroidWorld Library, Copyright 2011 Bryan Chadwick * * * * FILE: ./android/world/World.java * * * * This file is part of AndroidWorld. * * * * AndroidWorld 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. * * * * AndroidWorld 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 AndroidWorld. If not, see <http://www.gnu.org/licenses/>. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package android.world; import android.app.Activity; import android.image.Scene; /** * <style type='text/css'><!-- * .def{ color: #000000; } * .com{ font-style: italic; color: #880000; } * .keyw{ font-weight: bold; color: #000088; } * .num{ color: #00AA00; } * .str{ color: #CC00AB; } * .prim{ color: #0000FF; } * .func{ color: #BB7733; } * img.example{ padding-left: 50px; padding-bottom: 30px; } * table.events td{ padding-bottom: 20px; } * table.events{ padding-left: 30px; padding-bottom: 20px; } * --></style> * * 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 <i>extend</i> this class, and implement an {@link android.world.World#onDraw onDraw} * method. Other handler methods ({@link android.world.World#tickRate tickRate}, {@link android.world.World#onTick onTick}, * {@link android.world.World#onMouse onMouse}, {@link android.world.World#onKey onKey}, {@link android.world.World#onRelease onRelease}, * {@link android.world.World#onOrientation onOrientation}, {@link android.world.World#stopWhen stopWhen}, * and {@link android.world.World#lastScene lastScene}) are optional, and can be overridden to add * new functionality. * <p> * See the individual methods for detailed documentation. * </p> * * <p> * <h3>Extending World</h3> * <blockquote> * * <p> * Developing an Android application is a bit more work, since it requires the * <a href='http://developer.android.com/sdk/index.html'>Android development kit</a> for Eclipse. After that, you can create an * Android Project and fill in all the parameters. The World/VoidWorld classes * only require a simple hook to be added to the application's <tt>Activity</tt>. * </p> * * Below is a simple example of a <code>World</code> that adds a new point at each mouse click. The world * contains a {@link android.image.Scene Scene} and a new {@link android.image.Circle Circle} is placed for each * <code class='str'>"button-down"</code> event received. The World is created in the * class that extends <tt>Activity</tt>. * * <pre> * <span class="keyw">import</span> android.app.Activity; * <span class="keyw">import</span> android.os.Bundle; * <span class="keyw">import</span> android.view.Display; * * <span class="keyw">import</span> android.image.*; * <span class="keyw">import</span> android.world.World; * * <span class="keyw">public</span> <span class="keyw">class</span> MousePoints <span class="keyw">extends</span> Activity{ * <span class="com">// Called when the activity is first created.</span> * <span class="keyw">public</span> <span class="keyw">void</span> <span class="func">onCreate</span>(Bundle savedState) { * <span class="keyw">super</span>.<span class="func">onCreate</span>(savedState); * * Display dis = <span class="func">getWindowManager</span>().<span class="func">getDefaultDisplay</span>(); * * <span class="com">// Create and start the World </span> * <span class="keyw">new</span> <span class="func">MousePointsWorld</span>(<span class="keyw">new</span> <span class="func">EmptyScene</span>(dis.<span class="func">getWidth</span>(), dis.<span class="func">getHeight</span>()-<span class='num'>50</span>)) * .<span class="func">bigBang</span>(<span class="keyw">this</span>); * } * } * * <span class="keyw">class</span> MousePointsWorld <span class="keyw">extends</span> World{ * Scene scene; * * <span class="func">MousePointsWorld</span>(Scene scene){ * <span class="keyw">this</span>.scene = scene; * } * * <span class="keyw">public</span> Scene <span class="func">onDraw</span>(){ <span class="keyw">return</span> <span class="keyw">this</span>.scene; } * * <span class="keyw">public</span> World <span class="func">onMouse</span>(<span class="keyw">int</span> x, <span class="keyw">int</span> y, <span class="prim">String</span> me){ * <span class="keyw">if</span>(!me.<span class="func">equals</span>(<span class="str">"button-down"</span>)){ * <span class="keyw">return</span> <span class="keyw">this</span>; * }<span class="keyw">else</span>{ * <span class="keyw">return</span> <span class="keyw">new</span> <span class="func">MousePointsWorld</span>( * <span class="keyw">this</span>.scene.<span class="func">placeImage</span>(<span class="keyw">new</span> <span class="func">Circle</span>(<span class="num">20</span>, <span class="str">"solid"</span>, <span class="str">"red"</span>) * .<span class="func">overlay</span>(<span class="keyw">new</span> <span class="func">Circle</span>(<span class="num">20</span>, <span class="str">"outline"</span>, <span class="str">"black"</span>)), x, y)); * } * } * } * </pre> * * After a few finger-taps, the device will look something like this:<br/><br/> * * <img class="example" src="test/mouse-points.png" /> * </blockquote> * </p> */ public abstract class World{ /** The BigBang/Handler */ private BigBang bigbang; /** Default Tick rate for the world: ~33 frames per second */ public static double DEFAULT_TICK_RATE = 0.03; /** 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 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; /** Menu Key event String. The menu key will usually be intercepted to open a save dialog * to enable the capture of application/game screen-shots. */ public static String KEY_MENU = BigBang.KEY_MENU; /** Search Key event String */ public static String KEY_SEARCH = BigBang.KEY_SEARCH; /** Return a visualization of this <tt>World</tt> as a {@link android.image.Scene Scene}. * See {@link android.image.EmptyScene}, {@link android.image.Scene#placeImage(Image, int, int)}, and * {@link android.image.Scene#addLine(int, int, int, int, String)} for documentation on * constructing <tt>Scene</tt>s */ public abstract Scene onDraw(); /** Return the tick rate for this World in <i>seconds</i>. For example, * <span class='num'>0.5</span> means two <i>tick</i>. 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 <i>next world</i> on each clock tick.*/ public World onTick(){ return this; } /** Produce a (possibly) new World when a touch event is triggered. * <tt>x</tt> and <tt>y</tt> are the location of the event on the device screen, * and <tt>event</tt> is a <tt>String</tt> that describes what kind of event * occurred. * * <p> * In addition to the normal World events (button-down/up) Android devices * include a <tt class='str'>"long-button-down"</tt> event, for when the * the user does a long press (touch and hold). * </p> * <p> * <b>Possible Mouse Events</b> * <table class='events'> * <tr><td style="text-align:right"><tt class='str'>"button-down"</tt> : </td> * <td>The user <i>presses</i> the touch screen of the device</td></tr> * <tr><td style="text-align:right"><tt class='str'>"long-button-down"</tt> : </td> * <td>The user <i>presses and holds</i> on the touch screen of the device</td></tr> * <tr><td style="text-align:right"><tt class='str'>"button-up"</tt> : </td> * <td>The user <i>releases</i> the touch screen of the device</td></tr> * <tr><td style="text-align:right"><tt class='str'>"move"</tt> : </td> * <td>The user <i>moves</i> the mouse in the World window</td></tr> * <tr><td style="text-align:right"><tt class='str'>"drag"</tt> : </td> * <td>The user <i>touches</i> and <i>moves</i> on the touch screen of the device</td></tr> * </table> * </p> */ public World onMouse(int x, int y, String event){ return this; } /** Produce a (possibly) new World when a key is * pressed. The given <tt>event</tt> is a <tt>String</tt> that * describes what key was pressed. * * <p> * <b>Special Keys</b> * <table class='events'> * <tr><td style="text-align:right"><tt class='str'>"up"</tt> : </td> * <td>The user presses the <i>up-arrow</i> key</td></tr> * <tr><td style="text-align:right"><tt class='str'>"down"</tt> : </td> * <td>The user presses the <i>down-arrow</i> key</td></tr> * <tr><td style="text-align:right"><tt class='str'>"left"</tt> : </td> * <td>The user presses the <i>left-arrow</i> key</td></tr> * <tr><td style="text-align:right"><tt class='str'>"right"</tt> : </td> * <td>The user presses the <i>right-arrow</i> key</td></tr> * </table> * * Other keys generate a single character <tt>String</tt> that * represents the key pressed. For example, Pressing the <i>B</i> key on * the keyboard generates <tt class='str'>"b"</tt> as an event. * If the shift key is held while pressing <i>B</i> then <tt * class='str'>"B"</tt> is generated. * </p> */ public World onKey(String event){ return this; } /** Produce a (possibly) new World when a key is released. The given <tt>event</tt> * is a <tt>String</tt> that describes which key was released. * * <p> * <b>Special Keys</b> * <table class='events'> * <tr><td style="text-align:right"><tt class='str'>"up"</tt> : </td> * <td>The user presses the <i>up-arrow</i> key</td></tr> * <tr><td style="text-align:right"><tt class='str'>"down"</tt> : </td> * <td>The user presses the <i>down-arrow</i> key</td></tr> * <tr><td style="text-align:right"><tt class='str'>"left"</tt> : </td> * <td>The user presses the <i>left-arrow</i> key</td></tr> * <tr><td style="text-align:right"><tt class='str'>"right"</tt> : </td> * <td>The user presses the <i>right-arrow</i> key</td></tr> * </table> * * Other keys generate a single character <tt>String</tt> that * represents the key released. For example, Pressing then releasing the <i>B</i> key on * the keyboard generates <tt class='str'>"b"</tt> as an <tt>onKey</tt> event and again * as an <tt>onRelease</tt> event. If the shift key is held while pressing/releasing <i>B</i> then <tt * class='str'>"B"</tt> is generated. * </p> */ public World onRelease(String event){ return this; } /** Determine if the World/interaction/animation should be * stopped. Returning a value of <tt class='keyw'>true</tt> * discontinues all events (mouse, key, ticks) and causes {@link * android.world.World#lastScene} to be used to draw the final * <tt>Scene</tt>. */ public boolean stopWhen(){ return false; } /** Returns the <tt>Scene</tt> that should be displayed when the * interaction/animation completes ({@link android.world.World#stopWhen} * returns <tt class='keyw'>true</tt>). */ public Scene lastScene(){ return this.onDraw(); } /** Produce a (possibly) new World when a device orientation event is triggered. * <tt>x</tt>, <tt>y</tt>, and <tt>z</tt> make-up the new orientation vector * for the device. When the device is resting on a flat, level surface the * orientation should be entirly in the <tt>z</tt> direction (typically * straight out the the back of the device). * * <p> * If you are only concerned with the "<i>down</i>" angle (direction of down * with respect to the screen) you can calculate it with a method as follows: * <pre> * <span class='keyw'>double</span> down(<span class='keyw'>double</span> x, <span class='keyw'>double</span> y, <span class='keyw'>double</span> z){ * <span class='keyw'>double</span> ang = Math.acos(x/Math.sqrt(x*x + y*y))+Math.PI; * <span class='keyw'>if</span>(y < <span class='num'>0</span>)<span class='keyw'>return</span> <span class='num'>2</span>*Math.PI-ang; * <span class='keyw'>return</span> ang; * } * </pre> * </p> */ public World onOrientation(double x, double y, double z){ return this; } /** Kick off the interaction/animation. This method returns the final * state of the world after the user closes the World window. */ public World bigBang(Activity act){ return (World)this.makeBigBang().bigBang(act); } /** Kick off the interaction/animation in LANDSCAPE mode. */ public World bigBangLandscape(Activity act){ return (World)this.makeBigBang().bigBangLandscape(act); } /** Kick off the interaction/animation FULLSCREEN mode. This method returns the final * state of the world after the user closes the World window. */ public World bigBangFullscreen(Activity act){ return (World)this.makeBigBang().bigBangFullscreen(act); } /** Kick off the interaction/animation in FULLSCREEN/LANDSCAPE mode. */ public World bigBangLandscapeFullscreen(Activity act){ return (World)this.makeBigBang().bigBangLandscapeFullscreen(act); } /** Get a bigbang instance for this World */ private BigBang makeBigBang(){ this.bigbang = 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()) .orientation(new WorldOrient()); return this.bigbang; } /** Use to Disable Screen Shots */ public static void noScreenShots(){ BigBang.Handler.screenShots = false; } /** Use to temperarily disable onTick/interactions */ public void pause(){ this.bigbang.pause(); } /** Use to renable onTick/interactions */ public void unpause(){ this.bigbang.unpause(); } /** 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(); } } /** Wrapper for LastScene callback */ private static class WorldOrient{ @SuppressWarnings("unused") World apply(World w, float x, float y, float z){ return w.onOrientation(x,y,z); } } }