/* The KeyboardAnimation applet provides a generic framework for applets both display an animation and respond to keyboard events. The animation runs only when the applet has the keyboard focus and can respond to key presses. The appearance of the applet changes, depending on whether it has the keyboard focus. Note that each time a new frame of the animation is to be displayed, it is drawn completely from scratch. When the applet has the keyboard focus, a cyan border is drawn around it. When it does not have the keyboard focus, the border is in the applet's background color and a message "Click to activate" is displayed in the applet's foreground color and font. This class would be appropriate, for example, as a basis for a typical arcade game, such as Space Invaders. (Except that the preformance won't be so good.) To use this framework, define a subclass of KeyboardAnimationApplet and override the drawFrame() method. This method is responsible for drawing one frame of the animation. If you need to some initialization at the time the applet is created, override the doInitialization() method. This method is called once when the applet is created. (You should NOT override the standard applet methods, init(), start(), stop(), or destroy() unless you call the inherited versions from this class. These routines perform important functions in this class.) In this class, the applet is already set up to "listen" for keyboard events. To make your applet respond to keyboard events, you should override one or more of the methods keyPressed(), keyReleased(), and keyTyped(). (The applet also listens for MouseEvents, and you can override the mouse handling events if you want. But if you do override mousePressed(), be sure to call requestFocus() in that routine.) To respond to key presses, you should have some instance variables that affect the image drawn. Change these variables in the keyPressed, keyReleased, or keyTyped methods. (Alternatively, instead of defining a subclass, you could copy this file, change its name and the name of the class, and edit it.) (This applet is requires Java 1.1, since it uses Java 1.1 style event handling.) David Eck Department of Mathematics and Computer Science Hobart and William Smith Colleges Geneva, NY 14456 eck@hws.edu October 2, 1998 Small modifications 18 February 2000 */ import java.awt.*; import java.awt.event.*; public class KeyboardAnimationApplet extends java.applet.Applet implements Runnable, KeyListener, FocusListener, MouseListener { protected void doInitialization(int width, int height) { // This routine is called once when the applet is first created. // You can override it to do initialzation of your instance // variables. It's also a good place to call setFrameCount() // and setMillisecondsPerFrame(), if you want to customize these // values. The parameters tell the size of the drawing area // at the time the applet is created. } protected void drawFrame(Graphics g, int width, int height) { // This routine should be overridden in any subclass of KeyboardAnimationApplet. // It is responsible for drawing one frame of the animation. The frame // is drawn to the graphics context g. The parameters width and height // give the size of the drawing area. drawFrame() should begin by // filling the drawing area with a background color (as is done in this // version of drawFrame). It should then draw the contents of the // frame. The routine can call getFrameNumber() to dermine which frame // it is supposed to draw. It can call getElapsedTime() to find out // how long the animation has been running, in milliseconds. // Note that this routine should not take a long time to execute! // As an example, the elapsed number of seconds and the frame number // are output. g.setColor(Color.lightGray); g.fillRect(0,0,width,height); g.setColor(Color.black); g.drawString("Elapsed Time: " + (getElapsedTime()/1000),10,20); g.drawString("Frame Number: " + (getFrameNumber()),10,35); } public void keyTyped(KeyEvent evt) { // Method to respond when the user types a character. Use // char key = evt.getKeyChar() to find out which character // was typed. Note that this method is part of the // KeyListener interface. } public void keyPressed(KeyEvent evt) { // Method to respond to key presses. Use int code = evt.getKeyCode() to // get a code number for the key pressed. The value of this code // is given by constants in the KeyEvent class such as KeyEvent.VK_LEFT // for the left arrow key and KeyEvent.VK_X for the "X" key. Override this // method if you want to respond when the user presses special keys like // the arrow keys. Note that this routine is part of the KeyListener // interface } public void keyReleased(KeyEvent evt) { // Method to respond when the user releases a key. Use evt.getKeyCode() // to get the code number of the key that was released. Override this // method if you want to respond when the user releases a key. This method // is part of the KeyListener interface. } public int getFrameNumber() { // Get the current frame number. The frame number will be incremented // each time a new frame is to be drawn. The first frame number is 0. // (If frameCount is greater than zero, and if frameNumber is greater than // or equal to frameCount, then frameNumber returns to 0.) For a keyboard // applet, you are not too likely to need frame numbers, actually. return frameNumber; } public void setFrameNumber(int frameNumber) { // Set the current frame number. This is the value returned by getFrameNumber(). if (frameNumber < 0) this.frameNumber = 0; else this.frameNumber = frameNumber; } public long getElapsedTime() { // return the total number of milliseconds that the animation has been // running (not including the time when the applet is suspended by // the system or when the applet does not have the keyboard focus). return elapsedTime; } public void setFrameCount(int max) { // If you want your animation to loop through a set of frames over // and over, you should call this routine to set the frameCount to // the number of frames in the animation. Frames will be numbered // from 0 to frameCount - 1. If you specify a value <= 0, the // frameNumber will increase indefinitely without ever returning // to zero. The default value of frameCount is -1, meaning that // by default frameNumber does NOT loop. if (max <= 0) this.frameCount = -1; else frameCount = max; } public void setMillisecondsPerFrame(int time) { // Set the approximate number of milliseconds to be used for each frame. // For example, set time = 1000 if you want each frame to be displayed for // about a second. The time is only approximate, and the actual display // time will probably be a bit longer. The default value of 40 is // probably OK for a game. millisecondsPerFrame = time; } public void setMinimumSleepTime(int time) { // An applet must allow some time for the computer to do other tasks. // In order to do this, the animation applet "sleeps" between frames. // This routine sets a minimum sleep time that will be applied even if // that will increase the display time for a frame above the value // specified in setMillisecondsPerFrame(). The parameter is given in // milliseconds. The default value is 10. You can set this to // any positive value. if (time <= 0) minimumSleepTime = 1; else minimumSleepTime = time; } public void setFocusBorderColor(Color c) { // Set the color of the three-pixel border that surrounds the applet // when the applet has the keyboard focus. The default color is cyan. focusBorderColor = c; } // This rest of this file is private stuff that you don't have to know about // when you write your own animations. private int frameNumber = 0; private int frameCount = -1; private int millisecondsPerFrame = 40; private int minimumSleepTime = 10; private long startTime; private long oldElapsedTime; private long elapsedTime; private Thread runner; private Image OSC; private Graphics OSG; private final static int GO = 0, SUSPEND = 1, TERMINATE = 2; private int status = SUSPEND; private int width = -1; private int height = -1; private boolean focussed = false; // set to true when the applet has the keyboard focus Color focusBorderColor = Color.cyan; public void init() { setBackground(Color.gray); // Color used for border when applet doesn't have focus. setForeground(Color.red); setFont(new Font("SanSerif",Font.BOLD,14)); addFocusListener(this); addKeyListener(this); addMouseListener(this); doInitialization(getSize().width - 6, getSize().height - 6); } synchronized public void start() { // Called by the system when the applet is first started // or restarted after being stopped. This routine creates // or restarts the thread that runs the animation. Also, // if focussed is true, then the animation will start. So // we should start the timing mechanism by setting startTime == 1. if (runner == null || !runner.isAlive()) { runner = new Thread(this); runner.start(); } status = GO; if (focussed) startTime = -1; // signal to run() to compute startTime notify(); } synchronized public void stop() { // Called by the system to suspend the applet. Suspend the // animation thread by setting status to SUSPEND. // Also, update oldElapsedTime, which keeps track of the // total running time of the animation time between // calls to start() and stop(). Also, if focussed is // true, then the animation will change state from running // to paused, so we should record the elapsed time. if (focussed) oldElapsedTime += (System.currentTimeMillis() - startTime); status = SUSPEND; notify(); } public void destroy() { // Called by the system when the applet is being permanently // destroyed. This tells the animation thread to stop by // setting status to TERMINATE. if (runner != null && runner.isAlive()) { synchronized(this) { status = TERMINATE; notify(); } } } public void update(Graphics g) { // Called by system when applet needs to be redrawn. paint(g); } synchronized public void paint(Graphics g) { // Draw the current frame on the applet drawing area. If the // applet has focus, draw a cyan border around the frame. Otherwise, // draw a message telling the user to click on the applet to // activate it. if (width != getSize().width || height != getSize().height) { // if size has changed, recreate frame doSetup(); if (OSC != null) drawFrame(OSG,width-6,height-6); } if (OSC == null) { // if not enough memory for OSC, draw an error message g.setColor(getBackground()); g.fillRect(0,0,width,height); g.setColor(getForeground()); g.drawString("Sorry, out of Memory!", 10,25); return; } g.drawImage(OSC,3,3,this); if (focussed) // Draw a 3-pixel border. If the applet has the g.setColor(focusBorderColor); // focus, draw it in focusBorderColor; otherwise, else // draw it in the background color. g.setColor(getBackground()); g.drawRect(0,0,width-1,height-1); g.drawRect(1,1,width-3,height-3); g.drawRect(2,2,width-5,height-5); if (!focussed) { // If the applet does not have the focus, g.setColor(getForeground()); // print a message for the user. g.drawString("Click to activate",10,height-12); } } // end paint private void doSetup() { // creates OSC and graphics context for OSC width = getSize().width; height = getSize().height; OSC = null; // free up any memory currently used by OSC before allocating new memory try { OSC = createImage(width-6,height-6); OSG = OSC.getGraphics(); OSG.setColor(Color.black); OSG.setFont(new Font("Serif",Font.PLAIN,12)); } catch (OutOfMemoryError e) { OSC = null; OSG = null; } } public void run() { // Runs the animation. The animation thread executes this routine. long lastFrameTime = 0; while (true) { synchronized(this) { while (status == SUSPEND || !focussed) { try { wait(); // animation has been suspended; wait for it to be restarted } catch (InterruptedException e) { } } if (status == TERMINATE) { // exit from run() routine and terminate animation thread return; } if (width != getSize().width || height != getSize().height) // check for applet size change doSetup(); if (startTime == -1) { startTime = System.currentTimeMillis(); elapsedTime = oldElapsedTime; } else elapsedTime = oldElapsedTime + (System.currentTimeMillis() - startTime); if (frameCount >= 0 && frameNumber >= frameCount) frameNumber = 0; if (OSC != null) drawFrame(OSG,width-6,height-6); // draw current fram to OSC frameNumber++; } long time = System.currentTimeMillis(); long sleepTime = (lastFrameTime + millisecondsPerFrame) - time; if (sleepTime < minimumSleepTime) sleepTime = minimumSleepTime; repaint(); // tell system to redraw the applet to display the new frame try { synchronized(this) { wait(sleepTime); } } catch (InterruptedException e) { } lastFrameTime = System.currentTimeMillis(); } } synchronized public void focusGained(FocusEvent evt) { // The applet now has the input focus. Set focussed = true and repaint. // Also, if both (focussed && status == GO), the animation will start, // so we have to restart the timing utility by setting startTime = -1; focussed = true; repaint(); // redraw with cyan border if (status == GO) startTime = -1; // signal to run() to compute startTime notify(); } synchronized public void focusLost(FocusEvent evt) { // The applet has lostthe input focus. Set focussed = false and repaint. // Also, if both (focussed && status == GO) were previously true, then // the animation will be stopped at this point, so we should record the time. focussed = false; repaint(); // redraw without cyan border if (status == GO) oldElapsedTime += (System.currentTimeMillis() - startTime); notify(); } public void mousePressed(MouseEvent evt) { // Request the input focus when the user clicks on // the applet. (On most platforms, there is no need // for this. However, Sun's own Java implementation // requires it.) requestFocus(); } public void mouseEntered(MouseEvent evt) { } // Required by the public void mouseExited(MouseEvent evt) { } // MouseListener public void mouseReleased(MouseEvent evt) { } // interface. public void mouseClicked(MouseEvent evt) { } } // end class AnimationApplet