// Animate.java // An applet that demonstrates a minimum-functionality // solution to the Animation program assignment (Assignment J2) // in CSE 341, Spring 1999, University of Washington. // Steve Tanimoto, 9 April 1999. // This can also be run as an application (without a browser). // The class definitions for this program are spread over 3 files. // 1. The file Animate.java, this one, contains class Animate // and class AnimationCanvas. // 2. The file AnimationObject.java, contains class AnimationObject, // and various inner classes of it, including DrawPanel and DrawCanvas. // 3. The file AnimationThread.java contains class AnimationThread. // Here is a description of the classes in this particular file: // Class Animate is the main applet class. It provides for setting // up the main layout, including two buttons at the top ("Create Object" // and "Go!"), the AnimationCanvas in the middle, and a panel at the // the bottom that will hold as many AnimationObjects' DrawPanels // as there is room for. Making the applet wider will allow more // AnimationObjects to be placed there. // The other top-level class defined in this file is AnimationCanvas. // A single instance of AnimationCanvas is where the animation takes place. // It provides methods that allow the user to draw a motion trajectory for // an AnimationObject, and it provides the animate method that sets up // the animation threads. // Various inner classes provide the methods that respond to button // activations and mouse dragging. import java.applet.*; import java.util.*; import java.awt.*; import java.awt.event.*; public class Animate extends Applet { public static void main(String args []) { Frame f = new Frame(); //Create a window. f.setSize(600,500); //Enlarge it. Animate myAnimate = new Animate(); //Make an applet instance. myAnimate .init(); //Initialize the applet. f.add(myAnimate ); //Put it in the window. f.show(); //Show the window. } // supports for re-visiting web page without re-initializing, // (but this may not work on all browsers). boolean initialized = false; // Where animation objects are drawn and selected: Panel menuPanel; // Where trajectory drawing and animation happen: AnimationCanvas animationCanvas; // Stores references to all the animation objects created by the user: Vector animationObjects; Applet theApplet; // Like "this" but accessible from inner classes. // Members that support assigning different colors to the trajectories // of different animation objects. Cycles through 5 different colors. int lastTrajectoryColor = 0; static Color colors[]; static final int NCOLORS = 5; public Animate() { // Constructor is really that of Applet. } public void init() { // Set up all the components and lay them out. if (initialized) return; // Avoid reinitialization initialized = true; setLayout(new BorderLayout()); Button createButton = new Button("Create Object"); Button goButton = new Button("Go!"); createButton.addActionListener(new CBActionListener()); goButton.addActionListener(new GBActionListener()); Panel commandButtonPanel = new Panel(); commandButtonPanel.add(createButton); commandButtonPanel.add(goButton); add(commandButtonPanel, BorderLayout.NORTH); animationCanvas = new AnimationCanvas(this); animationCanvas.init(); animationCanvas.setSize(500, 400); add(animationCanvas, BorderLayout.CENTER); menuPanel = new Panel(); menuPanel.setBackground(Color.red); menuPanel.setSize(500,300); menuPanel.setLayout(new FlowLayout()); add(menuPanel, BorderLayout.SOUTH); animationObjects = new Vector(); theApplet = this; colors = new Color[NCOLORS]; colors[0] = Color.red; colors[1] = Color.blue; colors[2] = Color.black; colors[3] = Color.magenta; colors[4] = Color.cyan; } Color newTrajectoryColor() { // Return the next color. return colors[lastTrajectoryColor++ % NCOLORS]; } class CBActionListener implements ActionListener { // This takes care of the "Create Object" button. public void actionPerformed(ActionEvent e) { AnimationObject ao = new AnimationObject (); ao.setTrajectoryColor(newTrajectoryColor()); animationObjects.addElement(ao); menuPanel.add(ao.dp); validate(); } } class GBActionListener implements ActionListener { // Handles the "Go!" button. public void actionPerformed(ActionEvent e) { animationCanvas.animate(animationObjects); } } } class AnimationCanvas extends Canvas { // Values used in drawing and painting the trajectories: static final int SMIDGEON = 5; static final int PEN_RADIUS = 2; int lastX, lastY; Animate theA; // a reference back to the applet. // Here we keep a ref to the animation object whose trajectory // is being drawn: AnimationObject theAO; AnimationCanvas(Animate theA) { // Constructor this.theA = theA; // records ref to applet. } // Register mouse handler for drawing trajectories: void init() { addMouseListener(new AnimationCanvasMouseHandler()); addMouseMotionListener(new AnimationCanvasMouseHandler()); } // Either paint the animation in its current state or paint // all the trajectories, if no animation is running. public void paint(Graphics g) { if (AnimationThread.animationInProgress()) { for (Enumeration theObjects = theA.animationObjects.elements(); theObjects.hasMoreElements(); ) { ((AnimationObject)theObjects.nextElement()).paintOnAnimationCanvas(g); } theAO.paintOnAnimationCanvas(g); } else { // Draw all the trajectories: setBackground(Color.green); for (Enumeration theObjects = theA.animationObjects.elements(); theObjects.hasMoreElements(); ) { AnimationObject ao = (AnimationObject) theObjects.nextElement(); if (! (ao == null)) { g.setColor(ao.getTrajectoryColor()); for (Enumeration dots = ao.trajectory.elements(); dots.hasMoreElements(); ) { Point dot = (Point) dots.nextElement(); g.fillOval(dot.x - PEN_RADIUS, dot.y - PEN_RADIUS, 2 * PEN_RADIUS, 2 * PEN_RADIUS); } } } } } public class AnimationCanvasMouseHandler extends MouseAdapter implements MouseMotionListener { public void mousePressed(MouseEvent e) { theAO = AnimationObject.currentAO; if (theAO == null) return; // Start a new trajectory for the selected object: theAO.trajectory = new Vector(); // Always draw something when mouse is first pressed. lastX = -1000; lastY = -1000; mouseDragged(e); } public void mouseReleased(MouseEvent e) { } public void mouseMoved(MouseEvent e) { } public void mouseDragged(MouseEvent e) { if (theAO == null) return; int x = e.getX(); int y = e.getY(); // If mouse has moved enough, then add a new point: if ( Math.abs(x - lastX) > SMIDGEON || Math.abs(y - lastY) > SMIDGEON) { theAO.trajectory.addElement(new Point(e.getX(), e.getY())); lastX = x; lastY = y; // Remember this for next time. repaint(); } } } void animate(Vector allAnimationObjects) { // Find the AnimationObject with the longest trajectory // so that it can be made the repainting thread. AnimationObject longest = null; int length = -1; for (Enumeration theObjects = allAnimationObjects.elements(); theObjects.hasMoreElements(); ) { AnimationObject ao = (AnimationObject) theObjects.nextElement(); int thisLength = ao.trajectory.size(); if (thisLength > length) { longest = ao; length = thisLength; } } AnimationThread aThread; int i = 0; boolean repaintingThread; // Create one thread per animation object and start them: for (Enumeration theObjects = allAnimationObjects.elements(); theObjects.hasMoreElements(); ) { AnimationObject ao = (AnimationObject) theObjects.nextElement(); System.out.println("Starting animation thread " + i++); if (ao == longest) repaintingThread = true; else repaintingThread = false; aThread = new AnimationThread(ao, this, repaintingThread); aThread.start(); } } }