will get some default layout which might look like Figure 15.shellF "Buttons" (buttonF "A Button" >+< buttonF "Another Button")
Figure 15. When no layout is specified in the program, the automatic layout system chooses one.
But sooner or later, we will want to have control over the layout. The GUI library lets us do this two different ways:
placerFthat has appeared in some of the previous examples. It allows you to attach layout information to an arbitrary fudget. Usually, you first combine some fudgets using combinators like
listF, and then apply
placerFto the combination to specify a layout. This is a fairly easy method for adding layout information to a program. However, the layout possibilities are somewhat limited by the structure of the program.
Figure 16. Different placers.
The parameter to
matrixP specifies the number of columns
the matrix should have. The types of the placers are
revPreverses the list of boxes it is applied to. Another higher order placer is
flipP, which transforms a placer into a mirror symmetric placer, with respect to the line x = y (that is, it flips the x and y coordinates):
Placers can be applied to fudgets by means ofverticalP = flipP horizontalP
As an example, suppose we want to specify that the two buttons in Figure 15 should have vertical layout. We could then write
The result can be seen in Figure 17. In a similar way, the first button could be placed below, to the right of, or to the left of the second button, by using the placersshellF "Buttons" (placerF verticalP (buttonF "A Button" >+< buttonF "Another Button"))
revP horizontalP, respectively.
Figure 17. The same GUI elements as in Figure 15, but the program explicitly specifies vertical layout.
Abstract fudgets do not have a
corresponding box in the layout. This means that the presence of
mapstateF in the definition of
does not leave a hole in the layout of
verticalCounterF = placerF verticalP counterF counterF = intDispF >==< mapstateF count 0 >==< (buttonF "Up" >+< buttonF "Down")
Figure 18. An up/down counter with vertical layout. Abstract fudgets do not have a corresponding box in the layout.
What if we want the display to appear between the two buttons? With the placers we have seen, the two buttons will appear together in the layout, since they appear together in the program structure. One solution is to use a placer operator that allows the order of the boxes to be permuted:
to get the display in the middle. This kind of solution works, but it will soon become quite complicated to write and understand. A more general solution is to use name layout (Section 11.2).permuteP [2,1,3] verticalP
Placers are used to specify the layout of a group of boxes. In contrast, spacers are used to wrap a box around a single box. Spacers can be used to determine how a box should be aligned if it is given too much space, or to add extra space around a box. Examples of spacers that deal with alignment can be seen in Figure 19.
Figure 19. Spacers for alignment.
The topmost box (placed with
horizontalP) must fill up
all the available space. The lower three boxes have been placed
inside a box which consumes the extra space. The spacers used
are derived from the spacer
hAlignS, whose argument
states the ratio between the space to the left of the box and
the total available extra space:
flipS. It too flips the x and y coordinates, and lets us define some useful vertical spacers:
compS, we can compose spacers, and define a spacer that centers both horizontally and vertically:
hMarginS left right, where
hMarginS, we can derive
marginS, which adds an equal amount of space on all sides of a box:
Spacers can be applied to fudgets by means ofvMarginS above below = flipS (hMarginS above below) marginS s = vMarginS s s `compS` hMarginS s s
spacerF s fwill apply the spacer s to all boxes in f which are not enclosed in other boxes. We can also modify a placer by wrapping a spacer around the box that the placer assembles:
spacerP leftS horizontalPgives a horizontal placer which will left adjust its boxes.
NameLayout. Here are the basic functions for constructing
To apply the layout to named boxes, we use
nameLayoutF:Figure 18 can be changed, so that the display appears between the up and down buttons (Figure 20):
nlCounterF = nameLayoutF layout counterF counterF = nameF dispN intDispF >==< mapstateF count 0 >==< (nameF upN (buttonF filledTriangleUp) >+< nameF downN (buttonF filledTriangleDown)) -- only layout below layout = placeNL verticalP (map leafNL [upN, dispN, downN]) upN = "up" downN = "down" dispN = "disp"
Figure 20. With name layout, the order of the GUI elements in the window does not have to correspond to their order in the program text.
Now, we can control the layout of the two buttons and the display, without changing the rest of the program.
The actual strings used for names are unimportant, as long as they are unique within the part of the fudget structure where they are in scope. So instead we can write
(upN:downN:dispN:_) = map show [1..]
placerFapplied to some placer at some selected points in the fudget hierarchy. This gives the programmer more control over layout and is still perfectly safe, but there is a coupling between how the fudgets have been composed and how they appear on the screen. This is not necessarily bad, but it limits the freedom in the choice of layout.
nameFapplied to a layout specification which, by referring to the names, can achieve a layout of the GUI elements completely unrelated to how they were composed.
In this solution it is possible to make mistakes, however. For the layout specification to work properly, the name of every named box should occur exactly once in the layout specification. If you forget to mention a box, or if you mention it twice, or if you name a box that does not exist, the layout will not work properly. These mistakes are not detected at compile time, but give rise run-time errors or a weird layout.
The problem with the name layout solution is that it requires a certain consistency between two different parts of the program. Maintaining this consistency during program development is of course an extra burden on the programmer.
Can a type system be used to make name layout safe? It would perhaps be possible to include layout information in some form in the types of GUI fudgets and catch some mistakes with the ordinary Haskell type system. However, the requirement that each name occurs exactly once in the layout specification suggests that you would need a type system with linear types [Hol88] to catch all mistakes.