Snap to square grid

This commit is contained in:
alonso.torres 2020-05-15 15:00:40 +02:00 committed by Andrés Moya
parent 3308d762f1
commit d2229f43c7
12 changed files with 159 additions and 171 deletions

View file

@ -575,6 +575,10 @@
height: 1rem; height: 1rem;
fill: $color-gray-20; fill: $color-gray-20;
} }
&:hover svg, &.is-active svg {
fill: $color-primary;
}
} }
.element-set-content .input-row { .element-set-content .input-row {

View file

@ -66,7 +66,8 @@
:layers :layers
:element-options :element-options
:rules :rules
:dynamic-alignment}) :dynamic-alignment
:layouts})
(s/def ::options-mode #{:design :prototype}) (s/def ::options-mode #{:design :prototype})
@ -1525,6 +1526,17 @@
state state
(assoc-in [:workspace-data pid :objects frame-id :layouts index] data)))))) (assoc-in [:workspace-data pid :objects frame-id :layouts index] data))))))
(defn set-default-layout [type params]
(ptk/reify ::set-default-layout
dwc/IBatchedChange
;; TODO: Save into the backend
ptk/UpdateEvent
(update [_ state]
(->
state
(assoc-in [:workspace-page :options :saved-layouts type] params)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exports ;; Exports
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -42,6 +42,12 @@
(def workspace-page (def workspace-page
(l/derived :workspace-page st/state)) (l/derived :workspace-page st/state))
(def workspace-page-options
(l/derived :options workspace-page))
(def workspace-saved-layouts
(l/derived :saved-layouts workspace-page-options))
(def workspace-page-id (def workspace-page-id
(l/derived :id workspace-page)) (l/derived :id workspace-page))
@ -71,6 +77,9 @@
(def workspace-objects (def workspace-objects
(l/derived :objects workspace-data)) (l/derived :objects workspace-data))
(def workspace-frames
(l/derived cp/select-frames workspace-objects))
(defn object-by-id (defn object-by-id
[id] [id]
(letfn [(selector [state] (letfn [(selector [state]

View file

@ -73,7 +73,8 @@
[:& viewport {:page page [:& viewport {:page page
:key (:id page) :key (:id page)
:file file :file file
:local local}]]] :local local
:layout layout}]]]
[:& left-toolbar {:page page :layout layout}] [:& left-toolbar {:page page :layout layout}]

View file

@ -11,13 +11,15 @@
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.common.pages :as cp]
[uxbox.util.geom.shapes :as gsh]
[uxbox.util.geom.layout :as ula])) [uxbox.util.geom.layout :as ula]))
(mf/defc grid-layout [{:keys [frame zoom layout] :as props}] (mf/defc grid-layout [{:keys [frame zoom layout] :as props}]
(let [{:keys [color size] :as params} (-> layout :params) (let [{:keys [color size] :as params} (-> layout :params)
{color-value :value color-opacity :opacity} color {color-value :value color-opacity :opacity} (-> layout :params :color)
{frame-width :width frame-height :height :keys [x y]} frame] {frame-width :width frame-height :height :keys [x y]} frame]
[:g.grid [:g.layout
[:* [:*
(for [xs (range size frame-width size)] (for [xs (range size frame-width size)]
[:line {:key (str (:id frame) "-y-" xs) [:line {:key (str (:id frame) "-y-" xs)
@ -38,20 +40,20 @@
:stroke-opacity color-opacity :stroke-opacity color-opacity
:stroke-width (str (/ 1 zoom))}}])]])) :stroke-width (str (/ 1 zoom))}}])]]))
(mf/defc flex-layout [{:keys [frame zoom layout]}] (mf/defc flex-layout [{:keys [key frame zoom layout]}]
(let [{color-value :value color-opacity :opacity} (-> layout :params :color)] (let [{color-value :value color-opacity :opacity} (-> layout :params :color)]
(for [{:keys [x y width height]} (ula/layout-rects frame layout)] [:g.layout
[:rect {:x x (for [{:keys [x y width height]} (ula/layout-rects frame layout)]
:y y [:rect {:key (str key "-" x "-" y)
:width width :x x
:height height :y y
:style {:pointer-events "none" :width width
:fill color-value :height height
:opacity color-opacity}}]))) :style {:fill color-value
:opacity color-opacity}}])]))
(mf/defc layout-display [{:keys [frame]}] (mf/defc layout-display-frame [{:keys [frame zoom]}]
(let [zoom (mf/deref refs/selected-zoom) (let [layouts (:layouts frame)]
layouts (:layouts frame)]
(for [[index {:keys [type display] :as layout}] (map-indexed vector layouts)] (for [[index {:keys [type display] :as layout}] (map-indexed vector layouts)]
(let [props #js {:key (str (:id frame) "-layout-" index) (let [props #js {:key (str (:id frame) "-layout-" index)
:frame frame :frame frame
@ -62,3 +64,12 @@
:square [:> grid-layout props] :square [:> grid-layout props]
:column [:> flex-layout props] :column [:> flex-layout props]
:row [:> flex-layout props])))))) :row [:> flex-layout props]))))))
(mf/defc layout-display [{:keys [zoom]}]
(let [frames (mf/deref refs/workspace-frames)]
[:g.layout-display {:style {:pointer-events "none"}}
(for [frame frames]
[:& layout-display-frame {:key (str "layout-" (:id frame))
:zoom zoom
:frame (gsh/transform-shape frame)}])]))

