/* $Id: AnimationApplet.java,v 1.6 1999/08/19 16:38:52 gjb Exp $ ---------------------------------------------------------------------- yasuhara@cs.washington.edu, badros@cs.washington.edu CSE 341, Assignment 6 August 1999 */ import java.awt.*; import java.awt.event.*; import java.applet.*; /** AnimationApplet provides double buffering and two abstract methods, prepareNextFrame and drawNextFrame, that must be implemented by subclassing. */ public abstract class AnimationApplet extends Applet implements MouseListener, KeyListener { ///////////////////////// variables // start these off at invalid values to know whether // we should read them from the width/height parameters of the applet /** The width and the height of the applet */ protected int width = -1, height = -1; // // for double-buffering // /** The off-screen image used for double-buffering */ private Image bufferImage; /** The graphics handle that draws onto the bufferImage */ private Graphics bufferGraphics; // // edit-able parameters // /** Produces Selected trace output to System.out if set to true */ protected boolean debug = true; /** Approximate pause time between animation frames, in ms */ protected int sleepTime = 50; /** the thread that invokes the prepareNextFrame and drawNextFrame methods */ private Thread frameGenerator; /** the thread that handles the copying of the off-screen image to the display */ private Thread framePainter; /** Internal variable used for suspending the framePainter thread. */ private boolean suspendFlag = false; ///////////////////////// methods // // constructors // // (Note that the default constructor gets used when run as an // applet.) // // not really necessary; left here to remind you of the above // point public AnimationApplet() { // do nothing } // invoked from main public AnimationApplet(int width, int height) { this.width = width; this.height = height; } // // animation // // (Note how these are called below by the threads.) // /** Subclasses must implement prepareNextFrame to update data for next frame. See also the drawNextFrame method. */ // called by FramePainter thread abstract public void prepareNextFrame(); /** Subclasses must implement drawNextFrame to draw image for next frame. That method should draw the entire display on the given Graphics object which will always be an "off-screen" graphics buffer. The AnimationApplet manages copying this buffer onto the visible display. Nothing but drawing and trivial computations should occur in drawNextFrame as it needs to execute as quickly as possible. Put more complex computations and updating of state in prepareNextFrame. See also the prepareNextFrame method. */ // called by FrameGenerator thread abstract public void drawNextFrame(Graphics g); // // Applet overrides // // (If you choose to override these in a subclass, you must call // this class' versions from the overrides for animation to work // properly.) // /** Gets invoked by the browser/appletviewer once at initialization time (and also if it is explicitly restarted). Subclasses may need to override and extend this message, but be sure to chain with the superclasses method by calling super.init() */ public void init() { if (debug) System.out.println("init"); // if size not specified at construction... if (width < 0 || height < 0) { // set to whatever the applet size currently is Dimension size = getSize(); width = size.width; height = size.height; } // enable listener event handlers below addMouseListener(this); addKeyListener(this); if (null == frameGenerator) { frameGenerator = new FrameGenerator(); } if (null == framePainter) { framePainter = new FramePainter(); } } /** Gets invoked by the browser/appletviewer once after the init() method, and then again whenever the page is re-visited or re-viewed after having been left or minimized. Be sure to call super.start() when overriding this method. */ public void start() { if (debug) System.out.println("start"); //setSize(width, height); // setup for double-buffering: allocate off-screen Image // object to use for drawing bufferImage = createImage(width, height); bufferGraphics = bufferImage.getGraphics(); // start or resume threads to regularly update on-screen image if (frameGenerator.isAlive()) { // resume the thread suspendFlag = false; synchronized (framePainter) { framePainter.notify(); } } else { frameGenerator.start(); } if (!framePainter.isAlive()) framePainter.start(); } /** Gets invoked by the browser/appletviewer whenever the page is left or minimized. Be sure to call super.stop() when overriding this method. */ public void stop() { if (debug) System.out.println("stop"); suspendFlag = true; } // can't make abstract method synchronized, so wrap call; // synchronized so that drawNextFrame and paint never access // bufferGraphics while the other is in the middle of executing /** A wrapper to ensure that the drawNextFrame method is synchronized so that method and the paint method never access bufferGraphics simultaneously */ private synchronized void drawNextFrameWrapper() { // call abstract function drawNextFrame(bufferGraphics); } /** Writes off-screen buffer to screen all at once */ final public synchronized void paint(Graphics g) { if (bufferImage != null) g.drawImage(bufferImage, 0, 0, null); } /** Update the container. */ final public void update(Graphics g) { paint(g); } // // MouseListener implementation stubs // /** Called when a mouse button is clicked (i.e., pressed and released). Override this method to handle mouse click events. (Be sure to call super.mouseClicked(me) first to preserve debugging output). */ public void mouseClicked(MouseEvent me) { if (debug) System.out.println("mouseClicked"); } /** Called when the mouse pointer enters the applet canvas. Override this method to handle these events. (Be sure to call super.mouseEntered(me) first to preserve debugging output). */ public void mouseEntered(MouseEvent me) { if (debug) System.out.println("mouseEntered"); } /** Called when the mouse pointer exits the applet canvas. Override this method to handle these events. (Be sure to call super.mouseExited(me) first to preserve debugging output). */ public void mouseExited(MouseEvent me) { if (debug) System.out.println("mouseExited"); } /** Called when a mouse button is pressed. Override this method to handle these events. (Be sure to call super.mousePressed(me) first to preserve debugging output). */ public void mousePressed(MouseEvent me) { if (debug) System.out.println("mousePressed"); } /** Called when a mouse button is released. Override this method to handle these events. (Be sure to call super.mouseReleased(me) first to preserve debugging output). */ public void mouseReleased(MouseEvent me) { if (debug) System.out.println("mouseReleased"); } // // KeyListener implementation stubs // /** Called when a key is pressed. Override this method to handle these events. (Be sure to call super.keyPressed(ke) first to preserve debugging output). */ public void keyPressed(KeyEvent ke) { if (debug) System.out.println("keyPressed"); } /** Called when a key is released. Override this method to handle these events. (Be sure to call super.keyReleased(ke) first to preserve debugging output). */ public void keyReleased(KeyEvent ke) { if (debug) System.out.println("keyReleased"); } /** Called when a key is typed (i.e., pressed and released. Override this method to handle these events. (Be sure to call super.keyTyped(ke) first to preserve debugging output). */ public void keyTyped(KeyEvent ke) { if (debug) System.out.println("keyTyped"); } ///////////////////////// threads for animation // This thread repeatedly prepares and draws new frames to the // off-screen buffer, suspending itself and waiting for each new // frame to be painted to the screen by the FramePainter thread // before preparing the next. private class FrameGenerator extends Thread { public void run() { // main animation loop while (true) { prepareNextFrame(); drawNextFrameWrapper(); try { // don't generate next frame until it's painted to // the screen by the FramePainter; i.e. suspend // FrameGenerator thread // note absence of sleep(n) delay; // we instead block the thread until // notify-d by the FramePainter thread synchronized (AnimationApplet.this) { AnimationApplet.this.wait(); } } catch (InterruptedException e) { System.err.println("AnimationApplet:" + " exception " + e.getMessage()); System.exit(0); } } } } // This thread repeatedly paints the contents of the off-screen // buffer in bufferImage to the screen and sleeps (does nothing) // for some fixed number of milliseconds sleepTime. In addition, // after each painting, it resumes the FrameGenerator thread which // stops and waits after private class FramePainter extends Thread { public void run() { while (true) { // paint bufferGraphics to screen repaint(); // resume FrameGenerator thread to generate next frame synchronized (AnimationApplet.this) { AnimationApplet.this.notify(); } // pause for some fixed duration; sleepTime determines // frame rate, approxiately try { sleep(sleepTime); } catch (Exception e) { System.err.println("AnimationApplet.FramePainter:" + " exception " + e.getMessage()); System.exit(0); } if (suspendFlag) { try { synchronized(this) { while (suspendFlag) wait(); } } catch (InterruptedException e) { System.err.println("AnimationApplet:" + " exception " + e.getMessage()); System.exit(0); } } } } } } /* Local Variables: */ /* tab-width: 4 */ /* c-basic-offset: 4 */ /* End: */