Step 4: The Clock Face

Our analog clock is going to have 60 tick marks arranged in a circle, with three clock hands inside. Now, the program would run that much faster if we didn't have to draw all that every second. So, we're going to have two drawing buffers. At the very beginning, we call createFace() to draw all those tick marks in one buffer (tickImage). Then, every second, we copy that onto our primary drawing buffer (faceBuffer), exchanging 60 drawing operations for one, draw the clock hands, and finally copy faceBuffer onto the screen.

Here is the promised createFace() method, which is called once in the lifetime of the program by init(). Add this right before the getArcPoint() method:

// Draw the tick marks in tickImage (called only once)
private void createFace() {
	tickImage = createImage(size, size);
	Graphics g = tickImage.getGraphics();

	// Erase the background
	g.setColor(backColor);
	g.fillRect(0, 0, size, size);

	// Draw the minute tick marks (first the 5-minute ones, then
	// the smaller ones in between)
	Point center;
	g.setColor(tickColor);

	for(int tick = 0; tick < 60; tick += 5) {
		center = getArcPoint(tick, halfSize - tickRadius);
		g.fill3DRect(center.x - tickRadius, center.y - tickRadius,
			tickSize, tickSize, true);
	}

	if(drawSmallTicks) {
		g.setColor(backColor);

		for(int tick = 1; tick < 60; tick++) {
			if(tick % 5 > 0) {
				center = getArcPoint(tick, halfSize - tickRadius);
				g.fill3DRect(center.x - 1, center.y - 1, 3, 3, false);
			}
		}
	}
}

createFace in Detail

The rather long createFace() method draws the clock face without the hands—once—in tickImage. You should be familiar with the first few statements. We first create the drawing buffer tickImage with createImage(), making it the same size as the clock applet. Then we get a Graphics object that's linked to it. After that, we erase the background by drawing a filled rectangle using the background color.

Next, we draw the tick marks that are at five-minute intervals:

Point center;
g.setColor(tickColor);

for(int tick = 0; tick < 60; tick += 5) {
	center = getArcPoint(tick, halfSize - tickRadius);
	g.fill3DRect(center.x - tickRadius, center.y - tickRadius,
		tickSize, tickSize, true);
}

We define a Point object called center and set the drawing color to tickColor. Then we draw the tick marks using a for loop. This statement executes a body repeatedly until a condition is true. The syntax is:

for(initial-statement; boolean-expression; update-statement)
	loop-body

First, initial-statement is executed. Then, if the Boolean expression is true, loop-body is executed followed by update-statement. The expression is evaluated again and the body executed, repeatedly, until the expression is false. Then the program resumes after the loop body.

So, take a look at our for statement here:

for(int tick = 0; tick < 60; tick += 5) { ... }

In the initial-statement part, we declare an integer called tick and set it to 0. Then the boolean-expression is evaluated. Obviously, tick is less than 60, so loop-body is executed (we'll get to that in a sec). Then, in the update-statement part, tick is incremented by 5. boolean-expression is evaluated again, and since tick is still less than 60, loop-body is executed. This continues on, giving us the sequence tick = 0, 5, 10, ... 55. After that, tick is incremented to 60, boolean-expression is false, and the loop exits.

Inside the loop, we call getArcPoint() to get the point at "clock angle" tick and radius halfSize - tickRadius, which puts the point right inside the edge of the clock, but leaving enough room for the tick mark.

Then we call fill3DRect() to draw a 3D rectangle, using the current color (tickColor):

g.fill3DRect(center.x - tickRadius, center.y - tickRadius,
	tickSize, tickSize, true);

This method takes five arguments: the coordinates of the upper-left corner, the width and height, and a boolean value. If the last parameter is true, the rectangle is drawn raised; if false, it's lowered. Here, the tick mark is centered on (center.x, center.y) and is a raised square with sides of length tickSize. Java automatically determines the shading of the beveled edges, so the box looks something like this: 3D tick mark.

After this we draw the tick marks that are at one-minute intervals, if drawSmallTicks is true:

if(drawSmallTicks) {
	g.setColor(backColor);

	for(int tick = 1; tick < 60; tick++) {
		if(tick % 5 > 0) {
			center = getArcPoint(tick, halfSize - tickRadius);
			g.fill3DRect(center.x - 1, center.y - 1, 3, 3, false);
		}
	}
}

We first change the drawing color to backColor. Then comes the loop: we count from 1 to 59, incrementing tick by one each time.

Inside the loop body, we check if tick is a multiple of 5 (so we won't overwrite the larger tick marks) using the modulo operator (%):

if(tick % 5 > 0)

The expression (a % b) returns the remainder when a is divided by b. So, if tick isn't a multiple of 5, tick % 5 will be greater than 0, and the code inside the if statement will then be executed:

center = getArcPoint(tick, halfSize - tickRadius);
g.fill3DRect(center.x - 1, center.y - 1, 3, 3, false);

We use getArcPoint() to get the center of the tick mark again, and call fill3DRect(). This time, it's a 3x3 square, and it's lowered (false).

If you want a sneak peek right now at your handiwork, make one small change to the paint() method:

public void paint(Graphics g) {
	g.drawImage(tickImage, 0, 0, null);
}

Compile the applet and open ClockApplet.html in your Web browser. You should now see a ring of 60 tick marks. Try changing the size of the applet to something less than 100 pixels—the smaller tick marks disappear. Be sure you change tickImage in the paint() method back to faceBuffer before continuing on to the next step:

public void paint(Graphics g) {
	g.drawImage(faceBuffer, 0, 0, null);
}

1