All Projects → lispnik → Iup

lispnik / Iup

Licence: other
Common Lisp CFFI bindings to the IUP Portable User Interface library (pre-ALPHA)

Labels

Projects that are alternatives of or similar to Iup

Pydiff
A simple GUI for python's difflib to compare files and directories
Stars: ✭ 74 (-13.95%)
Mutual labels:  gui
Abr Broadcaster
A real time encoder for Adaptive Bitrate Broadcast
Stars: ✭ 80 (-6.98%)
Mutual labels:  gui
Imgui Rs
Rust bindings for Dear ImGui
Stars: ✭ 1,258 (+1362.79%)
Mutual labels:  gui
Examples
Learn to create a desktop app with Python and Qt
Stars: ✭ 1,196 (+1290.7%)
Mutual labels:  gui
Chrysalisp
Parallel OS, with GUI, Terminal, OO Assembler, Class libraries, C-Script compiler, Lisp interpreter and more...
Stars: ✭ 1,205 (+1301.16%)
Mutual labels:  gui
Ui
Cross-platform UI library written in V
Stars: ✭ 1,223 (+1322.09%)
Mutual labels:  gui
Swiftgui
SwiftGUI is an API inspired by SwiftUI DSL, using Dear ImGui as renderer and running on macOS 10.13+ and iOS 11+
Stars: ✭ 74 (-13.95%)
Mutual labels:  gui
Gnvim
GUI for neovim, without any web bloat
Stars: ✭ 1,271 (+1377.91%)
Mutual labels:  gui
Ptdesigner
Library and GUI tool for designing and generation of procedural textures, made as a part of my Bachelor thesis.
Stars: ✭ 77 (-10.47%)
Mutual labels:  gui
Qode
Qode is a lightly modified fork of Node.js that merges Node's event loop with Qt's event loop. It is designed to be used together with @nodegui/nodegui.
Stars: ✭ 84 (-2.33%)
Mutual labels:  gui
Pyrustic
Lightweight framework and software suite to help develop, package, and publish Python desktop applications
Stars: ✭ 75 (-12.79%)
Mutual labels:  gui
Nplusminer
NPlusMiner + GUI | NVIDIA/AMD/CPU miner | AI | Autoupdate | MultiRig remote management
Stars: ✭ 75 (-12.79%)
Mutual labels:  gui
Patternfly Design
Use this repo to file all new feature or design change requests for the PatternFly project
Stars: ✭ 82 (-4.65%)
Mutual labels:  gui
Morda
🐶 GUI library in C++
Stars: ✭ 75 (-12.79%)
Mutual labels:  gui
Deep Learning Training Gui
Train and predict your model on pre-trained deep learning models through the GUI (web app). No more many parameters, no more data preprocessing.
Stars: ✭ 85 (-1.16%)
Mutual labels:  gui
Moonnuklear
Lua bindings for Nuklear
Stars: ✭ 74 (-13.95%)
Mutual labels:  gui
Magicgui
build GUIs from python functions, using magic.
Stars: ✭ 80 (-6.98%)
Mutual labels:  gui
Neuron
Python project for creating desktop applications with HTML and Javascript
Stars: ✭ 86 (+0%)
Mutual labels:  gui
Bumblebee
🚕 A spreadsheet-like data preparation web app that works over Optimus (pandas, dask, cuDF, dask-cuDF and PySpark)
Stars: ✭ 86 (+0%)
Mutual labels:  gui
Iced
A cross-platform GUI library for Rust, inspired by Elm
Stars: ✭ 12,176 (+14058.14%)
Mutual labels:  gui

#+STARTUP: showall #+TITLE: Common Lisp CFFI bindings to the IUP Portable User Interface library

[[./docs/screenshots/sample-01.png]]

[[./docs/screenshots/sample-02.png]]

  • Compatibility

IUP 3.29

  • Introduction

  • Shared Libraries

