QML Haskell Library

Making UIs in haskell is a pain. Not because the libraries are bad, but because they are either hard to use or short lived. My reason for thinking this is because making UI bindings is just not a normal haskeller’s idea of fun. They may make a library, and then abandon it.

There is basically one UI library that has stood the test of time, and that is gi-gtk, whi is here because it’s auto-generated using gobject-introspection. You get a close to C interface (yuck), but at least they are well maintained!

But at work, I have made applications for embedded linux, and this is what I wanted to do in Haskell. The issue with GTK is that, not only is a nightmare, but it’s also designed for desktop applications. There is no way to launch a gtk application without either wayland or X server running for example. For this reason, I decided to make my own, probably short lived, GUI framework. I chose QML to base it on, because QML is what I have used at Work.

What I wanted

I wanted a framework like brick. Brick is a fantastic framework for making TUIs. It uses what is increasingly called the “elm architecture”, meaning that you define two functions, one for drawing a UI from a state, and another for updating a state from events.

The downside is that your UI drawing function can get huge and complex. This is what I hoped to change in my QML library.

How to use my library

First thing to note is that it isn’t published on hackage. This will remain the case until it becomes stable (if that ever happens). You will have to use a cabal.project file to download the git.

When you have the library, you include it with:

{-# LANGUAGE TypeApplications, DataKinds #-}

import Graphics.UI.Qml

To use the library, you first need to write some QML. The QML can get valued from an object exposed from haskell, and also call slots.

In the haskell, you need to specify two types:

  • Your event type
  • Your State type

For example, from the test application in the git:

type State = (Int, String)

data Event = Incr | Decr | Welcome String Int | Nop

We will then define two functions, one to map our state to a view model, and one to take events and update the state:

We start with the type:

viewController :: State -> QViewModel Event

Simple enough! We then need to create our object that we can call from QML:

viewController (count, msg) = rootObject "hs" $ do

This means from QML we can write hs.foo() to call a slot named foo, and we can use properties such as hs.var.

Lets look at a slot:

qSlot "welcome" (CType @[String, Int])
    (\name age -> return $ Welcome name age)

We first give it a name for the QML, then we tell haskell about the type of the slot using CType. We finally give a lambda for what to do with the arguments when the slot is called. It must return some kind of event.

An example of a slot with no arguments would be:

qSlot "increment" (CType @'[])
    (return Incr)

We then move onto properties:

variables $ do
    qProperty @Int "Bar" 42

This would mean that from qml, hs.Bar is 42. You may need to tell haskell what the type is if it can’t infer it! That’s what I have done with the @Int.

Normally, you wouldn’t use a constant, but rather a variable from your State type.

Things to note!

Firstly, the slots will not work correctly if you don’t give GHC the -threaded option. You can do this by adding ghc-options: -threaded to your cabal file in the executable section.

Every call to your view controller function must contain every property!

Tying it all together

We need to populate a type with our functions:

let app = QmlApp
    { qmlFile = QmlFile "main.qml"
      -- Example for loading from qt designer:
      -- qmlFile = QRC "test/gui.qmlrc" [":/imports"] "qrc:/gui.qml"
    , appUpdate = update
    , appViewModel = viewController
    , externalEvents = Nothing
    }
runQApplication app (0, "Fill out details")

And we are done! If you want other parts of your app to send events to the GUI part, then you need to create a TBQueue e from the STM library. You can send events that way!

Apps using it

Well, so far, it’s only what I have made. I’m working on an audio player for raspberry pi, where people can scan a QR code and add songs to the queue. Budget sonos! Here is a picture! pi-radio