cwidget/README.layout
Daniel Burrows <Daniel_Burrows@brown.edu> -- written 7/17/00

  A brief dissertation on the layout system to be used in cwidget.  I
saw ad-hoc fixes starting to accumulate, and I figure that a
thought-out design document won't hurt at all, even if I have to
rewrite most of the code to comply with it :-)

So, there is a distinct need for widgets to (a) advertise information
about their desired size, size constraints, und so weiter, (b) learn
when they have been given a new physical size and adjust themselves
accordingly (possibly resizing more widgets in a recursive manner),
and (c) announce when their requested size has changed and they might
(consequently) need a resize.

  So, here's how we go about doing it.  This is not necessarily the
objectively *best* way, it is merely *a* way that is fairly logical, capable,
and not overly difficult to implement.  Complaints should come with an attached
and working example of a better layout system, please :-)

SIZE REQUESTS

To request a desired size, widgets should implement the "size_request"
virtual method.  This essentially calculates the desired size and
returns it.  (it would be possible, but IMO too much work, to cache
this in a member variable.  Size requests should happen relatively
rarely.. *crosses fingers*)

Sizes will be stored as a separate type, in which w and h values have
the expected meanings (ie, w==0 h==0 means the widget is so small it's
invisible) In particular, the (0,0) "infinite-resize" hack is NOT
supported in this system; other mechanisms should be used to make
widgets occupy all available space.

The desired size will be treated not only as a target size, but also
(whenever possible) as a *minimum* size.  This means that widgets
which lay other widgets out should never give a widget less size than
it requests, unless doing so is absolutely necessary (and even then
they should avoid it :P )  Unfortunately, this may not be
possible--which means that properly-written widgets should handle
situations where they are allocated dramatically smaller windows than
expected and not blow up, summon demons, etc.  (well, summoning demons
is permissible if and only if the user has requested such action) As a
special case, the widget will be automatically "hidden" (by setting
its window to NULL) if either the width or height is 0.

GIVING SIZES TO WIDGETS

When a widget's owner assigns it a size, it should call
widget->alloc_size(x, y, w, h).  This routine will set
all the various `official' things needed to give the widget the
appropriate coordinates inside the enclosing widget.

alloc_size will make the widget invisible if either w or h is 0, as
described above.

Widgets which contain other widgets should call alloc_size on all
their visible children when they themselves have their size allocated
(which is why this is a virtual method :) )

ANNOUNCING SIZE CHANGES

Calling the "toplevel::queuelayout()" routine will cause a widget to
be laid out again when the cwidget main loop is re-entered.  In the
main loop of the code (or in a toplevel::poll()), the queue will be
examined and layout operations performed on the topmost ancestor of
each item in the queue.  (no widget will be laid out more than once --
at least, not in the final version :) ) This will take place before
queued screen redraws, of course :-)

(an interesting note is that since, of course, all widgets have their ultimate
 ancestor in the toplevel widget, a queued resize just means that that widget
 is laid out again)

WHEN TO LAYOUT

A signal "do_layout" is available; when this is emitted, widgets should
recalculate their layout and reassign sizes and windows to child widgets via
alloc_size() (this may be triggered from within alloc_size)

TABLE LAYOUT

Well, this isn't strictly within the bounds of what I was originally
planning to says, but it's complicated and bears writing down...

The mechanism for table layout is derived from GTK+ code and -- again --
is not particularly optimal but should work (mostly).  I will
only describe the computations necessary for rows; the algorithm is
perfectly symmetrical with respect to rows and columns.  (that is,
rows and columns are orthogonal to one another :-) )

Each algorithm will temporarily store information about the width of
rows and columns.  (this can optionally be cached after the algorithm
completes, of course)

  REQUISITION:

  (a) The table whose size is being requested will collect all its
widgets and sort them according to how many rows each occupies.

  (b) The table will iterate over the new list by row count in
increasing order.  For each widget, the total height of the rows it
spans will be calculated and compared to its requested height.  If the
total width is less than the widget's requested width, the height of
each row spanned by the widget is incremented by height_diff/nrows,
where height_diff is the shortfall and nrows is the number of rows the
widget spans.  (doing this is a bit tricky; I like the GTK+ approach,
which is to iterate over the rows, incrementing each by
height_diff/nrows, then decrementing height_diff by that amount and
decrementing nrows by 1)

  ALLOCATION:

  This is trickier, as there may be shortfall or leftover size.

  (a) Initial row sizes are calculated as in REQUISITION

  (b) If the allocated size is larger than the requested size, the
number of rows which are allowed to be expanded (ie, at least one
member of the row requests expansion (attributes are determined at
time of insertion into the table..)) is calculated.  The extra size is
then distributed amongst these rows in the same way that extra size is
distributed in step (b) above.

  (c) If, on the other hand, the allocated size is less than the
requested size, the number of rows which are allowed to be SHRUNK is
calculated, and an attempt is made to shrink the table until it fits
within the requested space.
  IMPORTANT NOTE: this may not be successful.  In this case, the table
will bail and simply truncate its lower extremities.  However, this
shouldn't be necessary (cross fingers..) except in extreme circumstances..
