
GTK, if you’re unfamiliar with it, is a library for creating graphical widgets for the desktop. These widgets can take in user input and/or show application output. GTK comes with a nice selection of widgets. Widgets range from the humble checkbox to a full-blown file explorer. For the most part, you’ll never need to create your own widget but sometimes what’s in stock isn’t enough.

Gifcurry — a cross-platform GIF creator slash video editor made with Haskell — needed a custom widget to interface with user selected time slices. There is the main time slice of the GIF and the time slices for each dynamic text overlay added.
The widget consists of two major areas. The top area shows a zoomed-in view of the GIF time slice itself. The bottom area shows all time slices as they fall between the beginning and end of the video. In both parts, the white line represents the current video’s position. The top part also gets the video position clock. For convenience, the user can click anywhere on the GIF time slice to change the video’s current position. As the input changes, the widget updates to match giving immediate feedback.
The Draw Area Widget
The draw area widget is the foundation for any fully custom widget.
The GtkDrawingArea widget is used for creating custom user interface elements. It’s essentially a blank widget; you can draw on it. — https://developer.gnome.org/gtk3/stable/GtkDrawingArea.html

After you create the draw area, be sure it’s application paintable. Make sure to apply the event masks you wish to capture like a mouse button click.
GI.Gtk.widgetAddEvents
myWidget
[GI.Gdk.EventMaskAllEventsMask]
The Draw Widget Queue

GTK takes a lazy approach to drawing widgets. It will redraw your widget when some or all of it has been invalidated like being covered up by another window. To keep a constant render loop going, you’ll have to force a redraw by scheduling it.
GI.Glib.timeoutAdd
GI.Glib.PRIORITY_DEFAULT
1 $
GI.Gtk.widgetQueueDraw myWidget
return True
Once queued, your widget will receive an onWidgetDraw event. This event is your chance to update any of your widget’s graphics.
The On Draw Event

GTK lets you register a callback for a widget’s onWidgetDraw event. Inside the callback is where you’ll draw on your widget. To draw on your widget, you’ll need another library known as Cairo.
Cairo is a 2D graphics library with support for multiple output devices. — https://cairographics.org/
There are two major Haskell projects that produce bindings for GTK and its related libraries. There is the older one, gtk2hs, and the newer one, haskell-gi. Each comes with its own Cairo binding. For the draw calls, you’ll need the older one (cairo) provided by gtk2hs.
The newer binding (gi-cairo) doesn’t bind to most of the Cairo API as haskell-gi generates the binding automatically using GObject Introspection but the introspection data for Cairo is incomplete. If you do use gi-cairo, there is a workaround to bridge its binding to the more hand-rolled binding provided by gtk2hs.
Nevertheless, your registered callback will receive a context. You’ll pass this context off to Cairo and perform all your drawing calls in the Render monad.
GI.Gtk.onWidgetDraw
myWidget $
\ context-> do
If you want to call something in the IO monad, don’t forget the liftIO function.
(r, g, b) <-
liftIO $
getRgb color
If you’ve ever done any HTML5 canvas drawing, you’ll be familiar with the context.
document.getElementById('my-canvas').getContext('2d');
Relating it to GTK, the GtkDrawingArea is the canvas and the context is…well…the context. Anyway, the context isn’t all that important. It can, however, be a stumbling block when trying to integrate haskell-gi’s gi-cairo with gtk2hs’ cairo.

Like the HTML5 canvas API, Cairo comes with all the primitives you’d expect. You can draw lines, rectangles, ellipses, and even text (you’ll need Pango for advanced text rendering). Gifcurry uses a PNG pattern fill to color the time slices. The purple area is the GIF time slice and the green is a text overlay time slice.
GRC.rectangle x y w h
GRC.setSourceRGBA 0.1 0.1 0.1 1.0
GRC.fill
The On Widget Event
If your widget is only to show application output, then you’re done at this point and can ship it. If you want to capture user input, you’ll need to register a callback for the event(s) you wish to deal with.
GI.Gtk.onWidgetButtonPressEvent
myWidget $
\ eventButton-> do
Gifcurry captures the left mouse button click to update the video’s current position. This allows users to bounce around the video. Using a little algebra and geometry, Gifcurry translates the mouse’s 2D spatial coordinates to the video’s 1D time.

The End
It’s not everyday that someone uses GTK, Haskell, and needs a custom widget. But for those that do, your only limitation now is your imagination. If you only need a slightly modified widget, consider using CSS. GTK widgets can be styled with CSS (just like the web) for a truly custom look. Gifcurry uses CSS to style its about dialog.

If you haven’t tried GTK but are into native-desktop, front-end development, give it a spin. You can achieve the same look and feel as Electron and spare your users from downloading more RAM. And while you’re at it, give Haskell a try if you haven’t. Haskell works for you so you can spend more time creating great widgets.
Interested in GTK apps? Take a look at Movie Monad. Need a video to GIF maker? Download Gifcurry. Enjoyed this article? Clap it up. Thanks!
✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst on Twitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.