23 Automatic layout

The layout combinators in Chapter 11 are used to specify the position and size of graphical objects. Today, these objects can be of two types: GUI fudgets or drawings as described in Chapter 27. The original layout system was designed for the GUI fudgets, and its implementation will be described in this section.

The purpose of the automatic layout system is to relieve the application programmer of the task to specify the exact position and size of each GUI fudget. This task has several dynamic aspects. To start with, it depends on factors that are not known until the program has started. For example, the size of a text drawn in a label fudget depends on the the font and size chosen, and can only be determined after the label fudget has communicated with the X server. Individual GUI fudgets can also change their size at any time, and the user might resize a shell window. Both these activities may imply that a number of GUI fudgets must change their position and size.

The layout system also simplifies the implementation of the individual GUI fudget, in that it does not have to worry about the place and position of other GUI fudgets. It must only specify an initial request for a size, and the layout system will allocate a place and actual size.

The implementation of the layout system operates by moving and resizing rectangular units corresponding to the group fudgets (Section 22.1). Remember that a group fudget basically consists of a stream processor controlling an X window, possibly with a number of group fudgets inside it. Each group fudget also contains a piece of the layout system, which is responsible for the placement and sizing of each immediate subgroup fudget. The responsibility is only one level deep: a group fudget does not control any group contained in any of its groups. As an example, the group corresponding to k1 in Figure 51 is responsible for the layout of k2 and k4 (but not k 3).

This division of responsibility is natural, since a group is easily placed and resized by the single Xlib command ( ConfigureWindow). All subwindows inside the group will follow and keep their relative positions.

The mechanism of the layout system can be studied by looking at the message traffic between a group fudget and its immediate subgroups.

The group fudget has a filter (other filters are described in Chapter 24), called autoLayoutF, which listens for layout messages that are output on the low-level streams from the subgroups.

data LayoutMessage
  =  LayoutRequest LayoutRequest
  |  LayoutName String
  |  LayoutPlacer Placer
  |  LayoutSpacer Spacer
The subgroups decide what sizes they need, and output a layout request.
data LayoutRequest
  = Layout {  minsize :: Size,
              fixedh, fixedv :: Bool }
The field minsize is the requested size, and fixedh (fixedv) being true specifies that the size is not stretchable in the horizontal (vertical) direction. (Some placers use this information to allocate extra space to stretchable boxes only.)

The layout filter also receives placers and spacers that the programmer has wrapped around subgroups. Since all layout messages are tagged with a path, the layout filter can associate the placers and spacers with the wrapped subgroups, by analysing the paths. The constructor LayoutName is used in a similar way to associate a subgroup with a name.

The placers and spacers are functions that decide the actual layout. A placer operates on a list of layout requests, yielding one single request for space needed for the placement of all the associated subgroups. Looking back at the discussion of boxes in Section 11.1, we will recognise that there will be one layout request corresponding to each box.

In contrast to the placers, a spacer takes a single request as an argument, and the layout filter maps it on all requests corresponding to the enclosed boxes associated with the spacer, yielding the same number of requests.

type Placer = [LayoutRequest] -> (LayoutRequest, Rect -> [Rect])

type Spacer = LayoutRequest -> (LayoutRequest, Rect -> Rect)
As can be seen from these types, placers and spacers also return a residual function, which we will describe below.

Having collected layout requests and having applied the layout functions to them, the group fudget must decide on one single layout request to output. Since programs should work even if no layout is specified by the programmer, a default placer is wrapped around the subgroups.

The default placer used is called autoP, which picks a suitable layout based on the layout requests at hand. In the current implementation, it simply chooses between verticalP and horizontalP, based on two preferences:

  1. layouts which do not waste space by unwanted stretching are preferred over those that do,
  2. square layouts are preferred over long and narrow layouts.
Future implementations could conceivably take more parameters into account when choosing a layout and have a wider choice of layouts to choose between.

Having produced a single layout request, the group fudget outputs it, and it will be handled by the enclosing group, unless the group is a shell group. In this case, the minsize field in the request is used to set the size of the shell window.

The XEvent type includes constructors that are used to report changes in layout to the GUI fudgets:

data XEvent =  ...
            |  LayoutPlace  Rect
            |  LayoutSize   Size
The propagation of these layout events start in the shell group fudget. When the shell window has been resized, the X server sends a ConfigureNotify event containing the new size to the shell group fudget. Note that this event is generated regardless of whether the resize operation was done by the program (as a result of a layout request) or the user (via the window manager). Anyhow, the shell group fudget generates an event of the form LayoutPlace (Rect 0 s), to the layout filter. This informs the layout filter that the rectangle from the origin to s is available for the subgroups. Now, the layout filter applies the residual placers and spacers to this rectangle in a reversed pattern. Each residual placer will yield a list of rectangles, where the elements correspond to the list of requests (and thus to the boxes) that was fed to the original placer. Similarly, residual spacers are mapped over rectangles to adjust positions of the associated subgroups.

When this reversed process is finished, the layout filter outputs one LayoutPlace message to each subgroup, which will move itself accordingly, pass the message to its layout filter, and so the process goes on recursively.

When a group receives a LayoutPlace message, it also sends a LayoutSize message to the kernel stream processor, so that it can adjust the graphical content of its window to the new geometry. Note that the kernel only needs to know the size of its window, and not its place. This is due to the fact that all window operations use local coordinates.

23.1 The historic steps of fudget layout

  1. Initially, there was no support at all for layout. The programmer had to explicitly specify the size and position of each GUI fudget.
  2. Then, automatic layout was implemented, with the restriction that each GUI fudget had to correspond to exactly one box. This implied that when two GUI fudgets where composed, a placer had to be specified. The combinators for parallel and serial composition had an extra placer argument. As a result, the layout was too tightly coupled to the dataflow between the GUI fudgets.
  3. The current system allows many boxes per fudget. Together with named layout, this allows more flexible layout.