Suppose we wanted to be able to control what background colour the Hello, world string should have. One way of providing the customisation of the background colour would be to parameterise the label function over the colour
label :: String -> Colour -> Component (Label, DisplayHandle)
so label "Hello, World" blue would have blue background. Unfortunately, the number of customisation options can in some cases be quite large, as we may in some cases want to control the font and the justification used in label, say. Using parameterisation, this will lead to extremely wieldy interface to the simplest of components, requiring default values for all customisation options to be supplied.
One way to reduce the clutter is to use a data type to encode all the different customisation options, and pass a list of these values to the function creating the user interface component:
data LabelResource = LabelBgroundColour Colour | LabelFgroundColour Colour | LabelJustify Justification label :: String -> [LabelResource] -> Component (Label, DisplayHandle)
The application interface to the function becomes less complex, requiring the user only to supply the empty list if all defaults should apply. However, it introduces a rather special purpose type for each component, and, in part, due to the rather global namespace of constructor names in Haskell 1.2, unique (and as result long!) names have to be used for each of these options. The result is cleaner type signatures, but equally complex calling interfaces for the programmer.
In Haggis, we choose to not directly make use of types and the type system for controlling customisation of user interface components. Instead, each function that creates a component is passed and environment containing amongst other things a Style.
The Style is an environment containing a mapping between name strings for customisation options and the values to use (also represented using strings). The syntax for specifying style values is based on that of the resource syntax used for Xt based applications:
withStyle :: [String] -> Component a -> Component a blueLabel str = withStyle ["*background: blue"] (label str)
The withStyle combinator augments the Style environment, here setting the background for the label to blue. The label interrogates the Style environment for values for its customisation options:
resolveStyle :: Style -> String -> (String -> a) -> IO a label str env = ... resolveStyle style "background" (read) >>= \ bground -> resolveStyle style "foreground" (read) >>= \ fground -> ...
For this scheme to work, we have, of course, to be able to convert the customisation values to and from text strings.
The Style environment is hierarchical, and also supports the adding of name qualifiers, See section Style environments for details.
An added benefit of using strings to encode trivial customisation values is that we do not have to recompile a program just to change the label's background from blue to red. Style values can be specified on the command line or in separate style/resource files:
sof% ghc-haggis labelExample.lhs -o label sof% ./label -style "*background: red"
Also, note there is nothing inherently user interface specific about the Style environment, it can also be used for configuring the application. Here is a version of the Hello, World program that uses Style values to optionally override the label to display:
main =
wopen ["*title: "++lab]
(\ env ->
resolveStyleDefault (getDCStyle env) "label" lab (id) >>= \ str ->
label str env) >>
return ()
where
lab = "Hello, World"
So to finally explain the full type of the glyph function:
type Component a = DC -> IO a glyph :: Picture -> Component (Glyph, DisplayHandle)
The extra argument encoded with the Component type synonym is the environment or DisplayContext passed to all functions creating user interface components. See section DisplayContext environments for more inexampleion on what this environment contains other than Styles.