|User Interface Layout|
May 25, 2008, 3:12:49 am
It struck me recently just how wildly different user interface layout engines can be - and how they ultimate affect the way we build and design our user interfaces. I'd like to get my thoughts on the matter straight and writing them down is usually the best way. So I present to you four of the most common strategies I've seen for laying out content and widgets on a screen.
The worst kind of layout is one that doesn't reduce the overhead for the developer. This is the fixed layout - which has the developer place widgets on the screen at fixed positions that are defined usually as percentages and pixel offsets. This is the kind of layout we have in VisualWorks and while it works for most widget layouts, it fails to capture the real intent of the widgets position and usually fails badly when font sizes change or widgets start to become dynamically resized.
Stacks and Grids
This is by far the most common and probably the oldest approach to laying out widgets on the screen. The premise is simple - most stuff we put on a screen are tiled vertically, horizontally or in a grid of vertical and horizontally spaced cells. Usually, in this sort of layout we have a Container of some sort and we put widgets inside the container. The container will then decide what layout algorithm its going to use on its immediate children. This kind of layout rarely alters the containers size based on the size of its children, but sometimes special cases are included.
Most forms are laid out in a grid, with labels in one column and widgets in a second column, evenly spaced vertically so that nothing overlaps. Often this grid will also be fixed sized - unless we're dealing with a grid widget, in which case the columns can be resized by the user and may even have the capacity to shrink-to-fit.
This kind of layout, while simple, is rather uninspiring and can lead to serious complexity when trying to achieve a more dynamic layout that alters and adjusts itself as font sizes change or field data grows the widget that contains it.
Stacks and grids are what you generally find in frameworks like Qt and GTK.
One of the more intriguing forms of user interface layout is the constraint based layout approach. The idea here is to describe the attachments between the widgets you're putting on the screen and let some sort of iterative engine calculate the ultimate position of each widget. The algorithm in question doesn't have to accept circular dependencies - but the more interesting ones do.
A constraint might be along the lines of, make widget1s top align with label1s top; make label2s top align with widget1s bottom; make widget2s top align with widget1s top. This is a 'glue' connection between two edges of a widgets bounding box. There are other kinds of constraints - such as springs, tensors and the like, but the basic premise is to have the user interface come to a balance. This sort of interface is very dynamic as it relies on the individual widgets explaining their intrinsic extent and using that to calculate the ultimate layout.
The big drawback with this kind of layout is how fiddly it is to build - you have to specify all these little connections everywhere. Some user interface builders will visually show you these interactions as visual springs and bars - but this quickly becomes hard to navigate and understand and will often draw the developers attention away from the significant details. It lacks the simplicity of Stacks and Grids, but gives the developer a much more sophisticated way of describing the layout of their widgets.
VisualAge Smalltalk had a constraint based layout algorithm, though it could not handle circular dependencies. I've seen a few physics engine type demos in VisualWorks that also use constraint based layout of visuals, but I've not seen them applied to actual user interface screens.
You are probably very familiar with flowed layout, but perhaps haven't even realized you were looking at it. In fact, you're looking at it right now - flow layout is the core concept of web browser rendering engines. Instead of laying out widgets by drilling in from the root of a tree to its leaves, the flow layout instead starts with the leaves and lays out breadth first, not depth first.
In this way, it turns widgets in to words, like you write in a word processor, widgets flow from left to right across the screen and wrap on to a new line when they run out of space. The concept of a line is important, because flow bases its layout concepts upon it. The widgets get aligned along the line as they are laid out - so if a widget is tall, then the other widgets on the same line will have a new baseline that matches the tall widget.
The idea of wrapping is also interesting, it declares loudly that we're not sure exactly how wide our area will be and we don't care how tall our area will be. This encourages scrolled areas or larger content areas contain all of the widgets. The concept also requires explicit hints to the layout engine as to what to do when - for example, when do we do the equivalent of the Enter key and start a new line? how should we align our content on a line.. vertically and horizontally? Essentially, flowed widget layout has the same concerns as a word processor.
At the heart of a flowed layout engine are implicit constraints - instead of writing all the rules to attach one widget to another and spring it to a new row when it runs out of space, those constraints are a given. The CSS rendering model is an example of flow - though it is not a perfect model. It favors vertical layout over horizontal and that is a flaw, it also has inconsistent rules with regards to when it should shrink an area arounds it content and when it should expand to fill the available space.
You'd think that the rules would be simple, eg: the width and height could be set to a fixed value, a percentage or told to shrink-to-fit. In fact, the CSS model does not let you explicitly request 'shrink-to-fit' and the percentage value depends on how the parent container was processed. So more can be done here.
There are some big advantages to taking liberties with the constraints model in flow: for example, you can tell the layout engine that it is vertically oriented, not horizontally oriented, or that it flows from back-to-front to achieve Bidi RTL layout. It also encourages a content view on the widgets, such that any particular widget might be editable - you will often not have a specific InputField widget, but instead of a Label with a border and its mode set to Editable. Alignment is something that is intrinsic in the actual layout algorithm itself, so it need not be a special property on an individual widget either.
So what is the end result of these four different layout models? Well, for one, they're all completely incompatible with each other. The Fixed layout approach is a root-to-leaf layout algorithm, one pass, fixed values, very simple to implement and program with. It generally requires the layout engine to know the percentage and fixed offsets for each and every widget so this information is usually stored on the widget itself.
The Stacks and Grids are usually implemented as special containers - you put your widgets in a VerticalStackContainer or the like to get a specific kind of layout happening, thus putting the layout decisions directly in to the widget hierarchy itself.
The constraints model requires that the entire container be laid out in its entirety, as a single operation - when one bit of bounds changes somewhere, the whole thing has to be recomputed to make sure the tensors and springs settle correctly.
Finally, the flow model requires that the widgets be processed like a document, doing the children before the containers and in sibling order. This is also a whole-layout sort of operation like the constraints model, however it can be done incrementally based on where a change has occured - flow never flows backward, only forward, so you only have to relayout stuff after the point of change.
What does this mean for a widget framework? Well, it means that any reasonable decisions about how to layout widgets must not actually be part of the widget framework proper. Instead, widgets need to be placed somewhere on the screen and be able to be moved somewhere else on the screen.. the layout engine must be separate from the widgets. I say must, but I really mean is - should; they -should- be separate if you want your widget framework to be able to do all the different kinds of layout described above.
Most widget frameworks (read: nearly all), specifically advocate one of the above approaches to widget layout. Some of them hardcode that ideal in to the way they build user interfaces - the best frameworks don't. The common frameworks often do, but will provide you an 'out' to break the rules. For example, in GTK or Qt, its easy enough to implement your own kind of Container.
Which of the above approaches do you find the most useful when building your user interfaces? and which of the above do you wish you could use on a regular basis but do not because your widget framework doesn't make them easy to do?
By on May 25, 2008, 11:26:18 pm
By on May 26, 2008, 12:12:21 am
By on May 26, 2008, 12:59:57 am
By  on May 26, 2008, 10:15:29 am
By Michael Lucas-Smith on May 26, 2008, 12:44:26 pm
By Jo Vermeulen on June 23, 2008, 9:55:12 am