[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