PolyArea is a GUI program written in Python that lets you to draw polygons and find their area. I developed PolyArea with the help of my son Saar and presented it in The PolyArea Project: Part 1, where I described the design and architecture of PolyArea, the implementation of the algorithm itself, and the software development practices we have used. In Part 2 I describe the graphic and UI aspect of PolyArea. It's worth noting that we have made an unorthodox choice when we picked up PyGame as our foundation library for graphics and UI.
But calculating the area of simple polygons (polygons with no holes) is not as simple as it seems. In addition, we wanted to have a nice UI where you can draw polygons on the screen and see a visualization of the algorithm in action. The complete source code is available here.
pygame is a set of Python modules for developing games and other multimedia programs that's built on top of the SDL library. The reason we picked PyGame as our foundation library for graphics and UI is simply that Saar had some familiarity with it from the book Hello World! Computer Programming for Kids and Other Beginners. PyGame is lightweight and doesn't use callbacks heavily, which makes it straightforwardto understand. However, you can still shoot yourself in the foot more easily than with a more structured framework.
PolyArea doesn't have a standard UI -- no menu, no toolbar, no buttons. You have a drawing surface where you can draw polygons with the mouse. There are a couple of keyboard commands to paint rectangles inside the finished polygon and to start over. That's about it. The accumulated area of the triangles that the polygon is composed of is displayed in the corner. PyGame is good enough for this purpose because it provides drawing functions and event handling for the mouse and keyboard.
The Main Loop
With PyGame you need to write your own main loop and spin the wheels. I wrote a generic main loop class (defined in mainloop.py) that maintains a list of display objects, handles events, handles collisions, updates the state of all the objects and renders the objects on the screen. This is pretty much what a game does. The generic main loop doesn't actually implement all these operations that some of them are specific to every game.
The class can be used as is for simple programs that just draw on the screen and don't care about events. You can just instantiate it, add objects, and call the run() method, which will continue to update() and render() your objects in each iteration.
The class can also be used as a base class for more complicated programs. You should subclass it with your your own class and implement the handle_events() and handle_collisions() methods.
The __init__() method lets you set the size of the window, caption, and background color. It defines an empty list of objects (the objects you put in this list will be rendered to the screen later). It initializes the PyGame library itself, creates a screen object (corresponds to the application window) with the requested size, fills it with the background object and sets the caption.
<code> class MainLoop(object): def __init__(self, screen_size, caption, background_color=(255, 255, 255)): self.objects =  pygame.init() self.screen = pygame.display.set_mode(screen_size) self.screen.fill(background_color) pygame.display.set_caption(caption) </code>
The run() method implements the main loop itself. It is an endless loop (while True). The program will exit though by responding to the quit event as you'll see later. The method calls in each iteration the methods for handling events (e.g., mouse clicks and key presses), updating objects, handling collisions (not used in the PolyArea program), and most importantly rendering the objects to the screen.
<code> def run(self): while True: self.handle_events(pygame.event.get()) self._update_objects() self.handle_collisions() self._render_objects() </code>
The implementation of the handle_events() just checks for the QUIT event and exits if it's present. As a generic main loop implementation there isn't much that can be done.
<code> def handle_events(self, events): """ """ if pygame.locals.QUIT in events: sys.exit() </code>
The handle_collisions() method is empty, which means you need to override it in your main loop subclass. It is not used in PolyArea because there are no moving objects. In a game, after all, the object positions have been updated you would want to handle collisions such as missiles hitting targets, characters running into walls, etc.
<code> def handle_collisions(self): """ """ </code>
The _update_objects() method is fully implemented. It simply iterates over all the objects and calls the update() method of each object. Nothing else is needed.
<code> def _update_objects(self): for o in self.objects: o.update() </code>
The _rednder_objects() method iterates over all the objects (after they have been updated and all the collisions were handled) and calls the render() method of each one passing the screen object (on which the objects will render themselves). The rendering typically takes place on an off-screen buffer and then the entire off-screen buffer is displayed as one image on the actual screen by calling the pygame.display.flip() function.
<code> def _render_objects(self): for o in self.objects: o.render(self.screen) pygame.display.flip() </code>