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:
placerF
that 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 >+<
, >==<
, and listF
,
and then apply placerF
to 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. shellF
.
Figure 16. Different placers.
The parameter to matrixP
specifies the number of columns
the matrix should have. The types of the placers are
The placerhorizontalP :: Placer verticalP :: Placer matrixP :: Int -> Placer revP :: Placer -> Placer
revP
reverses 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):Hence, we can defineflipP :: Placer -> Placer
verticalP
asPlacers can be applied to fudgets by means ofverticalP = flipP horizontalP
placerF
:It applies the placer to all boxes in the argument fudget. The order of the boxes is left to right, with respect to the combinatorsplacerF :: Placer -> F a b -> F a b
listF
, >==<
and >+<
, etc.
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
verticalP
, horizontalP
or 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 counterF
in
Figure 18,
does not leave a hole in the layout of verticalCounterF
.
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:
We can then replacepermuteP :: [Int] -> Placer -> Placer
verticalP
with
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:
There is a corresponding spacer tohAlignS :: Alignment -> Spacer leftS = hAlignS 0 hCenterS = hAlignS 0.5 rightS = hAlignS 1
flipP
, namely flipS
. It too flips the x and y coordinates, and
lets us define some useful vertical spacers:WithflipS :: Spacer -> Spacer vAlignS a = flipS (hAlignS a) topS = flipS leftS vCenterS = flipS hCenterS bottomS = flipS rightS
compS
, we can compose spacers, and define a spacer that
centers both horizontally and vertically:To add extra space to the left and right of a box, we usecompS :: Spacer -> Spacer -> Spacer centerS = vCenterS `compS` hCenterS
hMarginS left right
, whereDistances are given in number of pixels.(Footnote: This is easy to implement, but makes programs somewhat device dependent.) FromhMarginS :: Distance -> Distance -> Spacer type Distance = Int
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
:The fudgetspacerF :: Spacer -> F a b -> F a b
spacerF s f
will 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:For example,spacerP :: Spacer -> Placer -> Placer
spacerP leftS horizontalP
gives a horizontal
placer which will left adjust its boxes.nameF
:The layout of the boxes that have been named in this way, is specified using the typetype LName = String nameF :: LName -> F a b -> F a b
NameLayout
. Here are the basic
functions for constructing NameLayout
values:To apply the layout to named boxes, we useleafNL :: LName -> NameLayout placeNL :: Placer -> [NameLayout] -> NameLayout spaceNL :: Spacer -> NameLayout -> NameLayout
nameLayoutF
:As an application of name layout, we show how the vertical counter in Figure 18 can be changed, so that the display appears between the up and down buttons (Figure 20):nameLayoutF :: NameLayout -> F a b -> F a b
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..]
placerF
applied 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.nameF
applied 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.