emacs.d/clones/lispcookbook.github.io/cl-cookbook/gui.html

1099 lines
43 KiB
HTML
Raw Normal View History

2022-08-02 12:34:59 +02:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="generator" content=
"HTML Tidy for HTML5 for Linux version 5.2.0">
<title>GUI toolkits</title>
<meta charset="utf-8">
<meta name="description" content="A collection of examples of using Common Lisp">
<meta name="viewport" content=
"width=device-width, initial-scale=1">
<link rel="stylesheet" href=
"assets/style.css">
<script type="text/javascript" src=
"assets/highlight-lisp.js">
</script>
<script type="text/javascript" src=
"assets/jquery-3.2.1.min.js">
</script>
<script type="text/javascript" src=
"assets/jquery.toc/jquery.toc.min.js">
</script>
<script type="text/javascript" src=
"assets/toggle-toc.js">
</script>
<link rel="stylesheet" href=
"assets/github.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<h1 id="title-xs"><a href="index.html">The Common Lisp Cookbook</a> &ndash; GUI toolkits</h1>
<div id="logo-container">
<a href="index.html">
<img id="logo" src="assets/cl-logo-blue.png"/>
</a>
<div id="searchform-container">
<form onsubmit="duckSearch()" action="javascript:void(0)">
<input id="searchField" type="text" value="" placeholder="Search...">
</form>
</div>
<div id="toc-container" class="toc-close">
<div id="toc-title">Table of Contents</div>
<ul id="toc" class="list-unstyled"></ul>
</div>
</div>
<div id="content-container">
<h1 id="title-non-xs"><a href="index.html">The Common Lisp Cookbook</a> &ndash; GUI toolkits</h1>
<!-- Announcement we can keep for 1 month or more. I remove it and re-add it from time to time. -->
<p class="announce">
📹 <a href="https://www.udemy.com/course/common-lisp-programming/?couponCode=6926D599AA-LISP4ALL">NEW! Learn Lisp in videos and support our contributors with this 40% discount.</a>
</p>
<p class="announce-neutral">
📕 <a href="index.html#download-in-epub">Get the EPUB and PDF</a>
</p>
<div id="content"
<p>Lisp has a long and rich history and so does the development of
Graphical User Interfaces in Lisp. In fact, the first GUI builder was
written in Lisp (and sold to Apple. It is now Interface Builder).</p>
<p>Lisp is also famous and unrivalled for its interactive development
capabilities, a feature even more worth having to develop GUI
applications. Can you imagine compiling one function and seeing your
GUI update instantly? We can do this with many GUI frameworks today,
even though the details differ from one to another.</p>
<p>Finally, a key part in building software is how to build it and ship
it to users. Here also, we can build self-contained binaries, for
the three main operating systems, that users can run with a double
click.</p>
<p>We aim here to give you the relevant information to help you choose
the right GUI framework and to put you on tracks. Dont hesitate to
<a href="https://github.com/LispCookbook/cl-cookbook/issues/">contribute</a>, to
send more examples and to furnish the upstream documentations.</p>
<h2 id="introduction">Introduction</h2>
<p>In this recipe, well present the following GUI toolkits:</p>
<ul>
<li><a href="https://www.tcl.tk">Tk</a> with <a href="http://www.peter-herth.de/ltk/ltkdoc/">Ltk</a></li>
<li><a href="https://doc.qt.io/archives/qt-4.8/index.html">Qt4</a> with <a href="https://github.com/Shinmera/qtools">Qtools</a></li>
<li><a href="http://webserver2.tecgraf.puc-rio.br/iup/">IUP</a> with <a href="https://github.com/lispnik/iup/">lispnik/iup</a></li>
<li><a href="https://www.gtk.org/">Gtk3</a> with <a href="https://github.com/Ferada/cl-cffi-gtk/">cl-cffi-gtk</a></li>
<li><a href="https://github.com/Immediate-Mode-UI/Nuklear">Nuklear</a> with <a href="https://github.com/borodust/bodge-nuklear">Bodge-Nuklear</a></li>
</ul>
<p>In addition, you might want to have a look to:</p>
<ul>
<li>the <a href="http://www.lispworks.com/products/capi.html">CAPI</a> toolkit (Common Application Programming Interface),
which is proprietary and made by LispWorks. It is a complete and cross-platform
toolkit (Windows, Gtk+, Cocoa), very praised by its users. LispWorks
also has <a href="http://www.lispworks.com/products/lw4mr.html">iOS and Android
runtimes</a>. Example
software built with CAPI include <a href="https://scorecloud.com/">ScoreCloud</a>. It is possible to
try it with the LispWorks free demo.</li>
<li><a href="https://franz.com/products/allegro-common-lisp/acl_ide.lhtml">Allegro CLs IDE and Common Graphics windowing system</a> (proprietary): Allegros IDE is a general environment for developing applications. It works in concert with a windowing system called Common Graphics. The IDE is available for Allegro CLs Microsoft Windows, on Linux platforms, Free BSD and on the Mac.
<ul>
<li>NEW! 🎉 since Allegro CL 10.1 (released in March of 2022), the IDE, and the Common Graphics GUI toolkit, runs in the browser. It is called <a href="https://franz.com/ftp/pri/acl/cgjs/doc.html">CG/JS</a>.</li>
</ul>
</li>
<li><a href="https://ccl.clozure.com/docs/ccl.html#the-objective-c-bridge">CCLs built-in Cocoa
interface</a>,
used to build applications such as <a href="https://opusmodus.com/">Opusmodus</a>.</li>
<li>Clozure CLs built-in <a href="https://ccl.clozure.com/docs/ccl.html#the-objective-c-bridge">Objective-C bridge</a> and <a href="https://github.com/plkrueger/CocoaInterface/">CocoaInterface</a>, a Cocoa interface for CCL. Build Cocoa user interface windows dynamically using Lisp code and bypass the typical Xcode processes.
<ul>
<li>the bridge is good at catching ObjC errors and turning them into Lisp errors, so one can have an iterative REPL-based development cycle for a macOS GUI application.</li>
</ul>
</li>
<li><a href="https://common-lisp.net/project/mcclim/">McCLIM</a> and <a href="https://github.com/earl-ducaine/cl-garnet">Garnet</a> are toolkit in 100% Common Lisp. McClim even has <a href="https://techfak.de/~jmoringe/mcclim-broadway-7.ogv">a prototype</a> running in the browser with the Broadway protocol and Garnet has an ongoing interface to Gtk.</li>
<li><a href="https://github.com/Shirakumo/alloy">Alloy</a>, another very new toolkit in 100% Common Lisp, used for example in the <a href="https://github.com/shinmera/kandria">Kandria</a> game.</li>
<li><a href="https://notabug.org/cage/nodgui">nodgui</a>, a fork of Ltk, with syntax sugar and additional widgets.</li>
<li><a href="https://gitlab.com/eql">eql, eql5, eql5-android</a>, embedded Qt4 and Qt5 Lisp, embedded in ECL, embeddable in Qt. Port of EQL5 to the Android platform.</li>
<li>this <a href="https://github.com/defunkydrummer/abcl-jazz">demo using Java Swing from ABCL</a></li>
<li><a href="https://github.com/mifpasoti/Gtk-Demos">examples of using Gtk without C files with SBCL</a>, as well as GTK-server.</li>
<li>and, last but not least, <a href="http://ceramic.github.io/">Ceramic</a>, to ship a cross-platform web app with Electron.</li>
</ul>
<p>as well as the other ones listed on <a href="https://github.com/CodyReichert/awesome-cl#Gui">awesome-cl#gui</a> and <a href="https://www.cliki.net/GUI">Cliki</a>.</p>
<h3 id="tk-ltk">Tk (Ltk)</h3>
<p><a href="https://www.tcl.tk">Tk</a> (or Tcl/Tk, where Tcl is the programming language) has the
infamous reputation of having an outdated look. This is not (so) true
anymore since its version 8 of 1997 (!). It is probably better than
you think:</p>
<p><img src="assets/gui/ltk-on-macos.png" alt="" /></p>
<p>Tk doesnt have a great choice of widgets, but it has a useful canvas,
and it has a couple of unique features: we can develop a graphical
interface <strong>fully interactively</strong> and we can run the GUI <strong>remotely</strong>
from the core app.</p>
<p>So, Tk isnt fancy, but it is an used and proven GUI toolkit (and
programming language) still used in the industry. It can be a great
choice to quickly create simple GUIs, to leverage its ease of deployment, or
when stability is required.</p>
<p>The Lisp binding is <a href="http://www.peter-herth.de/ltk/ltkdoc/">Ltk</a>.</p>
<ul>
<li><strong>Written in</strong>: Tcl</li>
<li>
<p><strong>Portability</strong>: cross-platform (Windows, macOS, Linux).</p>
</li>
<li>
<p><strong>Widgets</strong>: this is not the fort of Tk. It has a <strong>small set</strong> of
default widgets, and misses important ones, for example a calendar. We
can find some in extensions (such as in <strong>Nodgui</strong>), but they dont
feel native, at all.</p>
</li>
<li>
<p><strong>Interactive development</strong>: very much.</p>
</li>
<li>
<p><strong>Graphical builder</strong>: no</p>
</li>
<li><strong>Other features</strong>:
<ul>
<li><strong>remote execution</strong>: the connection between Lisp and Tcl/Tk is
done via a stream. It is thus possible to run the Lisp program on
one computer, and to display the GUI on another one. The only
thing required on the client computer is tcl/tk installed and the
remote.tcl script. See <a href="http://www.peter-herth.de/ltk/ltkdoc/node46.html">Ltk-remote</a>.</li>
</ul>
</li>
<li><strong>Bindings documentation</strong>: short but complete. Nodgui too.</li>
<li><strong>Bindings stability</strong>: very stable</li>
<li><strong>Bindings activity</strong>: low.</li>
<li><strong>Licence</strong>: Tcl/Tk is BSD-style, Ltk is LGPL.</li>
<li>Example applications:
<ul>
<li><a href="https://notabug.org/cage/fulci/">Fulci</a> - a program to organise your movie collections.</li>
<li><a href="https://github.com/mijohnson99/ltk-small-games">Ltk small games</a> - snake and tic-tac-toe.</li>
<li><a href="https://github.com/VitoVan/cl-pkr">cl-pkr</a> - a cross-platform color picker.</li>
<li><a href="https://github.com/vindarel/cl-torrents">cl-torrents</a> - searching torrents on popular trackers. CLI, readline and a simple Tk GUI.</li>
</ul>
</li>
<li>More examples:
<ul>
<li><a href="https://peterlane.netlify.app/ltk-examples/">https://peterlane.netlify.app/ltk-examples/</a>: LTk examples for the <a href="https://tkdocs.com/tutorial/index.html">tkdocs</a> tutorial.</li>
<li><a href="https://peterlane.netlify.app/ltk-plotchart/">LTk Plotchart</a> - A wrapper around the tklib/plotchart library to work with LTk. This includes over 20 different chart types (xy-plots, gantt charts, 3d-bar charts etc…).</li>
</ul>
</li>
</ul>
<p><strong>List of widgets</strong></p>
<p>(please dont suppose the list is exhaustive)</p>
<pre><code>Button Canvas Check-button Entry Frame Label Labelframe Listbox
Menu Menubutton Message
Paned-window
Radio-button Scale
Scrollbar Spinbox Text
Toplevel Widget Canvas
Ltk-megawidgets:
progress
history-entry
menu-entry
</code></pre>
<p>Nodgui adds:</p>
<pre><code>treelist tooltip searchable-listbox date-picker calendar autocomplete-listbox
password-entry progress-bar-star notify-window
dot-plot bar-chart equalizer-bar
swap-list
</code></pre>
<h3 id="qt4-qtools">Qt4 (Qtools)</h3>
<p>Do we need to present Qt and <a href="https://doc.qt.io/archives/qt-4.8/index.html">Qt4</a>? Qt is huge and contains
everything and the kitchen sink. Qt not only provides UI widgets, but
numerous other layers (networking, D-BUS…).</p>
<p>Qt is free for open-source software, however youll want to check the
conditions to ship proprietary ones.</p>
<p>The <a href="https://github.com/Shinmera/qtools">Qtools</a> bindings target Qt4. The Qt5 Lisp bindings are
yet to be created.</p>
<p>A companion library for Qtools, that youll want to check out once you
made your first Qtool application, is
<a href="https://github.com/Shinmera/qtools-ui">Qtools-ui</a>, a collection of
useful widgets and pre-made components. It comes with short
<a href="https://www.youtube.com/playlist?list=PLkDl6Irujx9Mh3BWdBmt4JtIrwYgihTWp">demonstrations
videos</a>.</p>
<!-- possible future: gobject-introspection -->
<ul>
<li><strong>Framework written in</strong>: C++</li>
<li><strong>Framework Portability</strong>: multi-platform, Android, embedded systems, WASM.</li>
<li>
<p><strong>Bindings Portability</strong>: Qtools runs on x86 desktop platforms on Windows, macOS and GNU/Linux.</p>
</li>
<li>
<p><strong>Widgets choice</strong>: large.</p>
</li>
<li>
<p><strong>Graphical builder</strong>: yes.</p>
</li>
<li>
<p><strong>Other features</strong>: Web browser, a lot more.</p>
</li>
<li><strong>Bindings documentation</strong>: lengthy explanations, a few examples. Prior Qt knowledge is required.</li>
<li><strong>Bindings stability</strong>: stable</li>
<li><strong>Bindings activity</strong>: active</li>
<li><strong>Qt Licence</strong>: both commercial and open source licences.</li>
<li>Example applications:
<ul>
<li>https://github.com/Shinmera/qtools/tree/master/examples</li>
<li>https://github.com/Shirakumo/lionchat</li>
<li>https://github.com/shinmera/halftone - a simple image viewer</li>
</ul>
</li>
</ul>
<h3 id="gtk3-cl-cffi-gtk">Gtk+3 (cl-cffi-gtk)</h3>
<p><a href="https://www.gtk.org/">Gtk+3</a> is the primary library used to build <a href="https://www.gnome.org/">GNOME</a>
applications. Its (currently most advanced) lisp bindings is
<a href="https://github.com/Ferada/cl-cffi-gtk/">cl-cffi-gtk</a>. While primarily created for GNU/Linux, Gtk
works fine under macOS and can now also be used on Windows.</p>
<ul>
<li><strong>Framework written in</strong>: C</li>
<li>
<p><strong>Portability</strong>: GNU/Linux and macOS, also Windows.</p>
</li>
<li>
<p><strong>Widgets choice</strong>: large.</p>
</li>
<li><strong>Graphical builder</strong>: yes: Glade.</li>
<li>
<p><strong>Other features</strong>: web browser (WebKitGTK)</p>
</li>
<li><strong>Bindings documentation</strong>: very good: http://www.crategus.com/books/cl-gtk/gtk-tutorial.html</li>
<li><strong>Bindings stability</strong>: stable</li>
<li><strong>Bindings activity</strong>: low activity, active development.</li>
<li><strong>Licence</strong>: LGPL</li>
<li>Example applications:
<ul>
<li>an <a href="https://github.com/ralph-schleicher/atmosphere-calculator">Atmosphere Calculator</a>, built with Glade.</li>
</ul>
</li>
<li>more documentation and examples:
<ul>
<li><a href="https://dev.to/goober99/learn-common-lisp-by-example-gtk-gui-with-sbcl-5e5c">Learn Common Lisp by Example: GTK GUI with SBCL</a></li>
</ul>
</li>
</ul>
<h3 id="iup-lispnikiup">IUP (lispnik/IUP)</h3>
<p><a href="http://webserver2.tecgraf.puc-rio.br/iup/">IUP</a> is a cross-platform GUI toolkit actively developed
at the PUC university of Rio de Janeiro, Brazil. It uses <strong>native
controls</strong>: 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 <strong>small API</strong>.</p>
<p>The Lisp bindings are <a href="https://github.com/lispnik/iup/">lispnik/iup</a>. 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.</p>
<p>IUP stands as a great solution in between Tk and Gtk or Qt.</p>
<ul>
<li><strong>Framework written in</strong>: C (official API also in Lua and LED)</li>
<li>
<p><strong>Portability</strong>: Windows and Linux, work started for
Cocoa, iOS, Android, WASM.</p>
</li>
<li>
<p><strong>Widgets choice</strong>: medium.</p>
</li>
<li>
<p><strong>Graphical builder</strong>: yes: <a href="http://webserver2.tecgraf.puc-rio.br/iup/en/iupvisualled.html">IupVisualLED</a></p>
</li>
<li>
<p><strong>Other features</strong>: OpenGL, Web browser (WebKitGTK on GNU/Linux), plotting, Scintilla text editor</p>
</li>
<li><strong>Bindings documentation</strong>: good examples and good readme, otherwise low.</li>
<li><strong>Bindings stability</strong>: alpha (but fully generated and working nicely).</li>
<li><strong>Bindings activity</strong>: low but steady, and reactive to new IUP versions.</li>
<li><strong>Licence</strong>: IUP and the bindings are MIT licenced.</li>
</ul>
<p><strong>List of widgets</strong></p>
<pre><code>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
</code></pre>
<!-- editor's note: found missing a list view with columns. -->
<p><img src="assets/iup-demo.png" alt="" /></p>
<h3 id="nuklear-bodge-nuklear">Nuklear (Bodge-Nuklear)</h3>
<p><a href="https://github.com/Immediate-Mode-UI/Nuklear">Nuklear</a> is a small <a href="https://en.wikipedia.org/wiki/Immediate_mode_GUI">immediate-mode</a> GUI toolkit:</p>
<blockquote>
<p><a href="https://github.com/Immediate-Mode-UI/Nuklear">Nuklear</a> is a minimal-state, immediate-mode graphical user interface toolkit written in ANSI C and licensed under public domain. It was designed as a simple embeddable user interface for application and does not have any dependencies, a default render backend or OS window/input handling but instead provides a highly modular, library-based approach, with simple input state for input and draw commands describing primitive shapes as output. So instead of providing a layered library that tries to abstract over a number of platform and render backends, it focuses only on the actual UI.</p>
</blockquote>
<p>its Lisp binding is <a href="https://github.com/borodust/bodge-nuklear">Bodge-Nuklear</a>, and its higher level companions <a href="https://github.com/borodust/bodge-ui">bodge-ui</a> and <a href="https://github.com/borodust/bodge-ui-window">bodge-ui-window</a>.</p>
<p>Unlike traditional UI frameworks, Nuklear allows the developer to take
over the rendering loop or the input management. This might require
more setup, but it makes Nuklear particularly well suited for games,
or for applications where you want to create new controls.</p>
<ul>
<li><strong>Framework written in</strong>: ANSI C, single-header library.</li>
<li>
<p><strong>Portability</strong>: where C runs. Nuklear doesnt contain
platform-specific code. No direct OS or window handling is done in
Nuklear. Instead <em>all input state has to be provided by platform
specific code</em>.</p>
</li>
<li>
<p><strong>Widgets choice</strong>: small.</p>
</li>
<li>
<p><strong>Graphical builder</strong>: no.</p>
</li>
<li>
<p><strong>Other features</strong>: fully skinnable and customisable.</p>
</li>
<li><strong>Bindings stability</strong>: stable</li>
<li><strong>Bindings activity</strong>: active</li>
<li><strong>Licence</strong>: MIT or Public Domain (unlicence).</li>
<li>Example applications:
<ul>
<li><a href="https://github.com/borodust/trivial-gamekit">Trivial-gamekit</a></li>
<li><a href="https://github.com/thicksteadTHpp/Obvius/">Obvius</a> - a resurrected image processing library.</li>
<li><a href="https://github.com/borodust/notalone">Notalone</a> - an autumn 2017 Lisp Game Jam entry.</li>
</ul>
</li>
</ul>
<p><strong>List of widgets</strong></p>
<p>Non-exhaustive list:</p>
<pre><code>buttons, progressbar, image selector, (collapsable) tree, list, grid, range, slider, color picker,
date-picker
</code></pre>
<p><img src="assets/gui/nuklear.png" alt="" /></p>
<h2 id="getting-started">Getting started</h2>
<h3 id="tk">Tk</h3>
<p>Ltk is quick and easy to grasp.</p>
<pre><code class="language-lisp">(ql:quickload "ltk")
(in-package :ltk-user)
</code></pre>
<p><strong>How to create widgets</strong></p>
<p>All widgets are created with a regular <code>make-instance</code> and the widget name:</p>
<pre><code class="language-lisp">(make-instance 'button)
(make-instance 'treeview)
</code></pre>
<p>This makes Ltk explorable with the default symbol completion.</p>
<p><strong>How to start the main loop</strong></p>
<p>As with most bindings, the GUI-related code must be started inside a macro that
handles the main loop, here <code>with-ltk</code>:</p>
<pre><code class="language-lisp">(with-ltk ()
(let ((frame (make-instance 'frame)))
…))
</code></pre>
<p><strong>How to display widgets</strong></p>
<p>After we created some widgets, we must place them on the layout. There
are a few Tk systems for that, but the most recent one and the one we
should start with is the <code>grid</code>. <code>grid</code> is a function that takes as
arguments the widget, its column, its row, and a few optional
parameters.</p>
<p>As with any Lisp code in a regular environment, the functions
signatures are indicated by the editor. It makes Ltk explorable.</p>
<p>Heres how to display a button:</p>
<pre><code class="language-lisp">(with-ltk ()
(let ((button (make-instance 'button :text "hello")))
(grid button 0 0)))
</code></pre>
<p>Thats all there is to it.</p>
<h4 id="reacting-to-events">Reacting to events</h4>
<p>Many widgets have a <code>:command</code> argument that accept a lambda which is
executed when the widgets event is started. In the case of a button,
that will be on a click:</p>
<pre><code class="language-lisp">(make-instance 'button
:text "Hello"
:command (lambda ()
(format t "clicked")))
</code></pre>
<h4 id="interactive-development">Interactive development</h4>
<p>When we start the Tk process in the background with <code>(start-wish)</code>, we
can create widgets and place them on the grid interactively.</p>
<p>See <a href="http://www.peter-herth.de/ltk/ltkdoc/node8.html">the documentation</a>.</p>
<p>Once were done, we can <code>(exit-wish)</code>.</p>
<h4 id="nodgui">Nodgui</h4>
<p>To try the Nodgui demo, do:</p>
<pre><code class="language-lisp">(ql:quickload "nodgui")
(nodgui.demo:demo)
</code></pre>
<h3 id="qt4">Qt4</h3>
<pre><code class="language-lisp">(ql:quickload '(:qtools :qtcore :qtgui))
</code></pre>
<pre><code class="language-lisp">(defpackage #:qtools-test
(:use #:cl+qt)
(:export #:main))
(in-package :qtools-test)
(in-readtable :qtools)
</code></pre>
<p>We create our main widget that will contain the rest:</p>
<pre><code class="language-lisp">(define-widget main-window (QWidget)
())
</code></pre>
<p>We create an input field and a button inside this main widget:</p>
<pre><code class="language-lisp">(define-subwidget (main-window name) (q+:make-qlineedit main-window)
(setf (q+:placeholder-text name) "Your name please."))
</code></pre>
<pre><code class="language-lisp">(define-subwidget (main-window go-button) (q+:make-qpushbutton "Go!" main-window))
</code></pre>
<p>We stack them horizontally:</p>
<pre><code class="language-lisp">(define-subwidget (main-window layout) (q+:make-qhboxlayout main-window)
(q+:add-widget layout name)
(q+:add-widget layout go-button))
</code></pre>
<p>and we show them:</p>
<pre><code class="language-lisp">(with-main-window
(window 'main-window))
</code></pre>
<p><img src="assets/gui/qtools-intro.png" alt="" /></p>
<p>Thats cool, but we dont react to the click event yet.</p>
<h4 id="reacting-to-events-1">Reacting to events</h4>
<p>Reacting to events in Qt happens through signals and slots. <strong>Slots</strong> are
functions that receive or “connect to” signals, and <strong>signals</strong> are event carriers.</p>
<p>Widgets already send their own signals: for example, a button sends a
“pressed” event. So, most of the time, we only need to connect to them.</p>
<p>However, had we extra needs, we can create our own set of signals.</p>
<h5 id="built-in-events">Built-in events</h5>
<p>We want to connect our <code>go-button</code> to the <code>pressed</code> and
<code>return-pressed</code> events and display a message box.</p>
<ul>
<li>we need to do this inside a <code>define-slot</code> function,</li>
<li>where we establish the connection to those events,</li>
<li>and where we create the message box. We grab the text of the <code>name</code>
input field with <code>(q+:text name)</code>.</li>
</ul>
<pre><code class="language-lisp">(define-slot (main-window go-button) ()
(declare (connected go-button (pressed)))
(declare (connected name (return-pressed)))
(q+:qmessagebox-information main-window
"Greetings" ;; title
(format NIL "Good day to you, ~a!" (q+:text name))))
</code></pre>
<p>And voilà. Run it with</p>
<pre><code class="language-lisp">(with-main-window (window 'main-window))
</code></pre>
<h5 id="custom-events">Custom events</h5>
<p>Well implement the same functionality as above, but for demonstration
purposes well create our own signal named <code>name-set</code> to throw when
the button is clicked.</p>
<p>We start by defining the signal, which happens inside the
<code>main-window</code>, and which is of type <code>string</code>:</p>
<pre><code class="language-lisp">(define-signal (main-window name-set) (string))
</code></pre>
<p>We create a <strong>first slot</strong> to make our button react to the <code>pressed</code>
and <code>return-pressed</code> events. But instead of creating the message box
here, as above, we send the <code>name-set</code> signal, with the value of our
input field..</p>
<pre><code class="language-lisp">(define-slot (main-window go-button) ()
(declare (connected go-button (pressed)))
(declare (connected name (return-pressed)))
(signal! main-window (name-set string) (q+:text name)))
</code></pre>
<p>So far, nobody reacts to <code>name-set</code>. We create a <strong>second slot</strong> that
connects to it, and displays our message. Here again, we precise the
parameter type.</p>
<pre><code class="language-lisp">(define-slot (main-window name-set) ((new-name string))
(declare (connected main-window (name-set string)))
(q+:qmessagebox-information main-window "Greetings" (format NIL "Good day to you, ~a!" new-name)))
</code></pre>
<p>and run it:</p>
<pre><code class="language-lisp">(with-main-window (window 'main-window))
</code></pre>
<h4 id="building-and-deployment">Building and deployment</h4>
<p>It is possible to build a binary and bundle it together with all the
necessary shared libraries.</p>
<p>Please read <a href="https://github.com/Shinmera/qtools#deployment">https://github.com/Shinmera/qtools#deployment</a>.</p>
<p>You might also like <a href="https://github.com/phoe-trash/furcadia-post-splitter/blob/master/.travis.yml">this Travis CI script</a> to build a self-contained binary for the three OSes.</p>
<h3 id="gtk3">Gtk3</h3>
<p>The
<a href="http://www.crategus.com/books/cl-gtk/gtk-tutorial.html">documentation</a>
is exceptionally good, including for beginners.</p>
<p>The library to quickload is <code>cl-cffi-gtk</code>. It is made of numerous
ones, that we have to <code>:use</code> for our package.</p>
<pre><code class="language-lisp">(ql:quickload "cl-cffi-gtk")
(defpackage :gtk-tutorial
(:use :gtk :gdk :gdk-pixbuf :gobject
:glib :gio :pango :cairo :common-lisp))
(in-package :gtk-tutorial)
</code></pre>
<p><strong>How to run the main loop</strong></p>
<p>As with the other libraries, everything happens inside the main loop
wrapper, here <code>with-main-loop</code>.</p>
<p><strong>How to create a window</strong></p>
<p><code>(make-instance 'gtk-window :type :toplevel :title "hello" ...)</code>.</p>
<p><strong>How to create a widget</strong></p>
<p>All widgets have a corresponding class. We can create them with
<code>make-instance 'widget-class</code>, but we preferably use the constructors.</p>
<p>The constructors end with (or contain) “new”:</p>
<pre><code class="language-lisp">(gtk-label-new)
(gtk-button-new-with-label "Label")
</code></pre>
<p><strong>How to create a layout</strong></p>
<pre><code class="language-lisp">(let ((box (make-instance 'gtk-box :orientation :horizontal :spacing 6))) ...)
</code></pre>
<p>then pack a widget onto the box:</p>
<pre><code class="language-lisp">(gtk-box-pack-start box mybutton-1)
</code></pre>
<p>and add the box to the window:</p>
<pre><code class="language-lisp">(gtk-container-add window box)
</code></pre>
<p>and display them all:</p>
<pre><code class="language-lisp">(gtk-widget-show-all window)
</code></pre>
<h4 id="reacting-to-events-2">Reacting to events</h4>
<p>Use <code>g-signal-connect</code> + the concerned widget + the event name (as a
string) + a lambda, that takes the widget as argument:</p>
<pre><code class="language-lisp">(g-signal-connect window "destroy"
(lambda (widget)
(declare (ignore widget))
(leave-gtk-main)))
</code></pre>
<p>Or again:</p>
<pre><code class="language-lisp">(g-signal-connect button "clicked"
(lambda (widget)
(declare (ignore widget))
(format t "Button was pressed.~%")))
</code></pre>
<h4 id="full-example">Full example</h4>
<pre><code class="language-lisp">(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))))
</code></pre>
<p><img src="assets/gui/gtk3-hello-buttons.png" alt="" /></p>
<h3 id="iup">IUP</h3>
<p>Please check the installation instructions upstream. You may need one
system dependency on GNU/Linux, and to modify an environment variable
on Windows.</p>
<p>Finally, do:</p>
<pre><code class="language-lisp">(ql:quickload "iup")
</code></pre>
<p>We are not going to <code>:use</code> IUP (it is a bad practice generally after all).</p>
<pre><code class="language-lisp">(defpackage :test-iup
(:use :cl))
(in-package :test-iup)
</code></pre>
<p>The following snippet creates a dialog frame to display a text label.</p>
<pre><code class="language-lisp">(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)
</code></pre>
<p>Important note for SBCL: we currently must trap division-by-zero
errors (see advancement on <a href="https://github.com/lispnik/iup/issues/30">this
issue</a>). So, run snippets
like so:</p>
<pre><code class="language-lisp">(defun run-gui-function ()
#-sbcl (gui-function)
#+sbcl
(sb-int:with-float-traps-masked
(:divide-by-zero :invalid)
(gui-function)))
</code></pre>
<p><strong>How to run the main loop</strong></p>
<p>As with all the bindings seen so far, widgets are shown inside a
<code>with-iup</code> macro, and with a call to <code>iup:main-loop</code>.</p>
<p><strong>How to create widgets</strong></p>
<p>The constructor function is the name of the widget: <code>iup:label</code>,
<code>iup:dialog</code>.</p>
<p><strong>How to display a widget</strong></p>
<p>Be sure to “show” it: <code>(iup:show dialog)</code>.</p>
<p>You can group widgets on <code>frame</code>s, and stack them vertically or
horizontally (with <code>vbox</code> or <code>hbox</code>, see the example below).</p>
<p>To allow a widget to be expanded on window resize, use <code>:expand
:yes</code> (or <code>:horizontal</code> and <code>:vertical</code>).</p>
<p>Use also the <code>:alignement</code> properties.</p>
<p><strong>How to get and set a widgets attributes</strong></p>
<p>Use <code>(iup:attribute widget attribute)</code> to get the attributes value,
and use <code>setf</code> on it to set it.</p>
<h4 id="reacting-to-events-3">Reacting to events</h4>
<p>Most widgets take an <code>:action</code> parameter that takes a lambda function
with one parameter (the handle).</p>
<pre><code class="language-lisp">(iup:button :title "Test &amp;1"
:expand :yes
:tip "Callback inline at control creation"
:action (lambda (handle)
(iup:message "title" "button1's action callback")
iup:+default+))
</code></pre>
<p>Below we create a label and put a button below it. We display a
message dialog when we click on the button.</p>
<pre><code class="language-lisp">(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))
</code></pre>
<p>Heres 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.</p>
<pre><code class="language-lisp">(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)))
</code></pre>
<h4 id="list-widget-example">List widget example</h4>
<p>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.</p>
<pre><code class="language-lisp">(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)))
</code></pre>
<h3 id="nuklear">Nuklear</h3>
<p><strong>Disclaimer</strong>: as per the authors words at the time of writing,
bodge-ui is in early stages of development and not ready for general
use yet. There are some quirks that need to be fixed, which might
require some changes in the API.</p>
<p><code>bodge-ui</code> is not in Quicklisp but in its own Quicklisp distribution. Lets install it:</p>
<pre><code class="language-lisp">(ql-dist:install-dist "http://bodge.borodust.org/dist/org.borodust.bodge.txt" :replace t :prompt nil)
</code></pre>
<p>Uncomment and evaluate this line only if you want to enable the OpenGL 2
renderer:</p>
<pre><code class="language-lisp">;; (cl:pushnew :bodge-gl2 cl:*features*)
</code></pre>
<p>Quickload <code>bodge-ui-window</code>:</p>
<pre><code class="language-lisp">(ql:quickload "bodge-ui-window")
</code></pre>
<p>We can run the built-in example:</p>
<pre><code class="language-lisp">(ql:quickload "bodge-ui-window/examples")
(bodge-ui-window.example.basic:run)
</code></pre>
<p>Now lets define a package to write a simple application.</p>
<pre><code class="language-lisp">(cl:defpackage :bodge-ui-window-test
(:use :cl :bodge-ui :bodge-host))
(in-package :bodge-ui-window-test)
</code></pre>
<pre><code class="language-lisp">(defpanel (main-panel
(:title "Hello Bodge UI")
(:origin 200 50)
(:width 400) (:height 400)
(:options :movable :resizable
:minimizable :scrollable
:closable))
(label :text "Nested widgets:")
(horizontal-layout
(radio-group
(radio :label "Option 1")
(radio :label "Option 2" :activated t))
(vertical-layout
(check-box :label "Check 1" :width 100)
(check-box :label "Check 2"))
(vertical-layout
(label :text "Awesomely" :align :left)
(label :text "Stacked" :align :centered)
(label :text "Labels" :align :right)))
(label :text "Expand by width:")
(horizontal-layout
(button :label "Dynamic")
(button :label "Min-Width" :width 80)
(button :label "Fixed-Width" :expandable nil :width 100))
(label :text "Expand by width:")
(horizontal-layout
(button :label "1.0" :expand-ratio 1.0)
(button :label "0.75" :expand-ratio 0.75)
(button :label "0.5" :expand-ratio 0.5))
(label :text "Rest:")
(button :label "Top-level Button"))
(defparameter *window-width* 800)
(defparameter *window-height* 600)
(defclass main-window (bodge-ui-window:ui-window) ()
(:default-initargs
:title "Bodge UI Window Example"
:width *window-width*
:height *window-height*
:panels '(main-panel)
:floating t
:opengl-version #+bodge-gl2 '(2 1)
#+bodge-gl2 '(3 3)))
(defun run ()
(bodge-host:open-window (make-instance 'main-window)))
</code></pre>
<p>and run it:</p>
<pre><code class="language-lisp">(run)
</code></pre>
<p><img src="assets/gui/nuklear-test.png" alt="" /></p>
<p>To react to events, use the following signals:</p>
<pre><code>:on-click
:on-hover
:on-leave
:on-change
:on-mouse-press
:on-mouse-release
</code></pre>
<p>They take as argument a function with one argument, the panel. But
beware: they will be called on each rendering cycle when the widget is
on the given state, so potentially a lot of times.</p>
<h4 id="interactive-development-1">Interactive development</h4>
<p>If you ran the example in the REPL, you couldnt see whats cool. Put
the code in a lisp file and run it, so than you get the window. Now
you can change the panel widgets and the layout, and your changes will
be immediately applied while the application is running!</p>
<h2 id="conclusion">Conclusion</h2>
<p>Have fun, and dont hesitate to share your experience and your apps.</p>
<p class="page-source">
Page source: <a href="https://github.com/LispCookbook/cl-cookbook/blob/master/gui.md">gui.md</a>
</p>
</div>
<script type="text/javascript">
// Don't write the TOC on the index.
if (window.location.pathname != "/cl-cookbook/") {
$("#toc").toc({
content: "#content", // will ignore the first h1 with the site+page title.
headings: "h1,h2,h3,h4"});
}
$("#two-cols + ul").css({
"column-count": "2",
});
$("#contributors + ul").css({
"column-count": "4",
});
</script>
<div>
<footer class="footer">
<hr/>
&copy; 2002&ndash;2021 the Common Lisp Cookbook Project
</footer>
</div>
<div id="toc-btn">T<br>O<br>C</div>
</div>
<script text="javascript">
HighlightLisp.highlight_auto({className: null});
</script>
<script type="text/javascript">
function duckSearch() {
var searchField = document.getElementById("searchField");
if (searchField && searchField.value) {
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
window.location.href = "https://duckduckgo.com/?kj=b2&kf=-1&ko=1&q=" + query;
// https://duckduckgo.com/params
// kj=b2: blue header in results page
// kf=-1: no favicons
}
}
</script>
<script async defer data-domain="lispcookbook.github.io/cl-cookbook" src="https://plausible.io/js/plausible.js"></script>
</body>
</html>