We continue our tour of GUI toolkits for CL with Gtk+3 and cl-cffi-gtk.

The previosu posts are:

This blog post series was initially written for the Common Lisp Cookbook, you can (and should) read it there:

https://lispcookbook.github.io/cl-cookbook/gui.html

Gtk+3 (cl-cffi-gtk)

Gtk+3 is the primary library used to build GNOME applications. Its (currently most advanced) lisp bindings is cl-cffi-gtk. While primarily created for GNU/Linux, Gtk works fine under macOS and can now also be used on Windows.

  • Framework written in: C
  • Portability: GNU/Linux and macOS, also Windows.

  • Widgets choice: large.

  • Graphical builder: yes: Glade.

  • Other features: web browser (WebKitGTK)

  • Bindings documentation: very good: http://www.crategus.com/books/cl-gtk/gtk-tutorial.html

  • Bindings stability: stable

  • Bindings activity: low activity, active development.

  • Licence: LGPL

  • Example applications:

Getting started

The documentation is exceptionally good, including for beginners.

The library to quickload is cl-cffi-gtk. It is made of numerous ones, that we have to :use for our package.

(ql:quickload :cl-cffi-gtk)

(defpackage :gtk-tutorial
  (:use :gtk :gdk :gdk-pixbuf :gobject
   :glib :gio :pango :cairo :common-lisp))

(in-package :gtk-tutorial)

How to run the main loop

As with the other libraries, everything happens inside the main loop wrapper, here with-main-loop.

How to create a window

(make-instance 'gtk-window :type :toplevel :title "hello" ...).

How to create a widget

All widgets have a corresponding class. We can create them with make-instance 'widget-class, but we preferably use the constructors.

The constructors end with (or contain) “new”:

(gtk-label-new)
(gtk-button-new-with-label "Label")

How to create a layout

(let ((box (make-instance 'gtk-box :orientation :horizontal :spacing 6))) ...)

then pack a widget onto the box:

(gtk-box-pack-start box mybutton-1)

and add the box to the window:

(gtk-container-add window box)

and display them all:

(gtk-widget-show-all window)

Reacting to events

Use g-signal-connect + the concerned widget + the event name (as a string) + a lambda, that takes the widget as argument:

(g-signal-connect window "destroy"
  (lambda (widget)
    (declare (ignore widget))
    (leave-gtk-main)))

Or again:

(g-signal-connect button "clicked"
  (lambda (widget)
    (declare (ignore widget))
    (format t "Button was pressed.~%")))

Full example

(defun hello-world ()
  ;; in the docs, this is example-upgraded-hello-world-2.
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :type :toplevel
                                 :title "Hello Buttons"
                                 :default-width 250
                                 :default-height 75
                                 :border-width 12))
          (box (make-instance 'gtk-box
                              :orientation :horizontal
                              :spacing 6)))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (let ((button (gtk-button-new-with-label "Button 1")))
        (g-signal-connect button "clicked"
                          (lambda (widget)
                            (declare (ignore widget))
                            (format t "Button 1 was pressed.~%")))
        (gtk-box-pack-start box button))
      (let ((button (gtk-button-new-with-label "Button 2")))
        (g-signal-connect button "clicked"
                          (lambda (widget)
                            (declare (ignore widget))
                            (format t "Button 2 was pressed.~%")))
        (gtk-box-pack-start box button))
      (gtk-container-add window box)
      (gtk-widget-show-all window))))

Next is IUP, a not very famous but really great toolkit!