Get the Tecgraf shared libraries (.so and .dll files) from the Tecgraf project pages and install them per your operating system. Usually this means setting LD_LIBRARY_PATH on Linux or PATH on Windows.

  • [[https://www.tecgraf.puc-rio.br/iup/]]
  • [[https://www.tecgraf.puc-rio.br/cd/]]
  • [[https://www.tecgraf.puc-rio.br/im/]]

There is, however, a [[https://github.com/lispnik/tecgraf-libs][companion project]] which can be used to download and install the Tecgraf IUP, CD and IM shared libraries for x86/64-bit Linux and Windows automatically:

#+begin_src lisp :results silent (ql:quickload "tecgraf-libs") #+end_src

This downloads the archives from each of the project pages according to the platform your lisp is running on (Linux or Windows). It unpacks the archives, and copies all of the shared libraries to a single directory.

It is a one-off step is is not needed subsequently.

NOTE: On Linux, the tecgraf-libs system needs the patchelf command available (usually available via sudo apt install patchelf). patchelf is used to set the shared library .so files origin to the library's location so that the multiple shared libraries loaded via CFFI uses are found relative to each other.

NOTE: On Windows, the environment variable PATH should be modified so it points to the directory where the libs are downloaded. This has to be done before starting your Lisp implementation. Example of doing it and starting CCL:

#+begin_src SET PATH=C:\mylisp\projects\tecgraf-libs\libs; wx86cl64.exe #+end_src

To tell CFFI to find these libraries, use:

#+begin_src lisp (ql:quickload "cffi")

(pushnew (asdf:system-relative-pathname :tecgraf-libs #p"libs/") cffi:foreign-library-directories) #+end_src

#+RESULTS: : (#P"/home/mkennedy/.roswell/local-projects/lispnik/tecgraf-libs/libs/")

  • Loading IUP

(First, read the previous part on getting the Tecgraf libraries and installing).

Requirements:

Download/clone the following systems:

  • lispnik/tecgraf-base

  • lispnik/pffft

Those are required by Iup.

Then load iup.asd in the usual way (e.g. (ql:quickload "iup")

NOTE: For SBCL, you need to set a larger heap size to compile the bindings, e.g. --dynamic-space-size 2048

  • Hello, World!

#+begin_src lisp :results silent :export none :tangle examples/hello.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload "iup"))

(defpackage #:iup-examples.hello (:use #:common-lisp) (:export #:hello))

(in-package #:iup-examples.hello) #+end_src

#+begin_src lisp :results silent :tangle examples/hello.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)))) #+end_src

#+begin_src lisp :results silent :tangle examples/hello.lisp #-sbcl (hello)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (hello)) #+end_src

[[./docs/screenshots/helloworld.png]] [[./docs/screenshots/helloworld-2.png]]

  • Callbacks

** Simple Example

#+begin_src lisp :results silent :export none :tangle examples/callback.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload "iup"))

(defpackage #:iup-examples.callback (:use #:common-lisp) (:export #:callback))

(in-package #:iup-examples.callback) #+end_src

#+begin_src lisp :results silent :tangle examples/callback.lisp (defun callback () (iup:with-iup () (let* ((button1 (iup:button :title "Test &1" :expand :yes :tip "Callback inline at control creation" :action (lambda (handle) (message "button1's action callback") iup:+default+))) (button2 (iup:button :title "Test &2" :expand :yes :tip "Callback set later using (SETF (IUP:CALLBACK ..) ..)")) (button3 (iup:button :title "Test &3" :expand :yes :tip "Callback example using symbol-referenced function at control creation" :action 'test3-callback)) (button4 (iup:button :title "Test &4" :expand :yes :tip "Callback example using symbol-referenced function later using (SETF (IUP:CALLBACK ..) ..)")) (vbox (iup:vbox (list button1 button2 button3 button4) :gap "10" :margin "10x10" :alignment :acenter)) (dialog (iup:dialog vbox :title "Callback Example"))) (setf (iup:callback button2 :action) (lambda (handle) (message "button2's action callback") iup:+default+)) (setf (iup:callback button4 :action) 'test4-callback) (iup:show dialog) (iup:main-loop))))

(defun test3-callback (handle) (message "button3's action callback") iup:+default+)

(defun test4-callback (handle) (message "button4's action callback") iup:+default+)

(defun message (message) (iup:message "Callback Example" message)) #+end_src

#+begin_src lisp :results silent :tangle examples/callback.lisp #-sbcl (callback)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (callback)) #+end_src

[[./docs/screenshots/callback-1.png]] [[./docs/screenshots/callback-2.png]]

[[./docs/screenshots/callback-3.png]] [[./docs/screenshots/callback-4.png]]

** Color Mixer

Consider capturing state by creating a closure over the controls that make up state:

#+begin_src lisp :results silent :export none :tangle examples/mixer.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload "iup"))

(defpackage #:iup-examples.mixer (:use #:common-lisp) (:export #:mixer))

(in-package #:iup-examples.mixer)

(defun make-mixer-action (r g b button label) (lambda (handle) (declare (ignore handle)) (let ((color (format nil "~A ~A ~A" (floor (iup:attribute r :value 'number)) (floor (iup:attribute g :value 'number)) (floor (iup:attribute b :value 'number))))) (setf (iup:attribute button :fgcolor) color (iup:attribute label :title) color) (iup:refresh button)) iup:+default+))

(defun mixer () (iup:with-iup () (let* ((button (iup:flat-button :expand :yes :canfocus :no)) (label (iup:label :expand :horizontal :title "#x00000" :alignment "ACENTER:ACENTER")) (r (iup:val :expand :horizontal :min 0 :max 255)) (g (iup:val :expand :horizontal :min 0 :max 255)) (b (iup:val :expand :horizontal :min 0 :max 255)) (vbox (iup:vbox (list (iup:grid-box (list (iup:label :title "&Red") r (iup:label :title "&Green") g (iup:label :title "&Blue") b) :numdiv 2 :cgapcol 10 :cgaplin 5) button label) :cmargin 5 :cgap 5 :margin "x5")) (dialog (iup:dialog vbox :title "Color Mixer Example" :size "QUARTERxQUARTER"))) (loop :with action := (make-mixer-action r g b button label) :for handle :in (list r g b) :do (setf (iup:callback handle :valuechanged_cb) action)) (iup:show dialog) (iup:main-loop))))

#-sbcl (mixer)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (mixer)) #+end_src

[[./docs/screenshots/mixer-01.png]]

[[./docs/screenshots/mixer-02.png]]

  • Idle Action

#+begin_src lisp :results silent :export none :tangle examples/idle.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload "iup"))

(defpackage #:iup-examples.idle (:use #:common-lisp) (:export #:idle))

(in-package #:iup-examples.idle) #+end_src

There is a global callback for running functions when IUP event loop is idle. See also [[https://webserver2.tecgraf.puc-rio.br/iup/en/call/iup_idle_action.html][IUP: Idle Action]]. In Lisp, it can be set using (SETF IUP:IDLE-ACTION). Note: Idle actions run a lot more often than you'd expect. Try the following example application to get an idea for just how often.

An idle action function should return IUP:+DEFAULT+, IUP:+CLOSE+ or IUP:+IGNORE+. IUP:+DEFAULT will cause the idle action function to be called repeatedly. IUP:+IGNORE+ will run idle action once. IUP:+CLOSE+ will exit the event loop.

#+begin_src lisp :results silent :tangle examples/idle.lisp (defun idle () (iup:with-iup () (let* ((counter (iup:label :fontsize 24 :title 0 :expand :yes :alignment :acenter)) (start-button (iup:button :title "&Start" :expand :horizontal)) (stop-button (iup:button :title "S&top" :expand :horizontal)) (do-nothing nil) (do-nothing-toggle (iup:toggle :title "Do nothing" :action (lambda (handle state) (setf do-nothing (not do-nothing)) iup:+default+))) (vbox (iup:vbox (list counter (iup:hbox (list start-button stop-button do-nothing-toggle) :cgap 5)) :margin "5x5")) (dialog (iup:dialog vbox :title (format nil "Idle Example on ~A" (lisp-implementation-type)) :size "QUARTERxQUARTER"))) (setf (iup:callback start-button :action) (lambda (handle) (setf (iup:idle-action) (lambda () (unless do-nothing (setf (iup:attribute counter :title) (1+ (iup:attribute counter :title 'number)))) iup:+default+)) iup:+default+)) (setf (iup:callback stop-button :action) (lambda (handle) (setf (iup:idle-action) nil) iup:+default+)) (iup:show dialog) (iup:main-loop)))) #+end_src

#+begin_src lisp :results silent :export none :tangle examples/idle.lisp #-sbcl (idle)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (idle)) #+end_src

[[./docs/screenshots/idle-01.png]]

[[./docs/screenshots/idle-02.png]]

  • Canvas

In this example, we'll port the Sierpinski Carpet fractal that appeared the chapter on graphics in [[https://www.apress.com/us/book/9781484211779][Common Lisp Recipes: A Problem-Solution Approach]].

We need a spinner (an up and down arrow-controlled number field) and a canvas to draw on to get started. In this example, rather than specify the callbacks inline, as anonymous lamba forms, we will create separate functions and set them later using (SETF IUP:CALLBACK). LEVELS will keep track how deep to draw the fractal.

#+begin_src lisp :results silent :export none :tangle examples/sierpinski.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload '("iup" "iup-cd" "cd")))

(defpackage #:iup-examples.sierpinksi (:use #:common-lisp) (:export #:sierpinksi))

(in-package #:iup-examples.sierpinksi) #+end_src

#+begin_src lisp :results silent :tangle examples/sierpinski.lisp (defparameter levels 0)

(defun sierpinski () (iup:with-iup () (let* ((canvas (iup:canvas :rastersize "200x200")) (spin (iup:text :spin "YES" :spinmin 0 :spinmax 4)) (vbox (iup:vbox (list canvas spin) :alignment "ACENTER")) (dialog (iup:dialog vbox :title "Sierpinski Carpet"))) (setf (iup:callback canvas :map_cb) 'canvas-map (iup:callback canvas :unmap_cb) 'canvas-unmap (iup:callback canvas :action) 'canvas-redraw (iup:callback spin :spin_cb) 'canvas-spin ,levels 0) (iup:show-xy dialog iup:+center+ iup:+center+) (iup:main-loop)))) #+end_src

** Notes on Callback Naming

Each IUP widget supports a number of callbacks. In IUP, these are strings. In the Lisp bindings, they can be specified as keywords. For example, :UNMAP_CB. These are rather unlispy names, but do come from IUP via its introspection mechanism. In a future version of these bindings, it might be possible to have lispier names. e.g. :UNMAP-CALLBACK.

** CD, a 2D Graphics Library

IUP has support for CD, a cross platform 2D Graphics Library. We have support in Lisp via [[https://github.com/lispnik/cd][CD]] bindings.

The following code is entirely CD dependent and can be used in non-IUP canvas applications.

#+caption: Adapted from Edi's LTK example in Common Lisp Recipes: A Problem-Solution Approach #+begin_src lisp :results silent :tangle examples/sierpinski.lisp (defun sierpinski-draw (canvas level) (multiple-value-bind (w h) (cd:size canvas) (labels ((square (x y x-size y-size) (cd:box canvas x (+ x x-size) y (+ y y-size))) (recurse (x y x-size y-size level) (let ((x-step (/ x-size 3)) (y-step (/ y-size 3))) (square (+ x x-step) (+ y y-step) x-step y-step) (when (plusp level) (dolist (x-next (list x (+ x x-step) (+ x x-step x-step))) (dolist (y-next (list y (+ y y-step) (+ y y-step y-step))) (recurse x-next y-next x-step y-step (1- level)))))))) (recurse 0 0 w h level)))) #+end_src

For example, we can write it to [[./docs/sierpinski.pdf][PDF]] and print out to hang on your wall:

#+begin_src lisp :results silent (ql:quickload "cd-pdf")

(let ((canvas (cd:create-canvas (cd-pdf:context-pdf) "docs/sierpinski.pdf"))) (unwind-protect (sierpinski-draw canvas 4) (cd:kill canvas))) #+end_src

In our IUP example however, we'll use it with IUP's CD support and arrange for the canvas to be draw on via CANVAS-REDRAW which will be triggered by the canvas widget's action callback.

#+begin_src lisp :results silent :tangle examples/sierpinski.lisp (defparameter canvas nil)

(defun canvas-redraw (handle x y) (cd:activate canvas) (cd:clear canvas) (setf (cd:foreground canvas) cd:+red+) (sierpinski-draw canvas levels) (cd:flush canvas) iup:+default+) #+end_src

We can ignore HANDLE, X, and Y in our callback handler in this example. Those are IUP widget that triggered the callback and location on the canvas.

First we activate the canvas to draw on, clear whatever was there, set a drawing color for the the foreground of the canvas, then draw to the canvas using SIERPINSKI-DRAW.

The last step is to flush the canvas. This triggers a backing buffer swap, so all of the drawing appears instantly. If we don't do this, we don't see anything on the screen because it will still be in the off-screen drawing buffer.

** Attributes

It is a good idea to separate your UI presentation from its undelying model. In our case, the UI "model" is a special variable LEVELS which holds the depth to draw the fractal as an integer. We need this updated when the user clicks on the spinner widget.

#+begin_src lisp :results silent :tangle examples/sierpinski.lisp (defun canvas-spin (handle pos) (setf levels (iup:attribute handle :value 'number)) (canvas-redraw nil nil nil) iup:+default+) #+end_src

We can get the number from the spinner widget and assign it to LEVELS using IUP:ATTRIBUTE. It takes a IUP handle from which to get the :VALUE attribute.

IUP widget value attributes are mostly strings. The third argument, 'INTEGER converts the string to an integer for convenience, rather than having to PARSE-INTEGER ourselves.

** Fiddly bits

Lastly, we need to associate the CD canvas with a IUP canvas, but we can't do this until we have the handle of the IUP canvas, so we can't set it up in the LET* form in our main function like we did with everything else.

Luckily IUP provides callbacks for when the component is "mapped" onto the user's display which allow us to deal with this dependency in an elegant manner.

#+begin_src lisp :results silent :tangle examples/sierpinski.lisp (defun canvas-map (handle) (setf canvas (cd:create-canvas (iup-cd:context-iup-dbuffer) handle)) iup:+default+)

(defun canvas-unmap (handle) (cd:kill canvas) iup:+default+) #+end_src

#+begin_src lisp :results silent :tangle examples/sierpinski.lisp #-sbcl (sierpinski)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (sierpinski)) #+end_src

[[./docs/screenshots/sierpinski.png]] [[./docs/screenshots/sierpinski-02.png]]

  • Using IUP Additional Controls

The [[https://www.tecgraf.puc-rio.br/iup/en/ctrl/iupcells.html][cells control]] "creates a grid widget (set of cells) that enables several application-specific drawing, such as: chess tables, tiles editors, degrade scales, drawable spreadsheets and so forth".

It's included in the standard IUP distribution downloads, but it's not automatically loaded. The Lisp bindings do the same thing, so to use it, we need to depend on IUP-CONTROLS.

#+begin_src lisp :results silent :export none :tangle examples/cells.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload '("iup" "iup-controls" "cd")))

(defpackage #:iup-examples.cells-checkerboard (:use #:common-lisp) (:export #:cells-checkerboard))

(in-package #:iup-examples.cells-checkerboard) #+end_src

We start with the same boiler plate, but this time we need to call IUP-CONTROLS:OPEN ahead of using the cells control.

#+begin_src lisp :results silent :tangle examples/cells.lisp (defun cells-checkerboard () (iup:with-iup () (iup-controls:open) (let* ((cells (iup-controls:cells :draw_cb 'draw :width_cb 'width :height_cb 'height :nlines_cb 'nlines :ncols_cb 'ncols :mouseclick_cb 'click)) (vbox (iup:vbox (list cells))) (dialog (iup:dialog vbox :title "Cells Checkerboard" :rastersize "440x480" :shrink "YES"))) (iup:show-xy dialog iup:+center+ iup:+center+) (iup:main-loop)))) #+end_src

Cells has a number of callbacks related rows, columns, sizing etc.

#+begin_src lisp :results silent :tangle examples/cells.lisp (defun nlines (handle) 8) (defun ncols (handle) 8) (defun height (handle i) 50) (defun width (handle j) 50) #+end_src

When DRAW is called, we get a canvas on which to draw:

#+begin_src lisp :results silent :tangle examples/cells.lisp (defun draw (handle i j xmin xmax ymin ymax canvas) (if (or (and (oddp i) (oddp j)) (and (oddp (1+ i)) (oddp (1+ j)))) (setf (cd:foreground canvas) cd:+black+) (setf (cd:foreground canvas) cd:+white+)) (cd:box canvas xmin xmax ymin ymax) iup::+default+) #+end_src

When out click callback is called:

#+begin_src lisp :results silent :tangle examples/cells.lisp (defun click (handle button pressed line column x y status) (iup:message "Clicked!" (format nil "Callback arguments~%~S" (list :button button :pressed pressed :line line :column column :x x :y y :status (iup:status-plist status)))) iup:+default+) #+end_src

#+begin_src lisp :results silent :export none :tangle examples/cells.lisp #-sbcl (cells-checkerboard)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (cells-checkerboard)) #+end_src

[[./docs/screenshots/checkerboard-01.png]] [[./docs/screenshots/checkerboard-02.png]]

[[./docs/screenshots/checkerboard-03.png]] [[./docs/screenshots/checkerboard-04.png]]

(lol button 49)

  • Detachable Box

#+begin_src lisp :results silent :export none :tangle examples/detached.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload "iup"))

(defpackage #:iup-examples.detached (:use #:common-lisp) (:export #:detached))

(in-package #:iup-examples.detached) #+end_src

#+begin_src lisp :results silent :tangle examples/detached.lisp (defun detached () (iup:with-iup () (let* ((button1 (iup:button :title "Detach Me!" :action 'button-detach-callback :expand :yes :handlename "detach")) (multi-line (iup:multi-line :expand :yes :visiblelines 5)) (hbox (iup:hbox (list button1 multi-line) :margin "10x0")) (dbox (iup:detach-box hbox :orientation :vertical :detached_cb 'detached-callback :handlename "dbox")) (label (iup:label :title "Label" :expand :vertical)) (button2 (iup:button :title "Restore me!" :expand :yes :active :no :action 'button-restore-callback :handlename "restore")) (text (iup:text :expand :horizontal)) (dialog (iup:dialog (iup:vbox (list dbox label button2 text) :margin "10x10" :gap 10) :title "IupDetachBox Example" :rastersize "300x300")))

    (iup:show dialog)
    (iup:main-loop))))

#+end_src

** Handle Names

Instead of accessing other elements via lexical scope, it's sometimes useful to refer to them by name. This example uses the HANDLENAME attribute to associate a name with an IUP handle.

#+begin_src lisp :results silent :tangle examples/detached.lisp (defun detached-callback (handle new-parent x y) (setf (iup:attribute new-parent :title) "New Dialog" (iup:attribute (iup:handle "restore") :active) :yes (iup:attribute (iup:handle "detach") :active) :no) iup:+default+)

(defun button-restore-callback (button) (setf (iup:attribute (iup:handle "dbox") :restore) nil (iup:attribute button :active) :no (iup:attribute (iup:handle "detach") :active) :yes) iup:+default+)

(defun button-detach-callback (button) (setf (iup:attribute (iup:handle "dbox") :detach) nil (iup:attribute button :active) :no (iup:attribute (iup:handle "restore") :active) :yes) iup:+default+) #+end_src

#+begin_src lisp :results silent :export none :tangle examples/detached.lisp #-sbcl (detached)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (detached)) #+end_src

[[./docs/screenshots/detach-01.png]] [[./docs/screenshots/detach-02.png]]

FIXME look into problem with restore not being active after detach

FIXME insert example of using restart to recover from error in callback

  • Tabs Example

Demonstrates the use of (SETF IUP:ATTRIBUTE) for setting attributes not available via control's constructor function.

#+begin_src lisp :results silent :export none :tangle examples/tabs.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload "iup"))

(defpackage #:iup-examples.tabs (:use #:common-lisp) (:export #:tabs))

(in-package #:iup-examples.tabs) #+end_src

#+begin_src lisp :results silent :tangle examples/tabs.lisp (defun tabs () (iup:with-iup () (let* ((vbox1 (iup:vbox (list (iup:label :title "Inside Tab A") (iup:button :title "Button A")))) (vbox2 (iup:vbox (list (iup:label :title "Inside Tab B") (iup:button :title "Button B")))) (tabs1 (iup:tabs (list vbox1 vbox2))) (vbox3 (iup:vbox (list (iup:label :title "Inside C") (iup:button :title "Button C")))) (vbox4 (iup:vbox (list (iup:label :title "Inside D") (iup:button :title "Button D")))) (tabs2 (iup:tabs (list vbox3 vbox4))) (box (iup:hbox (list tabs1 tabs2) :margin "10x10" :gap "10")) (dialog (iup:dialog box :title "IUP Tabs" :size "200x80"))) (setf (iup:attribute vbox1 :tabtitle) "Tab A" (iup:attribute vbox2 :tabtitle) "Tab B" (iup:attribute vbox3 :tabtitle) "Tab C" (iup:attribute vbox4 :tabtitle) "Tab D") (iup:show dialog) (iup:main-loop)))) #+end_src

#+begin_src lisp :results silent :export none :tangle examples/tabs.lisp #-sbcl (tabs)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (tabs)) #+end_src

[[./docs/screenshots/tabs-01.png]] [[./docs/screenshots/tabs-02.png]]

  • Plotting

Example [[./examples/plot.lisp][./examples/plot.lisp]]

[[./docs/screenshots/plot-01.png]]

[[./docs/screenshots/plot-02.png]]

  • OpenGL

For this example, we'll take advantage for [[https://github.com/3b/cl-opengl][cl-opengland and cl-glu]]. Don't forget to depend on iup-gl (part of these bindings) as well.

Much of this example is tedious old-style OpenGL. We'll only highlight the IUP/OpenGL integration points here. It suffices to say, we've got a function CUBE which draws OpenGL things to the current buffer.

#+begin_src lisp :export none :results silent :tangle examples/cube.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload '("iup" "iup-gl" "cl-opengl" "cl-glu")))

(defpackage #:iup-examples.cube (:use #:common-lisp) (:export #:cube))

(in-package #:iup-examples.cube) #+end_src

#+begin_src lisp :results silent :tangle examples/cube.lisp (defvar canvas nil) (defvar tt 0.0)

(defvar vertices #((-1 -1 1) (-1 1 1) (1 1 1) (1 -1 1) (-1 -1 -1) (-1 1 -1) (1 1 -1) (1 -1 -1)))

(defun polygon (a b c d) (gl:begin :polygon) (apply #'gl:vertex (aref vertices a)) (apply #'gl:vertex (aref vertices b)) (apply #'gl:vertex (aref vertices c)) (apply #'gl:vertex (aref vertices d)) (gl:end))

(defun color-cube () (gl:color 1 0 0) (gl:normal 1 0 0) (polygon 2 3 7 6) (gl:color 0 1 0) (gl:normal 0 1 0) (polygon 1 2 6 5) (gl:color 0 0 1) (gl:normal 0 0 1) (polygon 0 3 2 1) (gl:color 1 0 1) (gl:normal 0 -1 0) (polygon 3 0 4 7) (gl:color 1 1 0) (gl:normal 0 0 -1) (polygon 4 5 6 7) (gl:color 0 1 1) (gl:normal -1 0 0) (polygon 5 4 0 1)) #+end_src

#+begin_src lisp :results silent :tangle examples/cube.lisp (defun cube () (iup:with-iup () (iup-gl:open) (setf canvas (iup-gl:canvas :rastersize "640x480" :buffer "DOUBLE" :action 'repaint :resize_cb 'resize)) (let* ((dialog (iup:dialog canvas :title "IUP OpenGL"))) ;; FIXME (iup-cffi::%iup-set-function :idle_action 'idle) (setf (iup:attribute canvas :depthsize) "16") (iup:show dialog) (iup:main-loop)))) #+end_src

Our example has three callbacks: repaint, resize and a global idle function callback which we'll use to rotate a cube relative to time variable TT.

#+begin_src lisp :results silent :tangle examples/cube.lisp (defun repaint (handle posx posy) (iup-gl:make-current handle) (gl:clear-color 0.3 0.3 0.3 1.0) (gl:clear :color-buffer-bit :depth-buffer-bit) (gl:enable :depth-test) (gl:matrix-mode :modelview) (gl:with-pushed-matrix (gl:translate 0 0 0) (gl:scale 1 1 1) (gl:rotate tt 0 0 1) (color-cube)) (iup-gl:swap-buffers handle) iup::+default+)

(defun resize (handle width height) (iup-gl:make-current handle) (gl:viewport 0 0 width height) (gl:matrix-mode :modelview) (gl:load-identity) (gl:matrix-mode :projection) (gl:load-identity) (glu:perspective 60 (/ 4 3) 1 15) (glu:look-at 3 3 3 0 0 0 0 0 1) iup::+default+) #+end_src

#+begin_src lisp :results silent :tangle examples/cube.lisp ;;; FIXME ;; (cffi:defcallback idle-cb :int () ;; (incf tt) ;; (iup-gl:make-current canvas) ;; (repaint canvas) ;; iup::+default+) #+end_src

#+begin_src lisp :results silent :tangle examples/cube.lisp #-sbcl (cube)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (cube)) #+end_src

[[./docs/screenshots/opengl.png]]

[[./docs/screenshots/opengl-01.png]]

  • Trees

This is a port of the [[http://webserver2.tecgraf.puc-rio.br/iup/en/basic/index.html#Trees][Lua tree example from the IUP documentation]]. It goes one step further by allowing the tree to be expanded recursively as branches open.

#+begin_src lisp :results silent :export none :tangle examples/tree.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload '("iup" "iup-controls" "uiop")))

(defpackage #:iup-examples.tree (:use #:common-lisp) (:export #:tree))

(in-package #:iup-examples.tree) #+end_src

#+begin_src lisp :results silent :tangle examples/tree.lisp (defun get-dir (pathname) (assert (uiop:directory-pathname-p pathname)) (loop for pathname in (uiop:directory* (make-pathname :name :wild :defaults pathname)) if (uiop:directory-pathname-p pathname) collect pathname into dirs else collect pathname into files finally (return (values dirs files))))

(defun fill-tree (tree id pathname) (multiple-value-bind (dirs files) (get-dir pathname) (dolist (file files) (setf (iup:attribute tree :addleaf) (namestring file))) (dolist (dir dirs) (setf (iup:attribute tree :addbranch) (namestring dir))) (setf (iup:attribute tree :title) (namestring pathname)))) #+end_src

#+begin_src lisp :results silent :tangle examples/tree.lisp (defun map-callback (handle) (fill-tree handle 0 "/") iup:+default+)

(defun branchopen-callback (handle id) (setf (iup:attribute handle (format nil "DELNODEA" id)) "CHILDREN") (fill-tree handle id (iup:attribute handle (format nil "TITLEA" id))) iup:+default+)

(defun tree () (iup:with-iup () (let* ((tree (iup:tree :minsize "200x300" :map_cb 'map-callback :branchopen_cb 'branchopen-callback)) (dialog (iup:dialog tree :title "Tree Example"))) (iup:show dialog) (iup:main-loop)))) #+end_src

#+begin_src lisp :results silent :export none :tangle examples/tree.lisp #-sbcl (tree)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (tree)) #+end_src

[[./docs/screenshots/tree-02.png]]

  • Built-in Dialogs

IUP includes a number of dialogs, including one that embeds the [[https://www.scintilla.org/][Scintilla]] editor control.

#+begin_src lisp :results silent :tangle examples/dialogs.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload '("iup" "iup-scintilla")))

(defpackage #:iup-examples.dialogs (:use #:common-lisp) (:export #:dialogs))

(in-package #:iup-examples.dialogs) #+end_src

#+begin_src lisp :results silent :tangle examples/dialogs.lisp (defun dialogs () (iup:with-iup () (iup-scintilla:open) (flet ((button (title callback) (iup:button :title title :action callback :expand :horizontal))) (let* ((dialog (iup:dialog (iup:vbox (list (button "File Dialog" 'file-dialog) (button "Message Dialog" 'message-dialog) (button "Color Dialog" 'color-dialog) (button "Font Dialog" 'font-dialog) (button "Scintilla Dialog" 'scintilla-dialog) (button "Layout Dialog" 'layout-dialog))) :title "IUP Predefined Dialogs"))) (iup:show dialog) (iup:main-loop))))) #+end_src

[[./docs/screenshots/dialogs-01.png]] [[./docs/screenshots/dialogs-02.png]]

** Using IUP:POPUP for Modal Dialogs

Often a UI designs for grabbing the user's attention via modal dialogs where the dialog is shown above the rest of the application and prevents interaction with the rest of the application. IUP:POPUP lets you achive this.

** File Dialog

#+begin_src lisp :results silent :tangle examples/dialogs.lisp (defun file-dialog (handle) (let ((dialog (iup:file-dialog))) (unwind-protect (progn (iup:popup dialog iup:+center+ iup:+center+) (iup:message "File Dialog Example" (format nil "Selected ~A" (iup:attribute dialog :value)))) (iup:destroy dialog))) iup:+default+) #+end_src

NOTE: Because modal dialogs are often created over the course of a program's runtime, they need to be destroyed after use, via IUP:DESTROY.

[[./docs/screenshots/filedialog-01.png]] [[./docs/screenshots/filedialog-02.png]]

[[./docs/screenshots/filedialog-03.png]] [[./docs/screenshots/filedialog-04.png]]

** Message Dialog

Message dialogs are like IUP:MESSAGE except that they allow for more configuration (result buttons, etc.).

#+begin_src lisp :results silent :tangle examples/dialogs.lisp (defun message-dialog (handle) (let ((dialog (iup:message-dialog :dialogtype :warning :buttons :retrycancel))) (unwind-protect (progn (setf (iup:attribute dialog :value) "Heap exhausted, game over.") (iup:popup dialog iup:+center+ iup:+center+) (iup:message "Message Dialog" (format nil "Got button response ~S" (iup:attribute dialog :buttonresponse)))) (iup:destroy dialog))) iup:+default+) #+end_src

[[./docs/screenshots/messagedialog-01.png]] [[./docs/screenshots/messagedialog-02.png]]

[[./docs/screenshots/messagedialog-03.png]] [[./docs/screenshots/messagedialog-04.png]]

** Color Dialog

#+begin_src lisp :results silent :tangle examples/dialogs.lisp (defun color-dialog (handle) (let ((dialog (iup:color-dialog :title "IUP Color Dialog" :showhex "YES" :showcolortable "YES" :showalpha "YES"))) (unwind-protect (progn (iup:popup dialog iup:+center+ iup:+center+) (iup:message "Result" (format nil "Got button response S%Got color ~A RGB (~A HSI, ~A)" (iup:attribute dialog :status) (iup:attribute dialog :value) (iup:attribute dialog :valuehsi) (iup:attribute dialog :valuehex)))))) iup:+default+) #+end_src

[[./docs/screenshots/colordialog-01.png]] [[./docs/screenshots/colordialog-02.png]]

[[./docs/screenshots/colordialog-03.png]] [[./docs/screenshots/colordialog-04.png]]

** Font Dialog

#+begin_src lisp :results silent :tangle examples/dialogs.lisp (defun font-dialog (handle) (let ((dialog (iup:font-dialog :title "IUP Font Dialog"))) (unwind-protect (progn (iup:popup dialog iup:+center+ iup:+center+) (iup:message "Result" (format nil "Got button response S%Got font ~S" (iup:attribute dialog :status) (iup:attribute dialog :value)))) (iup:destroy dialog))) iup:+default+) #+end_src

[[./docs/screenshots/fontdialog-01.png]] [[./docs/screenshots/fontdialog-02.png]]

[[./docs/screenshots/fontdialog-03.png]] [[./docs/screenshots/fontdialog-04.png]] ** Scintilla Dialog

#+begin_src lisp :results silent :tangle examples/dialogs.lisp (defun scintilla-dialog (handle) (let ((dialog (iup-scintilla:scintilla-dialog :title "IUP Scintilla Dialog"))) (unwind-protect (iup:popup dialog iup:+center+ iup:+center+) (iup:destroy dialog)))) #+end_src

[[./docs/screenshots/scintilladialog-01.png]]

[[./docs/screenshots/scintilladialog-02.png]]

(There is also a separate, more customizable Scintilla control: IUP-SCINTILLA:SCINTILLA.)

** IUP Layout Dialog

The layout dialog lets you visually inspect and edit an existing dialog and it's children or create a new dialog from scretch. It is extremely useful for experimenting and iterating on UI design.

You can use it as a visual GUI builder, similar to Glade in GTK+.

You can export a dialog and load it from file via IUP:LOAD. The export format is a [[https://www.tecgraf.puc-rio.br/iup/en/led.html][IUP LED file]].

#+begin_src lisp :results silent :tangle examples/dialogs.lisp (defun layout-dialog (handle) (let ((dialog (iup:layout-dialog nil))) (unwind-protect (iup:popup dialog iup:+center+ iup:+center+) (iup:destroy dialog))) iup:+default+) #+end_src

[[./docs/screenshots/layoutdialog-01.png]]

[[./docs/screenshots/layoutdialog-02.png]]

** Get Text Dialog

#+begin_src lisp :results silent :tangle examples/dialogs.lisp #+end_src

** List Dialog

#+begin_src lisp :results silent :tangle examples/dialogs.lisp #+end_src

** Get Param Dialog

#+begin_src lisp :results silent :tangle examples/dialogs.lisp #+end_src

** Alarm Dialog

#+begin_src lisp :results silent :tangle examples/dialogs.lisp #+end_src

#+begin_src lisp :results silent :tangle examples/dialogs.lisp #-sbcl (dialogs)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (dialogs)) #+end_src

  • Application Icons and IUP-IM

In this example, we'll set the application icon via an arbitrary image file, demonstrating the use of the IUP-IM system. The IM-IUP system provides support in our IUP bindings for interoperability with [[http://webserver2.tecgraf.puc-rio.br/im/][IM]], an imaging toolkit, also by Tecgraf.

#+begin_src lisp :results silent :export none :tangle examples/icon.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload '("iup" "iup-im")))

(defpackage #:iup-examples.icon (:use #:common-lisp) (:export #:icon))

(in-package #:iup-examples.icon) #+end_src

Here we load an image from the filesystem using IUP-IM:LOAD-IMAGE and associate the global handle name lispalien with it. We can use this handle name in labels, buttons, etc. to set the image that should be used. In the case of dialogs however, we can use the handle name to specify the image that should be displayed in the application's title bar.

IM supports a large number of formats. Here we use a .ICO file.

#+begin_src lisp :results silent :tangle examples/icon.lisp (defun icon () (iup:with-iup () (let ((icon (iup-im:load-image (asdf:system-relative-pathname "iup" "examples/lispalien.ico")))) (setf (iup:handle "lispalien") icon)) (let* ((label (iup:flat-label :image "lispalien" :expand :yes)) (dialog (iup:dialog label :title "Icon from File" :icon "lispalien" :size "THIRDxTHIRD"))) (iup:show dialog) (iup:main-loop)))) #+end_src

#+begin_src lisp :results silent :export none :tangle examples/icon.lisp #-sbcl (icon)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (icon)) #+end_src

[[./docs/screenshots/icon-01.png]] [[./docs/screenshots/icon-02.png]]

  • Drag and Drop

** File Drag and Drop from Applications

#+begin_src lisp :results silent :export none :tangle examples/drophash.lisp ;;; Generated from org-mode, do not edit

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload '("iup" "ironclad")))

(defpackage #:iup-examples.drophash (:use #:common-lisp) (:export #:drophash))

(in-package #:iup-examples.drophash) #+end_src

This example demonstrates how to receive files via a drag and drop operation from another other application as well as sliding box layout and techniques for keeping the UI responsive during computationally intensive operations.

We'll create a simple GUI wrapper for computing file digests using [[https://github.com/sharplispers/ironclad][Ironclad]], a cryptographic toolkit written in Common Lisp. The drop down lists all the digest algorithms Ironclad supports, and a label will be used to receive file drops. Once the digest is computed, they are appended to a result panel.

#+begin_src lisp :results silent :tangle examples/drophash.lisp (defun drophash () (iup:with-iup () (let* ((list (iup:list :dropdown :yes :expand :horizontal :handlename "list")) (label (iup:flat-label :title "Drop files for hash" :alignment "ACENTER:ACENTER" :font "Helvetica, 24" :dropfilestarget :yes :dropfiles_cb 'drop-files-callback :expand :yes)) (frame (iup:frame label)) (results (iup:multi-line :expand :yes :readonly :yes :visiblelines 7 :handlename "results")) (vbox (iup:vbox (list list frame (iup:sbox results :direction :north)) :margin "10x10" :cgap 5)) (dialog (iup:dialog vbox :title "Drop Hash" :size "HALFxHALF"))) (loop for digest in (ironclad:list-all-digests) for i from 1 do (setf (iup:attribute list i) digest) finally (setf (iup:attribute list :valuestring) 'ironclad:sha256)) (iup:show dialog) (iup:main-loop)))) #+end_src

When files are dropped onto the drop target (in this case, an IUP flat label), the drop files callback is called. If multiple files are dropped at the same time, then the callback will be invoked for each file.

#+begin_src lisp :results silent :tangle examples/drophash.lisp (defun drop-files-callback (handle filename num x y) (let* ((digest (intern (iup:attribute (iup:handle "list") :valuestring) "IRONCLAD")) (digest-hex (ironclad:byte-array-to-hex-string (ironclad:digest-file digest filename)))) (setf (iup:attribute (iup:handle "results") :append) (format nil "~A ~A" filename digest-hex))) (iup:flush) iup:+default+) #+end_src

When multiple files are dropped, the callback will be invoked in rapid succession and the UI will seem unresponsive. This is why the example calls IUP:FLUSH after each file is processed. IUP:FLUSH will run any pending UI operations (such as the append to the results text box).

This helps, and indeed the results pane updates in real-time as files are processed, however the UI will become unresponsive again when the digest of large files are computed.

It is best not to do any computationally expensive operations in the UI thread. We'll cover off-loading from the UI thread as well as revisit this example for better responsiveness later.

#+begin_src lisp :results silent :tangle examples/drophash.lisp #-sbcl (drophash)

,#+sbcl (sb-int:with-float-traps-masked (:divide-by-zero :invalid) (drophash)) #+end_src

[[./docs/screenshots/drophash-01.png]]

[[./docs/screenshots/drophash-02.png]]

  • Examples

Checkout the [[./examples][examples]] directory for the examples in this document as well as these other examples.

** LTK Demonstration Port

Includes example usage of IUP:TIMER for canvas animations.

[[./docs/screenshots/ltkdemo-01.png]]

[[./docs/screenshots/ltkdemo-02.png]]

  • Bindings Generation Internals

There are dozens of IUP controls and each control has dozens of callbacks and attributes. Fortunately IUP controls can be introspected to gain information on what the control is, what its callbacks and attributes are (and their arguments and types).

The iup-classesdb system uses this information to to automatically generate binding metadata from which the bindings are generated. This provides for a much nicer development experience:

[[./docs/screenshots/generation-01.png]]

The following sections describe how this works in more detail.

** Maintainer

The maintainer is typically someone with access to the Git repository for these bindings. When a new release of IUP comes out, the maintainer needs to update the metadata so that any new or removed controls, attributes or callbacks are reflected in the Lisp bindings:

#+begin_src plantuml :file docs/binding-maintainer.png :results silent () --> "(asdf:load-system :iup-classesdb)" as Load Load --> "(iup-classesdb:regenerate)" as Regen Regen --> "classesdb.lisp-sexp" as Sexp Sexp --> () #+end_src

[[./docs/binding-maintainer.png]]

[[file:classesdb.lisp-sexp][classesdb.lisp-sexp]] is the output metadata. The maintainer typically commits this file to version control so the metadata is available for everyone.

** User

The first time the user compiles the IUP bindings, classesdb.lisp-sexp is processed by macros at compile time and generates all function definitions for IUP controls. Note, that classesdb.lisp-sexpr is not actually needed when the user loads the system.

For the curious, the generation looks like the following, for each IUP system: IUP, IUP-CONTROLS, IUP-GL, IUP-GLCONTROLS, IUP-PLOT, IUP-MGLPLOT, IUP-OLECONTROL, IUP-SCINTILLA, IUP-WEB and IUP-TUIO.

#+begin_src lisp :results silent :export none (iup::defiupclasses "IUP") #+end_src

The process is roughly:

  1. load each shared library
  2. introspect for the available IUP classes (i.e. metadata about controls) availabe
  3. For each class, generate the bindings in its own package.

#+begin_src plantuml :file docs/binding-generation.png :results silent () --> "(asdf:compile-system :iup)" as Load "classesdb.lisp-sexp" as Sexpr --> Load Load --> () #+end_src

#+begin_src plantuml :file docs/binding-generation-2.png :results silent () --> "(asdf:load-system :iup)" as Load Load --> () #+end_src

[[./docs/binding-generation.png]][[./docs/binding-generation-2.png]]

** Why classesdb.lisp-sexp?

Extracting the metadata actually requires a complete GUI stack running. On Linux, this means having an X11 display available. This turns out to be a bit of a problem for continuous integration systems.

Although there are embedded X11 servers that can be used, I didn't know what might be necessary for Windows or even macOS (when it's supported) for CI/CD. Hence the classesdb.lisp-sexp is the maintainer's job to regenerate when necessary.

** Example IUP 3.25 to 3.26

Among other changes, IUP 3.26 introduced [[http://webserver2.tecgraf.puc-rio.br/iup/en/elem/iupmultibox.html][IupMultiBox]] as a new control container with 19 attributes and defaults. Regenerating classesdb.lisp-sexp automatically collected these changes so that the corresponding Lisp function IUP:MULTIBOX is created and exported automatically from the IUP package.

  • Interactive Development

TBD

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].