[PEAK] Some notes on trellis.ui.layout
Phillip J. Eby
pje at telecommunity.com
Tue Oct 23 21:08:25 EDT 2007
Well, it looks like trellis.ui is going to see some attention before
trellis.db, and before the new recalc algorithm gets done.
Specifically, we'll be doing a trellis.ui.layout module that handles
various sorts of GUI layout calculations, beginning with elementary
"nested windows", space negotiation, and splitters.
A separate trellis.ui.text_layout will implement some simple text
widgets for testing purposes, so that you can use ASCII drawings in
doctests to verify the behavior of the layout framework visually,
rather than by testing a bunch of numbers.
We'll need some sort of TextDisplay object, that can be treated like
a 2D array of characters. Getting a 2D slice of a textscreen would
return a viewport, and setting a slice would update the content of
the display. It should be possible to do reads and writes that are
"out of bounds", so that you can take a slice of a display to get a
"window" that clips any writes outside that window. Taking a slice
of a slice should of course be possible.
Last, but not least, converting a TextDisplay to a string should
produce the appropriate ASCII output, and the internal structure of a
TextDisplay should be trellis-based so that a simple viewer can
reprint the display when it has been updated.
For space negotiation, we want to be able to lay out rows or columns
of objects according to some information about their preferred,
minimum, and maximum sizes in the appropriate dimension. The result
would be a series of offsets and allocated sizes, which then become
the viewports (co-ordinate-wise, but not necessarily clipping-wise)
for the contained objects.
(Note that this suggests that TextDisplay needs to distinguish
between viewport subslices, and clipping subslices...)
For splitters, a splitter will act as a space negotiator for its
children, except that the allocation calculation is only done when
the splitter's overall size is changed (i.e. due to a change in its
parent window size) and when the splitter is created. The rest of
the time, the space negotiation of its children is hardwired. (Note,
by the way, that the only difference between a horizontal and
vertical splitter is in which cells of the parent and child it
attaches to. If it attaches to "x" and "width" cells, it's a
horizontal splitter, for example.)
General-purpose space negotiation is of course more complex. If the
space to be allocated is equal to the sum of the preferred, minimum,
or maximum sizes, then the allocation is obvious. In all other
cases, there will be missing or leftover space to be accounted for,
and all the leftovers or missing bits must be handled, within some
degree of clipping. (That is, if co-ordinates are integers, then
fractional parts must be eliminated by picking where to dump or
remove the remainders.)
If the total space is less than the sum of the minimum sizes, then we
must presumably shrink our allocations according to some
proportion. If the space is greater than the sum of the maximum
sizes, then we must expand them in the same way.
If we are between the minimum and preferred, or preferred and
maximum, then we should either expand or shrink from the preferred
size, as is appropriate.
Or, to put it more simply, one could say that we give each component
its preferred size, and then proportionally allot the expansion or
shrinkage to each one. i.e.:
leftovers = total_size - sum(ob.preferredSize for ob in obs)
We can then proceed to dole out portions of the (positive or
negative) leftovers, based on assigned proportions.
Proportion assignment is somewhat of an open issue. wxWidgets uses a
weight that can be 0 (meaning don't resize), or a non-zero positive
number. This number is divided by the total weights of the items, to
allocate a fraction of the leftover space to that item. This
approach has the advantage of simplicity, but I wonder if there's a
better way.
That's because I'd ultimately like to see ui.layout being able to do
flexible grid/table layout the way Basser Lout does, using simple
"join" operators. I have a pretty good idea of how to convert
Lout-style joins into a "grid bag" layout, except for dealing with
the translation of min/max/preferred sizes and proportionate expansion.
Of course, that's probably because Lout doesn't have a notion of
flexible sizes!
Anyway, it's probably the case that the weighted approach used by wx
can be used, but I'd really like to see if there are any other
space-allocation techniques in other GUI frameworks that handle the
situation better. Especially useful would be documentation on how wx
actually uses the size hint information, and how any other frameworks
handle "grid bag" space allocation.
Hm. One final note... this email is somewhat mis-titled, in that the
modules should probably be peak.ui.layout and peak.ui.text_layout
rather than the long-winded peak.trellis.ui.layout and
peak.trellis.ui.text_layout. Flat is better than nested, after all,
and it's not too likely there'll ever be some other peak.ui.layout
that *doesn't* use the Trellis. :)
More information about the PEAK
mailing list