A few words from Agical

A Calva workflow for quil drawing

Quil is a Clojure and ClojureScript wrapper around Processing. Quil takes learning how to code within the context of the visual arts and applies 10X to it. (I say being a passionate Clojurian.)

With Clojure and its REPL comes the promise that you dynamically can tweak and form your sketches, as they are running. That’s what this is article is about. There is a Calva context, but things generally apply accross all Clojure editors/IDEs. I will assume you know some Clojure, but you should be able to follow pretty well regardless, because I also will try to keep this at a beginner’s level. My aim is to leverage Quil to try to explain things in a way that could help you ”see” how the Clojure editor relates to the Clojure REPL. (In case you haven’t yet ”seen” this.)

Let’s start with inspiring ourselves! Check this GIF out:

It shows Calva employing the Clojure REPL to modify a running Quil/Processing sketch. There are a few things happening there. The relevant ones for the purpose of this article are:

So far we have only been inspecting the sketch. Which is powerful. Then the real magic happens:

  1. We type a new version of the draw-state function, modifying how the y coordinate is calculated.
  2. We send the whole text of the new draw-state function to the REPL, which evaluates it, leading to that a new definition of the function is ”installed” in the namespace.
  3. The following tick, Quil will call our new function, resulting in that the circle movement starts following a different path.

Even though I have been coding Clojure in this Interactive Programming way for quite a while now, my mind is still blown! This way of working is why I know I will stay with Clojure for the forseeable future. It is nothing short of delightful. Yes, there are other environments/languages offering this (and even beyond), but none of them is as practical and soundly founded as Clojure is.

How to get there

There is a mindboggling feeling to be evaluating your program into existence. But, and trust me on this, none of this magic comes from Calva. It is the Clojure REPL at work. In theory that is all you need to know. I am writing this short guide anyway, because, on the Clojurians Slack, Leif Eric Fredheim made me aware that the Quil wiki instructions for how to get to a dynamic Quil workflow can lead a bit astray. Especially if you don’t know how to map the general REPL advice to the REPL UI that Calva provides.

As an example I have made a highly regular Clojure app, which is using tools-deps. I created it with deps-new. It’s a GitHub project template, available here: https://github.com/PEZ/a-quil-workflow

  1. Use the template (there is a green button for this at the top of the repository page) and clone it to your computer.
  2. Open the project root in VS Code and (assuming you have Calva installed) use the command Calva: Start a Project REPL and connect (a.k.a Jack-in) to get the REPL connected to your editor.
    1. Select the deps.edn Project type
    2. Don’t select any alias
    3. Wait a few seconds for the REPL to be connected (the indicator in the status bar turns ”Calva orange” and the output window will also show when it is done)
  3. Open the src/a_quil_workflow/core.clj file and issue the command Calva: Load current file and dependencies

This will pop open a floating Java window with the example sketch running.

The relevant parts of the example project are:

The sketch definition file

Let’s have a quick look at core.clj:

(ns a-quil-workflow.core
  (:require [a-quil-workflow.drawing :as drawing]
            [quil.applet :as qa]
            [quil.core :as q]
            [quil.middleware :as qm]))

(q/defsketch example
  :title "An example quil sketch"
  :size [500 300]
  ; setup function called only once, during sketch initialization.
  :setup drawing/setup
  ; update-state is called on each iteration before draw-state.
  :update drawing/update-state
  :draw drawing/draw-state
  :features [:keep-on-top]
  ; This sketch uses functional-mode middleware.
  ; Check quil wiki for more info about middlewares and particularly
  ; fun-mode.
  :middleware [qm/fun-mode])

  (use 'a-quil-workflow.drawing :reload)
  (qa/with-applet a-quil-workflow.core/example (q/no-loop))
  (qa/with-applet a-quil-workflow.core/example (q/start-loop))
  (qa/with-applet a-quil-workflow.core/example (q/random 10)))

The reason that we have the sketch definition in a separate file is so that we shall be able to reload drawing.clj at will. Because every time we evaluate the sketch definition, a new sketch window will spawn.

