Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A trivial clojurescript paint program using Reagent and core.async. Greatly inspired by the RxJS paint example.
<html>
<head>
<script src="out/goog/base.js" type="text/javascript"></script>
<script src="hello_world.js" type="text/javascript"></script>
<script type="text/javascript">goog.require("hello_world.core");</script>
</head>
<body>
</body>
<script type="text/javascript">hello_world.core.run()</script>
</html>
(defproject hello-world "0.1.0-SNAPSHOT"
:description "FIXME: write this!"
:url "http://example.com/FIXME"
:dependencies [[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-2173"]
[reagent "0.4.1"]
[org.clojure/core.async "0.1.267.0-0d7780-alpha"]
[org.clojure/core.match "0.2.1"]]
:plugins [[lein-cljsbuild "1.0.2"]]
:source-paths ["src"]
:cljsbuild {
:builds [{:id "hello-world"
:source-paths ["src"]
:compiler {
:preamble ["reagent/react.js"]
:output-to "hello_world.js"
:output-dir "out"
:optimizations :whitespace
:source-map "hello_world.js.map"}}]})
; Actual path: src/hello_world/core.cljs
(ns hello-world.core
(:require [reagent.core :as reagent :refer [atom]]
[cljs.core.async :as async
:refer [<! >! chan close! sliding-buffer put! alts!]]
[cljs.core.match])
(:require-macros [cljs.core.async.macros :refer [go go-loop]]
[cljs.core.match.macros :refer [match]]))
(enable-console-print!)
(defn controller [inchan state-ref]
(go-loop [cur-x nil cur-y nil mouse-state :up]
(match [(<! inchan) mouse-state]
[({:type "mousedown" :x x :y y} :as e) :up]
(do
(recur x y :down))
[({:type "mousemove" :x x :y y} :as e) :down]
(do
(swap! state-ref update-in [:current-path] conj {:x x :y y})
(recur x y :down))
[({:type "mouseup" :x x :y y} :as e) :down]
(do
(swap! state-ref (fn [{:keys [current-path paths] :as state}]
(assoc state :paths (conj paths current-path) :current-path [])))
(recur x y :up))
[s e] (recur cur-x cur-y mouse-state))))
(defn path-component [path fill-color]
(let [xys (map (fn [{:keys [x y]}] (str x " " y)) path)
points (apply str (interpose ", " xys))]
[:polyline {:points points :stroke fill-color :fill "none"}]))
(defn event-handler-fn [comms component e]
(let [bounds (. (reagent/dom-node component) getBoundingClientRect)
x (- (.-pageX e) (.-top bounds))
y (- (.-pageY e) (.-left bounds))]
(put! comms {:type (.-type e) :x x :y y})))
(defn hello-world-app [{:keys [state-ref comms] :as props}]
(let [{:keys [paths current-path]} @state-ref
component (reagent/current-component)
handler-fn (partial event-handler-fn comms component)]
[:svg {:height 480 :width 640
:on-mouse-up handler-fn :on-mouse-down handler-fn :on-mouse-move handler-fn
:style {:border "thin solid black"}}
(cons [path-component current-path "red"]
(map #(vector path-component % "black") paths))]))
(defn ^:export run []
(let [paths-atom (atom {:paths []})
ch (chan)
proc (controller ch paths-atom)]
(reagent/render-component
[hello-world-app {:state-ref paths-atom :comms ch}]
(.-body js/document))
(go
(let [exit (<! proc)]
(prn :exit! exit)))))
@scgilardi

This comment has been minimized.

Copy link

@scgilardi scgilardi commented Sep 23, 2015

excellent example. thank you.

re: event-handler-fn, the ".-top" and ".-left" are swapped: x should subtract off the left and y should subtract off the top. Also, the bounds rect itself is in "client" coordinates (getBoundingCientRect). When the enclosing view is a div that might be scrolled, using .-clientX and .-clientY (rather than .-page*) worked properly for me.

@chrismurrph

This comment has been minimized.

Copy link

@chrismurrph chrismurrph commented Oct 31, 2015

Short but great for showing how to do something graphical and interactive with Reagent/React. I have made it work with Figwheel and put it in a project here. My intention being to morph it into an interactive graphical display of values over time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.