Sunday, October 23, 2005

It's been a while since I posted here. So, time to get caught up on what I have been doing in the recent past, and plan to do in the not too distant future.

Progress has been slow, about all I have done in the past 8 weeks is implement static text widgets, recode the editable text fields, and do some refactoring to support container widgets more correctly.

I'm currently implementing box and spacer widgets. Up to now, each widget/control I support has had to specify its own geometry (x, y, width, height) in markup. This can be a workable solution, but it has two problems.

First, it makes things harder on developers to come up with a nicely layed out user interface. By requiring developers to specify the x, y coordinates and dimensions of the elements that make up their UIs, much trial and error is usually involved in coming up with a suitable layout for a window or dialog.

Second, and more importantly, statically defined UIs are not adaptive. You might be asking, "adaptive to what?" Good question. For one, to changing requirements. Add a button, or any other control to a dialog, and you, as the developer, will likely have to go back to your code and reassign new positions and dimensions to existing controls to maintain a decent looking user interface. What a pain.

Layouts often need to be adaptive to runtime state changes, too, and this is what really makes dynamic layout a must-have in modern UIs. Examples: font sizes may change, requiring controls to use more (or less) real estate in a window, effecting the layout of other controls. Or, due to program state, controls may be hidden or removed completely (or, conversely, show or added). Or, the user may simply want to resize a window, and it is desireable for the UI to make best use of the available space.

Layout widgets like boxes, spacers, and grids are missing from the core legacy toolkits on the platforms we care about in this book: Win32, Xlib, and legacy MacOS Toolbox all pre-date the concept. MFC and Cocoa (if you are writing the code by hand), which are higher level and sit above their respective core GUI toolkits, also do not support dynamic layouts. Toolkits that do support dynamic layout include Qt, Gtk+, wxWidgets (and there are surely others).

There are a couple of ways a layout engine can implement dynamic layout. If the layout engine's widgets are layered above a native toolkit that implements a dynamic layout, then concrete platform implementations can be used. This is a good choice for the Linux version of my layout engine, because I am layering above Gtk+. On MacOS X and Windows, however, it makes more sense to implement the semantics of boxes, grids, and spacers locally. Of course, once a toolkit has the generic support, there is little need to rely on concrete toolkit implementations to fall back on. This means in my case, all of the container objects will be implemented by me.

It would probably help for me to describe what boxes, grids, and spacers are. In a nutshell, a box is a container widget that arranges its children either vertically (vertical boxes, or vboxes) or horizontally (horizontal boxes, or hboxes). As children are added, they are packed into the space owned by the box, how they are packed (left to right, for example) depends on what kind of box they are. One commonly sees horizontal boxes used to aggregate buttons (e.g., Ok, Cancel) at the bottom of a dialog. A wide variety of window layouts can be achieved simply thru the use of boxes (in concert with spacers, described below).

Grids, on the other hand, are very similar to tables in HTML, resembling a spreadsheet, if you will. A grid consists of rows and columns of cells. Each cell houses a child widget, and each child is typically given an equal amount of space within which to display itself. Grids are important, no doubt, but less commonly used that are boxes.

The final widget, the spacer, provides a very important role. Spacers are fillers that exist between widgets in a box. As the size of the box expands (or contracts), spacers change their size in order to fill in the space that remains. As an example, consider a dialog with a horizontal box that contains two buttons, Ok and Cancel. The UI designer of the project would like the two buttons arranged such that they always are always centered in the dialog regardless of the dialog size. By adding a spacer to the left of the Ok button, and another to the right of the Cancel button, and by instructing each spacer to take up half of the space that remains in the box, we can achieve this effect.

A drawing would make this more clear perhaps. And I will provide one (or more) at a later time.

More will be posted when I get a decent implementation of boxes and spacers implemented in my toolkit (I'll probably leave grids as an exercise to the reader).