There are also some convenience forms in the Rich comment block. (You evaluate those with the Calva: Evaluate top level form command, default bound to alt+enter. One of the forms reloads the drawing namespace. You will probably not use this much, because it is easier to re-evaluate definitions one by one in drawing.clj. (It’s good to know about the technique as well.) There is one form for stopping Quil from calling the :update and :draw functions, and one for starting the loop again. The last form shows how you can use quil functions that need to be called in the context of an applet/sketch. (Why quil.core/random needs this? I have no idea. Please enlighten me if you know.)

The reloadable drawing file

Here’s drawing.clj, where we define the :setup, :update, and :draw functions referenced in the sketch definition:

(ns a-quil-workflow.drawing
  (:require [quil.core :as q]))

;; defonce so that we can reload the `draw-state` function
(defonce calva-logo (ref nil))

(defn setup []
  (q/frame-rate 30)
  (q/color-mode :hsb)
  (dosync (ref-set calva-logo (q/load-image "calva-symbol+logo.jpg")))
  ; setup function returns initial state. It contains
  ; circle color and position.
  {:color 0
   :angle 0})

(defn update-state [state]
  ; Update sketch state by changing circle color and position.
  {:color (mod (+ (:color state) 0.7) 255)
   :angle (+ (:angle state) 0.1)})

(defn draw-state [state]
  #_(q/background 240)
  (q/image @calva-logo 0 0)
  ; Set circle color.
  (q/fill (:color state) 255 255 75)
  (q/stroke-weight 3)
  ; Calculate x and y coordinates of the circle.
  (let [angle (:angle state)
        x (* 200 (q/cos angle))
        y (* 100 (q/sin (* 3 angle)))]
    ; Move origin point to the center of the sketch.
    (q/with-translation [(/ (q/width) 2)
                         (/ (q/height) 2)]
      ; Draw 
      (q/line 0 0 x y)
      (q/ellipse x y 80 80))))

  ;; Evaluate these using the custom commands shortcut `ctrl+space t`
  (q/frame-rate 60)
  [(q/mouse-x) (q/mouse-y)])

Note that the image file is read in setup and the resulting Quil image is then used in draw-state. We use a ref to achieve this and we define the symbol calva-logo using the defonce macro, which will make it only be defined the first time the (defonce calva-log (ref nil)) form is evaluated. If we had used def here we would not be able to reload the file, because setup is called only once when the sketch is ”started”.

Two notes about this:

  1. We don’t really need to reload the file. Re-evaluating the definitions in it as we modify them is enough. But I sometimes confuse myself and wonder if the file contents represent the running program, and then it’s nice if things are setup so that I can save and reload the file.
  2. What if we want to change/update the image? Can we do this without recreating the sketch? Yes. Like so:
    1. Modify the form to use def instead
    2. Evaluate it
    3. Modify it back to use defonce
    4. Modify the (dosync (ref-set ...)) form in the setup function
    5. Evaluate this form as well (only the (dosync ...) form, not the setup function)
    You might want to stop the Quil loop before you do this and start it afterwards, because while the image is defined as nil, the sketch will crash every tick. If you can stand that thought, though, no need to stop/start. 😄

The workflow when updating the sketch is like what is shown in the GIF above. We modify the functions and re-evaluate them (alt+enter with the cursor somewhere inside the function). Your REPL workflow will benefit from factoring the sketch into many small functions.

FDD: My development workflow is full of failures. Small functions in combination with the REPL makes it small failures. When I tried to describe to Michiel Borkent how I fail myself forward, he immediately dubbed it Failure Driven Development. He’s good with naming!

Workflow shortcuts

As an astute reader you now wonder about those Rich comments at the end of drawing.clj, right? I’m happy you do! The example project has some Calva configuration that I’d love to tell you about. Here’s .vscode/settings.json:

  "calva.customREPLCommandSnippets": [
      "key": "e",
      "name": "Evaluate Current Form with Quil Applet",
      "snippet": "(quil.applet/with-applet a-quil-workflow.core/example $current-form)"
      "key": "c",
      "name": "Call Current Form with Quil Applet",
      "snippet": "(quil.applet/with-applet a-quil-workflow.core/example ($current-form))"
      "key": "t",
      "name": "Evaluate Current Top-level Form with Quil Applet",
      "snippet": "(quil.applet/with-applet a-quil-workflow.core/example $top-level-form)"
      "key": "n",
      "name": "Stop Quil Loop",
      "snippet": "(quil.applet/with-applet a-quil-workflow.core/example (quil.core/no-loop))"
      "key": "s",
      "name": "Start Quil Loop",
      "snippet": "(quil.applet/with-applet a-quil-workflow.core/example (quil.core/start-loop))"
      "key": "r",
      "name": "Reload drawing namespace",
      "snippet": "(use 'a-quil-workflow.drawing :reload)"

These set up a few Custom REPL Commands:

If you forget about which shortcut binds to which command, just use ctrl+alt+space space to bring up a menu with all the custom commands.

I encourage you to try this project out and giving the custom commands a spin. Define some of your own. Please get inspired by them and copy them to your Quil projects (you’ll need to modify them, of course). And why stop with your Quil projects? You probably have more projects that could benefit from some workflow convenience like this. I would love to hear from you about neat custom commands you come up with.

Happy coding! ❤️

I hope you have learnt at least something about the toolbox at your disposal when you take command over your programming workflow.

And I hope to see you in the #calva and/or #quil channels on the Clojurians Slack!