Friday, February 27, 2009

A high-level GUI library for Haskell

There seems to still be a void in the Haskell world regarding GUI libraries. On one hand we have low level libraries based on well know solutions but doing everything in the IO monad, to cut short, on another functional reactive libraries that just don't seem to be accessible for haskell programmers of my level, somehow... And I'm not the only one: just the other day I saw a new attempt being made at a functional easy to use GUI library: Barrie. It looks promising, but I had already started my own, so I think I'm going to continue a little bit, I find I'm learning a lot in the process...

The ideas being my own library, tentatively called UITree are the following:

- you build your GUI using combinators that start from simple widgets and use composite widgets and layouts to augment a tree
- event handler can be attached to widgets in the tree. The handlers are of type (ui, state)->IO(ui,state): you get the current ui tree and the current state, and you transform them. The IO bit is to allow you to do IO actions if you want but you can do without.

- inside the event handlers you manipulate the ui tree a bit like JQuery does with HTML DOM: you can select particular widgets, operate on them, etc... Most operations are just transformers from the UITree type to the same type, so you can chain them.

- since you can't reference directly the widgets in your fonctional tree, you can give them explicit ids, so there is a disconnect between the actual tree and the handlers, which is similar to JQuery (you cannot rely on the compiler to make sure you manipulate an existing widget).

For the moment I got some small samples to work with WXWidgets (Barrie uses GTK, by the way).
The API is bound to change, but this is how a frame containing a close button and a button that increases a internal integer state and displays the current state:

simpleState :: IO (Int)
start simpleUI 0 -- start the WXWidgets UI with the given UI tree and initial state
simpleUI=(frame [text := "Simple"]) -- the enclosing frame
(panel []) -- a panel to enclose our children
(row [space := 5] -- children in a row, a bit of space
(button [text := "0"] [command :> (return . doInc)]), -- a button showing 0 and increasing the state when clicked
(button [wid:="close", text := "Close!"] [command :> (pureUI doClose)]) -- a button to close the frame, pureUI is just a helper function that shows we're not modifying the state
doInc (tree,st)=(set [text := (show (st+1))] tree,(st+1)) -- set the button text and state to state + 1
doClose tree=close $ top tree -- close the top of the tree (the frame)

Note how the ui building and the handlers are pure functions, so you just manipulate your tree and your state.


Daniel Swe said...

A quick tip: please have good defaults. Space for example. Should it be 0 or something else? What does standard Windows/Gnome/OSX programs use?

Jared said...

I agree with Daniel.

In Windows (Visual Studio .NET at least) you drop a control on there and it has nice default snapping and padding, a few pixels (maybe 5 or so). It makes it easy to make GUIs that aren't too cluttered, and it helps keep widgets (especially buttons) lined up.

Another tip: I haven't played too much with WxWidgets or your GUI lib, but consider making it easy and possible to make resizable GUIs.

In .NET, the docking (none, fill, left, right, top, bottom) and nesting of panels make non-trivial resizable layouts intuitive and doable (with alittle practice). When I build GUIs I assume my GUI will be used on screens as small as 1100x700 and as large as 30 inch 2560x1600 mammoths. Some GUIs (fixed sized dialogs) would not need resizing but I find software to be much more usable if it can utilize the space I have onscreen.