Functional mode (fun mode)

Jelle Akkerman edited this page Feb 3, 2016 · 30 revisions

tl;dr: go to example and figure out what fun-mode does by yourself

What is functional mode?

Functional mode (fun-mode) is a Quil middleware that introduces functional style to Quil. It is available starting from Quil 2.1.0. Initially Quil had no special techniques to deal with state, so you had to come up with your own, like modifying an atom inside draw function and use the new value as the current state. fun-mode fixes it by making state explicit and handling it for you.

What does fun-mode do?

Fun-mode changes semantics of all handler functions: setup, draw, mouse-click, mouse-wheel, etc. Here are the changes:

  • Value returned from setup function is used as initial state. Thus, you should not use set-state! inside setup function.
  • New update function is introduced. update is a pure function, which takes an old state and returns a new state. It will be called before draw on each frame except the first one.
  • draw function takes state as an argument and draws the passed state. Value returned from draw is ignored.
  • All other handlers, like mouse and keyboard handlers, take one or two arguments. First argument is an old state. Most handlers receive additional second argument - event object. Value returned from handler is used as a new state.
How to enable fun-mode

To enable fun-mode, do the following:

  • Update all your functions as described above to take and return arguments.
  • Add quil.middleware/fun-mode as middleware:
(ns my-sketch
  (:require [quil.core :as q]
            [quil.middleware :as m]))

...

(q/defsketch fun-sketch
  ...
  :middleware [m/fun-mode])
Signature of handlers

Some handlers take an additional parameter - event. Example of an event will be provided below each handler.

setup: (fn []) -> initial-state
update: (fn [old-state]) -> new-state
draw: (fn [old-state])
focus-gained: (fn [old-state]) -> new-state
focus-lost: (fn [old-state]) -> new-state
mouse-entered: (fn [old-state event]) -> new-state

{:x 10 :y 20}

mouse-exited: (fn [old-state event]) -> new-state

{:x 10 :y 20}

mouse-pressed: (fn [old-state event]) -> new-state

{:x 10 :y 20 
 :button :left ; possible values are :left, :right, :center
}

mouse-released: (fn [old-state event]) -> new-state

{:x 10 :y 20}

mouse-clicked: (fn [old-state event]) -> new-state

{:x 10 :y 20 
 :button :left ; possible values are :left, :right, :center
}

mouse-moved: (fn [old-state event]) -> new-state

{:x 10 :y 20 
 :p-x 5 :p-y 10 ; p-x and p-y are previous mouse position
}

mouse-dragged: (fn [old-state event]) -> new-state

{:x 10 :y 20 
 :p-x 5 :p-y 10 ; p-x and p-y are previous mouse position
 :button :left ; possible values are :left, :right, :center
}

mouse-wheel: (fn [old-state event]) -> new-state
event is a number, either 1 or -1
key-pressed: (fn [old-state event]) -> new-state

{:key :c      ; might be nil, see quil.core/key-as-keyword
 :key-code 67 ; see quil.core/key-code
 :raw-key \c  ; see quil.core/raw-key
}

key-released: (fn [old-state]) -> new-state
key-typed: (fn [old-state event]) -> new-state

{:key :c      ; might be nil, see quil.core/key-as-keyword
 :key-code 67 ; see quil.core/key-code
 :raw-key \c  ; see quil.core/raw-key
}

on-close: (fn [old-state])

How does fun-mode work?

It is a Quil middleware which uses only public functions from quil.core. Take a look at fun_mode.clj.

Example

Here is a sketch that uses fun-mode. The idea of the sketch is to draw a circle which follows the mouse. It expands on every frame and shrinks when mouse is moved. All functions except draw are pure functions and draw only draws (yay!).

(ns fun-mode-sketch
  (:require [quil.core :as q]
            [quil.middleware :as m]))

(def min-r 10)

(defn setup []
  ; initial state
  {:x 0 :y 0 :r min-r})

(defn update [state]
  ; increase radius of the circle by 1 on each frame
  (update-in state [:r] inc))

(defn draw [state]
  (q/background 255)
  (q/ellipse (:x state) (:y state) (:r state) (:r state)))

; decrease radius by 1 but keeping it not less than min-r
(defn shrink [r]
  (max min-r (dec r)))

(defn mouse-moved [state event]
  (-> state
      ; set circle position to mouse position
      (assoc :x (:x event) :y (:y event))
      ; decrease radius
      (update-in [:r] shrink)))

(q/defsketch example
  :size [200 200]
  :setup setup
  :draw draw
  :update update
  :mouse-moved mouse-moved
  :middleware [m/fun-mode])

And result:
animation