Lecture 8: Getting Mouse Input in Graphical Applications. Basic animations
The last function we defined last time describes what we do when the mouse
is pressed. We know that it is invoked by the run time system when the mouse
button is pressed and at invocation time it receives the x
and
y
coordinates of the mouse pointer in its two parameters.
This function tries to find a circle that has the mouse pointer inside. To find the circle we loop through the array of references and ask each circle if the two coordinates are inside it.public void mousePressed(int x, int y) { for (int i = 0; i < radii.length; i++) if (circles[i].inside(x, y)) { selectedCircle = circles[i]; break; } }
So we need to define an inside()
method:
This will be an instance method part of theboolean inside(int xMouse, int yMouse) { if (Math.sqrt((x + radius - xMouse) * (x + radius - xMouse) + (y + radius - yMouse) * (y + radius - yMouse)) <= radius) return true; else return false; }
Circle
class,
and it will return either true
or false
depending
on whether the mouse is inside or outside of the circle.
We also need to define and use a Circle
variable
in which we will keep a reference to the first circle that replies withCircle selectedCircle;
true
when asked if the mouse pointer is inside
it. Note that this should be null
if no Circle
is currently selected. Notice that if more than one circle qualifies we select the one that has the smallest index in the array of circles that we maintain.
We also need to point out that we loop for radii.length
number of times, and we do that relying on the fact that the arrays of
radii
, xCoords
, yCoords
and
colors
are parallel arrays with the same length.
For this example the paint()
method of the application
(Step3
) will be the same as the one for the application we
developed at the previous stage:
public void paint(Graphics g) { for (int i = 0; i < radii.length; i++) circles[i].draw(g); }
For the drawing routine we will use a different approach though, in that
we will make use of the XOR
painting mode. In this mode the
circle may appear in a slightly different color but when drawn twice the
background is restored, so it's a very cheap (fast) way of simulating
the movement of an object against a certain background.
We will have more to say about this later.void draw(Graphics g) { g.setColor(color); g.setXORMode(Color.black); g.fillOval(x, y, 2 * radius, 2 * radius); }
Now we need to define the other two methods that handle mouse input.
If we drag the mouse and a circle is selected we need to have it follow the mouse pointer.
So we need to define apublic void mouseDragged(int x, int y) { if (selectedCircle != null) selectedCircle.moveTo(getGraphics(), x, y); }
moveTo
method as an instance method
in the Circle
class. I think the lab notes call this method
jumpTo
but a little change of notation never hurt anyone.
You notice that we first erase the circle by redrawing it at the same exact location and then drawing it fresh at the current location of the mouse pointer. (We remember that drawing invoid moveTo(Graphics g, int newX, int newY) { draw(g); x = newX - radius; y = newY - radius; draw(g); }
XOR
mode twice in a
row has the effect of restoring the background to what it was before we drew
there for the first time).
You should also note that we need to subtract the radius
from the current mouse pointer's position because we want the circle to
move along with the mouse as if we would be holding it by its center,
and the x
and y
that we store as instance
variables are in fact the coordinates of the lower left corner of the
circle's bounding rectangle (top left corner because the coordinates on
the screen are reversed along the y axis).
When the mouse is released, and if there is a circle being moved at the time
(that is, if the selectedCircle
variable is not null
)
then we need to place the circle at that location and forget about carrying
the circle around any more.
And we're done. Here's the framework ofpublic void mouseReleased(int x, int y) { if (selectedCircle != null) { selectedCircle.moveTo(getGraphics(), x, y); selectedCircle = null; } }
Step3
:
Since the coordinates change but the radius and the color don't (at least in our examples here) we decide to make this distinction in the names of the parameters passed to the constructor. So we need to make use of theimport java.awt.*; import BreezyGUI.*; public class Step3 extends GBFrame { Color[] colors = ... ; int[] xCoords = ... ; int[] yCoords = ... ; int[] radii = ... ; public void paint(Graphics g) { for (int i = 0; i < radii.length; i++) circles[i].draw(g); } int numberOfCircles = 6; Circle[] circles = new Circle[numberOfCircles]; Step3() { for (int i = 0; i < numberOfCircles; i++) { circles[i] = new Circle(...); // same as in Step2 } } Circle selectedCircle; public void mousePressed(int x, int y) { ... } public void mouseDragged(int x, int y) { ... } public void mouseReleased(int x, int y) { ... } public static void main(String[] args) { Frame f = new Step3(); f.setSize(500, 300); f.setVisible(true); } } class Circle { private int x, y, radius; private Color color; Circle( int xInitially, int yInitially, int radius, Color color) { x = xInitially; y = yInitially; this.radius = radius; this.color = color; }
this
variable to initialize the last two.
void draw(Graphics g) { ... } void moveTo(Graphics g, int newX, int newY) { ... } boolean inside(int xMouse, int yMouse) { ... } }
Threads A thread is a single sequential flow of control within a process.
A single process can have multiple concurrently executing
threads. Java provides a Thread
class for threads just as it
provides a String
class for strings, etc., in the standard
Java class libraries.
Here's a short example:
Enough about threads, let's get back totucotuco.cs.indiana.edu% vi ThreadsTest.java tucotuco.cs.indiana.edu% cat ThreadsTest.java public class ThreadsTest { public static void main(String[] args) { PingPong a = new PingPong("Ping", 300); PingPong b = new PingPong("Pong", 700); b.start(); a.start(); } } class PingPong extends Thread { private String word; private int delay; PingPong (String w, int d) { word = w; delay = d; } public void run() { while (true) { try { System.out.println(word + "..."); sleep(delay); } catch (InterruptedException e) { return; } } } } tucotuco.cs.indiana.edu% javac ThreadsTest.java tucotuco.cs.indiana.edu% java ThreadsTest Pong... Ping... Ping... Ping... Pong... Ping... Ping... Pong... Ping... Ping... Pong... Ping... Ping... ^Ctucotuco.cs.indiana.edu%
Step 4
. We need to write a function that describes a simulation step:
This is one simulation step only. Its name is probably unfortunate. We should have called itpublic void run() { Graphics g = getGraphics(); for (int i = 0; i < numberOfCircles; i++) circles[i].advance(g); repaint(); }
simulationStep()
or something of that kind, to
avoid any confusion with any terminology for threads. We'll call this repeatedly, every 300 milliseconds, from a driver thread.
class Driver extends Thread { Step4 myFrame; Driver (Step4 passedFrame) { myFrame = passedFrame; } public void run() { while (true) { try { sleep(300); myFrame.run(); } catch (InterruptedException e) { } } } }
Step 4
's main
will initialize a Driver
and start()
it.
We need to devinepublic static void main(String[] args) { Step4 f = new Step4(); f.setSize(500, 300); f.setVisible(true); Driver d = new Driver(f); d.start(); }
advance()
for the simulation.
Each circle should know how to advance()
so it's an
instance method.
The part invoid advance(Graphics g) { int newX, newY; newX = currentX + deltaX; if (newX < Step4.LOW_X) { newX = Step4.LOW_X + (Step4.LOW_X - newX); deltaX = -deltaX; } if (newX + radius * 2 > Step4.HIGH_X) { newX = Step4.HIGH_X - 2 * radius - (newX + radius * 2 - Step4.HIGH_X); deltaX = -deltaX; } newY = currentY + deltaY; if (newY < Step4.LOW_Y) { newY = Step4.LOW_Y + (Step4.LOW_Y - newY); deltaY = -deltaY; } if (newY + radius * 2 > Step4.HIGH_Y) { newY = Step4.HIGH_Y - 2 * radius - (newY + radius * 2 - Step4.HIGH_Y); deltaY = -deltaY; } moveTo(g, newX, newY); }
blue
controls
the bouncing and will be explained in class.
We need to define the constants in Step4
for the bounding
rectangle of our simulation.
Thepublic final static int LOW_X = 30, LOW_Y = 30, HIGH_X = 400, HIGH_Y = 300;
paint()
changes only slightly:
The only thing that's left is to describe how thepublic void paint(Graphics g) { g.setColor(Color.black); g.drawRect(LOW_X - 1, LOW_Y - 1, HIGH_X+1 - LOW_X, HIGH_Y+1-LOW_Y); for (int i = 0; i < numberOfCircles; i++) circles[i].draw(g); }
Circle
class is representing movement.
In the snippet below we use currentX
, and currentY
for the location variables and deltaX
and deltaY
for
the direction of movement.
There are eight directions of movement and they correspond to adding 1 or subtracting 1 from the current values of the coordinates. The 9th case is the one when the circle doesn't move, so the coordinates are changed withprivate int currentX, currentY, radius; private int deltaX, deltaY;
0
each one of them (they, in fact, remain unchanged). Here's the initialization part:
Since we no longer useCircle( int xInitially, int yInitially, int radiusInitially, Color colorInitially) { currentX = xInitially; currentY = yInitially; radius = radiusInitially; color = colorInitially; deltaX = (int)(Math.random() * 3) - 1; // { -1, 0, 1} deltaY = (int)(Math.random() * 3) - 1; // { -1, 0, 1} if (deltaX == 0 && deltaY == 0) { // everybody should be moving switch (((int)Math.random() * 8 + 1)) { case 1: deltaX = -1; deltaY = -1; break; case 2: deltaX = -1; deltaY = 0; break; case 3: deltaX = -1; deltaY = 1; break; case 4: deltaX = 1; deltaY = -1; break; case 5: deltaX = 1; deltaY = 0; break; case 6: deltaX = 1; deltaY = 1; break; case 7: deltaX = 0; deltaY = -1; break; case 8: deltaX = 0; deltaY = 1; break; default: break; } } }
XOR
mode of painting
the image will have a bit of flickering. We can fix this in several ways (for example doing double buffering, but that's outside of the scope of what we want to do now) but at least thevoid draw(Graphics g) { g.setColor(color); g.fillOval(currentX, currentY, 2 * radius, 2 * radius); }
moveTo
method is shorter
since all drawing happens invoid moveTo(Graphics g, int newX, int newY) { currentX = newX; currentY = newY; }
paint()
.
You now have everything you need to put together Step 3
and Step 4
to finish your assignment.