
/*
    This file defines a class, BBCanvas, that is used in BoundingBallsApplet.
    An object belonging to the class BBCanvas is a "canvas" that displays
    a moving ball.  A separate thread is run to do the animation, and an
    off-screen canvas is used to improve its quality.  The ball starts out
    in the middle of the canvas, with a randomly chosen velocity.
*/


import java.awt.*;

class BBCanvas extends Canvas implements Runnable {

   int threadDelay = 40;   // Time, in milliseconds, inserted between frames.

   Image OSC = null;  // An "off-screen canvas", used for double buffering.
   Graphics OSG;      // A graphics object that can be used for drawing to OSC.
   int width, height; // The width and height of the canvas.

   boolean running = true;  // This state variable is "true" when the ball
                            // should be moving, and "false" when it is 
                            // standing still.

   double x;   // The left edge of the ball.
   double y;   // The right edge of the ball.

   double dx;  // The amount by which x changes from one frame to the next.
   double dy;  // The amount by which y changed from one frame to the next.

   Color ballColor = Color.red;  // The color used for the ball.

   int ballSize = 10;            // The diameter of the ball.


   BBCanvas() {  
          // Constructor merely sets the background color
          // (Note:  The Canvas's width and height are not
          //  established when the constructor is called,
          //  so the "off-screen canvas" can't be created here.
      setBackground(Color.black);
   }


   synchronized void initialize() {

          // initialize() is called from the run() method, defined below,
          // AFTER the off-screen canvas has been created ans the values
          // of width and height have already been determined

      x = width / 2;   // Put the ball at the middle of the canvas.
      y = height / 2;

      do {                            // Choose random velocity, not too small.
         dx = 20*Math.random() - 10;
         dy = 20*Math.random() - 10;
      } while (Math.abs(dx) < 2 && Math.abs(dy) < 2);

   }


   synchronized void doTimeStep() {

         // doTimeStep() is called over and over by the run() method
         // It updates variables, redraws the offscreen canvas, and
         // calls repaint() to make the changes visible on the screen.

      if (running == false)  // If the "running" state variable is false,
         return;             // then don't do anything.

      // First, erase the off-screen canvas by filling it with black

      OSG.setColor(Color.black);      
      OSG.fillRect(0,0,width,height);

      // Now, move the ball by adding dx to x and adding dy to y:

      x += dx;
      y += dy;

      // Next check whether the ball has hit one of the edges:

      if (x <= 0)           // Ball has hit left edge.
         dx = Math.abs(dx); // Make sure ball is moving right (with dx > 0)
      else if (x + ballSize >= width)  // Ball has hit right edge.
         dx = -Math.abs(dx);           // Make sure ball is moving left.

      if (y <= 0)           // Ball has hit top edge.
         dy = Math.abs(dy); // Make sure ball is moving down (dy > 0).
      else if (y + ballSize >= height) // Ball has hit bottom edge.
         dy = -Math.abs(dy);           // Make sure ball is moving up.

      OSG.setColor(ballColor);          // draw the ball (in its current location)
      OSG.fillOval((int)x, (int)y, ballSize, ballSize);

      repaint();  // Call repaint() to tell system to redraw screen

   }  // end doTimeStep()



   // -- Methods called from the applet in response to user actions, --
   // -- such as clicking on a button or checkbox.                   --


   synchronized void faster() {
         // This method makes the ball go faster by multiplying its
         // speed by 1.25.  (This is called from the applet.)
      dx = 1.25 * dx;
      dy = 1.25 * dy;
   }


   synchronized void slower() {
          // This method makes the ball go slower by multiplying its
          // speed by 0.8.  (This is called from the applet.)
      dx = 0.8 * dx;
      dy = 0.8 * dy;
   }


   synchronized void setRunning(boolean newRunning) {
        // Set the state variable "running" to the specified new value.
        // The ball stops moving when running is false.
        // This method is called from the applet.
      running = newRunning;
   }



   //------------------------ painting ------------------------------
   //
   // This should not have to be changed.

   synchronized public void paint(Graphics g) {  // Copies OSC to the screen.
      if (OSC != null)
         g.drawImage(OSC,0,0,this);
      else {
         g.setColor(getBackground());
         g.fillRect(0,0,size().width,size().height);
      }
   }

   public void update(Graphics g) {  // Simplified update method.
      paint(g);                      // (Default method erases the screen
   }                                 //       before calling paint().)

   //-------------------- thread stuff and run method -----------------
   //
   // It shoud not be necessary to change this.

   private Thread runner;
   
   void startCanvas() {  // This is called when the applet starts.
      if (OSC == null) { // Off-screen canvas must be created before starting the thread.
         width = size().width;
         height = size().height;
         OSC = createImage(width,height);
         OSG = OSC.getGraphics();
         OSG.setColor(getBackground());
         OSG.fillRect(0,0,width,height);
      }
      if (runner == null || !runner.isAlive()) {  // create and start a thread
         runner = new Thread(this);
         runner.start();
      }
   }

   void stopCanvas() {  // This is called when the applet is stopped
      if (runner != null && runner.isAlive())
         runner.stop();
   }

   void destroyCanvas() {  // This is called when the applet is destroyed.
      if (runner != null && runner.isAlive())
         runner.stop();
      runner = null;
   }

   public void run() {  // The run method to be executed by the runner thread.
      initialize();
      while (true) {
         try { 
            Thread.sleep(threadDelay);
         }
         catch (InterruptedException e) { }
         doTimeStep();
      }
   }  // end run()

}