Clojurescript, re-frame and P5

I’m trying out creating generative art using clojurescript and quil (a clojurescript implementation of P5).

Routing and directory structure

The main issue to be solved was to get routing working, so that I don’t have to go into the source code to switch between visuals.

quil routing

In the previous post of visualisation in clojurescript I created rect and circle in the views.cljs file. That’s not how it should be done. The views.cljs file should contain the different pages instead.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
(defn home-panel []
  (let [name (re-frame/subscribe [::subs/name])]
    [:div
     [:h1
      (str "Art generator")]
     [:p (str "Name: " @name)]]))

(defmethod routes/panels :home-panel [] [home-panel])

;; about

(defn about-panel []
  [:div
   [:h1 "This is the About Page."]])

(defmethod routes/panels :about-panel [] [about-panel])

To add a tab that shows circles (using quil), we create a new file generators/circles.cljs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(ns genart-clj.generators.circles
  (:require [quil.core :as q]
            [quil.middleware :as middleware]))

(defn sketch-setup []
    ;; set up here
)

(defn sketch-draw []
    ;; draw here
)

(defn create []
    (q/defsketch sketch
      :size [width height]
      :setup #'sketch-setup
      :draw #'sketch-draw
      :middleware [middleware/fun-mode]))

In the views.cljs file we add circles.cljs to the :require and create a new view:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(ns genart-clj.views
  (:require
   [re-frame.core :as re-frame]
   [genart-clj.events :as events]
   [genart-clj.routes :as routes]
   [genart-clj.subs :as subs]
   [genart-clj.generators.circles :as circles]))

;; ...

(defn circles-panel []
  [:div
   [:h1 "Circles"]
   [:div#sketch]
   (circles/create)
   ])

(defmethod routes/panels :circles-panel [] [circles-panel])

Unfortunately, this does not always work. The problem seems to be that the div with id sketch is not mounted yet before the image is created and is attached to this (non-existing) div.

Workaround

A possible workaround is to - instead of generating the image immediately - ask for the user to click a button first. When clicked, the image is created and mounted in that div.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
;; ...

(defn generate-image [generator]
  (-> js/document
      (.querySelectorAll "p5Canvas")
      (map #(.remove %)))
  (.log js/console "hi")
  (generator))

(defn circles-panel []
  [:div
   [:h1 "Circles"]
   [:button {:class "btn btn-primary"
             :on-click #(generate-image circles/create)} "Generate..."]
   [:div#sketch]
   ])

;; ...

The result before clicking:

genart button

After clicking:

genart button clicked