IUP is a cross-platform GUI toolkit actively developed at the PUC university of Rio de Janeiro, Brazil. It uses native controls: the Windows API for Windows, Gtk3 for GNU/Linux. At the time of writing, it has a Cocoa port in the works (as well as iOS, Android and WASM ones). A particularity of IUP is its small API.
The Lisp bindings are lispnik/iup. They are nicely done in that they are automatically generated from the C sources. They can follow new IUP versions with a minimal work and the required steps are documented. All this gives us good guarantee over the bus factor.
IUP stands as a great solution in between Tk and Gtk or Qt.
Previous posts of the serie:
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
- Framework written in: C (official API also in Lua and LED)
Portability: Windows and Linux, work started for Cocoa, iOS, Android, WASM.
Widgets choice: medium.
Graphical builder: yes: IupVisualLED
Other features: OpenGL, Web browser (WebKitGTK on GNU/Linux), plotting, Scintilla text editor
Bindings documentation: good examples and good readme, otherwise low.
Bindings stability: alpha (but fully generated and working nicely)
Bindings activity: low
Licence: IUP and the bindings are MIT licenced.
List of widgets
Radio, Tabs, FlatTabs, ScrollBox, DetachBox,
Button, FlatButton, DropButton, Calendar, Canvas, Colorbar, ColorBrowser, DatePick, Dial, Gauge, Label, FlatLabel,
FlatSeparator, Link, List, FlatList, ProgressBar, Spin, Text, Toggle, Tree, Val,
listDialog, Alarm, Color, Message, Font, Scintilla, file-dialog…
Cells, Matrix, MatrixEx, MatrixList,
GLCanvas, Plot, MglPlot, OleControl, WebBrowser (WebKit/Gtk+)…
drag-and-drop
Getting started
Please check the installation instructions upstream. You may need one system dependency on GNU/Linux, and to modify an environment variable on Windows.
Finally, do:
(ql:quickload :iup)
We are not going to :use
IUP (it is a bad practice generally after all).
(defpackage :test-iup
(:use :cl))
(in-package :test-iup)
The following snippet creates a dialog frame to display a text label.
(defun hello ()
(iup:with-iup ()
(let* ((label (iup:label :title (format nil "Hello, World!~%IUP ~A~%~A ~A"
(iup:version)
(lisp-implementation-type)
(lisp-implementation-version))))
(dialog (iup:dialog label :title "Hello, World!")))
(iup:show dialog)
(iup:main-loop))))
(hello)
Important note for SBCL: we currently must trap division-by-zero errors (see advancement on this issue). So, run snippets like so:
(defun run-gui-function ()
#-sbcl (gui-function)
#+sbcl
(sb-int:with-float-traps-masked
(:divide-by-zero :invalid)
(gui-function)))
How to run the main loop
As with all the bindings seen so far, widgets are shown inside a
with-iup
macro, and with a call to iup:main-loop
.
How to create widgets
The constructor function is the name of the widget: iup:label
,
iup:dialog
.
How to display a widget
Be sure to “show” it: (iup:show dialog)
.
You can group widgets on frame
s, and stack them vertically or
horizontally (with vbox
or hbox
, see the example below).
To allow a widget to be expanded on window resize, use :expand
:yes
(or :horizontal
and :vertical
).
Use also the :alignement
properties.
How to get and set a widget’s attributes
Use (iup:attribute widget attribute)
to get the attribute’s value,
and use setf
on it to set it.
Reacting to events
Most widgets take an :action
parameter that takes a lambda function
with one parameter (the handle).
(iup:button :title "Test &1"
:expand :yes
:tip "Callback inline at control creation"
:action (lambda (handle)
(iup:message "title" "button1's action callback")
iup:+default+))
Below we create a label and put a button below it. We display a message dialog when we click on the button.
(defun click-button ()
(iup:with-iup ()
(let* ((label (iup:label :title (format nil "Hello, World!~%IUP ~A~%~A ~A"
(iup:version)
(lisp-implementation-type)
(lisp-implementation-version))))
(button (iup:button :title "Click me"
:expand :yes
:tip "yes, click me"
:action (lambda (handle)
(declare (ignorable handle))
(iup:message "title" "button clicked")
iup:+default+)))
(vbox
(iup:vbox (list label button)
:gap "10"
:margin "10x10"
:alignment :acenter))
(dialog (iup:dialog vbox :title "Hello, World!")))
(iup:show dialog)
(iup:main-loop))))
#+sbcl
(sb-int:with-float-traps-masked
(:divide-by-zero :invalid)
(click-button))
Here’s a similar example to make a counter of clicks. We use a label and its title to hold the count. The title is an integer.
(defun counter ()
(iup:with-iup ()
(let* ((counter (iup:label :title 0))
(label (iup:label :title (format nil "The button was clicked ~a time(s)."
(iup:attribute counter :title))))
(button (iup:button :title "Click me"
:expand :yes
:tip "yes, click me"
:action (lambda (handle)
(declare (ignorable handle))
(setf (iup:attribute counter :title)
(1+ (iup:attribute counter :title 'number)))
(setf (iup:attribute label :title)
(format nil "The button was clicked ~a times."
(iup:attribute counter :title)))
iup:+default+)))
(vbox
(iup:vbox (list label button)
:gap "10"
:margin "10x10"
:alignment :acenter))
(dialog (iup:dialog vbox :title "Counter")))
(iup:show dialog)
(iup:main-loop))))
(defun run-counter ()
#-sbcl
(counter)
#+sbcl
(sb-int:with-float-traps-masked
(:divide-by-zero :invalid)
(counter)))
List widget example
Below we create three list widgets with simple and multiple selection, we set their default value (the pre-selected row) and we place them horizontally side by side.
(defun list-test ()
(iup:with-iup ()
(let* ((list-1 (iup:list :tip "List 1" ;; tooltip
;; multiple selection
:multiple :yes
:expand :yes))
(list-2 (iup:list :value 2 ;; default index of the selected row
:tip "List 2" :expand :yes))
(list-3 (iup:list :value 9 :tip "List 3" :expand :yes))
(frame (iup:frame
(iup:hbox
(progn
;; populate the lists: display integers.
(loop for i from 1 upto 10
do (setf (iup:attribute list-1 i)
(format nil "~A" i))
do (setf (iup:attribute list-2 i)
(format nil "~A" (+ i 10)))
do (setf (iup:attribute list-3 i)
(format nil "~A" (+ i 50))))
;; hbox wants a list of widgets.
(list list-1 list-2 list-3)))
:title "IUP List"))
(dialog (iup:dialog frame :menu "menu" :title "List example")))
(iup:map dialog)
(iup:show dialog)
(iup:main-loop))))
(defun run-list-test ()
#-sbcl (hello)
#+sbcl
(sb-int:with-float-traps-masked
(:divide-by-zero :invalid)
(list-test)))
Next is a different toolkit very well suited for games and that enables fully interactive development: Nuklear.