Skip to content

Instantly share code, notes, and snippets.

@selfsame
Created January 28, 2016 08:37
Show Gist options
  • Select an option

  • Save selfsame/632290c0006d11922cde to your computer and use it in GitHub Desktop.

Select an option

Save selfsame/632290c0006d11922cde to your computer and use it in GitHub Desktop.
(ns sync.sheets
(:require-macros [reagent.ratom :refer [reaction]])
(:require
[om.next :as om :refer-macros [defui]]
[om.dom :as dom]
[heh.core :as heh :refer [private private!] :refer-macros [html]]
[reagent.core :as r]
[goog.events :as events]
[css.units :as units]
[css.core :as css]
[dollar.bill :as $ :refer [$]]
[pdfn.core :refer [and* or* not* is*] :refer-macros [defpdfn pdfn inspect]]
[cljs.pprint :as pprint]
[fast.style]
[sync.mouse])
(:import
[goog.events EventType]))
(defn ratom? [v] (instance? reagent.ratom/RAtom v))
(def UID (atom 0))
(defn uid [] (swap! UID inc))
(defn preparse [v]
(cond (integer? v) (str v "px")
(keyword? v) (clj->js v)
(number? v) (str v)
(vector? v) (mapv preparse v)
:else v))
(defn parse [v]
(let [res
(cond (string? v) (fast.style/parse-css-value v)
(vector? v) (mapv parse v)
:else [v])]
(if (or (sequential? res)(array? res))
(case (count res) 0 nil 1 (first res) (vec res))
res)))
(defn virtual-css [data]
(zipmap (keys data) (map (comp parse preparse) (vals data))))
(defn render-css-block [rule m]
(str "\n" rule " {\n"
(apply str (mapcat (fn [[k v]]
(interleave ["" (clj->js k) (str v)] [" " ":" ";\n"])) m)) "}"))
; DATA
(def app-state {})
(def app-style (r/atom (virtual-css
{:background "#D6C2AB"
:text "black"
:font-size "1em"
:resizer-size "0.5em"})))
(def default-layout
[1 [0.05 [0.5 :menu][0.5 :tabs]]
[0.89 [0.15
[0.3 [0.5 :tool] [0.5 :mode]]
[0.5 :style]
[0.2 :selection]]
[0.7 :workspace]
[0.25 :outliner]]
[0.06 [0.7 :context]
[0.3 :meta]]])
(def window-dims (r/atom {:w 640 :h 480 :x 0 :y 0}))
;LAYOUT
(defn register-layout
([data] (let [r (atom {})] (reset! UID -1) (register-layout data r) r))
([data registry]
(if-not (vector? data) data
(let [id (uid)]
(swap! registry conj
{id {:id id
:view (first (filter keyword? data))
:ratio (r/atom (first data))
:children (mapv #(register-layout % registry) (filter vector? data))}}) id))))
(def LAYOUT (register-layout default-layout))
(def axis-toggle {:h :v :v :h})
(def axis-dim {:h :h :v :w})
(def axis-axis {:h :y :v :x})
(defn measure-window [_]
(reset! window-dims {:w (.-innerWidth js/window) :h (.-innerHeight js/window) :x 0 :y 0}))
(defn derive-dims [me ratom axis prev]
(let [dim-k (axis-dim axis)
axis-k (axis-axis axis)]
(reaction
(assoc
(if (:dims prev) (assoc @ratom axis-k (int (+ (get @(:dims prev) axis-k) (get @(:dims prev) dim-k) )))
@ratom)
dim-k (int (* (or @(:ratio me) 1.0) (get @ratom dim-k)))))))
(defn create-resizer [data -a -b]
(let [[a b] (mapv @data [-a -b])
id (uid)
size (:resizer-size @app-style)]
(swap! data conj {id {:id id :resizer true
:axis (:axis b) :neighbors (mapv :id [a b])
:dims (reaction
(update
(assoc @(:dims b) (axis-dim (:axis b)) size)
(axis-axis (:axis b))
#(- % (* size 0.5)))) }})))
(defn ratom-walk
([data] (ratom-walk data 0 window-dims :v nil))
([data id ratom axis prev]
(let [me (get @data id)
children (:children me)]
(swap! data update-in [id] conj {:axis axis :dims (derive-dims me ratom axis prev)})
(vec (map-indexed
(fn [idx child]
(swap! data update-in [child] conj {:parent id})
(ratom-walk data child (:dims (get @data id)) (axis-toggle axis) (get @data (get children (dec idx)))))
children))
(reduce (fn [a b] (if a (do (create-resizer data a b) b) b)) nil children))))
(defn legal-ratios [a b delta thresh]
(let [-a (+ a delta)
-b (- b delta)
res
(if (or
(< -a thresh)
(< (- 1 thresh) -a)
(< -b thresh)
(< (- 1 thresh) -b))
0 delta)]
[res (- res)]))
(defn resize-fn [props delta]
(prn "resize")
(let [[a b] (map @LAYOUT (:neighbors props))
parent (@LAYOUT (:parent a))
delta (get delta ({:h 1 :v 0} (:axis props)) delta)
ax (axis-axis (:axis props))
dim (axis-dim (:axis props))
p-dim (get @(:dims parent) dim)
[r-a r-b] (legal-ratios @(:ratio a) @(:ratio b) (/ delta p-dim) (/ 24 p-dim))]
(swap! (:ratio a) #(+ % r-a))
(swap! (:ratio b) #(+ % r-b))))
; OM NEXT
(defn get-normalized [state key]
(let [st @state]
(into [] (map #(get-in st %)) (get st key))))
(defn read-local
[{:keys [query ast state] :as env} k params]
(if (om/ident? (:key ast))
(get-in @state (:key ast))
(om/db->tree query (get @state k) @state)))
(defpdfn ^:inline read)
(pdfn read [env k params] {}
{:value (get @(:state env) k)})
(pdfn read [env k params]
{k (is* :interface)}
{:value (filterv (or* :view :resizer) (vals @LAYOUT))})
(defpdfn ^:inline mutate)
(pdfn mutate [env k props] {:action #()})
(def reconciler (om/reconciler {
:logger nil
:state app-state
:parser (om/parser {
:read read
:mutate mutate})}))
; OM ui
(defui Panel
Object
(render [this]
(let [props (om/props this)]
(html
(<div.panel
(class (str "pid" (:id props) " " ({:h "horizontal" :v "vertical"} (:axis props))))
(<div (style {:margin "0.2em 0.5em"})
(<code (str (:view props)) )))))))
(def panel (om/factory Panel))
(defui Resizer
Object
(render [this]
(let [props (om/props this)
axis-idx ({:h 0 :v 1} (:axis props))]
(html
(<div.resizer
(class (str "pid" (:id props) " " ({:h "horizontal" :v "vertical"} (:axis props))
(if (:active (om/get-state this)) " active")))
(onMouseDown [e]
(om/set-state! this {:active true})
(sync.mouse/drag-fns! {
:drag #(resize-fn props (vec (:delta %)))
:end #(om/set-state! this {:active false})})) )))))
(def resizer (om/factory Resizer))
(defui Main
static om/IQueryParams
(params [this] {})
static om/IQuery
(query [this]
`[:interface])
Object
(render [this]
(let [props (om/props this)]
(pprint/pprint (filter :view (:interface props)))
(html
(<div
(map panel (filter :view (:interface props)))
(map resizer (filter :resizer (:interface props))))))))
; REAGENT
(defn panels []
[:style (str
(render-css-block
".panel"
{:position "absolute"
:box-sizing "border-box"
:background (:background @app-style)})
(render-css-block
".resizer"
{:position "absolute"
:height (:resizer-size @app-style)
:box-sizing "border-box"
:background "#8E785F"
:cursor "n-resize"})
(render-css-block
".resizer.vertical"
{:cursor "e-resize"})
(render-css-block
".resizer:hover, .resizer.active"
{:background "#61D8CB"}))])
(defn panel-block [data]
(let [{:keys [id dims]} data]
[:style (render-css-block
(str ".pid" id)
{:top (str (int (:y @dims)) "px")
:left (str (int (:x @dims)) "px")
:height (str (int (:h @dims)) "px")
:width (str (int (:w @dims)) "px")})]))
(defn interface []
(vec (concat [:div [panels]]
(mapv panel-block (filterv (or* :view :resizer) (vals @LAYOUT))))))
(defonce -virtual-el ($/append (first ($ "head")) ($ "<div id='virtual'></div>")))
(defn demo []
(events/listen js/window EventType.RESIZE measure-window)
(measure-window nil)
(ratom-walk LAYOUT)
(r/render-component [interface] (first ($ "#virtual")))
(om/add-root! reconciler Main (first ($ "#main"))))
;TODO
'[transform panel tree]
'((x) cascade reactive dependencies and build reagent components)
'((x) om/next read vector of leafs {:id 0 :view :foo})
'[interaction]
'((x) resize handles)
'(( ) add/remove parts of the tree - look at Blender and Unity)
'[system]
'(( ) ns for reagent style utils)
'(( ) ns for subdividing ui fns)
'(( ) ns for ratomic style)
'(( ) ns for om/next read and mutate fns)
'[conversion]
'(( ) rewrite core for om/next)
'(( ) load document and sync workspace with sub-ui)
'(( ) convert app-state for selection and other basics needed by workspace)
'(( ) rewrite css with ratoms)
'(( ) integrate thi.ng/color)
'(( ) base colors #{background, forground, active} reacted into UI variations)
'(( ) base weights for deriving #{alpha contrast saturation layout-spacing})
'[components]
'(( ) outliner)
'(( ) style)
'(( ) selection)
'(( ) menu)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment