import java.applet.*; import java.awt.*; import java.awt.event.*; /** * This class defines the applet, and sets up the Model and View objects. * This program is more complex than it needs to be, because it enforces * a strict separation between the model (the part that does the actual * computation), the display, and the controls in the user interface (the * Start/Stop button). This is called the MVC (Model-View-Controller) * pattern, and while it may be overkill for this example, MVC provides an * excellent basis for more complex animations. */ public class Controller extends Applet { // Declare components here, where they are visible to inner classes Panel buttonPanel = new Panel (); Button runButton = new Button (" Start "); // Create the model (the ball) and the view (the animation) Thread animation; Model model = new Model(); View view = new View(); /** * Lays out the applet components, starts a new thread for the * view to do its animation, and gives the model, view, and * controller access to one another. */ public void init() { // Lay out components setLayout(new BorderLayout()); this.add(BorderLayout.CENTER, view); buttonPanel.add (runButton); buttonPanel.setBackground(Color.lightGray); this.add (BorderLayout.SOUTH, buttonPanel); // Get applet parameters and put them where they belong view.delay = Integer.valueOf(getParameter("delay")).intValue(); // Tell the view about the model and the controller view.model = model; view.controller = this; // Get everything set up for the view BEFORE starting its Thread view.alive = true; view.okToRun = false; // Give the view its own thread in which to do the animation animation = new Thread(view); animation.start(); // Attach actions to components runButton.addActionListener (new ActionListener () { public void actionPerformed (ActionEvent event) { if (view.okToRun) { view.okToRun = false; showStatus ("Animation is stopped."); runButton.setLabel (" Start "); } else { view.okToRun = true; showStatus ("Animation is running."); runButton.setLabel (" Stop "); } }}); } /** * Finds the size of the canvas (this can't be done in init() * because the canvas isn't ready yet) and gives the animation * permission to run. */ public void start() { model.xLimit = view.getSize().width - model.BALL_SIZE; model.yLimit = view.getSize().height - model.BALL_SIZE; } /** * Withdraws permission for the animation to run. */ public void stop() { view.okToRun = false; } /** * Tells the animation thread to stop running. */ public void destroy() { view.alive = false; } } // ------------------------------------------------------------------------------------- /** * This class models the action of a bouncing ball. It is important to note * that this class is independent of the GUI. For this animation, the * Model does need to know the size of the box that the ball is bouncing * around in, and this is set from outside. Similarly, it provides a * makeOneStep() method so that the model can be controlled from outside the * class; but neither of these requires the model to know anything at all * about the rest of the program */ class Model { final int BALL_SIZE = 20; int xPosition = 0; int yPosition = 0; int xLimit, yLimit; int xDelta = 4; int yDelta = 3; /** * Moves the ball one step, by adding xDelta and yDelta to its xPosition * and yPosition, respectively, and checking for bounces off the "walls". */ void makeOneStep() { xPosition += xDelta; if(xPosition < 0 || xPosition >= xLimit) { xDelta = -xDelta; xPosition += xDelta; } yPosition += yDelta; if(yPosition < 0 || yPosition >= yLimit) { yDelta = -yDelta; yPosition += yDelta; } } } // ------------------------------------------------------------------------------------- /** * This class runs in a separate (animation) thread and controls all the * painting. */ class View extends Canvas implements Runnable { Controller controller; Model model; Dimension size = getSize(); int delay = 1000; int stepNumber = 0; boolean alive, okToRun; Image offscreenImage = null; Graphics offscreenGraphics = null; /** * Sets up an (almost) infinite loop to do the animation, and every * delay milliseconds asks the model to update itself, then draws the * result. */ public void run() { // "alive" will be true until the Applet is destroyed while(alive) { // "okToRun" will be true if the Applet is started and not stopped if(okToRun) { model.makeOneStep(); repaint(); // Note: uses Graphics for this Canvas, not for the Applet } // Control the speed of execution try { Thread.sleep(delay); } catch(InterruptedException e) {} } } /** * To avoid flicker, this method paints into the Graphics * of an offscreen Image. */ public void update(Graphics g) { // Don't do anything without permission from the controller if (!okToRun) return; // Create the offscreen Graphics if (offscreenImage == null) { offscreenImage = createImage(getSize().width, getSize().height); offscreenGraphics = offscreenImage.getGraphics(); } // Paint into the offscreen Graphics background(offscreenGraphics); paint(offscreenGraphics); // Copy the offscreen onto the screen g.drawImage(offscreenImage, 0, 0, null); } /** * Repaints the canvas based on information that it gets from the model. */ public void paint(Graphics g) { // Don't do anything without permission from the controller if (!okToRun) return; // Draw new ball g.setColor(Color.red); g.fillOval(model.xPosition, model.yPosition, model.BALL_SIZE, model.BALL_SIZE); // Display steps and position in the status line; // this is a good place to put debugging information. if(++stepNumber % 10 == 0) controller.showStatus("Step " + stepNumber + ", x = " + model.xPosition + ", y = " + model.yPosition); } /** * Paints a background on the given Graphics. */ void background(Graphics g) { int width = getSize().width; int height = getSize().height; int squareSize = 2 * model.BALL_SIZE; g.setColor(Color.blue); g.fillRect(0, 0, width, height); g.setColor(Color.green); for (int x = 0; x < width; x += 2 * squareSize) { for (int y = 0; y < height; y += 2 * squareSize) { g.fillRect(x, y, squareSize, squareSize); g.fillRect(x + squareSize, y + squareSize, squareSize, squareSize); } } } }