├── .gitignore
├── README.md
├── info.txt
├── lib
└── clojure-1.3.0.jar
├── pom.xml
├── project.clj
├── src
└── webfui
│ ├── core.cljs
│ ├── dom.cljs
│ ├── dom_manipulation.cljs
│ ├── framework.cljs
│ ├── framework
│ └── macros.clj
│ ├── html.cljs
│ ├── macros.clj
│ ├── plugin
│ ├── core.cljs
│ ├── form_values.cljs
│ ├── mouse.cljs
│ └── scrolling.cljs
│ ├── state_patches.cljs
│ └── utilities.cljs
├── target
└── stale
│ └── dependencies
├── test
└── webfui
│ └── test
│ └── core.clj
└── webfui-examples
├── .gitignore
├── .lein-plugins
└── checksum
├── README.md
├── project.clj
├── resources
└── public
│ ├── css
│ ├── POCKC___.eot
│ ├── POCKC___.svg
│ ├── POCKC___.ttf
│ ├── POCKC___.woff
│ ├── add_two_numbers.css
│ ├── add_two_numbers_low_level.css
│ ├── calculator.css
│ ├── calculator_ajax.css
│ ├── calculator_many.css
│ ├── inverse_kinematics.css
│ ├── mouse_tracking.css
│ ├── scrolling.css
│ └── webfui_examples.css
│ ├── index.html
│ └── js
│ └── .gitignore
├── src-clj
└── webfui_examples
│ ├── server.clj
│ └── views
│ ├── add_two_numbers.clj
│ ├── add_two_numbers_low_level.clj
│ ├── calculator.clj
│ ├── calculator_ajax.clj
│ ├── calculator_many.clj
│ ├── common.clj
│ ├── inverse_kinematics.clj
│ ├── mouse_tracking.clj
│ ├── scrolling.clj
│ └── welcome.clj
└── src-cljs
├── add_two_numbers
└── core.cljs
├── add_two_numbers_low_level
└── core.cljs
├── calculator
└── core.cljs
├── calculator_ajax
└── core.cljs
├── calculator_many
└── core.cljs
├── inverse_kinematics
└── core.cljs
├── mouse_tracking
└── core.cljs
└── scrolling
└── core.cljs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebFUI - Client Side Web Development Framework For ClojureScript
2 |
3 | 
4 |
5 | ## Philosophy of WebFUI
6 |
7 | The goal of WebFUI is to let you do client-side Web programming in ClojureScript without having to ever deal with the DOM. Instead, all DOM is generated in realtime from a Clojure atom that contains just "plain old data" (called [EDN](https://github.com/edn-format/edn) in Clojure.) This "DOM EDN" is kept synchronized with a state atom that also contains EDN, where all the state for your program is kept.
8 |
9 | You, the programmer, is only responsible for providing the functions shown in red in the picture below:
10 |
11 | 
12 |
13 | These functions in red can be written 100% in the functional style, which is one of the benefits of WebFUI. Also, having EDN as the arguments/results for all your app functions makes debugging/unit testing extremely easy. Finally, WebFUI code is extremely succinct- [This fully-featured calculator app](http://lisperati.com/webfui/calculator.html) consists of [only 92 lines of code](https://github.com/drcode/webfui/blob/master/webfui-examples/src-cljs/calculator/core.cljs), which includes all html generation!
14 |
15 | *Note: WebFUI is still an alpha project and has limitations. Currently it only supports only Webkit based browsers (Chrome, Safari, including on Android/iOS).*
16 |
17 | ## Tech Demo Showing Off WebFUI
18 |
19 | Here is an [inverse kinematics demo written 100% in ClojureScript](http://lisperati.com/webfui/inverse_kinematics.html)- Browse the source code in the `webfui-examples` directory. This example is pure HTML5, and using WebFUI means none of the application code directly manipulates the DOM. Click on the figure to drag it around the screen.
20 | For further demos, check out this [calculator](http://lisperati.com/webfui/calculator.html) and [this example of mouse interaction](http://lisperati.com/webfui/mouse_tracking.html) (drag numbers in the circle on top of each other to see what it does.)
21 |
22 | ## Performance Challenges
23 |
24 | One of the key design choices in WebFUI is that the DOM and the program state are strongly linked via a super simple mechanism: *Any* change to the state updates *all* the DOM. Similarly *any* changes to the DOM (Such as by the user entering text into a form field) updates *all* the state. This makes your code very simple, but can affect performance of your app.
25 |
26 | However, WebFUI mitigates the inefficiency of this two-way synchronization of state and the DOM in various ways. First of all, ClojureScript has highly-efficient persistent data structures that WebFUI can use to minimize these performance penalties. Also, WebFUI performs delta calculations during DOM changes to send as few DOM updates as possible to the web browser. Luckily, all the browser vendors are currently in a javascript performance arms race, making this less of a problem over time.
27 |
28 | Because of these optimizations, WebFUI has reasonable performance in most real-world applications (use the examples included with webfui as a guide to judge app responsiveness.) However, on some devices, such as older iPad/iPhones, Javascript is still relatively slow, and you'll notice some delays in a WebFUI-based HTML5 app.
29 |
30 | ## Installing WebFUI
31 |
32 | WebFUI is best installed by using [leiningen](https://github.com/technomancy/leiningen). Include the following in the dependencies of your project.clj:
33 |
34 | ```clojure
35 | :dependencies [[webfui "0.2.1"]]
36 | ```
37 |
38 | Your application needs to be a ClojureScript application, and I recommend you use the [lein-cljsbuild plugin](https://github.com/emezeske/lein-cljsbuild) for leiningen to build it.
39 |
40 | ## Compiling the Examples
41 |
42 | In the webfui-examples directory you'll see examples of webfui in action. Just run `lein deps;lein cljsbuild once;lein run` and fire up your Safari, Chrome, or iOS browser to "localhost:8080".
43 |
44 | ## A Simple WebFUI App
45 |
46 | Here is an entire concrete example program using WebFUI. It displays two edit fields and displays the sum of the numbers entered into those fields as a result (try it [here](http://lisperati.com/webfui/add_two_numbers.html))
47 |
48 | ```clojure
49 | (ns webfui-examples.add-two-numbers.core
50 | (:use [webfui.framework :only [launch-app]])
51 | (:use-macros [webfui.framework.macros :only [add-dom-watch]]))
52 |
53 | (defn render-all [state]❶
54 | (let [{:keys [a b]} state]
55 | [:div [:input#a {:watch :watch :value a}]❷
56 | " plus "
57 | [:input#b {:watch :watch :value b}]❸
58 | [:p " equals "]
59 | [:span (+ a b)]]))
60 |
61 | (defn valid-integer [s]❹
62 | (and (< (count s) 15) (re-matches #"^[0-9]+$" s)))
63 |
64 | (add-dom-watch :watch [state new-element]❺
65 | (let [{:keys [value id]} (second new-element)]
66 | (when (valid-integer value)
67 | {id (js/parseInt value)})))❻
68 |
69 | (launch-app (atom {:a 0 :b 0}) render-all)❼
70 | ```
71 |
72 | ## How State Changes Make it to the DOM
73 |
74 | We initialize a WebFUI app by calling `launch-app` ❼ and supplying a state atom and the `render-all` ❶ function. In this example, the state consists only of the values in the edit fields named `A` and `B`. As you can see, the `render-all` function just transforms the state into HTML. (using the same syntax used by [hiccup](https://github.com/weavejester/hiccup).)
75 |
76 | ## How DOM Changes Make it to the State
77 |
78 | This happens by attaching *DOM watchers* to the DOM. You can see right here ❷ and here ❸ that there is an attribute called `watch` attached to the generated html (WebFUI html can have special attributes that goes beyond standard html.) This will link the edit controls to a DOM watcher declared here ❺. The DOM watcher is responsible for updating the state to adjust to changes in the DOM: In this case, it means to update the A and B fields in the state (which it does by looking up the ID of the text field which is the same as the key in the state ❻) if the numbers entered in by the user into either text field changes.
79 |
80 | Note that the DOM watcher checks whether the fields contain valid integers with this function ❹. Because of the realtime nature of WebFUI you'll see that this automagically prevents a user from entering non-numeric values into the text fields.
81 |
82 | These DOM watchers are fundamentally different than javascript "on__" events in that they use a "Show, don't tell" design: You don't get an *event object* _telling_ you what changed, but instead get a concrete look at the proposed changes to the HTML via `new-element`❺ (which can be compared to the `state` to see what was there before) that _shows_ you what changed.
83 |
84 | ## Pinpoint Changes to the State
85 |
86 | As we've seen, in WebFUI the DOM is updated in a wholesale fashion from the program state, and the program state is updated wholesale from the DOM, as well. However, usually when we update our program state we want to do it in a pinpoint fashion. Because of this, the state returned from a DOM watcher is actually a *delta* applied to the program state: If a key is left unmentioned, it is left with its previous value, in a recursive fashion. To understand more about the model used for updating state using diffs, read the section on `webfui.state-patch` in [this document](https://docs.google.com/document/d/1KQn_saQurqgvxHiyuOZ7twK4K_w_VftBHrPKJolwEZ8/edit).
87 |
88 | ## WebFUI Plugins
89 |
90 | WebFUI has a plugin mechanism for adding support for different types of user interaction. Plugins are used to add support for mouse actions, calculating scroll bar positions, and many more plugins are planned for the future.
91 |
92 | [Read the following document to learn more about the design of WebFUI and the plugin architecture.](https://docs.google.com/document/d/1KQn_saQurqgvxHiyuOZ7twK4K_w_VftBHrPKJolwEZ8/edit)
93 |
94 | ## Mouse Support/Further Documentation
95 |
96 | To learn how to handle mouse interactions in WebFUI, please check the section on mouse support in [this document](https://docs.google.com/document/d/1KQn_saQurqgvxHiyuOZ7twK4K_w_VftBHrPKJolwEZ8/edit).
97 |
98 | ## License
99 |
100 | Distributed under the Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php), the same as Clojure.
101 |
--------------------------------------------------------------------------------
/info.txt:
--------------------------------------------------------------------------------
1 | *presentation notes
2 |
3 | What is the DOM?
4 |
5 | Problems with the DOM
6 | - It changes
7 | - Changing it is slow
8 |
9 | What is EDN?
10 |
11 | What is AJAX?
12 |
13 | How to build a web framework:
14 | - Should do things the Clojure Way
15 | - Code should be in the functional style
16 | - The browser DOM should be EDN data stored in an atom
17 |
18 | DOM is not a complete representation of browser state
19 | - Can't set button as pushed
20 | - Can't set scrollbar position
21 | - Can't set text selection
22 |
23 | Problem: AJAX requires side effects. Reframed problem: Our state atom no longer contains the whole world
24 |
25 | Original sin: Our state is _always_ corrupt in a program that uses AJAX.
26 |
27 | We have to contain the corruption (Can be done with FP)
28 | We have to repair the corruption (Requires imperative programming)
29 |
30 | "State Corruption" Design Pattern
31 |
32 | AJAX=Corruption of state
33 |
34 | Corruption is implicit- Make it explicit
35 |
36 | GET: State item has value of nil
37 | or selection variable has id that points to nonexistent item
38 |
39 | POST: Create new item in state with id=nil
40 |
41 | PUT: Have "holding area" in state, set source of truth=nil. Move from holding area back to final area
42 |
43 | DELETE: Just delete item
44 |
45 | *todos
46 | item insert/deletion in delta system
47 | text selection
48 | full IK artwork
49 | "preview" in calculator
50 |
51 | *Hello World
52 | (ns my-app.core
53 | (:use [webfui.framework :only [launch-app]]))
54 |
55 | (defn render-all []
56 | "hello world!")
57 |
58 | (launch-app (atom nil) render-all)
59 |
60 |
61 |
62 | *timing
63 | ~250 for mouseup
64 | ~150 for webfui code
65 | ~90 for parsing html
66 | ~90 for delta detection
67 |
--------------------------------------------------------------------------------
/lib/clojure-1.3.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drcode/webfui/e8ba6e9224542755e5780488cb43f0c5510827f9/lib/clojure-1.3.0.jar
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
(Right now Chrome & Safari work 100%, Firefox will be ready shortly.)
4 |5 |
15 | 16 | -------------------------------------------------------------------------------- /webfui-examples/resources/public/js/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/server.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.server 2 | (:use [noir.core :only [defpage]] 3 | [noir.response :only [redirect]]) 4 | (:require [noir.server :as server])) 5 | 6 | (server/load-views "src-clj/webfui_examples/views/") 7 | 8 | (defpage "/" [] 9 | (redirect "/index.html")) 10 | 11 | (def memory (atom 0)) 12 | 13 | (defpage [:get "/memory"] [] 14 | (str @memory)) 15 | 16 | (defpage [:put "/memory/:value"] {:keys [value]} 17 | (reset! memory value) 18 | (pr-str value)) 19 | 20 | (defn -main [& m] 21 | (let [mode (keyword (or (first m) :dev)) 22 | port (Integer. (get (System/getenv) "PORT" "8080"))] 23 | (server/start port {:mode mode 24 | :ns 'webfui-examples}))) 25 | 26 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/add_two_numbers.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.add-two-numbers 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/add_two_numbers" [] 8 | (common/layout "add_two_numbers")) 9 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/add_two_numbers_low_level.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.add-two-numbers-low-level 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/add_two_numbers_low_level" [] 8 | (common/layout "add_two_numbers_low_level")) 9 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/calculator.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.calculator 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/calculator" [] 8 | (common/layout "calculator")) 9 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/calculator_ajax.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.calculator-ajax 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/calculator_ajax" [] 8 | (common/layout "calculator_ajax")) 9 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/calculator_many.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.calculator-many 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/calculator_many" [] 8 | (common/layout "calculator_many")) 9 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/common.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.common 2 | (:use [noir.core :only [defpartial]] 3 | [hiccup.page :only [include-css include-js html5]] 4 | [hiccup.element :only [javascript-tag]])) 5 | 6 | (defpartial layout [name] 7 | (html5 8 | [:head 9 | [:title "webfui-examples"] 10 | (include-css (str "/css/" name ".css")) 11 | (javascript-tag "var CLOSURE_NO_DEPS = true;") 12 | (include-js (str "/js/" name ".js"))] 13 | [:body])) 14 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/inverse_kinematics.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.inverse-kinematics 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/inverse_kinematics" [] 8 | (common/layout "inverse_kinematics")) 9 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/mouse_tracking.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.mouse-tracking 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/mouse_tracking" [] 8 | (common/layout "mouse_tracking")) 9 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/scrolling.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.scrolling 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/scrolling" [] 8 | (common/layout "scrolling")) 9 | -------------------------------------------------------------------------------- /webfui-examples/src-clj/webfui_examples/views/welcome.clj: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.views.welcome 2 | (:require [webfui-examples.views.common :as common] 3 | [noir.content.getting-started]) 4 | (:use [noir.core :only [defpage]] 5 | [hiccup.core :only [html]])) 6 | 7 | (defpage "/welcome" [] 8 | (common/layout 9 | [:p "Welcome to webfui-examples"])) 10 | -------------------------------------------------------------------------------- /webfui-examples/src-cljs/add_two_numbers/core.cljs: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.add-two-numbers.core 2 | (:use [webfui.framework :only [launch-app]]) 3 | (:use-macros [webfui.framework.macros :only [add-dom-watch]])) 4 | 5 | (defn render-all [state] 6 | (let [{:keys [a b]} state] 7 | [:div [:input#a {:watch :watch :value a}] 8 | " plus " 9 | [:input#b {:watch :watch :value b}] 10 | [:p " equals "] 11 | [:span (+ a b)]])) 12 | 13 | (defn valid-integer [s] 14 | (and (< (count s) 15) (re-matches #"^[0-9]+$" s))) 15 | 16 | (add-dom-watch :watch [state new-element] 17 | (let [{:keys [value id]} (second new-element)] 18 | (when (valid-integer value) 19 | {id (js/parseInt value)}))) 20 | 21 | (launch-app (atom {:a 0 :b 0}) render-all) 22 | 23 | 24 | -------------------------------------------------------------------------------- /webfui-examples/src-cljs/add_two_numbers_low_level/core.cljs: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.add-two-numbers-low-level.core 2 | (:use [webfui.dom :only [defdom add-dom-watch]] 3 | [webfui.state-patches :only [patch]])) 4 | 5 | (def my-dom (atom nil)) 6 | 7 | (defdom my-dom) 8 | 9 | (def a 0) 10 | (def b 0) 11 | 12 | (defn render-all [old-dom] 13 | [:div [:input {:type :text :watch :a-watch :value a}] 14 | " plus " 15 | [:input {:type :text :watch :b-watch :value b}] 16 | [:p " equals "] 17 | [:span (+ a b)]]) 18 | 19 | (defn update-dom [] 20 | (swap! my-dom render-all)) 21 | 22 | (update-dom) 23 | 24 | (defn valid-integer [s] 25 | (and (< (count s) 15) (re-matches #"^[0-9]+$" s))) 26 | 27 | (add-dom-watch :a-watch 28 | (fn [old-element new-element] 29 | (let [new-a (get-in new-element [1 :value])] 30 | (when (valid-integer new-a) 31 | (def a (js/parseInt new-a)))) 32 | (update-dom))) 33 | 34 | (add-dom-watch :b-watch 35 | (fn [old-element new-element] 36 | (let [new-b (get-in new-element [1 :value])] 37 | (when (valid-integer new-b) 38 | (def b (js/parseInt new-b)))) 39 | (update-dom))) 40 | 41 | -------------------------------------------------------------------------------- /webfui-examples/src-cljs/calculator/core.cljs: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.calculator.core 2 | (:use [webfui.framework :only [launch-app]] 3 | [webfui.utilities :only [get-attribute clicked]]) 4 | (:use-macros [webfui.framework.macros :only [add-mouse-watch]])) 5 | 6 | (def initial-state {:amount nil 7 | :amount-decimal nil 8 | :accumulator 0 9 | :operation nil 10 | :memory 0}) 11 | 12 | (def calculator-keys [[[:ac "AC" :ac] [:ms "MS" :ms] [:mr "MR" :mr] [:div "÷" :op]] 13 | [[:7 "7" :num] [:8 "8" :num] [:9 "9" :num] [:mult "×" :op]] 14 | [[:4 "4" :num] [:5 "5" :num] [:6 "6" :num] [:minus "-" :op]] 15 | [[:1 "1" :num] [:2 "2" :num] [:3 "3" :num] [:plus "+" :op]] 16 | [[:period "." :period] [:0 "0" :num] [:eq "=" :op]]]) 17 | 18 | (def operations {:div / 19 | :mult * 20 | :minus - 21 | :plus + 22 | :eq identity}) 23 | 24 | (defn right-shorten [s] 25 | (subs s 0 (dec (count s)))) 26 | 27 | (defn format-accumulator [accumulator] 28 | (loop [s (.toFixed accumulator 12)] 29 | (case (last s) 30 | \0 (recur (right-shorten s)) 31 | \. (right-shorten s) 32 | s))) 33 | 34 | (defn check-overflow [s] 35 | (cond (and (<= (count s) 12) (not= (last s) \.)) s 36 | (some (partial = \.) s) (recur (right-shorten s)) 37 | :else "OVERFLOW")) 38 | 39 | (defn render-display [{:keys [amount amount-decimal accumulator]}] 40 | (check-overflow (cond (not amount) (format-accumulator accumulator) 41 | amount-decimal (.toFixed amount amount-decimal) 42 | :else (str amount)))) 43 | 44 | (defn render-all [state] 45 | [:table [:tbody [:tr [:td {:colspan 4} 46 | [:div#display (render-display state)]]] 47 | (for [row calculator-keys] 48 | [:tr (for [[sym label mouse] row] 49 | [:td {:colspan ({:eq 2} sym 1)} 50 | [:div {:id sym 51 | :mouse mouse} 52 | label]])])]]) 53 | 54 | (add-mouse-watch :num [state first-element last-element] 55 | (when (clicked first-element last-element) 56 | (let [{:keys [amount amount-decimal]} state 57 | digit (js/parseInt (name (get-attribute first-element :id)))] 58 | (if amount-decimal 59 | {:amount (+ amount (/ digit 10 (apply * (repeat amount-decimal 10)))) 60 | :amount-decimal (inc amount-decimal)} 61 | {:amount (+ (* amount 10) digit)})))) 62 | 63 | (add-mouse-watch :op [state first-element last-element] 64 | (when (clicked first-element last-element) 65 | (let [{:keys [amount operation accumulator]} state] 66 | {:amount nil 67 | :amount-decimal nil 68 | :accumulator (if (and amount operation) 69 | ((operations operation) accumulator amount) 70 | (or amount accumulator)) 71 | :operation (get-attribute first-element :id)}))) 72 | 73 | (add-mouse-watch :period [state first-element last-element] 74 | (when (clicked first-element last-element) 75 | (when-not (:amount-decimal state) 76 | {:amount-decimal 0}))) 77 | 78 | (add-mouse-watch :ac [state first-element last-element] 79 | (when (clicked first-element last-element) 80 | (assoc initial-state :memory (:memory state)))) 81 | 82 | (add-mouse-watch :ms [state first-element last-element] 83 | (when (clicked first-element last-element) 84 | (let [{:keys [amount accumulator]} state] 85 | {:memory (or amount accumulator)}))) 86 | 87 | (add-mouse-watch :mr [state first-element last-element] 88 | (when (clicked first-element last-element) 89 | (let [{:keys [memory]} state] 90 | {:amount memory}))) 91 | 92 | (launch-app (atom initial-state) render-all) -------------------------------------------------------------------------------- /webfui-examples/src-cljs/calculator_ajax/core.cljs: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.calculator-ajax.core 2 | (:use [webfui.framework :only [launch-app]] 3 | [webfui.utilities :only [get-attribute clicked]] 4 | [cljs.reader :only [read-string]]) 5 | (:use-macros [webfui.framework.macros :only [add-mouse-watch]]) 6 | (:require [goog.net.XhrIo :as xhr])) 7 | 8 | (def initial-state {:amount nil 9 | :amount-decimal nil 10 | :accumulator 0 11 | :operation nil 12 | :memory :unknown}) 13 | 14 | (def calculator-keys [[[:ac "AC" :ac] [:ms "MS" :ms] [:mr "MR" :mr] [:div "÷" :op]] 15 | [[:7 "7" :num] [:8 "8" :num] [:9 "9" :num] [:mult "×" :op]] 16 | [[:4 "4" :num] [:5 "5" :num] [:6 "6" :num] [:minus "-" :op]] 17 | [[:1 "1" :num] [:2 "2" :num] [:3 "3" :num] [:plus "+" :op]] 18 | [[:period "." :period] [:0 "0" :num] [:eq "=" :op]]]) 19 | 20 | (def operations {:div / 21 | :mult * 22 | :minus - 23 | :plus + 24 | :eq identity}) 25 | 26 | (defn right-shorten [s] 27 | (subs s 0 (dec (count s)))) 28 | 29 | (defn format-accumulator [accumulator] 30 | (loop [s (.toFixed accumulator 12)] 31 | (case (last s) 32 | \0 (recur (right-shorten s)) 33 | \. (right-shorten s) 34 | s))) 35 | 36 | (defn check-overflow [s] 37 | (cond (and (<= (count s) 12) (not= (last s) \.)) s 38 | (some (partial = \.) s) (recur (right-shorten s)) 39 | :else "OVERFLOW")) 40 | 41 | (defn render-display [{:keys [amount amount-decimal accumulator]}] 42 | (check-overflow (cond (not amount) (format-accumulator accumulator) 43 | amount-decimal (.toFixed amount amount-decimal) 44 | :else (str amount)))) 45 | 46 | (defn render-all [state] 47 | [:table [:tbody [:tr [:td {:colspan 4} 48 | [:div#display (render-display state)]]] 49 | (for [row calculator-keys] 50 | [:tr (for [[sym label mouse] row] 51 | [:td {:colspan ({:eq 2} sym 1)} 52 | [:div {:id sym 53 | :mouse mouse} 54 | label]])])]]) 55 | 56 | (add-mouse-watch :num [state first-element last-element] 57 | (when (clicked first-element last-element) 58 | (let [{:keys [amount amount-decimal]} state 59 | digit (js/parseInt (name (get-attribute first-element :id)))] 60 | (if amount-decimal 61 | {:amount (+ amount (/ digit 10 (apply * (repeat amount-decimal 10)))) 62 | :amount-decimal (inc amount-decimal)} 63 | {:amount (+ (* amount 10) digit)})))) 64 | 65 | (add-mouse-watch :op [state first-element last-element] 66 | (when (clicked first-element last-element) 67 | (let [{:keys [amount operation accumulator]} state] 68 | {:amount nil 69 | :amount-decimal nil 70 | :accumulator (if (and amount operation) 71 | ((operations operation) accumulator amount) 72 | (or amount accumulator)) 73 | :operation (get-attribute first-element :id)}))) 74 | 75 | (add-mouse-watch :period [state first-element last-element] 76 | (when (clicked first-element last-element) 77 | (when-not (:amount-decimal state) 78 | {:amount-decimal 0}))) 79 | 80 | (add-mouse-watch :ac [state first-element last-element] 81 | (when (clicked first-element last-element) 82 | (assoc initial-state :memory (:memory state)))) 83 | 84 | (add-mouse-watch :ms [state first-element last-element] 85 | (when (clicked first-element last-element) 86 | (let [{:keys [amount accumulator]} state] 87 | {:memory (or amount accumulator)}))) 88 | 89 | (add-mouse-watch :mr [state first-element last-element] 90 | (when (clicked first-element last-element) 91 | (let [{:keys [memory]} state] 92 | {:amount memory}))) 93 | 94 | (def my-state (atom initial-state)) 95 | 96 | (defn send [safe-state method uri fun] 97 | (xhr/send uri 98 | (fn [event] 99 | (let [response (.-target event)] 100 | (if (.isSuccess response) 101 | (fun (.getResponseText response)) 102 | (reset! my-state safe-state)))) 103 | (name method))) 104 | 105 | (defn memory-loaded [text] 106 | (let [memory (read-string text)] 107 | (swap! my-state assoc :memory memory :amount memory))) 108 | 109 | (defn memory-saved [] 110 | (swap! my-state assoc :memory :unknown)) 111 | 112 | (add-watch my-state :my-watch 113 | (fn [_ _ old new] 114 | (let [{:keys [amount memory]} new] 115 | (when (= amount :unknown) 116 | (if (= (:amount old) :unknown) 117 | (reset! my-state old) 118 | (send old :get "memory" memory-loaded))) 119 | (when (not= memory :unknown) 120 | (if (not= (:memory old) :unknown) 121 | (reset! my-state old) 122 | (send old :put (str "memory/" memory) memory-saved)))))) 123 | 124 | (launch-app my-state render-all) -------------------------------------------------------------------------------- /webfui-examples/src-cljs/calculator_many/core.cljs: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.calculator-many.core 2 | (:use [webfui.framework :only [launch-app]] 3 | [webfui.utilities :only [get-attribute clicked]]) 4 | (:use-macros [webfui.framework.macros :only [add-mouse-watch]])) 5 | 6 | (def calc-rows 4) 7 | (def calc-columns 10) 8 | (def calc-num (* calc-rows calc-columns)) 9 | 10 | (def initial-state (vec (repeat calc-num 11 | {:amount nil 12 | :amount-decimal nil 13 | :accumulator 0 14 | :operation nil 15 | :memory 0}))) 16 | 17 | (def calculator-keys [[[:ac "AC" :ac] [:ms "MS" :ms] [:mr "MR" :mr] [:div "÷" :op]] 18 | [[:7 "7" :num] [:8 "8" :num] [:9 "9" :num] [:mult "×" :op]] 19 | [[:4 "4" :num] [:5 "5" :num] [:6 "6" :num] [:minus "-" :op]] 20 | [[:1 "1" :num] [:2 "2" :num] [:3 "3" :num] [:plus "+" :op]] 21 | [[:period "." :period] [:0 "0" :num] [:eq "=" :op]]]) 22 | 23 | (def operations {:div / 24 | :mult * 25 | :minus - 26 | :plus + 27 | :eq identity}) 28 | 29 | (defn right-shorten [s] 30 | (subs s 0 (dec (count s)))) 31 | 32 | (defn format-accumulator [accumulator] 33 | (loop [s (.toFixed accumulator 12)] 34 | (case (last s) 35 | \0 (recur (right-shorten s)) 36 | \. (right-shorten s) 37 | s))) 38 | 39 | (defn check-overflow [s] 40 | (cond (and (<= (count s) 12) (not= (last s) \.)) s 41 | (some (partial = \.) s) (recur (right-shorten s)) 42 | :else "OVERFLOW")) 43 | 44 | (defn render-display [{:keys [amount amount-decimal accumulator]}] 45 | (check-overflow (cond (not amount) (format-accumulator accumulator) 46 | amount-decimal (.toFixed amount amount-decimal) 47 | :else (str amount)))) 48 | 49 | (defn render-all [state] 50 | [:table.grid [:tbody (for [row (range calc-rows)] 51 | [:tr (for [column (range calc-columns)] 52 | (let [calc-cur (+ (* row calc-columns) column)] 53 | [:td [:table.calc [:tbody [:tr [:td {:colspan 4} 54 | [:div#display 55 | (render-display (state calc-cur))]]] 56 | (for [row calculator-keys] 57 | [:tr (for [[sym label mouse] row] 58 | [:td {:colspan ({:eq 2} sym 1)} 59 | [:div {:id sym 60 | :data calc-cur 61 | :mouse mouse} 62 | label]])])]]]))])]]) 63 | 64 | (add-mouse-watch :num [state first-element last-element] 65 | (when (clicked first-element last-element) 66 | (let [index (get-attribute first-element :data) 67 | {:keys [amount amount-decimal]} (state index) 68 | digit (js/parseInt (name (get-attribute first-element :id)))] 69 | {index (if amount-decimal 70 | {:amount (+ amount (/ digit 10 (apply * (repeat amount-decimal 10)))) 71 | :amount-decimal (inc amount-decimal)} 72 | {:amount (+ (* amount 10) digit)})}))) 73 | 74 | (add-mouse-watch :op [state first-element last-element] 75 | (when (clicked first-element last-element) 76 | (let [index (get-attribute first-element :data) 77 | {:keys [amount operation accumulator]} (state index)] 78 | {index {:amount nil 79 | :amount-decimal nil 80 | :accumulator (if (and amount operation) 81 | ((operations operation) accumulator amount) 82 | (or amount accumulator)) 83 | :operation (get-attribute first-element :id)}}))) 84 | 85 | (add-mouse-watch :period [state first-element last-element] 86 | (when (clicked first-element last-element) 87 | (let [index (get-attribute first-element :data)] 88 | {index (when-not (:amount-decimal state) 89 | {:amount-decimal 0})}))) 90 | 91 | (add-mouse-watch :ac [state first-element last-element] 92 | (when (clicked first-element last-element) 93 | (let [index (get-attribute first-element :data)] 94 | {index (assoc (initial-state index) :memory (:memory (state index)))}))) 95 | 96 | (add-mouse-watch :ms [state first-element last-element] 97 | (when (clicked first-element last-element) 98 | (let [index (get-attribute first-element :data)] 99 | {index (let [{:keys [amount accumulator]} (state index)] 100 | {:memory (or amount accumulator)})}))) 101 | 102 | (add-mouse-watch :mr [state first-element last-element] 103 | (when (clicked first-element last-element) 104 | (let [index (get-attribute first-element :data)] 105 | {index (let [{:keys [memory]} (state index)] 106 | {:amount memory})}))) 107 | 108 | (launch-app (atom initial-state) render-all) -------------------------------------------------------------------------------- /webfui-examples/src-cljs/inverse_kinematics/core.cljs: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.inverse-kinematics.core 2 | (:use [webfui.framework :only [launch-app]]) 3 | (:use-macros [webfui.framework.macros :only [add-mouse-watch]])) 4 | 5 | (defn average [& numbers] 6 | (/ (apply + numbers) (count numbers))) 7 | 8 | (defn average-points [& points] 9 | [(apply average (map first points)) (apply average (map second points))]) 10 | 11 | (defn translate [[tx ty] [x y]] 12 | [(+ x tx) (+ y ty)]) 13 | 14 | (defn segment-angle [[x1 y1] [x2 y2]] 15 | (js/Math.atan2 (- y1 y2) (- x2 x1))) 16 | 17 | (defn sqr [x] 18 | (* x x)) 19 | 20 | (defn distance-squared [[x1 y1] [x2 y2]] 21 | (+ (sqr (- x1 x2)) (sqr (- y1 y2)))) 22 | 23 | (defn distance [[x1 y1] [x2 y2]] 24 | (js/Math.sqrt (+ (sqr (- x1 x2)) (sqr (- y1 y2))))) 25 | 26 | (defn normalize-angle [ang] 27 | (let [p js/Math.PI 28 | p2 (* p 2)] 29 | (cond (< ang (- p)) (normalize-angle (+ ang p2)) 30 | (>= ang p) (normalize-angle (- ang p2)) 31 | :else ang))) 32 | 33 | (defn angle-delta [angle-1 angle-2] 34 | (let [angle (normalize-angle (- angle-1 angle-2))] 35 | (if (<= angle js/Math.PI) 36 | angle 37 | (- (* js/Math.PI 2) angle)))) 38 | 39 | (defn move-point [[x y] angle dist] 40 | [(+ x (* (js/Math.cos angle) dist)) (- y (* (js/Math.sin angle) dist))]) 41 | 42 | (defn negative-point [[x y]] 43 | [(- x) (- y)]) 44 | 45 | (defn dynamic-goal-tree [tree] 46 | (let [[type from-point & rest] tree] 47 | (case type 48 | :branch (let [to-point (apply average-points (map second rest)) 49 | angle (segment-angle from-point to-point)] 50 | {:from-point from-point 51 | :to-point to-point 52 | :children (for [child rest] 53 | (let [[_ from-point-child] child 54 | angle-child (segment-angle from-point from-point-child) 55 | dist-child (distance from-point from-point-child) 56 | relative-angle (normalize-angle (- angle-child angle))] 57 | (assoc (dynamic-goal-tree child) 58 | :relative-angle relative-angle 59 | :distance dist-child)))}) 60 | :leaf {:from-point from-point 61 | :to-point (first rest)}))) 62 | 63 | (defn static-goal-tree [tree] 64 | (let [{:keys [from-point to-point children]} tree] 65 | (if (seq children) 66 | (concat [:branch from-point] 67 | (map static-goal-tree children)) 68 | [:leaf from-point to-point]))) 69 | 70 | (defn procrustes [& point-vectors] 71 | (let [corrected-points (for [points point-vectors] 72 | (let [[cx cy] (average-points points) 73 | translated (map (partial translate [(- cx) (- cy)]) points) 74 | scale (js/Math.sqrt (apply average (map (partial distance-squared [0 0]) translated)))] 75 | (for [[x y] translated] 76 | [(/ x scale) (/ y scale)]))) 77 | totals (for [[[x y] [w z]] (apply map vector corrected-points)] 78 | [(- (* w y) (* z x)) (+ (* w x) (* z y))]) 79 | numerator (apply + (map first totals)) 80 | denominator (apply + (map second totals))] 81 | (js/Math.atan2 numerator denominator))) 82 | 83 | (defn anneal-step [depth dtree goal-from-point] 84 | (let [{:keys [from-point to-point children]} dtree] 85 | (if (and (pos? depth) (> (distance-squared from-point goal-from-point) 0.0001)) 86 | (let [dist (distance from-point to-point) 87 | too-close (< dist 40) 88 | angle (segment-angle from-point to-point) 89 | angle-new (if too-close 90 | angle 91 | (segment-angle goal-from-point to-point)) 92 | delta (angle-delta angle angle-new) 93 | child-count (count children)] 94 | (if (and (> delta 0.01) (<= child-count 1)) 95 | (assoc dtree :from-point (move-point to-point (+ angle-new js/Math.PI) dist)) 96 | (let [points (for [child children] 97 | (let [{:keys [relative-angle distance]} child] 98 | (move-point goal-from-point (+ angle-new relative-angle) distance))) 99 | children-new (map (partial anneal-step (dec depth)) children points) 100 | points-new (map :from-point children-new) 101 | to-point-new (if (zero? child-count) 102 | (move-point goal-from-point angle-new dist) 103 | (apply average-points points-new)) 104 | angle-back (if too-close 105 | (+ angle js/Math.PI) 106 | (segment-angle to-point-new goal-from-point)) 107 | from-point-back (move-point to-point-new 108 | (if (<= 1 child-count) 109 | angle-back 110 | (+ angle-back (procrustes points points-new)) dist) 111 | dist)] 112 | (assoc dtree 113 | :from-point from-point-back 114 | :to-point to-point-new 115 | :children children-new)))) 116 | dtree))) 117 | 118 | (defn anneal [dtree goal-from-point] 119 | (let [breadth-first (if (and (= (count (:children dtree)) 1) (= (count (:children (first (:children dtree)))) 1)) 120 | (reduce (fn [acc n] 121 | (let [{:keys [from-point]} acc] 122 | (anneal-step 2 123 | acc 124 | goal-from-point))) 125 | dtree 126 | (range 30)) 127 | dtree) 128 | increasing-stability (reduce (fn [acc n] 129 | (let [{:keys [from-point]} acc] 130 | (anneal-step 1000 131 | acc 132 | (cond (< n 10) goal-from-point 133 | (< n 20) (average-points from-point goal-from-point) 134 | :else from-point)))) 135 | breadth-first 136 | (range 30))] 137 | increasing-stability)) 138 | 139 | #_(def initial-state {:tree (dynamic-goal-tree [:branch [50 50] 140 | [:leaf [100 100] [160 140]]])}) 141 | 142 | #_(def initial-state {:tree (dynamic-goal-tree [:branch [50 50] 143 | [:leaf [110 90] [160 140]] 144 | [:branch [:leaf [120,200] [200,200]]]])}) 145 | 146 | (defn connect-pinholes [skeleton] 147 | (assoc skeleton 148 | :boxes 149 | (let [{:keys [boxes pins]} skeleton] 150 | (reduce (fn [boxes pin] 151 | (let [[[box-from-index pin-from-index] [box-to-index pin-to-index]] pin] 152 | (update-in boxes 153 | [box-to-index :pinholes pin-to-index] 154 | (fn [_] 155 | (let [box-from (boxes box-from-index) 156 | pin-from ((:pinholes box-from) pin-from-index) 157 | box-to (boxes box-to-index) 158 | {:keys [left top]} box-to] 159 | [(- (+ (:left box-from) (first pin-from)) left) (- (+ (:top box-from) (second pin-from)) top)]))))) 160 | boxes 161 | pins)))) 162 | 163 | #_(def initial-state {:skeleton {:boxes [{:left 50 164 | :top 50 165 | :width 50 166 | :height 150 167 | :color :red 168 | :pinholes [[37 137]] 169 | :angle 0 170 | } 171 | {:left 75 172 | :top 175 173 | :width 50 174 | :height 125 175 | :color :blue 176 | :pinholes [[12 12]] 177 | :angle 0}] 178 | :pins [[[0 0] [1 0]]]}}) 179 | 180 | (def initial-state {:skeleton (connect-pinholes {:boxes [{:left 400 181 | :top 50 182 | :width 80 183 | :height 150 184 | :color :red 185 | :pinholes [[40 140]] 186 | :angle 0} 187 | {:left 350 188 | :top 175 189 | :width 180 190 | :height 250 191 | :color :blue 192 | :pinholes [nil nil nil nil nil] 193 | :angle 0} 194 | {:left 200 195 | :top 175 196 | :width 180 197 | :height 50 198 | :color :green 199 | :pinholes [[170 25] nil] 200 | :angle 0} 201 | {:left 80 202 | :top 180 203 | :width 140 204 | :height 40 205 | :color :yellow 206 | :pinholes [[135 20]] 207 | :angle 0} 208 | {:left 500 209 | :top 175 210 | :width 180 211 | :height 50 212 | :color :orange 213 | :pinholes [[10 25] nil] 214 | :angle 0} 215 | {:left 660 216 | :top 180 217 | :width 140 218 | :height 40 219 | :color :purple 220 | :pinholes [[10 20]] 221 | :angle 0} 222 | {:left 350 223 | :top 400 224 | :width 70 225 | :height 160 226 | :color :grey 227 | :pinholes [[35 10] nil] 228 | :angle 0} 229 | {:left 460 230 | :top 400 231 | :width 70 232 | :height 160 233 | :color :red 234 | :pinholes [[35 10] nil] 235 | :angle 0} 236 | {:left 360 237 | :top 540 238 | :width 50 239 | :height 140 240 | :color :purple 241 | :pinholes [[35 10]] 242 | :angle 0} 243 | {:left 470 244 | :top 540 245 | :width 50 246 | :height 140 247 | :color :green 248 | :pinholes [[35 10]] 249 | :angle 0} 250 | {:left 700 251 | :top 30 252 | :width 100 253 | :height 100 254 | :color :black 255 | :pinholes [] 256 | :angle 0 257 | } 258 | 259 | ] 260 | :pins [[[0 0] [1 0]] 261 | [[2 0] [1 1]] 262 | [[3 0] [2 1]] 263 | [[4 0] [1 2]] 264 | [[5 0] [4 1]] 265 | [[6 0] [1 3]] 266 | [[7 0] [1 4]] 267 | [[8 0] [6 1]] 268 | [[9 0] [7 1]] 269 | ]})}) 270 | 271 | (defn pin-positions [pins] 272 | (into {} 273 | (concat pins (map (comp vec reverse) pins)))) 274 | 275 | (defn counterpoint [width height point] 276 | (let [[x y] point 277 | cx (/ width 2) 278 | cy (/ height 2)] 279 | (cond (or (> (js/Math.abs (- x cx)) (/ cx 2)) (> (js/Math.abs (- y cy)) (/ cy 2))) [(- width x) (- height y)] 280 | (and (< x cx) (< y cy)) (average-points point [width height]) 281 | (< x cx) (average-points point [width 0]) 282 | (< y cy) (average-points point [0 height]) 283 | :else (average-points point [0 0])))) 284 | 285 | (defn skeleton-to-annotated-tree [skeleton index from-point from-pin] 286 | (let [{:keys [boxes pins]} skeleton 287 | pinpos (pin-positions pins)] 288 | ((fn f [index from-point from-pin] 289 | (let [box (boxes index) 290 | {:keys [left top pinholes width height angle]} box 291 | child-pinhole-indexes (filter (partial not= from-pin) (range (count pinholes))) 292 | child-pos (map pinholes child-pinhole-indexes) 293 | to-point (if (seq child-pos) 294 | (apply average-points child-pos) 295 | (counterpoint width height from-point)) 296 | top-left-corner [left top] 297 | relative-from-angle (segment-angle [0 0] from-point) 298 | from-distance (distance [0 0] from-point) 299 | relative-to-angle (segment-angle [0 0] to-point) 300 | to-distance (distance [0 0] to-point)] 301 | {:from-point (move-point top-left-corner (+ angle relative-from-angle) from-distance) 302 | :to-point (move-point top-left-corner (+ angle relative-to-angle) to-distance) 303 | :top-left-corner [left top] 304 | :box-index index 305 | :children (for [idx child-pinhole-indexes] 306 | (let [[child-box-index child-pinhole] (pinpos [index idx]) 307 | child-box (boxes child-box-index)] 308 | (f child-box-index ((child-box :pinholes) child-pinhole) child-pinhole)))})) 309 | index 310 | from-point 311 | from-pin))) 312 | 313 | (defn annotated-tree-to-static-tree [tree] 314 | (let [{:keys [from-point to-point children]} tree] 315 | (if (seq children) 316 | (concat [:branch from-point] (map annotated-tree-to-static-tree children)) 317 | [:leaf from-point to-point]))) 318 | 319 | (defn adjust-annotated-tree [tree stree] 320 | (let [[type from-point-new & rest] stree 321 | {:keys [children]} tree] 322 | (case type 323 | :leaf (assoc tree 324 | :from-point-new from-point-new 325 | :to-point-new (first rest)) 326 | :branch (assoc tree 327 | :from-point-new from-point-new 328 | :to-point-new (apply average-points (map second rest)) 329 | :children (map adjust-annotated-tree children rest))))) 330 | 331 | (defn reposition-rectangle [& foo] 332 | (let [[top-left-corner angle from-point to-point from-point-new to-point-new] foo] 333 | (let [origin-angle (segment-angle from-point top-left-corner) 334 | spine-angle (segment-angle from-point to-point) 335 | spine-angle-new (segment-angle from-point-new to-point-new) 336 | angle-diff (- spine-angle-new spine-angle) 337 | origin-angle-new (+ origin-angle angle-diff) 338 | origin-distance (distance from-point top-left-corner)] 339 | [(move-point from-point-new origin-angle-new origin-distance) (+ angle angle-diff)]))) 340 | 341 | 342 | (defn adjust-skeleton [skeleton tree] 343 | (let [{:keys [boxes]} skeleton 344 | boxes-new ((fn f [boxes tree] 345 | (let [{:keys [box-index from-point to-point from-point-new to-point-new children]} tree 346 | box (boxes box-index) 347 | {:keys [left top angle]} box 348 | [[left-new top-new] angle-new] (reposition-rectangle [left top] angle from-point to-point from-point-new to-point-new)] 349 | (reduce f (assoc boxes box-index (assoc box :left left-new :top top-new :angle angle-new)) children))) 350 | boxes 351 | tree)] 352 | (assoc skeleton :boxes boxes-new))) 353 | 354 | (defn render-tree [kin] 355 | (let [[type p1 & rest] kin 356 | [x1 y1] p1] 357 | (concat 358 | [[:div.node {:style {:left x1 :top y1}} "5"]] 359 | (case type 360 | :leaf (let [[[x2 y2]] rest] 361 | [[:div.node.destination {:style {:left x2 :top y2}} "3"]]) 362 | :branch (map render-tree rest))))) 363 | 364 | (defn render-skeleton [skeleton] 365 | (let [{:keys [boxes pins]} skeleton] 366 | (concat (map-indexed (fn [n box] 367 | (let [{:keys [left top width height color angle]} box] 368 | [:div.box {:mouse :box-mouse 369 | :data {:index n 370 | :angle angle} 371 | :style {:left left 372 | :top top 373 | :width width 374 | :height height 375 | :background-color color 376 | :-webkit-transform (str "rotate(" (- angle) "rad)")}}])) 377 | boxes) 378 | [] ;pin stuff here... 379 | ))) 380 | 381 | (defn render-all [state] 382 | (let [{:keys [tree]} state] 383 | [:div [:button {:mouse :push} "push"] (render-tree (static-goal-tree tree))])) 384 | 385 | (defn render-all [state] 386 | (let [{:keys [skeleton tree]} state] 387 | [:div (concat (render-skeleton skeleton) 388 | #_(when tree 389 | (render-tree tree)))])) 390 | 391 | (defn correct-coordinate [[x y] angle] 392 | (let [sa (segment-angle [0 0] [x y]) 393 | d (distance [0 0] [x y]) 394 | angle-new (- sa angle)] 395 | (move-point [0 0] angle-new d))) 396 | 397 | (defn pinhole-pos [box pinhole-index] 398 | (let [{:keys [top left angle pinholes]} box 399 | pos (pinholes pinhole-index) 400 | angle-old (segment-angle [0 0] pos) 401 | dist (distance [0 0] pos) 402 | angle-new (+ angle angle-old)] 403 | (move-point [left top] angle-new dist))) 404 | 405 | (defn reconnect-pinholes [skeleton] 406 | (let [{:keys [boxes pins]} skeleton] 407 | (assoc skeleton 408 | :boxes 409 | (reduce (fn [boxes from-index] 410 | (let [pinpos (pin-positions pins) 411 | pin (filter (fn [[[a b] [c d]]] 412 | (and (= a from-index) 413 | (< c a))) 414 | pinpos)] 415 | (if (seq pin) 416 | (let [[[[_ from-pinhole-index] [to-box-index to-pinhole-index]]] pin 417 | from-box (boxes from-index) 418 | to-box (boxes to-box-index) 419 | {:keys [top left]} from-box 420 | [x1 y1] (pinhole-pos from-box from-pinhole-index) 421 | [x2 y2] (pinhole-pos to-box to-pinhole-index)] 422 | (assoc boxes from-index 423 | (assoc from-box 424 | :left (- (+ left x2) x1) 425 | :top (- (+ top y2) y1)))) 426 | boxes))) 427 | boxes 428 | (range (count boxes)))))) 429 | 430 | (add-mouse-watch [:box-mouse :incremental] [state first-element last-element points] 431 | (let [{:keys [offset data]} (second first-element) 432 | [ox oy] offset 433 | relative-position (translate [(- ox) (- oy)] (first points)) 434 | {:keys [skeleton]} state 435 | {:keys [index angle]} data 436 | [x y] (correct-coordinate relative-position angle) 437 | [p2x p2y :as ending-point] (last points) 438 | tree (skeleton-to-annotated-tree skeleton index [x y] nil) 439 | stree (annotated-tree-to-static-tree tree) 440 | dtree (dynamic-goal-tree stree) 441 | annealed (anneal dtree [p2x p2y]) 442 | stree-new (static-goal-tree annealed) 443 | tree-new (adjust-annotated-tree tree stree-new) 444 | skeleton-new (reconnect-pinholes (adjust-skeleton skeleton tree-new))] 445 | {:skeleton skeleton-new 446 | :tree stree-new}) ) 447 | 448 | (launch-app (atom initial-state) render-all) 449 | 450 | -------------------------------------------------------------------------------- /webfui-examples/src-cljs/mouse_tracking/core.cljs: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.mouse-tracking.core 2 | (:use [webfui.framework :only [launch-app]] 3 | [webfui.utilities :only [get-attribute]]) 4 | (:use-macros [webfui.framework.macros :only [add-mouse-watch]])) 5 | 6 | (defn render-bubble [point size attributes] 7 | (let [[x y] point 8 | wid (* 50 (js/Math.sqrt size))] 9 | [:div (merge attributes 10 | {:style (merge {:left (- x (/ wid 2)) 11 | :top (- y (/ wid 2)) 12 | :width wid 13 | :height wid 14 | :line-height wid 15 | :background-color (str "rgb(" (* 4 size size) ",0," (- 260 (* 4 size size)) ")")} 16 | (:style attributes))}) 17 | size])) 18 | 19 | (defn render-all [state] 20 | (let [{:keys [dragged-item sizes]} state 21 | {:keys [point to from]} dragged-item] 22 | (list [:div.layer (for [index (range 13)] 23 | (render-bubble (map (fn [fun] 24 | (+ 450 (* 400 (fun (* index 0.4833))))) 25 | [js/Math.cos js/Math.sin]) 26 | (sizes index) 27 | {:style {:opacity (if dragged-item 28 | ({to 1 from 1} index 0.4) 29 | 1)} 30 | :data index 31 | :mouse :mouse}))] 32 | (if dragged-item ;this is a bit ugly because of a couple of webfui bug fixes that are pending 33 | [:div.layer (render-bubble point 34 | (sizes from) 35 | {:class :dragged 36 | :style {:background-color :black}})] 37 | [:div.layer])))) 38 | 39 | (add-mouse-watch :mouse [state first-element last-element points] 40 | (let [sizes (state :sizes) 41 | [from-index to-index] (map (comp :data second) [first-element last-element]) 42 | to-index (and (not= from-index to-index) to-index)] 43 | {:dragged-item (when (get-attribute first-element :active) 44 | {:point (last points) 45 | :from from-index 46 | :to to-index}) 47 | :sizes (if to-index 48 | (update-in sizes [to-index] + (sizes from-index)) 49 | sizes)})) 50 | 51 | (launch-app (atom {:sizes (vec (repeat 13 1))}) render-all) 52 | -------------------------------------------------------------------------------- /webfui-examples/src-cljs/scrolling/core.cljs: -------------------------------------------------------------------------------- 1 | (ns webfui-examples.scrolling.core 2 | (:use [webfui.framework :only [launch-app]] 3 | [webfui.plugin.core :only [register-plugin]] 4 | [webfui.plugin.scrolling :only [scrolling]] 5 | [webfui.utilities :only [get-attribute]]) 6 | (:use-macros [webfui.framework.macros :only [add-dom-watch]])) 7 | 8 | (register-plugin (scrolling.)) 9 | 10 | (defn render-all [state] 11 | (let [{:keys [position]} state] 12 | [:center [:div {:style {:width 200 13 | :height 200 14 | :overflow :auto} 15 | :scroll-top position 16 | :watch :container} 17 | [:div {:style {:height 400 18 | :background-color :cyan}}]] 19 | [:p [:input {:watch :text-field :value position}]]])) 20 | 21 | (defn try-parse [s] 22 | (let [n (js/parseInt s)] 23 | (if (js/isNaN n) 24 | 0 25 | n))) 26 | 27 | (add-dom-watch :container [state new-element] 28 | {:position (get-attribute new-element :scroll-top)}) 29 | 30 | (add-dom-watch :text-field [state new-element] 31 | {:position (try-parse (get-attribute new-element :value))}) 32 | 33 | (launch-app (atom {:position 40}) render-all) --------------------------------------------------------------------------------