To build the traffic light picture we presented at the beginning of this section, the different Picture values for the lights will have to be combined together. The Picture type provides three basic composition operators:
data Picture = ... | PicOverlay Picture Picture ...i.e., Overlay picA picB is a picture formed by putting picA on top of picB, so that their origins match up: \hbox{ \hspace{0.5in} \begin{minipage}[b]{2in} \begin{verbatim} picture = Overlay (ellipse (40,20)) (ellipse (20,40)) \end{verbatim} \end{minipage} \vbox{\vspace*{0.3in} \psfig{figure=overlay.eps}}}
picture = PicOverlay (ellipse (40,20)) (ellipse (20,40))
The bounding box of the combined picture is the bounding box of the
union of the bounding boxes for the two pictures.
... | PicClip Picture Picture ..Clip clip clipped is a new picture that clips the second picture by the clip mask defined by the first: \hbox{ \hspace{0.5in} \begin{minipage}[b]{2.3in} \begin{verbatim} picture = PicClip (Pen largeFont (text "Clip")) (lines 500) lines l = foldl1 (Overlay) [ rline (l*cos a,l*sin a) | a <- [0,(pi/72)..2*pi] \end{verbatim} \end{minipage} \vbox{\psfig{figure=clip.eps} \vspace*{0.1in}}} \vspace{0.2in}
picture =
PicClip
(Pen largeFont (text "Clip"))
(lines 500)
lines l =
foldl1 (Overlay)
[ rline (l*cos a,l*sin a)
| a <- [0,(pi/72)..2*pi]
inBox :: Picture -> Pictureis a picture combinator that puts a bounding rectangle around an arbitrary picture. This combinator could of course be expressed if we had a function for computing the bounding box of a picture, but in the same way as in See section Structured translation, we introduce a higher-level mechanism for expressing size constraints between two pictures being combined: @tindex{ConstrainOverlay} @tindex{RelSize}
...
| ConstrainOverlay RelSize RelSize
Picture Picture
data RelSize
= None
| Fixed Which Int
| Prop Which Double
data Which = First | Second
ConstrainOverlay None (Prop Second 2.0) picA picB is a
picture that when rendered, will align the origins of picA and
picB, drawing picA on top of picB. The operator
will also scale picB in the Y direction such that the size of
its bounding box will double that of picA along this
axis (the Which value indicates which of the two pictures is
constrained). The RelSize data type contains the different
types of size constraints we can place between the two dimensions,
None indicates that no size constraints should be imposed,
Fixed that the lengths should differ by a fixed amount.
The ConstrainOverlay constructor provides a superset of the
functionality of Overlay,
PicOverlay = ConstrainOverlay None Nonebut we choose to provide the PicOverlay constructor separately, due to its frequent occurrence in pictures.
Combining the PicOverlay operator with the structured translation operator in Section See section Structured translation, picture combinators that tile two pictures together can be expressed:
beside :: Picture -> Picture -> Picture beside picA picB = Overlay (Move (OffDir East) picA) (Move (OffDir West) picB) above :: Picture -> Picture -> Picture above picA picB = Overlay (Move (OffDir South) picA) (Move (OffDir North) picB)
The beside combinator overlays two pictures, but translates their local origins such that picA will be shifted to the left of the vertical axis and picB wholly to the right. The combinator above uses the same trick, but this time the translation is with respect to the horizontal axis.
As an example of these various composition operators, we can finally present the construction of the traffic light example presented at the beginning of this section, starting with a combinator for placing an arbitrary text string within a coloured oval:
light :: Colour -> String -> Picture
light col lab =
ConstrainOverlay
(Fixed Second 20)
(Fixed Second 20)
(withColour black $ centre $ Text lab)
(filledCircle col 2)
The light combinator centres the text string lab within an ellipse that has horizontal and vertical extent 20 units bigger than that of the extent of the picture representing the string. Using this combinator we can construct the pictures for the individual lights:
redTLight = light red "R" orangeTLight = light orange "O" greenTLight = light green "G"
To align the lights horizontally, we want to use the horizontal tiling operator beside, but want to add some `air' between the lights first:
besideSpace :: Unit -> Picture -> Picture -> Picture
besideSpace spc picA picB =
beside
picA
(Transform (xlt (spc,0)) $
moveWest picB)
besideSpace uses a Transform constructor to enlarge the bounding box of picB before invoking beside. The three traffic lights then become just:
\vspace{0.15in} \hbox{ \hspace{0.5in} \begin{minipage}[b]{2in} \begin{verbatim} lights = foldr1 (besideSpace 10) [redTLight, orangeTLight, greenTLight] \end{verbatim} \end{minipage} \vbox{\vspace*{0.3in} \psfig{figure=lights.eps}}}
lights =
foldr1
(besideSpace 10)
[redTLight, orangeTLight,
greenTLight]
The final step is then adding a black background for the casing of the traffic lights:
\vspace{0.15in} \hbox{ \hspace{0.5in} \begin{minipage}[b]{2in} \begin{verbatim} trafficLight = ConstrainOverlay (Fixed Second 20) (Fixed Second 20) (Move (OffDir Centre) lights) (Move (OffDir Centre) (Rectangle (2,2))) \end{verbatim} \end{minipage} \vbox{\psfig{figure=tlight.eps} \vspace*{0.3in}}}
trafficLight =
ConstrainOverlay
(Fixed Second 20)
(Fixed Second 20)
(Move (OffDir Centre)
lights)
(Move (OffDir Centre)
(Rectangle (2,2)))
This example, while small, demonstrates the compositional programming style that follows naturally, where complete Pictures are formed by repeatedly applying picture combinators to existing Pictures.