Text layout that works properly?

by havoc

For the version of Mugshot we’re going to release shortly, we redid the UI (it looks something like this [link went away over the years, sorry…]). Amazingly, there’s no good cross-platform way to implement a custom UI along these lines. The old version of Mugshot uses an embedded IE control on Windows and a custom GTK+ widget on Linux.

The solution we’re trying is a quickly-hacked-up custom canvas. (See gtk-devel post, current subversion location.)

It turns out that One Laptop Per Child has similar requirements, see for example these mockups (there are also newer mockups but I can’t find a link to them). We broke HippoCanvas out of the Mugshot sources so the Sugar project could try using it and see if it works for them also.

A couple years ago, throwing a canvas together in a few days like this would have been way too much work; the reason it’s plausible today is that Cairo and Pango handle so many of the hard parts.

HippoCanvas has some nice improvements over GtkWidget. The best one: modernizing the layout system. It’s not too often one notices GTK’s breakage here, but sometimes it’s very visible. The two big changes in HippoCanvas layout are to add height-for-width and to distinguish between “minimum size” and “natural size.” (GTK’s “size request” defines a minimum size for a widget).

The natural size feature makes ellipsized text work properly, something that’s essentially impossible in GTK+ (without writing a custom canvas widget anyway). Here’s a real-world example problem we had before implementing natural size – implementing this with three GtkLabel widgets would have this same bug:

In the first shot, one of the text items is ellipsized while the other
gets extra space. In the second, both get extra space even though in a
real app you’d probably want all the extra space to be on the right
side of the window (left-aligning the text).

The problem is that GTK+ only supports a minimum size. To allow text
to ellipsize, you have to set the minimum size to zero (or something
small). However, this means you have to pack the text item in a box
such that it expands when there’s extra space… and that means GTK+
will add bogus whitespace as in the above screenshots. If you don’t
pack the item such that it expands, it will just always be the minimum
size (i.e. zero).

HippoCanvas allows items to have a minimum size and a natural size.
The algorithm is that if the layout box gets more than the minimum
size, first it brings all items up to their natural size (whether they
are “expandable” or not), and then it distributes whitespace among
expandable items.

So for example, here’s a test layout at its natural size:

And its minimum size:

And two points in between:

The algorithm brings smaller items up to natural size first, so the
yellow item on the right is fully visible while the two longer items
are still ellipsized. If you think about an example like the “Blah
blah blah blah – Havoc” images earlier, the idea is to display all of
“Havoc” first, and then as much of “Blah blah blah blah” as possible.

Expandable items only matter once everything has its natural size,
like this:

One way to understand the problem with GTK is that it doesn’t support
the “expand=0 ellipsize=1” behavior illustrated in these screenshots.

A downside of a more-complex layout system like this is that layout
containers are tougher to implement. However, this is the wrong thing
to optimize for, because virtually nobody implements layout
containers. GTK+ doesn’t have very many but still has too many of
them; we consolidated GtkFixed, GtkHBox, GtkVBox, and
GtkAlignment/GtkMisc into a single box
, and made all canvas items have xalign/yalign
properties. This already covers 98% of layout needs.

In the end, this kind of corner case probably doesn’t matter very
much; after all, HTML, Flash, Windows, GTK+, etc. are all very
successful despite missing/flawed solutions for layout. But it’s
nice to get things right in this new code.

(This post was originally found at http://log.ometer.com/2006-10.html#14)

My Twitter account is @havocp.
Interested in becoming a better software developer? Sign up for my email list and I'll let you know when I write something new.