mirror of
https://github.com/penpot/penpot.git
synced 2025-01-27 00:49:28 -05:00
✨ First version drawing beziers
This commit is contained in:
parent
af68c26aea
commit
f3cce1904c
7 changed files with 255 additions and 56 deletions
|
@ -19,8 +19,12 @@
|
||||||
|
|
||||||
(defn center-rect
|
(defn center-rect
|
||||||
[{:keys [x y width height]}]
|
[{:keys [x y width height]}]
|
||||||
|
(when (and (mth/finite? x)
|
||||||
|
(mth/finite? y)
|
||||||
|
(mth/finite? width)
|
||||||
|
(mth/finite? height))
|
||||||
(gpt/point (+ x (/ width 2))
|
(gpt/point (+ x (/ width 2))
|
||||||
(+ y (/ height 2))))
|
(+ y (/ height 2)))))
|
||||||
|
|
||||||
(defn center-selrect
|
(defn center-selrect
|
||||||
"Calculate the center of the shape."
|
"Calculate the center of the shape."
|
||||||
|
|
|
@ -23,7 +23,8 @@
|
||||||
"Returns a transformation matrix without changing the shape properties.
|
"Returns a transformation matrix without changing the shape properties.
|
||||||
The result should be used in a `transform` attribute in svg"
|
The result should be used in a `transform` attribute in svg"
|
||||||
([{:keys [x y] :as shape}]
|
([{:keys [x y] :as shape}]
|
||||||
(let [shape-center (gco/center-shape shape)]
|
(let [shape-center (or (gco/center-shape shape)
|
||||||
|
(gpt/point 0 0))]
|
||||||
(-> (gmt/matrix)
|
(-> (gmt/matrix)
|
||||||
(gmt/translate shape-center)
|
(gmt/translate shape-center)
|
||||||
(gmt/multiply (:transform shape (gmt/matrix)))
|
(gmt/multiply (:transform shape (gmt/matrix)))
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
(defn finite?
|
(defn finite?
|
||||||
[v]
|
[v]
|
||||||
#?(:cljs (js/isFinite v)
|
#?(:cljs (and (not (nil? v)) (js/isFinite v))
|
||||||
:clj (Double/isFinite v)))
|
:clj (Double/isFinite v)))
|
||||||
|
|
||||||
(defn abs
|
(defn abs
|
||||||
|
|
|
@ -14,15 +14,12 @@
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.main.streams :as ms]
|
[app.main.streams :as ms]
|
||||||
[app.util.geom.path :as path]
|
[app.util.geom.path :as ugp]
|
||||||
[app.main.data.workspace.drawing.common :as common]))
|
[app.main.data.workspace.drawing.common :as common]))
|
||||||
|
|
||||||
(defn finish-event? [{:keys [type shift] :as event}]
|
(defn finish-event? [{:keys [type shift] :as event}]
|
||||||
(or (= event ::end-path-drawing)
|
(or (= event ::end-path-drawing)
|
||||||
(= event :interrupt)
|
(= event :interrupt)
|
||||||
#_(and (ms/mouse-event? event)
|
|
||||||
(or (= type :double-click)
|
|
||||||
(= type :context-menu)))
|
|
||||||
(and (ms/keyboard-event? event)
|
(and (ms/keyboard-event? event)
|
||||||
(= type :down)
|
(= type :down)
|
||||||
(= 13 (:key event)))))
|
(= 13 (:key event)))))
|
||||||
|
@ -78,26 +75,105 @@
|
||||||
(assoc-in [:workspace-drawing :object :last-point] nil)
|
(assoc-in [:workspace-drawing :object :last-point] nil)
|
||||||
(update-in [:workspace-drawing :object] calculate-selrect)))))
|
(update-in [:workspace-drawing :object] calculate-selrect)))))
|
||||||
|
|
||||||
|
(defn preview-next-point [{:keys [x y]}]
|
||||||
|
(ptk/reify ::add-node
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [point {:x x :y y}
|
||||||
|
{:keys [last-point prev-handler]} (get-in state [:workspace-drawing :object])
|
||||||
|
|
||||||
|
command (cond
|
||||||
|
(and last-point (not prev-handler))
|
||||||
|
{:command :line-to
|
||||||
|
:params point}
|
||||||
|
|
||||||
|
(and last-point prev-handler)
|
||||||
|
{:command :curve-to
|
||||||
|
:params (ugp/make-curve-params point prev-handler)}
|
||||||
|
|
||||||
|
:else
|
||||||
|
nil)
|
||||||
|
]
|
||||||
|
(-> state
|
||||||
|
(assoc-in [:workspace-drawing :object :preview] command))))))
|
||||||
|
|
||||||
(defn add-node [{:keys [x y]}]
|
(defn add-node [{:keys [x y]}]
|
||||||
(ptk/reify ::add-node
|
(ptk/reify ::add-node
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [point {:x x :y y}
|
(let [point {:x x :y y}
|
||||||
last-point (get-in state [:workspace-drawing :object :last-point])
|
{:keys [last-point prev-handler]} (get-in state [:workspace-drawing :object])
|
||||||
command (if last-point
|
|
||||||
|
command (cond
|
||||||
|
(and last-point (not prev-handler))
|
||||||
{:command :line-to
|
{:command :line-to
|
||||||
:params point}
|
:params point}
|
||||||
|
|
||||||
|
(and last-point prev-handler)
|
||||||
|
{:command :curve-to
|
||||||
|
:params (ugp/make-curve-params point prev-handler)}
|
||||||
|
|
||||||
|
:else
|
||||||
{:command :move-to
|
{:command :move-to
|
||||||
:params point})]
|
:params point})
|
||||||
|
]
|
||||||
(-> state
|
(-> state
|
||||||
(assoc-in [:workspace-drawing :object :last-point] point)
|
(assoc-in [:workspace-drawing :object :last-point] point)
|
||||||
(update-in [:workspace-drawing :object :content] (fnil conj []) command))))))
|
(update-in [:workspace-drawing :object] dissoc :prev-handler)
|
||||||
|
(update-in [:workspace-drawing :object :content] (fnil conj []) command)
|
||||||
|
(update-in [:workspace-drawing :object] calculate-selrect))))))
|
||||||
|
|
||||||
(defn drag-handler [{:keys [x y]}]
|
(defn drag-handler [{:keys [x y]}]
|
||||||
(ptk/reify ::drag-handler
|
(ptk/reify ::drag-handler
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(-> state))))
|
(let [change-handler (fn [content]
|
||||||
|
(let [last-idx (dec (count content))
|
||||||
|
last (get content last-idx nil)
|
||||||
|
prev (get content (dec last-idx) nil)
|
||||||
|
{last-x :x last-y :y} (:params last)
|
||||||
|
opposite (when last (ugp/opposite-handler (gpt/point last-x last-y) (gpt/point x y)))]
|
||||||
|
|
||||||
|
(cond
|
||||||
|
(and prev (= (:command last) :line-to))
|
||||||
|
(-> content
|
||||||
|
(assoc last-idx {:command :curve-to
|
||||||
|
:params {:x (-> last :params :x)
|
||||||
|
:y (-> last :params :y)
|
||||||
|
:c1x (-> prev :params :x)
|
||||||
|
:c1y (-> prev :params :y)
|
||||||
|
:c2x (-> last :params :x)
|
||||||
|
:c2y (-> last :params :y)}})
|
||||||
|
(update-in
|
||||||
|
[last-idx :params]
|
||||||
|
#(-> %
|
||||||
|
(assoc :c2x (:x opposite)
|
||||||
|
:c2y (:y opposite)))))
|
||||||
|
|
||||||
|
(= (:command last) :curve-to)
|
||||||
|
(update-in content
|
||||||
|
[last-idx :params]
|
||||||
|
#(-> %
|
||||||
|
(assoc :c2x (:x opposite)
|
||||||
|
:c2y (:y opposite))))
|
||||||
|
:else
|
||||||
|
content))
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
handler (gpt/point x y)]
|
||||||
|
(-> state
|
||||||
|
(update-in [:workspace-drawing :object :content] change-handler)
|
||||||
|
(assoc-in [:workspace-drawing :object :drag-handler] handler))))))
|
||||||
|
|
||||||
|
(defn finish-drag []
|
||||||
|
(ptk/reify ::finish-drag
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [handler (get-in state [:workspace-drawing :object :drag-handler])]
|
||||||
|
(-> state
|
||||||
|
(update-in [:workspace-drawing :object] dissoc :drag-handler)
|
||||||
|
(assoc-in [:workspace-drawing :object :prev-handler] handler))))))
|
||||||
|
|
||||||
(defn make-click-stream
|
(defn make-click-stream
|
||||||
[stream down-event]
|
[stream down-event]
|
||||||
|
@ -115,8 +191,9 @@
|
||||||
(rx/map #(drag-handler %)))]
|
(rx/map #(drag-handler %)))]
|
||||||
(->> (rx/timer 400)
|
(->> (rx/timer 400)
|
||||||
(rx/merge-map #(rx/concat
|
(rx/merge-map #(rx/concat
|
||||||
(add-node down-event)
|
(rx/of (add-node down-event))
|
||||||
drag-events)))))
|
drag-events
|
||||||
|
(rx/of (finish-drag)))))))
|
||||||
|
|
||||||
(defn make-dbl-click-stream
|
(defn make-dbl-click-stream
|
||||||
[stream down-event]
|
[stream down-event]
|
||||||
|
@ -133,12 +210,17 @@
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
|
|
||||||
;; clicks stream<[MouseEvent, Position]>
|
;; clicks stream<[MouseEvent, Position]>
|
||||||
(let [
|
(let [mouse-down (->> stream (rx/filter ms/mouse-down?))
|
||||||
|
|
||||||
mouse-down (->> stream (rx/filter ms/mouse-down?))
|
|
||||||
finish-events (->> stream (rx/filter finish-event?))
|
finish-events (->> stream (rx/filter finish-event?))
|
||||||
|
|
||||||
events (->> mouse-down
|
mousemove-events
|
||||||
|
(->> ms/mouse-position
|
||||||
|
(rx/take-until finish-events)
|
||||||
|
(rx/throttle 100)
|
||||||
|
(rx/map #(preview-next-point %)))
|
||||||
|
|
||||||
|
mousedown-events
|
||||||
|
(->> mouse-down
|
||||||
(rx/take-until finish-events)
|
(rx/take-until finish-events)
|
||||||
(rx/throttle 100)
|
(rx/throttle 100)
|
||||||
(rx/with-latest merge ms/mouse-position)
|
(rx/with-latest merge ms/mouse-position)
|
||||||
|
@ -152,7 +234,8 @@
|
||||||
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/of (init-path))
|
(rx/of (init-path))
|
||||||
events
|
(rx/merge mousemove-events
|
||||||
|
mousedown-events)
|
||||||
(rx/of (finish-path))
|
(rx/of (finish-path))
|
||||||
(rx/of common/handle-finish-drawing)))
|
(rx/of common/handle-finish-drawing)))
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
[app.main.data.workspace.drawing :as dd]
|
[app.main.data.workspace.drawing :as dd]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.workspace.shapes :as shapes]
|
[app.main.ui.workspace.shapes :as shapes]
|
||||||
|
[app.main.ui.workspace.shapes.path :refer [path-editor]]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
@ -22,10 +23,13 @@
|
||||||
|
|
||||||
(mf/defc draw-area
|
(mf/defc draw-area
|
||||||
[{:keys [shape zoom] :as props}]
|
[{:keys [shape zoom] :as props}]
|
||||||
(when (:id shape)
|
|
||||||
|
[:g.draw-area
|
||||||
|
[:& shapes/shape-wrapper {:shape shape}]
|
||||||
|
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
(:path :curve) [:& path-draw-area {:shape shape}]
|
:path [:& path-editor {:shape shape :zoom zoom}]
|
||||||
[:& generic-draw-area {:shape shape :zoom zoom}])))
|
#_:default [:& generic-draw-area {:shape shape :zoom zoom}])])
|
||||||
|
|
||||||
(mf/defc generic-draw-area
|
(mf/defc generic-draw-area
|
||||||
[{:keys [shape zoom]}]
|
[{:keys [shape zoom]}]
|
||||||
|
@ -34,19 +38,16 @@
|
||||||
(not (d/nan? x))
|
(not (d/nan? x))
|
||||||
(not (d/nan? y)))
|
(not (d/nan? y)))
|
||||||
|
|
||||||
[:g
|
|
||||||
[:& shapes/shape-wrapper {:shape shape}]
|
|
||||||
[:rect.main {:x x :y y
|
[:rect.main {:x x :y y
|
||||||
:width width
|
:width width
|
||||||
:height height
|
:height height
|
||||||
:style {:stroke "#1FDEA7"
|
:style {:stroke "#1FDEA7"
|
||||||
:fill "transparent"
|
:fill "transparent"
|
||||||
:stroke-width (/ 1 zoom)}}]])))
|
:stroke-width (/ 1 zoom)}}])))
|
||||||
|
|
||||||
(mf/defc path-draw-area
|
#_(mf/defc path-draw-area
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [locale (i18n/use-locale)
|
(let [locale (i18n/use-locale)
|
||||||
|
|
||||||
on-click
|
on-click
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
|
@ -62,14 +63,13 @@
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(st/emit! (dw/assign-cursor-tooltip nil)))]
|
(st/emit! (dw/assign-cursor-tooltip nil)))]
|
||||||
|
|
||||||
(when-let [{:keys [x y] :as segment} (first (:segments shape))]
|
[:g.drawing
|
||||||
[:g
|
|
||||||
[:& shapes/shape-wrapper {:shape shape}]
|
[:& shapes/shape-wrapper {:shape shape}]
|
||||||
(when (not= :curve (:type shape))
|
#_(when (not= :curve (:type shape))
|
||||||
[:circle.close-bezier
|
[:circle.close-bezier
|
||||||
{:cx x
|
{:cx x
|
||||||
:cy y
|
:cy y
|
||||||
:r 5
|
:r 5
|
||||||
:on-click on-click
|
:on-click on-click
|
||||||
:on-mouse-enter on-mouse-enter
|
:on-mouse-enter on-mouse-enter
|
||||||
:on-mouse-leave on-mouse-leave}])])))
|
:on-mouse-leave on-mouse-leave}])]))
|
||||||
|
|
|
@ -23,7 +23,8 @@
|
||||||
[app.main.ui.shapes.path :as path]
|
[app.main.ui.shapes.path :as path]
|
||||||
[app.main.ui.shapes.filters :as filters]
|
[app.main.ui.shapes.filters :as filters]
|
||||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||||
[app.main.ui.workspace.shapes.common :as common]))
|
[app.main.ui.workspace.shapes.common :as common]
|
||||||
|
[app.util.geom.path :as ugp]))
|
||||||
|
|
||||||
(mf/defc path-wrapper
|
(mf/defc path-wrapper
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
|
@ -56,3 +57,93 @@
|
||||||
[:& path/path-shape {:shape shape
|
[:& path/path-shape {:shape shape
|
||||||
:background? true}]]))
|
:background? true}]]))
|
||||||
|
|
||||||
|
|
||||||
|
(mf/defc path-handler [{:keys [point handler zoom selected]}]
|
||||||
|
(when (and point handler)
|
||||||
|
(let [{:keys [x y]} handler]
|
||||||
|
[:g.handler
|
||||||
|
[:line
|
||||||
|
{:x1 (:x point)
|
||||||
|
:y1 (:y point)
|
||||||
|
:x2 x
|
||||||
|
:y2 y
|
||||||
|
:style {:stroke "#B1B2B5"
|
||||||
|
:stroke-width (/ 1 zoom)}}]
|
||||||
|
[:rect
|
||||||
|
{:x (- x (/ 3 zoom))
|
||||||
|
:y (- y (/ 3 zoom))
|
||||||
|
:width (/ 6 zoom)
|
||||||
|
:height (/ 6 zoom)
|
||||||
|
:style {:stroke-width (/ 1 zoom)
|
||||||
|
:stroke (if selected "#000000" "#1FDEA7")
|
||||||
|
:fill (if selected "#1FDEA7" "#FFFFFF")}}]])))
|
||||||
|
|
||||||
|
(mf/defc path-editor
|
||||||
|
[{:keys [shape zoom]}]
|
||||||
|
|
||||||
|
(let [points (:points shape)
|
||||||
|
drag-handler (:drag-handler shape)
|
||||||
|
prev-handler (:prev-handler shape)
|
||||||
|
last-command (last (:content shape))
|
||||||
|
selected false
|
||||||
|
last-p (last points)
|
||||||
|
handlers (ugp/extract-handlers (:content shape))
|
||||||
|
handlers (if (and prev-handler (not drag-handler))
|
||||||
|
(conj handlers {:point last-p :prev prev-handler})
|
||||||
|
handlers)
|
||||||
|
]
|
||||||
|
|
||||||
|
[:g.path-editor
|
||||||
|
(when (and (:preview shape) (not (:drag-handler shape)))
|
||||||
|
[:*
|
||||||
|
[:path {:style {:fill "transparent"
|
||||||
|
:stroke "#DB00FF"
|
||||||
|
:stroke-width (/ 1 zoom)}
|
||||||
|
:d (ugp/content->path [{:command :move-to
|
||||||
|
:params {:x (:x last-p)
|
||||||
|
:y (:y last-p)}}
|
||||||
|
(:preview shape)])}]
|
||||||
|
[:circle
|
||||||
|
{:cx (-> shape :preview :params :x)
|
||||||
|
:cy (-> shape :preview :params :y)
|
||||||
|
:r (/ 3 zoom)
|
||||||
|
:style {:stroke-width (/ 1 zoom)
|
||||||
|
:stroke "#DB00FF"
|
||||||
|
:fill "#FFFFFF"}}]])
|
||||||
|
|
||||||
|
(for [{:keys [point prev next]} handlers]
|
||||||
|
[:*
|
||||||
|
[:& path-handler {:point point
|
||||||
|
:handler prev
|
||||||
|
:zoom zoom
|
||||||
|
:type :prev
|
||||||
|
:selected false}]
|
||||||
|
[:& path-handler {:point point
|
||||||
|
:handler next
|
||||||
|
:zoom zoom
|
||||||
|
:type :next
|
||||||
|
:selected false}]])
|
||||||
|
|
||||||
|
(when drag-handler
|
||||||
|
[:*
|
||||||
|
(when (not= :move-to (:command last-command))
|
||||||
|
[:& path-handler {:point last-p
|
||||||
|
:handler (ugp/opposite-handler last-p drag-handler)
|
||||||
|
:zoom zoom
|
||||||
|
:type :drag-opposite
|
||||||
|
:selected false}])
|
||||||
|
[:& path-handler {:point last-p
|
||||||
|
:handler drag-handler
|
||||||
|
:zoom zoom
|
||||||
|
:type :drag
|
||||||
|
:selected false}]])
|
||||||
|
|
||||||
|
(for [{:keys [x y] :as point} points]
|
||||||
|
[:circle
|
||||||
|
{:cx x
|
||||||
|
:cy y
|
||||||
|
:r (/ 3 zoom)
|
||||||
|
:style {:stroke-width (/ 1 zoom)
|
||||||
|
:stroke (if selected "#000000" "#1FDEA7")
|
||||||
|
:fill (if selected "#1FDEA7" "#FFFFFF")}
|
||||||
|
}])]))
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
(:require
|
(:require
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
[app.util.geom.path-impl-simplify :as impl-simplify]))
|
[app.util.geom.path-impl-simplify :as impl-simplify]))
|
||||||
|
|
||||||
(defn simplify
|
(defn simplify
|
||||||
|
@ -192,10 +193,29 @@
|
||||||
(map command->string)
|
(map command->string)
|
||||||
(str/join "")))
|
(str/join "")))
|
||||||
|
|
||||||
#_(let [path "M.343 15.974a.514.514 0 01-.317-.321c-.023-.07-.026-.23-.026-1.43 0-1.468-.001-1.445.09-1.586.02-.032 1.703-1.724 3.74-3.759a596.805 596.805 0 003.7-3.716c0-.009-.367-.384-.816-.833a29.9 29.9 0 01-.817-.833c0-.01.474-.49 1.054-1.07l1.053-1.053.948.946.947.947 1.417-1.413C12.366.806 12.765.418 12.856.357c.238-.161.52-.28.792-.334.17-.034.586-.03.76.008.801.173 1.41.794 1.57 1.603.03.15.03.569 0 .718a2.227 2.227 0 01-.334.793c-.061.09-.45.49-1.496 1.54L12.734 6.1l.947.948.947.947-1.053 1.054c-.58.58-1.061 1.054-1.07 1.054-.01 0-.384-.368-.833-.817-.45-.45-.824-.817-.834-.817-.009 0-1.68 1.666-3.716 3.701a493.093 493.093 0 01-3.759 3.74c-.14.091-.117.09-1.59.089-1.187 0-1.366-.004-1.43-.027zm6.024-4.633a592.723 592.723 0 003.663-3.68c0-.02-1.67-1.69-1.69-1.69-.01 0-1.666 1.648-3.68 3.663L.996 13.297v.834c0 .627.005.839.02.854.015.014.227.02.854.02h.833l3.664-3.664z"
|
(defn make-curve-params
|
||||||
content (path->content path)
|
([point]
|
||||||
new-path (content->path content)
|
(make-curve-params point point point))
|
||||||
]
|
|
||||||
(prn "path" path)
|
([point handler] (make-curve-params point handler point))
|
||||||
(.log js/console "?? 1" (clj->js content))
|
|
||||||
(prn "?? 2" (= path new-path) new-path))
|
([point h1 h2]
|
||||||
|
{:x (:x point)
|
||||||
|
:y (:y point)
|
||||||
|
:c1x (:x h1)
|
||||||
|
:c1y (:y h1)
|
||||||
|
:c2x (:x h2)
|
||||||
|
:c2y (:y h2)}))
|
||||||
|
|
||||||
|
(defn opposite-handler
|
||||||
|
[point handler]
|
||||||
|
(let [phv (gpt/to-vec point handler)
|
||||||
|
opposite (gpt/add point (gpt/negate phv))]
|
||||||
|
opposite))
|
||||||
|
|
||||||
|
(defn extract-handlers [content]
|
||||||
|
(let [extract (fn [{param1 :params :as cmd1} {param2 :params :as cmd2}]
|
||||||
|
{:point (gpt/point (:x param1) (:y param1))
|
||||||
|
:prev (when (:c2x param1) (gpt/point (:c2x param1) (:c2y param1)))
|
||||||
|
:next (when (:c1x param2) (gpt/point (:c1x param2) (:c1y param2)))})]
|
||||||
|
(map extract content (d/concat [] (rest content) [nil]))))
|
||||||
|
|
Loading…
Add table
Reference in a new issue