Created
February 24, 2014 22:50
-
-
Save cstorey/9198985 to your computer and use it in GitHub Desktop.
A trivial clojurescript paint program using Reagent and core.async. Greatly inspired by the RxJS paint example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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"}}]}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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))))) |
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
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.