View file

@ -1,79 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.ruler
(:require
[rumext.alpha :as mf]
[uxbox.main.constants :as c]
[uxbox.main.data.workspace :as udw]
[uxbox.main.store :as st]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.math :as mth]))
(mf/defc ruler-text
[{:keys [zoom ruler] :as props}]
#_(let [{:keys [start end]} ruler
distance (-> (gpt/distance (gpt/divide end zoom)
(gpt/divide start zoom))
(mth/precision 2))
angle (-> (gpt/angle end start)
(mth/precision 2))
transform1 (str "translate(" (+ (:x end) 35) "," (- (:y end) 10) ")")
transform2 (str "translate(" (+ (:x end) 25) "," (- (:y end) 30) ")")]
[:g
[:rect {:fill "black"
:fill-opacity "0.4"
:rx "3"
:ry "3"
:width "90"
:height "50"
:transform transform2}]
[:text {:transform transform1
:fill "white"}
[:tspan {:x "0"}
(str distance " px")]
[:tspan {:x "0" :y "20"}
(str angle "°")]]]))
(mf/defc ruler-line
[{:keys [zoom ruler] :as props}]
#_(let [{:keys [start end]} ruler]
[:line {:x1 (:x start)
:y1 (:y start)
:x2 (:x end)
:y2 (:y end)
:style {:cursor "cell"}
:stroke-width "1"
:stroke "red"}]))
(mf/defc ruler
[{:keys [ruler zoom] :as props}]
#_(letfn [(on-mouse-down [event]
(dom/stop-propagation event)
(st/emit! :interrupt
(udw/assign-cursor-tooltip nil)
(udw/start-ruler)))
(on-mouse-up [event]
(dom/stop-propagation event)
(st/emit! :interrupt))
(on-unmount []
(st/emit! :interrupt
(udw/clear-ruler)))]
(mf/use-effect (constantly on-unmount))
[:svg {:on-mouse-down on-mouse-down
:on-mouse-up on-mouse-up}
[:rect {:style {:fill "transparent"
:stroke "transparent"
:cursor "cell"}
:width c/viewport-width
:height c/viewport-height}]
(when ruler
[:g
[:& ruler-line {:ruler ruler}]
[:& ruler-text {:ruler ruler :zoom zoom}]])]))

View file

