art with code

2008-11-09

A simple C# analog clock with Mono and Cairo



In the previous post I said that I had to write my own analog clock app to get the look I wanted. Originally, I had written it in Ruby, using the Librend library, which drew the clock on an OpenGL texture using Cairo, and then displayed it. Today I thought to learn some C# and Gtk#, and rewrote the clock as a Mono application that draws in a Gtk window using Cairo.

I set up a GitHub project for the clock, it's at http://github.com/kig/simpleclock/tree/master

The source code is a documented 200 lines, so it's no big deal to read if you're interested. I'll walk through the perhaps most interesting bit below; the clock transform, which makes it simple to draw the clock parts.

To understand the clock transform, you first need to know how Cairo's coordinate system works. In Cairo's default transform the origin (i.e. 0,0) lies at the top-left corner of the canvas, Y grows down, and rotation increases clockwise from the right. In other words, it's the math 2D cartesian coordinate system with the Y-axis flipped.

What we want for the clock is to have the origin at the center of the clock face with rotation increasing clockwise from the top (i.e. 12:00.) That way, we can simply rotate by e.g. 2π(minute / 60) to draw the minute clock hand. Furthermore, we want to normalize the coordinates to run from -1.0 to 1.0 for the clock area, so that we can trivially scale the clock to different sizes without changing the drawing code.

Here's how I achieved that:

uint boxSize = Math.Min (width, height); // size of the clock box

// First, we center the clock box to the window.
cr.Translate ((width - boxSize) / 2.0, (height - boxSize) / 2.0);

// Then we scale the box so that -1.0 .. 1.0 spans the whole box.
cr.Scale (boxSize / 2.0, boxSize / 2.0);

// And move the origin to the center of the box.
cr.Translate (1.0, 1.0);

// Finally, rotate CCW by 90 degrees to make rotation 0 point up.
// We don't need to flip the rotation direction, because angle grows
// clockwise in the "Y grows down" default transform.
cr.Rotate (-Math.PI / 2.0);


Now drawing the hands is quite trivial, as you can see from the DrawMinuteHand -method:

void DrawMinuteHand (Context cr, uint minute)
{
double rot = (double)minute / 60.0; // => rot is between 0.0 and 1.0
cr.Save ();
cr.Rotate (rot * Math.PI * 2.0); // 2π(minute/60)
cr.Rectangle (0.0, -0.05, 0.8, 0.1); // draw the hand model
cr.Color = new Color (0, 0, 0);
cr.Fill ();
cr.Restore ();
}


To sum up my experiences from writing this little C# project: compiling programs with the Mono compiler is pretty nifty (not Ada-level nifty though, more like OCaml-nifty) and C# is straightforward enough. I was pleasantly surprised that I managed to get the program written in a couple hours :)

No comments:

Blog Archive