Circles in Java

Once I found a question on the web: How to make circles in Java, so that you can drag them using the mouse? The original question was a bit more complex - the author wanted to create some diagram editor - but this was the essence.

In Java, there is no "circle" graphical component that we could just create, add to a dialog window, and set it to be draggable by mouse. (At least not in Java SE 1.7.) Perhaps we could create it, but we don't need that for a simple program. It is enough to put a canvas into the window, and draw the circles on the canvas using the "drawOval" command.

How will we drag the circles using the mouse? When pressing the mouse button, we will look whether the mouse cursor points at any of the circles. If yes, we will remember it, and then after any mouse movement we will move the circle by the same vector; until the mouse button is released. How will we find out whether the mouse cursor is in one of the circles? The Pythagorean theorem; x,y of the clicked point; x,y, of the center of the circle; and the diameter of the circle.

After adding a few technical details I realised that this is the kind of a program explained best by writing it first, because you would have to write almost all of it while explaining, anyway. And when you write it first, you can be sure that you didn't forget anything critical, and that there is no typo.

Writing the program from scratch (without copying an existing code or using other than standard libraries) took me about fifteen minutes, and it was a rather nice exercise showing that I already travelled a few miles in Java. Before publishing it here, I only modified the program slightly: 1) I put the window creation code, originally in a separate function, directly into the "main" function, reducing the example by seven unnecessary lines; 2) I renamed the variables related to circle dragging, to make the concept clearer; and 3) I added a few lines to run the program inside a web page, so that I could put it directly into this page. The rest of the program stayed almost the same as I wrote it within those fifteen minutes, trying to have it ready as soon as possible.

ADDED: Only later I found that the program contained a mistake, which caused the application window to sometimes appear narrow, without the content. The mistake is fixed, the explanation is below.

If I tried to make it into a nicer example, I would probably change a few things: I would extract some parts of the code into separate functions, so that I wouldn't have the code indented across half the screen (a condition in a condition in a method in an anonymous class in a method in a class in a class). And I certainly wouldn't keep everything in one class: I would split it to multiple classes and those perhaps to multiple packages; I would separate the two-step painting from the contents that are painted; I could calculate which parts of the screen are necessary to repaint at each change, to make it faster; I could add a hierarchy of objects, to allow painting and dragging other objects, not just circles; I could fix the code to make it delete the higher object, not the lower one, after clicking on overlapping objects... and the resulting code would be twice or thrice as long, and I am not quite sure that it would make the readers more happy. So let it be as it is now, I guess it is not so bad. And when everything is in the same class, at least you can easier copy it from the web.

package sk.bur.blog;

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class Circles {

	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				Display display = new Display();
				display.init();
				final Frame frame = new Frame("Circles");
				frame.add(display);
				frame.setResizable(false);
				frame.pack();
				frame.addWindowListener(new WindowAdapter() {

					@Override
					public void windowClosing(WindowEvent e) {
						frame.dispose();
					}

				});
				frame.setVisible(true);
			}

		});
	}

	public static class CirclesApplet extends Applet {

		@Override
		public void init() {
			SwingUtilities.invokeLater(new Runnable() {

				@Override
				public void run() {
					Display display = new Display();
					display.init();
					add(display);
				}

			});
		}

	}

	static class Display extends Canvas {

		private Collection<Circle> circles = new ArrayList<Circle>();
		Circle draggedCircle = null;
		int draggedOriginalCenterX;
		int draggedOriginalCenterY;
		int draggedStartX;
		int draggedStartY;
		Random random = new Random();
		private Image buffer = null;

		Circle findCircle(int x, int y) {
			for (Circle circle : circles) {
				if (circle.contains(x, y)) {
					return circle;
				}
			}
			return null;
		}

		void init() {
			setSize(400, 300);
			addMouseListener(new MouseAdapter() {

				@Override
				public void mousePressed(MouseEvent e) {
					Circle clickedCircle = findCircle(e.getX(), e.getY());
					if (MouseEvent.BUTTON1 == e.getButton()) {
						// start dragging
						if (null != clickedCircle) {
							draggedCircle = clickedCircle;
							draggedOriginalCenterX = draggedCircle.centerX;
							draggedOriginalCenterY = draggedCircle.centerY;
							draggedStartX = e.getX();
							draggedStartY = e.getY();
						}
					}
					if (MouseEvent.BUTTON3 == e.getButton()) {
						// add or remove a circle
						if (null != clickedCircle) {
							circles.remove(clickedCircle);
						} else {
							Circle circle = new Circle();
							circle.centerX = e.getX();
							circle.centerY = e.getY();
							circle.radius = 10 + random.nextInt(10);
							// some nice colors
							circle.fillColor = Color.getHSBColor(
							  random.nextFloat(),
							  (1 + random.nextFloat()) / 2,
							  (1 + random.nextFloat()) / 2
							);
							circles.add(circle);
						}
						repaint();
					}
				}

				@Override
				public void mouseReleased(MouseEvent e) {
					if (MouseEvent.BUTTON1 == e.getButton()) {
						// stop dragging
						draggedCircle = null;
					}
				}

			});
			addMouseMotionListener(new MouseMotionAdapter() {

				@Override
				public void mouseDragged(MouseEvent e) {
					// dragging
					if (null != draggedCircle) {
						draggedCircle.centerX =
						  draggedOriginalCenterX - draggedStartX + e.getX();
						draggedCircle.centerY =
						  draggedOriginalCenterY - draggedStartY + e.getY();
						repaint();
					}
				}

			});
		}

		@Override
		public void paint(Graphics g) {
			if (null == buffer) {
				buffer = createImage(getWidth(), getHeight());
			}
			Graphics g2 = buffer.getGraphics();
			g2.setColor(Color.WHITE);
			g2.fillRect(0, 0, getWidth(), getHeight());
			for (Circle circle : circles) {
				g2.setColor(circle.fillColor);
				g2.fillOval(
				  circle.centerX - circle.radius, circle.centerY - circle.radius,
				  2 * circle.radius, 2 * circle.radius
				);
			}
			g.drawImage(buffer, 0, 0, null);
		}

		@Override
		public void update(Graphics g) {
			// we repaint the whole screen
			paint(g);
		}

	}

	static class Circle {

		public int centerX;
		public int centerY;
		public int radius;
		public Color fillColor;

		int sqr(int i) {
			return i * i;
		}

		boolean contains(int x, int y) {
			return sqr(x - centerX) + sqr(y - centerY) <= sqr(radius);
		}

	}

}

