1196 lines
47 KiB
HTML
1196 lines
47 KiB
HTML
<!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="icon" href=
|
||
"assets/cl-logo-blue.png"/>
|
||
<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> – 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> – 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 style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPY-XMAS2023" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover our contributor's Lisp course with this Christmas coupon.</a>
|
||
<strong>
|
||
Recently added: 18 videos on MACROS.
|
||
</strong>
|
||
<a style="font-size: 90%" href="https://github.com/vindarel/common-lisp-course-in-videos/">Learn more</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. Don’t 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, we’ll 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> and <a href="https://notabug.org/cage/nodgui">nodgui</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>
|
||
<ul>
|
||
<li>if you want Gtk4 bindings, see <a href="https://github.com/bohonghuang/cl-gtk4">cl-gtk4</a>. They are new bindings, released in September, 2022.</li>
|
||
</ul>
|
||
</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 CL’s IDE and Common Graphics windowing system</a> (proprietary): Allegro’s 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 CL’s 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">CCL’s built-in Cocoa
|
||
interface</a>,
|
||
used to build applications such as <a href="https://opusmodus.com/">Opusmodus</a>.</li>
|
||
<li>Clozure CL’s 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://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-and-nodgui">Tk (Ltk and nodgui)</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>This is a simple GUI with nodgui’s built-in theme (more on that below):</p>
|
||
|
||
<p><img src="assets/gui/nodgui-feet2meters-yaru.png" alt="" /></p>
|
||
|
||
<p>This is a treeview, with the same theme:</p>
|
||
|
||
<p><img src="assets/gui/nodgui-treeview-yaru.png" alt="" /></p>
|
||
|
||
<p>A toy mediaplayer, showing a tree list, checkboxes, buttons and labels, with the Arc theme:</p>
|
||
|
||
<p><img src="assets/gui/mediaplayer-nodgui-arc.png" alt="" /></p>
|
||
|
||
<p>This is a demo with a Macos theme:</p>
|
||
|
||
<p><img src="assets/gui/ltk-on-macos.png" alt="" /></p>
|
||
|
||
<p>In addition to those, we can use many of the <a href="https://ttkthemes.readthedocs.io/en/latest/themes.html">ttkthemes</a>, the <a href="https://github.com/rdbende/Forest-ttk-theme">Forest theme</a>, and more. See <a href="https://wiki.tcl-lang.org/page/List+of+ttk+Themes">this tcl/tk list</a>.</p>
|
||
|
||
<p>But what is Tk good for? Tk doesn’t 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. It is also cross-platform.</p>
|
||
|
||
<p>So, Tk isn’t native and doesn’t have the most advanced features,
|
||
but it is a 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>There are two Lisp bindings: <a href="http://www.peter-herth.de/ltk/ltkdoc/">Ltk</a> and <a href="https://notabug.org/cage/nodgui">nodgui</a>. Nodgui
|
||
(“No Drama GUI”) is a fork of Ltk, with added widgets (such as an
|
||
auto-completion list widget), an asynchronous event loop and, what we
|
||
really enjoy, the surprisingly nice-looking “Yaru” theme that comes
|
||
with the library. It is also very easy to install and use any other theme of
|
||
our choice, see below.</p>
|
||
|
||
<ul>
|
||
<li><strong>Tk is 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 date picker. We
|
||
can find some in extensions (such as in <strong>Nodgui</strong>), but they don’t
|
||
feel native, at all. The calendar is brought by a Tk extension and looks better.</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 for Ltk (mostly maintenance), active for nodgui (new features).</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 don’t 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 you’ll 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 <a href="https://lispcookbook.github.io/cl-cookbook/in the works">https://github.com/commonqt/commonqt5/</a> and not ready for prime time..</p>
|
||
|
||
<p>A companion library for Qtools, that you’ll 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. Includes a web browser window (WebkitGTK on Linux, IE’s WebBrowser on Windows).</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
|
||
WebBrowser
|
||
</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 doesn’t 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>Here’s 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>That’s 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 widget’s 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 we’re 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>
|
||
|
||
<p>but hey, to load the demo with the better looking theme, do:</p>
|
||
|
||
<pre><code class="language-lisp">(nodgui.demo:demo :theme "yaru")
|
||
</code></pre>
|
||
|
||
<p>or</p>
|
||
|
||
<pre><code class="language-lisp">(setf nodgui:*default-theme* "yaru")
|
||
(nodgui.demo:demo)
|
||
</code></pre>
|
||
|
||
<h4 id="nodgui-ui-themes">Nodgui UI themes</h4>
|
||
|
||
<p>To use the “yaru” theme that comes with nodgui, we can simply do:</p>
|
||
|
||
<pre><code class="language-lisp">(with-nodgui ()
|
||
(use-theme "yaru")
|
||
…)
|
||
</code></pre>
|
||
|
||
<p>or</p>
|
||
|
||
<pre><code class="language-lisp">(with-nodgui (:theme "yaru")
|
||
…)
|
||
</code></pre>
|
||
|
||
<p>or</p>
|
||
|
||
<pre><code class="language-lisp">(setf nodgui:*default-theme* "yaru")
|
||
(with-nodgui ()
|
||
…)
|
||
</code></pre>
|
||
|
||
<p>It is also possible to install and load another tcl theme. For example, clone the <a href="https://github.com/rdbende/Forest-ttk-theme">Forest ttk theme</a> or the <a href="https://github.com/TkinterEP/ttkthemes/">ttkthemes</a>. Your project directory would look like this:</p>
|
||
|
||
<pre><code>yourgui.asd
|
||
yourgui.lisp
|
||
ttkthemes/
|
||
</code></pre>
|
||
|
||
<p>Inside <code>ttkthemes/</code>, you will find themes under the <code>png/</code> directory (the other ones are currently not supported):</p>
|
||
|
||
<pre><code>/ttkthemes/ttkthemes/png/arc/arc.tcl
|
||
</code></pre>
|
||
|
||
<p>You need to load the .tcl file with nodgui, and tell it to use this theme:</p>
|
||
|
||
<pre><code class="language-lisp">(with-nodgui ()
|
||
(eval-tcl-file "/ttkthemes/ttkthemes/png/arc/arc.tcl")
|
||
(use-theme "arc")
|
||
… code here …)
|
||
</code></pre>
|
||
|
||
<p>and that’s it. Your application now uses a new and decently looking GUI theme.</p>
|
||
|
||
<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>That’s cool, but we don’t 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>We’ll implement the same functionality as above, but for demonstration
|
||
purposes we’ll 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 widget’s attributes</strong></p>
|
||
|
||
<p>Use <code>(iup:attribute widget attribute)</code> to get the attribute’s 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 &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>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.</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 author’s 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. Let’s 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 let’s 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 couldn’t see what’s 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 don’t 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/>
|
||
© 2002–2023 the Common Lisp Cookbook Project
|
||
<div>
|
||
📹 Discover <a style="color: darkgrey; text-decoration: underline", href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">vindarel's Lisp course on Udemy</a>
|
||
</div>
|
||
</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>
|