@ -22,8 +22,7 @@
[uxbox.util.geom.shapes :as geom] [uxbox.util.geom.shapes :as geom]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.main.streams :as ms] [uxbox.main.streams :as ms]
[uxbox.util.timers :as ts] [uxbox.util.timers :as ts]))
[uxbox.main.ui.workspace.layout-display :refer [layout-display]]))
(defn- frame-wrapper-factory-equals? (defn- frame-wrapper-factory-equals?
[np op] [np op]
@ -105,6 +104,5 @@
(:name shape)] (:name shape)]
[:& frame-shape [:& frame-shape
{:shape shape {:shape shape
:childs childs}] :childs childs}]])))))
[:& layout-display {:frame shape}]])))))

View file

@ -12,7 +12,9 @@
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.data :as d] [uxbox.util.data :as d]
[uxbox.common.data :refer [parse-integer]]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.refs :as refs]
[uxbox.main.data.workspace :as dw] [uxbox.main.data.workspace :as dw]
[uxbox.main.ui.icons :as i] [uxbox.main.ui.icons :as i]
[uxbox.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [uxbox.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
@ -29,27 +31,8 @@
[:div.advanced-options {} [:div.advanced-options {}
children]])) children]]))
(defonce ^:private default-params (mf/defc layout-options [{:keys [layout default-layout-params on-change on-remove]}]
{:square {:size 16 (prn "(render) layout" layout)
:color {:value "#59B9E2"
:opacity 0.9}}
:column {:size 12
:type :stretch
:item-width nil
:gutter 8
:margin 0
:color {:value "#DE4762"
:opacity 0.1}}
:row {:size 12
:type :stretch
:item-height nil
:gutter 8
:margin 0
:color {:value "#DE4762"
:opacity 0.1}}})
(mf/defc layout-options [{:keys [layout on-change on-remove]}]
(let [state (mf/use-state {:show-advanced-options false (let [state (mf/use-state {:show-advanced-options false
:changes {}}) :changes {}})
{:keys [type display params] :as layout} (d/deep-merge layout (:changes @state)) {:keys [type display params] :as layout} (d/deep-merge layout (:changes @state))
@ -61,17 +44,18 @@
18 12 10 8 6 4 3 2] 18 12 10 8 6 4 3 2]
emit-changes! (fn [update-fn] emit-changes! (fn [update-fn]
(swap! state update :changes update-fn) (swap! state update :changes update-fn)
(when on-change (on-change (d/deep-merge layout (-> @state :changes update-fn))))) (prn "(event) layout" (d/deep-merge layout (-> @state :changes update-fn)))
(when on-change (on-change (d/deep-merge layout (-> @state :changes update-fn)))))
handle-toggle-visibility (fn [event] handle-toggle-visibility (fn [event]
(emit-changes! #(update % :display not))) (emit-changes! (fn [changes] (update changes :display #(if (nil? %) false (not %))))))
handle-remove-layout (fn [event] handle-remove-layout (fn [event]
(when on-remove (on-remove))) (when on-remove (on-remove)))
handle-change-type (fn [type] handle-change-type (fn [type]
(let [defaults (type default-params) (let [defaults (type default-layout-params)
params (merge params (merge
defaults defaults
(select-keys (keys defaults) (-> @state :changes params))) (select-keys (keys defaults) (-> @state :changes params)))
@ -85,7 +69,7 @@
handle-change-event (fn [& keys] handle-change-event (fn [& keys]
(fn [event] (fn [event]
(let [change-fn (apply handle-change keys)] (let [change-fn (apply handle-change keys)]
(-> event dom/get-target dom/get-value change-fn)))) (-> event dom/get-target dom/get-value parse-integer change-fn))))
] ]
[:div.grid-option [:div.grid-option
@ -169,20 +153,45 @@
[:button.btn-options "Use default"] [:button.btn-options "Use default"]
[:button.btn-options "Set as default"]]]])) [:button.btn-options "Set as default"]]]]))
(defonce ^:private default-layout-params
{:square {:size 16
:color {:value "#59B9E2"
:opacity 0.9}}
:column {:size 12
:type :stretch
:item-width nil
:gutter 8
:margin 0
:color {:value "#DE4762"
:opacity 0.1}}
:row {:size 12
:type :stretch
:item-height nil
:gutter 8
:margin 0
:color {:value "#DE4762"
:opacity 0.1}}})
(mf/defc frame-layouts [{:keys [shape]}] (mf/defc frame-layouts [{:keys [shape]}]
(let [id (:id shape) (let [id (:id shape)
default-layout-params (merge default-layout-params (mf/deref refs/workspace-saved-layouts))
handle-create-layout #(st/emit! (dw/add-frame-layout id)) handle-create-layout #(st/emit! (dw/add-frame-layout id))
handle-remove-layout (fn [index] #(st/emit! (dw/remove-frame-layout id index))) handle-remove-layout (fn [index] #(st/emit! (dw/remove-frame-layout id index)))
handle-edit-layout (fn [index] #(st/emit! (dw/set-frame-layout id index %)))] handle-edit-layout (fn [index] #(st/emit! (dw/set-frame-layout id index %)))
handle-save-layout (fn [layout] (st/emit! (dw/set-default-layout (:type layout) (:params layout))))]
[:div.element-set [:div.element-set
[:div.element-set-title [:div.element-set-title
[:span "Grid & Layout"] [:span "Grid & Layout"]
[:div.add-page {:on-click handle-create-layout} i/close]] [:div.add-page {:on-click handle-create-layout} i/close]]
[:div.element-set-content (when (not (empty? (:layouts shape)))
(for [[index layout] (map-indexed vector (:layouts shape))] [:div.element-set-content
[:& layout-options {:key (str (:id shape) "-" index) (for [[index layout] (map-indexed vector (:layouts shape))]
:layout layout [:& layout-options {:key (str (:id shape) "-" index)
:on-change (handle-edit-layout index) :layout layout
:on-remove (handle-remove-layout index)}])]])) :default-layout-params default-layout-params
:on-change (handle-edit-layout index)
:on-remove (handle-remove-layout index)
:on-save-layout handle-save-layout}])])]))

View file

@ -26,11 +26,10 @@
[uxbox.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]] [uxbox.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]]
[uxbox.main.ui.workspace.shapes.interactions :refer [interactions]] [uxbox.main.ui.workspace.shapes.interactions :refer [interactions]]
[uxbox.main.ui.workspace.drawarea :refer [draw-area start-drawing]] [uxbox.main.ui.workspace.drawarea :refer [draw-area start-drawing]]
[uxbox.main.ui.workspace.grid :refer [grid]]
[uxbox.main.ui.workspace.ruler :refer [ruler]]
[uxbox.main.ui.workspace.selection :refer [selection-handlers]] [uxbox.main.ui.workspace.selection :refer [selection-handlers]]
[uxbox.main.ui.workspace.presence :as presence] [uxbox.main.ui.workspace.presence :as presence]
[uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]] [uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]]
[uxbox.main.ui.workspace.layout-display :refer [layout-display]]
[uxbox.util.math :as mth] [uxbox.util.math :as mth]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.object :as obj] [uxbox.util.object :as obj]
@ -127,7 +126,7 @@
:key (:id item)}]))])) :key (:id item)}]))]))
(mf/defc viewport (mf/defc viewport
[{:keys [page local] :as props}] [{:keys [page local layout] :as props}]
(let [{:keys [drawing-tool (let [{:keys [drawing-tool
options-mode options-mode
zoom zoom
@ -354,6 +353,7 @@
] ]
(mf/use-effect on-mount) (mf/use-effect on-mount)
[:svg.viewport [:svg.viewport
{:preserveAspectRatio "xMidYMid meet" {:preserveAspectRatio "xMidYMid meet"
:width (:width vport 0) :width (:width vport 0)
@ -381,22 +381,18 @@
:zoom zoom :zoom zoom
:edition edition}]) :edition edition}])
(when-let [drawing-shape (:drawing local)] (when-let [drawing-shape (:drawing local)]
[:& draw-area {:shape drawing-shape [:& draw-area {:shape drawing-shape
:zoom zoom :zoom zoom
:modifiers (:modifiers local)}]) :modifiers (:modifiers local)}])
(when (contains? layout :layouts)
[:& layout-display {:zoom zoom}])
[:& snap-feedback] [:& snap-feedback]
(when (contains? flags :grid) (when tooltip
[:& grid])] [:& cursor-tooltip {:zoom zoom :tooltip tooltip}])]
(when tooltip
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])
(when (contains? flags :ruler)
[:& ruler {:zoom zoom :ruler (:ruler local)}])
[:& presence/active-cursors {:page page}] [:& presence/active-cursors {:page page}]
[:& selection-rect {:data (:selrect local)}] [:& selection-rect {:data (:selrect local)}]

View file

@ -7,7 +7,9 @@
;; ;;
;; Copyright (c) 2020 UXBOX Labs SL ;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.util.geom.layout) (ns uxbox.util.geom.layout
(:require
[uxbox.util.geom.point :as gpt]))
(defn calculate-column-layout [{:keys [width height x y] :as frame} {:keys [size gutter margin item-width type] :as params}] (defn calculate-column-layout [{:keys [width height x y] :as frame} {:keys [size gutter margin item-width type] :as params}]
(let [parts (/ width size) (let [parts (/ width size)
@ -20,7 +22,7 @@
gutter (if (= :stretch type) (/ (- width (* item-width size) (* margin 2)) (dec size)) gutter) gutter (if (= :stretch type) (/ (- width (* item-width size) (* margin 2)) (dec size)) gutter)
next-x (fn [cur-val] (+ initial-offset x (* (+ item-width gutter) cur-val))) next-x (fn [cur-val] (+ initial-offset x (* (+ item-width gutter) cur-val)))
next-y (fn [cur-val] y)] next-y (fn [cur-val] y)]
[item-width item-height next-x next-y])) [size item-width item-height next-x next-y]))
(defn calculate-row-layout [{:keys [width height x y] :as frame} {:keys [size gutter margin item-height type] :as params}] (defn calculate-row-layout [{:keys [width height x y] :as frame} {:keys [size gutter margin item-height type] :as params}]
(let [{:keys [width height x y]} frame (let [{:keys [width height x y]} frame
@ -34,16 +36,51 @@
gutter (if (= :stretch type) (/ (- height (* item-height size) (* margin 2)) (dec size)) gutter) gutter (if (= :stretch type) (/ (- height (* item-height size) (* margin 2)) (dec size)) gutter)
next-x (fn [cur-val] x) next-x (fn [cur-val] x)
next-y (fn [cur-val] (+ initial-offset y (* (+ item-height gutter) cur-val)))] next-y (fn [cur-val] (+ initial-offset y (* (+ item-height gutter) cur-val)))]
[item-width item-height next-x next-y])) [size item-width item-height next-x next-y]))
(defn calculate-grid-layout [{:keys [width height x y] :as frame} {:keys [size] :as params}]
(let [col-size (quot width size)
row-size (quot height size)
as-row-col (fn [value] [(quot value col-size) (rem value col-size)])
next-x (fn [cur-val]
(let [[_ col] (as-row-col cur-val)] (+ x (* col size))))
next-y (fn [cur-val]
(let [[row _] (as-row-col cur-val)] (+ y (* row size))))]
[(* col-size row-size) size size next-x next-y]))
(defn layout-rects [frame layout] (defn layout-rects [frame layout]
(let [[item-width item-height next-x next-y] (let [layout-fn (case (-> layout :type)
(case (-> layout :type) :column calculate-column-layout
:column (calculate-column-layout frame (-> layout :params)) :row calculate-row-layout
:row (calculate-row-layout frame (-> layout :params)))] :square calculate-grid-layout)
[num-items item-width item-height next-x next-y] (layout-fn frame (-> layout :params))]
(->> (->>
(range 0 (-> layout :params :size)) (range 0 num-items)
(map #(hash-map :x (next-x %) (map #(hash-map :x (next-x %)
:y (next-y %) :y (next-y %)
:width item-width :width item-width
:height item-height))))) :height item-height)))))
(defn- layout-rect-points [{:keys [x y width height]}]
[(gpt/point x y)
(gpt/point (+ x width) y)
(gpt/point (+ x width) (+ y height))
(gpt/point x (+ y height))])
(defn- layout-snap-points
([shape coord] (mapcat #(layout-snap-points shape % coord) (:layouts shape)))
([shape {:keys [type display params] :as layout} coord]
(case type
:square (let [{:keys [x y width height]} shape
size (-> params :size)]
(if (= coord :x)
(mapcat #(vector (gpt/point (+ x %) y)
(gpt/point (+ x %) (+ y height))) (range size width size))
(mapcat #(vector (gpt/point x (+ y %))
(gpt/point (+ x width) (+ y %))) (range size height size))))
:column (when (= coord :x) (->> (layout-rects shape layout)
(mapcat layout-rect-points)))
:row (when (= coord :y) (->> (layout-rects shape layout)
(mapcat layout-rect-points))))))