Use RIGHT mouse button to add or remove circles.
Use LEFT mouse button to drag the circles.

Java applet with circles.

Download the program, including the source code.

And now, let's go through the individual parts of the program.

The program consists of four non-anonymous classes. The main "Circles" class contains the "main" method, where the program starts (after clicking on the program icon, or writing a command in the command line). The "CirclesApplet" class allows to run the program as an applet, that is, as a part of a web page. The "Display" class represents an area where the circles are displayed, and where we can drag them. (In case of an applet, it is the whole area of the applet, but in case of an application, the area is surrounded by a window frame.) The "Circle" class contains data about one circle: position of the center, radius, color. We also have two anonymous classes to close the window and to handle the mouse actions.

The "main" method and the "CirclesApplet" class are used to start the program in different environments: as an application on the computer, or as an applet on a web page. One of them is used, never both of them at the same time. But I added them both to the program to make an example how easy it is in Java to create a program capable of running both as an application and on a web page. (If we only want an application, we can remove the "CirclesApplet" class, and if we only want an applet, we can remove the "main" method.) In both cases a new "Display" object is created and added to the application or to the applet, and then in both cases it works exactly the same way. For application, the code to close the window is added. The window is prevented from being resized, which will make some parts of the program a bit more simple; the size of the applet is specified in the web page.

Let's look at the "Circle" class. It contains four pieces of data, which together make a logical whole. There is also a "contains" function, determining whether a point is inside the given circle or not. Who paid attention during the math classes knows the definition of the circle (a set of points in the plane, with a distance from the center not greater than the diameter) and the Pythagorean theorem (the square of the hypotenuse is equal to the sum of squares of the legs; but in graphics often used as: the square of the distance of two points is equal to the sum of squares of differences of their respective coordinates). So we have the center of the circle and the position of the mouse cursor -- one X minus the other X, the result squared; one Y minus the other Y, the result squared; and if together it is not greater that the radius squared, then the mouse cursor is within the circle. To make the formula easier to read in the program, we make a helpful function "sqr".

And how the main part of the program, the "Display" class. First, it contains a collection of the existing circles, called "circles". (The program says "Collection", but as well it could be "List" or "Set".) Second, which circle is being dragged, that is "draggedCircle". If it is null, then we are not dragging a circle. Third, the original center of the circle before it was dragged, "draggedOriginalCenterX" and "draggedOriginalCenterY"; and the point we are dragging it from, "draggedStartX" and "draggedStartY". (If we started dragging the circle by its center, the original center and the start of dragging would be the same, but this way the program can remember if we drag the circle using some other point.) These four numbers are meaningful only if the "draggedCircle" variable is not null. We also have a random number generator, called "random", used to make the program a bit varied. And finally the auxiliary off-screen image, called "buffer", where we prepare everything we want painted on the screen, and then paint the whole result.

This part with painting into the auxiliary image is a bit complicated. If you want to know what would happen without it, remove from the program all lines containing the word "buffer", and in the "paint" method paint directly in the "g" variable. The screen will flicker while painting. Why? Because in each step we remove all the circles and paint them again; and this repeated removing and painting causes the flickering. Using an auxiliary "buffer" image we remove the flickering, by first preparing the whole image, and only then painting it on the real screen; in the next step we again prepare the whole image and only then paint it on the real screen -- so the real screen is never cleared, only updated.

Is this the only way to remove the flickering of the screen? I guess it isn't, but it was my first idea, it was relatively easy to program, and on today's computers it works relatively fast. You don't see the program slowing down when you use it, although it keeps repainting also the parts of the picture it does not have to. (And if you once make a game where many things are moving in different parts of the scree, repainting everything may be most simple.) When creating a "Display" object, we leave the "buffer" variable empty and only create the image then the "paint" method is called for the first time. The "createImage" command used to create the auxiliary image requires the given graphical component, the "Display" object, to be already displayed on the screen. (This method is called lazy initialization. There are also other commands to create an auxiliary image, but this was the first one that came into my mind.) Then we paint everything into the auxiliary image, using the "g2" variable, and only at the end we paint this whole auxiliary image to the real screen, using the "g" variable.

A few more words about how does the "Canvas" class work. If we want to paint freely on a part of the screen, we need to create a subclass of "Canvas", and redefine its "paint" method. The method is called when something needs to be painted; and that can happen for two reasons. First, if the operating system ruined a part of the screen; for example by hiding our window behind some other window which has been moved away now. Then the "paint" method is called. Second, when we want to repaint something; for example when we moved one of the circles. Then we call a "repaint" method, which calls the "update" method, which calls the "paint" method. (Yeah, it is a bit complicated for reasons I am not going to explain now. Just remember that from other parts of a program we always call "repaint", never "paint" or "update" directly.) The "update" method by default erases the given part of the screen and then calls "paint"; but we don't need any erasing, because we paint the whole area in the "paint" method; this is why we modify the "update" method to just call "paint".

The drawing of the circles is itself straightforward: cycle through the circles; set the color, call the "drawOval" command. Note that the parameters of the "drawOval" command are different from what you might expect; essentially, it is a rectangle containing our circle (or an ellipsis, in general), which is why as the beginning of the rectangle we use "centerX - radius" and "centerY - radius", and as both sides of the rectangle we use "2 * radius".

And now the program logic itself, the reactions on the mouse. 1) When the left mouse button is pressed, and there is a circle under the mouse cursor, we will memorize the following: the circle, its original center, the position of the mouse cursor when pressed. We find whether there is a circle under the mouse cursor by calling the "findCircle" method, which asks all the circles whether they containt the given point. 2) When the left mouse button is released, we set the "draggedCircle" variable to "null", which means that we are no longer dragging any circle. 3) When we drag the mouse, and there is a circle being dragged, we set the circle new coordinates and repaint the screen. 4) This wasn't a part of the original task, but the circles have to appear somehow, therefore: When the right mouse button is pressed, and there is no circle under the mouse cursor, we create a new circle with a random radius and random color; if there is a circle under the mouse cursor, we remove it.

What else? There are a few details in the program I haven't mentioned yet. 1) The "pack" method sets the window size precisely to fit its contents. If we create a "Display" object and set its size to 400×300, and then we add the object to the application window, calling the "pack" method will set the window size to 400×300 plus the width of the frame; the "setResizable(false)" method prevents the user from resizing the window later. Sure, a nice application should allow the user to change the window side, but it would make a program a bit more complicated. Not too much, but... you know, there are many improvements like this and each of them would make this article a bit longer. 2) The variable containing the application window is declared as "final Frame frame", where the word "final" means: I promise that I will not change the contents of this variable later. This is necessary to access the "frame" variable declared in a method from inside of an anonymous object. 3) Why did I split creating of the display to a "new Display()" constructor and a "display.init()" method? Why not put all of this into the constructor? In this case it was mostly a matter of preference, but generally it is better to not make the constructors too complicated; specifically not to give a reference of the new object to other objects, because when other objects access an object whose constructor didn't finish yet, weird errors can happen. 4) What's with those random number formular? Well, "10 + random.nextInt(10)" is a random integer number between 10 and 19, and "(1 + random.nextFloat()) / 2" is a random decimal number between 1/2 and 1. The first one is the radius of the circle, to avoid small circles of size of one pixel; the second one is the saturation and intensity of the color of the circle, to avoid white, gray, and black circles.

ADDED: And the mistake I have found later... it is related to multithreading and it would be a long explanation. To make it short, things related to the user interface (it does not matter whether AWT or Swing) should not be called directly from the "main" method (or from the "init" method in applet), but should be wrapped in something like "SwingUtilities.invokeLater". That will make them run a bit later, in a moment convenient for the user interface. This is not needed for methods which are themselves called from the user interface, such as "mousePressed" or "paint".

Now this was a really lot today, but on the other hand... you have the whole program here, with graphical interface, controlled by the mouse -- it is a graphical editor... and all this just in 150 lines of code, which for this kind of programs is really not much. At least you have a lot to think about! ;-)

viliam@bur.sk