A digression into wxHaskell Layout

Eric Kow commented that it would be worthwhile to show the Layout version of the Diff control even though it doesn’t work correctly in this scenario (it works fine if the Layout is applied to an enclosing Frame instance).

Without further ado, here is the code for diffCtrl’ using Layout – you do not require the buildLayout function in this case at all. The Layout code is highlighted in red.

> diffViewer' p props =
>     do
>     let defaults   = [border := BorderStatic]
>     fn1 <- staticText p []
>     fn2 <- staticText p []
>     f1  <- window p []
>     f2  <- window p []
>     vsb <- scrollBarCreate p (-1) rectNull wxVERTICAL
>     hsb <- scrollBarCreate p (-1) rectNull wxHORIZONTAL
>     set f1 [ on paint := onPaint p DVorig f1 ]
>     set f2 [ on paint := onPaint p DVchanged f2 ]
>     let state  = DVS p fn1 fn2 f1 f2 vsb hsb Nothing dvf_default Map.empty
>         lay_fname w  = minsize (sz 400 (-1)) $ widget w
>         lay_diffp w  = minsize (sz 400 400)  $ fill $ widget w
>         lay_left     = column 5 [ lay_fname fn1, lay_diffp f1 ]
>         lay_right    = column 5 [ lay_fname fn1, lay_diffp f2 ]
>         layout'      = container p $ fill $ margin 5 $
>                            column 5 [ row 5 [ lay_left,
>                                               lay_right,
>                                               vfill $ widget vsb ],
>                                       hfill $ widget hsb ]
>     scrollBarSetEventHandler hsb (onScroll p hsb)
>     scrollBarSetEventHandler vsb (onScroll p vsb)
>     dvSetState p state
>     set p (defaults ++ props)
>     set p [layout := layout']
>     return p

To simplify explanation, I have split the layout into several components.

The first neat thing to notice is that constriction of layout is pure code – the transformation into sizers (which are bound the the IO monad) is done when the layout property is set on the owning DiffCtrl (Panel).Controls are specified by the widget (if they are ‘primitive’ widgets) and container (if they serve as containers for other widgets) combinators.

  • widget w tells the Layout system to insert a Window with identifier w in the appropriate location.
  • container p tells the Layout system that p is the container for the specified Layout. In the implementation, this means that Layout constructs a hierarchy of sizers which are owned by the container window.
  • minsize is a combinator which takes a Layout and gives it a minimum size. As an example, lay_fname w = minsize (sz 400 (-1)) $ widget w means insert the primitive control w and set a minimum width of 400 pixels (the -1 leaves the Layout to decide the best height).
  • row and column are combinators which lay out controls in rows and columns. In practice, this means that appropriate BoxSizer instances will be created into which the controls will be placed.
  • hfill, vfill and fill are combinators which request that the layout attempt to fill, either horizontally (hfill), vertically (vfill) or in both directions (fill) the available space. These are the combinators which prove problematic in our control in practice, and are the reason for defining the layout explicitly using Sizers.

The Layout will not become active until the layout Attribute has been set (set p [layout := layout’] in the code above). Setting the Layout in this way implicitly calls windowLayout and windowFit after the Sizers have been created.