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:
- an Atmosphere Calculator, built with Glade.
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!