View file

@ -12,17 +12,7 @@
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[clojure.set :as set] [clojure.set :as set]
[uxbox.util.geom.shapes :as gsh] [uxbox.util.geom.shapes :as gsh]
[uxbox.util.geom.point :as gpt] [uxbox.util.geom.point :as gpt]))
[uxbox.util.geom.layout :as gla]))
(defn- layout-rect-snaps [{:keys [x y width height]}]
#{(gpt/point x y)
(gpt/point (+ x width) y)
(gpt/point (+ x width) (+ y height))
(gpt/point x (+ y height))})
(defn- layout-snap-points [frame {:keys [type] :as layout}]
(mapcat layout-rect-snaps (gla/layout-rects frame layout)))
(defn- frame-snap-points [{:keys [x y width height layouts] :as frame}] (defn- frame-snap-points [{:keys [x y width height layouts] :as frame}]
(into #{(gpt/point x y) (into #{(gpt/point x y)
@ -32,11 +22,7 @@
(gpt/point (+ x width) (+ y height)) (gpt/point (+ x width) (+ y height))
(gpt/point (+ x (/ width 2)) (+ y height)) (gpt/point (+ x (/ width 2)) (+ y height))
(gpt/point x (+ y height)) (gpt/point x (+ y height))
(gpt/point x (+ y (/ height 2)))} (gpt/point x (+ y (/ height 2)))}))
(->>
layouts
(filter #(and (not= :grid (:type %)) (:display %)))
(mapcat #(layout-snap-points frame %)))))
(defn shape-snap-points (defn shape-snap-points
[shape] [shape]

View file

@ -14,7 +14,8 @@
[uxbox.common.pages :as cp] [uxbox.common.pages :as cp]
[uxbox.worker.impl :as impl] [uxbox.worker.impl :as impl]
[uxbox.util.range-tree :as rt] [uxbox.util.range-tree :as rt]
[uxbox.util.geom.snap-points :as snap])) [uxbox.util.geom.snap-points :as snap]
[uxbox.util.geom.layout :as gla]))
(defonce state (l/atom {})) (defonce state (l/atom {}))
@ -23,8 +24,11 @@
[shapes coord] [shapes coord]
(let [process-shape (fn [coord] (let [process-shape (fn [coord]
(fn [shape] (fn [shape]
(let [points (snap/shape-snap-points shape)] (concat
(map #(vector % (:id shape)) points)))) (let [points (snap/shape-snap-points shape)]
(map #(vector % (:id shape)) points))
(let [points (gla/layout-snap-points shape coord)]
(map #(vector % :layout) points)))))
into-tree (fn [tree [point _ :as data]] into-tree (fn [tree [point _ :as data]]
(rt/insert tree (coord point) data))] (rt/insert tree (coord point) data))]
(->> shapes (->> shapes