(ns quil.sketch (:require [quil.util :as u :include-macros true] [quil.middlewares.deprecated-options :as do] [goog.dom :as dom] [goog.events :as events] [goog.style :as style] [goog.object :as object] [goog.events.EventType :as EventType]) (:require-macros [quil.sketch])) (def ^:dynamic *applet* nil) (defn current-applet [] *applet*) (u/generate-quil-constants :cljs rendering-modes (:java2d :p2d :p3d :opengl)) (defn resolve-renderer [mode] (if (= :p3d mode) (aget js/p5.prototype "WEBGL") (u/resolve-constant-key mode rendering-modes))) (defn set-size [applet width height] (when-let [el (.-quil-canvas applet)] ; p5js creates a element inside provided
element ; we need to resize only the canvas as outer div will adapt automatically (let [inner-canvas (.querySelector el "canvas")] (.resizeCanvas applet width height) (.setAttribute inner-canvas "width" width) (.setAttribute inner-canvas "height" height) (aset (.-style inner-canvas) "width" (str width "px")) (aset (.-style inner-canvas) "height" (str height "px")) (set! (.-width applet) (.parseInt js/window (style/getComputedStyle inner-canvas "width"))) (set! (.-height applet) (.parseInt js/window (style/getComputedStyle inner-canvas "height")))))) (defn size ([width height] (.createCanvas (current-applet) (int width) (int height))) ([width height mode] (.createCanvas (current-applet) (int width) (int height) (resolve-renderer mode)))) (defn- bind-handlers [prc opts] (doseq [[processing-name quil-name] {:setup :setup :draw :draw :keyPressed :key-pressed :keyReleased :key-released :keyTyped :key-typed :mouseClicked :mouse-clicked :mouseDragged :mouse-dragged :mouseMoved :mouse-moved :mousePressed :mouse-pressed :mouseReleased :mouse-released :mouseOut :mouse-exited :mouseOver :mouse-entered :mouseWheel :mouse-wheel}] (when-let [handler (opts quil-name)] (aset prc (name processing-name) (fn [& args] (quil.sketch/with-sketch prc (apply handler args))))))) (defn in-fullscreen? [] (or (.-fullscreenElement js/document) (.-mozFullScreenElement js/document))) (defn add-fullscreen-support "Adds fullscreen support for the provided `p5` object. Fullscreen is enabled when the user presses `F11`. We turn the sketch `` element to fullscreen storing the old size in an `atom`. When the user cancels fullscreen (`F11` or `Esc`) we resize the sketch back to the old size." [applet] (let [old-size (atom nil) adjust-canvas-size (fn [] (if (in-fullscreen?) (do (reset! old-size [(.-width applet) (.-height applet)]) (set-size applet (-> js/window .-screen .-width) (-> js/window .-screen .-height))) (apply set-size applet @old-size)))] (events/listen js/window EventType/KEYDOWN (fn [event] (when (and (= (.-key event) "F11") (not (in-fullscreen?))) (.preventDefault event) (let [canvas (.-quil-canvas applet)] (cond (.-requestFullscreen canvas) (.requestFullscreen canvas) (.-mozRequestFullScreen canvas) (.mozRequestFullScreen canvas) :else (.warn js/console "Fullscreen mode is not supported in current browser.")))))) (events/listen js/document "fullscreenchange" adjust-canvas-size) (events/listen js/document "mozfullscreenchange" adjust-canvas-size) (events/listen js/document "fullscreenerror" #(.error js/console "Error while switching to/from fullscreen: " %)))) (defn make-sketch [options] (let [opts (->> (:middleware options []) (cons do/deprecated-options) (apply comp) (#(% options)) (merge {:size [500 300]})) sketch-size (:size opts) renderer (:renderer opts) features (set (:features opts)) setup (fn [] (->> (if renderer [renderer] []) (concat sketch-size) (apply size)) (when (:settings opts) ((:settings opts))) (when (:setup opts) ((:setup opts)))) mouse-wheel (when-let [wheel-handler (:mouse-wheel opts)] ; using (get "delta") because in advanced mode ; it will be renamed otherwise. (fn [evt] (wheel-handler (object/get evt "delta")))) opts (assoc opts :setup setup :mouse-wheel mouse-wheel) sketch (fn [prc] (bind-handlers prc opts) (set! (.-quil prc) (atom nil)) (set! (.-quil-internal-state prc) (atom u/initial-internal-state)))] sketch)) (defn destroy-previous-sketch [host-elem] (when-let [proc-obj (.-processing-obj host-elem)] (.remove proc-obj))) (defn sketch [& opts] (let [opts-map (apply hash-map opts) host-elem (:host opts-map) renderer (or (:renderer opts-map) :p2d) host-elem (if (string? host-elem) (.getElementById js/document host-elem) host-elem)] (if host-elem (do (if (.-processing-context host-elem) (when-not (= renderer (.-processing-context host-elem)) (.warn js/console "WARNING: Using different context on one canvas!")) (set! (.-processing-context host-elem) renderer)) (destroy-previous-sketch host-elem) (let [proc-obj (js/p5. (make-sketch opts-map) host-elem)] (set! (.-processing-obj host-elem) proc-obj) (set! (.-quil-canvas proc-obj) host-elem) (add-fullscreen-support proc-obj) proc-obj)) (.error js/console (if (:host opts-map) (str "ERROR: Cannot find host element: " (:host opts-map)) "ERROR: Cannot create sketch. :host is not specified or element not found."))))) (def sketch-init-list (atom (list))) (defn empty-body? [] (let [child (.-childNodes (.-body js/document))] ; seems hacky, we should come up with better way of ; checking whether body is empty or not (<= (.-length child) 1))) (defn add-canvas [canvas-id] (let [canvas (.createElement js/document "canvas")] (.setAttribute canvas "id" canvas-id) (.appendChild (.-body js/document) canvas))) (defn init-sketches [] (let [add-elem? (empty-body?)] (doseq [sk @sketch-init-list] (when add-elem? (add-canvas (:host-id sk))) ((:fn sk)))) (reset! sketch-init-list [])) (defn add-sketch-to-init-list [sk] (swap! sketch-init-list conj sk) ; if page already loaded immediately init sketch we just added (when (= (.-readyState js/document) "complete") (init-sketches))) (events/listenOnce js/window EventType/LOAD init-sketches)