0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-10 09:08:31 -05:00

Close paths and internals refactor

This commit is contained in:
alonso.torres 2020-11-23 22:10:12 +01:00
parent f339f1ee98
commit b66b0cb431
5 changed files with 431 additions and 294 deletions

View file

@ -17,9 +17,6 @@
[app.common.math :as mth]
[app.common.data :as d]))
(defn segments->points [segments]
segments)
(defn content->points [content]
(->> content
(map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y))))
@ -147,21 +144,6 @@
(mapv #(update % :params transform-params) content)))
(defn apply-content-modifiers [content modifiers]
(let [red-fn (fn [content [index params]]
(if (contains? content index)
(cond-> content
(:x params) (update-in [index :params :x] + (:x params))
(:y params) (update-in [index :params :y] + (:y params))
(:c1x params) (update-in [index :params :c1x] + (:c1x params))
(:c1y params) (update-in [index :params :c1y] + (:c1y params))
(:c2x params) (update-in [index :params :c2x] + (:c2x params))
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
content))]
(reduce red-fn content modifiers)))
(defn segments->content
([segments]
(segments->content segments false))

View file

@ -12,6 +12,7 @@
[beicon.core :as rx]
[potok.core :as ptk]
[app.common.math :as mth]
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.util.data :as ud]
@ -28,11 +29,16 @@
;; PRIVATE METHODS
(defn get-path-id [state]
(defn get-path-id
"Retrieves the currently editing path id"
[state]
(or (get-in state [:workspace-local :edition])
(get-in state [:workspace-drawing :object :id])))
(defn get-path [state & path]
(defn get-path
"Retrieves the location of the path object and additionaly can pass
the arguments. This location can be used in get-in, assoc-in... functions"
[state & path]
(let [edit-id (get-in state [:workspace-local :edition])
page-id (:current-page-id state)]
(cd/concat
@ -41,13 +47,9 @@
[:workspace-drawing :object])
path)))
(defn last-start-path [content]
(->> content
reverse
(cd/seek (fn [{cmd :command}] (= cmd :move-to)))
:params))
(defn update-selrect [shape]
(defn update-selrect
"Updates the selrect and points for a path"
[shape]
(let [selrect (gsh/content->selrect (:content shape))
points (gsh/rect->points selrect)]
(assoc shape :points points :selrect selrect)))
@ -56,8 +58,6 @@
"Calculates the next-node to be inserted."
[shape position prev-point prev-handler]
(let [last-command (-> shape :content last :command)
start-point (-> shape :content last-start-path)
add-line? (and prev-point (not prev-handler) (not= last-command :close-path))
add-curve? (and prev-point prev-handler (not= last-command :close-path))]
(cond
@ -76,53 +76,17 @@
(update :content (fnil conj []) command)
(update-selrect))))
(defn suffix-keyword
[kw suffix]
(let [strkw (if kw (name kw) "")]
(keyword (str strkw suffix))))
(defn move-handler-modifiers [content index prefix match-opposite? dx dy]
(let [[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
[ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y])
opposite-index (ugp/opposite-index content index prefix)]
(defn move-handler
[shape index handler-type match-opposite? position]
(let [content (:content shape)
[command next-command] (-> (ud/with-next content) (nth index))
(cond-> {}
:always
(update index assoc cx dx cy dy)
update-command
(fn [{cmd :command params :params :as command} param-prefix prev-command]
(if (#{:line-to :curve-to} cmd)
(let [command (if (= cmd :line-to)
{:command :curve-to
:params (ugp/make-curve-params params (:params prev-command))}
command)]
(-> command
(update :params assoc
(suffix-keyword param-prefix "x") (:x position)
(suffix-keyword param-prefix "y") (:y position))))
command))
update-content
(fn [shape index prefix]
(if (contains? (:content shape) index)
(let [prev-command (get-in shape [:content (dec index)])
content (-> shape :content (update index update-command prefix prev-command))]
(-> shape
(assoc :content content)
(update-selrect)))
shape))]
(cond-> shape
(= :prev handler-type)
(update-content index :c2)
(and (= :next handler-type) next-command)
(update-content (inc index) :c1)
match-opposite?
(move-handler
index
(if (= handler-type :prev) :next :prev)
false
(ugp/opposite-handler (gpt/point (:params command))
(gpt/point position))))))
(and match-opposite? opposite-index)
(update opposite-index assoc ocx (- dx) ocy (- dy)))))
(defn end-path-event? [{:keys [type shift] :as event}]
(or (= event ::end-path)
@ -175,29 +139,58 @@
(update-in [:workspace-local :edit-path id] dissoc :prev-handler)
(update-in (get-path state) append-node position last-point prev-handler))))))
(defn start-drag-handler []
(ptk/reify ::start-drag-handler
ptk/UpdateEvent
(update [_ state]
(let [content (get-in state (get-path state :content))
index (dec (count content))
command (get-in state (get-path state :content index :command))
make-curve
(fn [command]
(let [params (ugp/make-curve-params
(get-in content [index :params])
(get-in content [(dec index) :params]))]
(-> command
(assoc :command :curve-to :params params))))]
(cond-> state
(= command :line-to)
(update-in (get-path state :content index) make-curve))))))
(defn drag-handler [{:keys [x y]}]
(ptk/reify ::drag-handler
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)
position (gpt/point x y)
handler-position (gpt/point x y)
shape (get-in state (get-path state))
index (dec (count (:content shape)))]
content (:content shape)
index (dec (count content))
node-position (ugp/command->point (nth content index))
{dx :x dy :y} (gpt/subtract handler-position node-position)
match-opposite? true
modifiers (move-handler-modifiers content (inc index) :c1 match-opposite? dx dy)]
(-> state
(update-in (get-path state) move-handler index :next true position)
(assoc-in [:workspace-local :edit-path id :prev-handler] position)
(assoc-in [:workspace-local :edit-path id :drag-handler] position))))))
(assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers)
(assoc-in [:workspace-local :edit-path id :prev-handler] handler-position)
(assoc-in [:workspace-local :edit-path id :drag-handler] handler-position))))))
(defn finish-drag []
(ptk/reify ::finish-drag
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)
modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])
handler (get-in state [:workspace-local :edit-path id :drag-handler])]
(-> state
(update-in (get-path state :content) ugp/apply-content-modifiers modifiers)
(update-in [:workspace-local :edit-path id] dissoc :drag-handler)
(assoc-in [:workspace-local :edit-path id :prev-handler] handler))))
(update-in [:workspace-local :edit-path id] dissoc :content-modifiers)
(assoc-in [:workspace-local :edit-path id :prev-handler] handler)
(update-in (get-path state) update-selrect))))
ptk/WatchEvent
(watch [_ state stream]
@ -206,6 +199,76 @@
;; Update the preview because can be outdated after the dragging
(rx/of (preview-next-point handler))))))
(defn close-path [position]
(ptk/reify ::close-path
ptk/WatchEvent
(watch [_ state stream]
(rx/of (add-node position)
::end-path))))
(defn close-path-drag-start [position]
(ptk/reify ::close-path-drag-start
ptk/WatchEvent
(watch [_ state stream]
(let [zoom (get-in state [:workspace-local :zoom])
threshold (/ 5 zoom)
check-if-dragging
(fn [current-position]
(let [start (gpt/point position)
current (gpt/point current-position)]
(>= (gpt/distance start current) 100)))
stop-stream
(->> stream (rx/filter #(or (end-path-event? %)
(ms/mouse-up? %))))
position-stream
(->> ms/mouse-position
(rx/take-until stop-stream)
(rx/throttle 50))
drag-events-stream
(->> position-stream
(rx/map #(drag-handler %)))]
(rx/concat
(rx/of (close-path position))
(->> position-stream
(rx/filter check-if-dragging)
(rx/take 1)
(rx/merge-map
#(rx/concat
(rx/of (start-drag-handler))
drag-events-stream
(rx/of (finish-drag))))))))))
(defn close-path-drag-end [position]
(ptk/reify ::close-path-drag-end))
(defn path-pointer-enter [position]
(ptk/reify ::path-pointer-enter))
(defn path-pointer-leave [position]
(ptk/reify ::path-pointer-leave))
(defn start-path-from-point [position]
(ptk/reify ::start-path-from-point
ptk/WatchEvent
(watch [_ state stream]
(let [mouse-up (->> stream (rx/filter #(or (end-path-event? %)
(ms/mouse-up? %))))
drag-events (->> ms/mouse-position
(rx/take-until mouse-up)
(rx/map #(drag-handler %)))]
(rx/concat (rx/of (add-node position))
(rx/of (start-drag-handler))
drag-events
(rx/of (finish-drag))))
)))
;; EVENT STREAMS
(defn make-click-stream
@ -218,13 +281,15 @@
(defn make-drag-stream
[stream down-event]
(let [mouse-up (->> stream (rx/filter ms/mouse-up?))
(let [mouse-up (->> stream (rx/filter #(or (end-path-event? %)
(ms/mouse-up? %))))
drag-events (->> ms/mouse-position
(rx/take-until mouse-up)
(rx/map #(drag-handler %)))]
(->> (rx/timer 400)
(rx/merge-map #(rx/concat
(rx/of (add-node down-event))
(rx/of (start-drag-handler))
drag-events
(rx/of (finish-drag)))))))
@ -237,14 +302,27 @@
#(rx/of (add-node down-event)
::end-path))))
(defn make-node-events-stream
[stream]
(->> (rx/merge
(->> stream (rx/filter (ptk/type? ::close-path)))
(->> stream (rx/filter (ptk/type? ::close-path-drag-start))))
(rx/take 1)
(rx/merge-map #(rx/empty))))
;; MAIN ENTRIES
(defn handle-drawing-path
[id]
(ptk/reify ::handle-drawing-path
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)]
(-> state
(assoc-in [:workspace-local :edit-path id :edit-mode] :draw))))
ptk/WatchEvent
(watch [_ state stream]
(let [mouse-down (->> stream (rx/filter ms/mouse-down?))
end-path-events (->> stream (rx/filter end-path-event?))
@ -264,7 +342,8 @@
;; We change to the stream that emits the first event
(rx/switch-map
#(rx/race (make-click-stream stream %)
#(rx/race (make-node-events-stream stream)
(make-click-stream stream %)
(make-drag-stream stream %)
(make-dbl-click-stream stream %))))]
@ -276,67 +355,6 @@
#_(def handle-drawing-path
(ptk/reify ::handle-drawing-path
ptk/WatchEvent
(watch [_ state stream]
(let [{:keys [flags]} (:workspace-local state)
last-point (volatile! @ms/mouse-position)
stoper (->> (rx/filter stoper-event? stream)
(rx/share))
mouse (rx/sample 10 ms/mouse-position)
points (->> stream
(rx/filter ms/mouse-click?)
(rx/filter #(false? (:shift %)))
(rx/with-latest vector mouse)
(rx/map second))
counter (rx/merge (rx/scan #(inc %) 1 points) (rx/of 1))
stream' (->> mouse
(rx/with-latest vector ms/mouse-position-ctrl)
(rx/with-latest vector counter)
(rx/map flatten))
imm-transform #(vector (- % 7) (+ % 7) %)
immanted-zones (vec (concat
(map imm-transform (range 0 181 15))
(map (comp imm-transform -) (range 0 181 15))))
align-position (fn [angle pos]
(reduce (fn [pos [a1 a2 v]]
(if (< a1 angle a2)
(reduced (gpt/update-angle pos v))
pos))
pos
immanted-zones))]
(rx/merge
(rx/of #(initialize-drawing % @last-point))
(->> points
(rx/take-until stoper)
(rx/map (fn [pt] #(insert-point-segment % pt))))
(rx/concat
(->> stream'
(rx/take-until stoper)
(rx/map (fn [[point ctrl? index :as xxx]]
(let [point (if ctrl?
(as-> point $
(gpt/subtract $ @last-point)
(align-position (gpt/angle $) $)
(gpt/add $ @last-point))
point)]
#(update-point-segment % index point)))))
(rx/of finish-drawing-path
common/handle-finish-drawing)))))))
(defn stop-path-edit []
(ptk/reify ::stop-path-edit
ptk/UpdateEvent
@ -363,12 +381,12 @@
(rx/take 1)
(rx/map #(stop-path-edit))))))
(defn modify-point [index dx dy]
(defn modify-point [index prefix dx dy]
(ptk/reify ::modify-point
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace-local :edition])]
(let [id (get-in state [:workspace-local :edition])
[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])]
(-> state
(update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc
:c1x dx :c1y dy)
@ -376,19 +394,22 @@
:x dx :y dy :c2x dx :c2y dy)
)))))
(defn modify-handler [index type dx dy]
(defn modify-handler [id index prefix dx dy match-opposite?]
(ptk/reify ::modify-point
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace-local :edition])]
(let [s1 (if (= type :prev) -1 1)
s2 (if (= type :prev) 1 -1)]
(-> state
(update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc
:c1x (* s1 dx) :c1y (* s1 dy))
(update-in [:workspace-local :edit-path id :content-modifiers index] assoc
:c2x (* s2 dx) :c2y (* s2 dy) ))
)))))
(let [content (get-in state (get-path state :content))
[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
[ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y])
opposite-index (ugp/opposite-index content index prefix)]
(cond-> state
:always
(update-in [:workspace-local :edit-path id :content-modifiers index] assoc
cx dx cy dy)
(and match-opposite? opposite-index)
(update-in [:workspace-local :edit-path id :content-modifiers opposite-index] assoc
ocx (- dx) ocy (- dy)))))))
(defn apply-content-modifiers []
(ptk/reify ::apply-content-modifiers
@ -400,7 +421,7 @@
old-selrect (get-in state [:workspace-data :pages-index page-id :objects id :selrect])
old-points (get-in state [:workspace-data :pages-index page-id :objects id :points])
content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])
new-content (gsp/apply-content-modifiers old-content content-modifiers)
new-content (ugp/apply-content-modifiers old-content content-modifiers)
new-selrect (gsh/content->selrect new-content)
new-points (gsh/rect->points new-selrect)
rch [{:type :mod-obj
@ -478,46 +499,82 @@
(= mode :draw) (rx/of :interrupt)
:else (rx/of (finish-path id)))))))
(defn move-path-point [start-point end-point]
(ptk/reify ::move-point
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)
content (get-in state (get-path state :content))
{dx :x dy :y} (gpt/subtract end-point start-point)
handler-indices (-> (ugp/content->handlers content)
(get start-point))
command-for-point (fn [[index command]]
(let [point (ugp/command->point command)]
(= point start-point)))
point-indices (->> (d/enumerate content)
(filter command-for-point)
(map first))
point-reducer (fn [modifiers index]
(-> modifiers
(assoc-in [index :x] dx)
(assoc-in [index :y] dy)))
handler-reducer (fn [modifiers [index prefix]]
(let [cx (ud/prefix-keyword prefix :x)
cy (ud/prefix-keyword prefix :y)]
(-> modifiers
(assoc-in [index cx] dx)
(assoc-in [index cy] dy))))
modifiers (as-> (get-in state [:workspace-local :edit-path id :content-modifiers] {}) $
(reduce point-reducer $ point-indices)
(reduce handler-reducer $ handler-indices))]
(assoc-in state [:workspace-local :edit-path id :content-modifiers] modifiers)))))
(defn start-move-path-point
[index]
[position]
(ptk/reify ::start-move-path-point
ptk/WatchEvent
;; TODO REWRITE
(watch [_ state stream]
(let [id (get-in state [:workspace-local :edition])
start-point @ms/mouse-position
start-delta-x (get-in state [:workspace-local :edit-path id :content-modifiers index :x] 0)
start-delta-y (get-in state [:workspace-local :edit-path id :content-modifiers index :y] 0)]
(let [stopper (->> stream (rx/filter ms/mouse-up?))]
(rx/concat
(->> ms/mouse-position
(rx/take-until (->> stream (rx/filter ms/mouse-up?)))
(rx/map #(modify-point
index
(+ start-delta-x (- (:x %) (:x start-point)))
(+ start-delta-y (- (:y %) (:y start-point))))))
(rx/concat (rx/of (apply-content-modifiers)))
)))))
(rx/take-until stopper)
(rx/map #(move-path-point position %)))
(rx/of (apply-content-modifiers)))))))
(defn start-move-handler
[index type]
[index prefix]
(ptk/reify ::start-move-handler
ptk/WatchEvent
(watch [_ state stream]
(let [id (get-in state [:workspace-local :edition])
[cx cy] (if (= :prev type) [:c2x :c2y] [:c1x :c1y])
cidx (if (= :prev type) index (inc index))
[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
start-point @ms/mouse-position
start-delta-x (get-in state [:workspace-local :edit-path id :content-modifiers cidx cx] 0)
start-delta-y (get-in state [:workspace-local :edit-path id :content-modifiers cidx cy] 0)]
start-delta-x (get-in state [:workspace-local :edit-path id :content-modifiers index cx] 0)
start-delta-y (get-in state [:workspace-local :edit-path id :content-modifiers index cy] 0)]
(rx/concat
(->> ms/mouse-position
(rx/take-until (->> stream (rx/filter ms/mouse-up?)))
(rx/map #(modify-handler
index
type
(+ start-delta-x (- (:x %) (:x start-point)))
(+ start-delta-y (- (:y %) (:y start-point)))))
(rx/with-latest vector ms/mouse-position-alt)
(rx/map
(fn [[pos alt?]]
(modify-handler
id
index
prefix
(+ start-delta-x (- (:x pos) (:x start-point)))
(+ start-delta-y (- (:y pos) (:y start-point)))
(not alt?))))
)
(rx/concat (rx/of (apply-content-modifiers))))))))
@ -568,13 +625,21 @@
(-> state
(update-in [:workspace-local :edit-path id :selected] (fnil conj #{}) [index type]))))))
(defn select-node [index]
(defn select-node [position]
(ptk/reify ::select-node
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace-local :edition])]
(-> state
(update-in [:workspace-local :edit-path id :selected] (fnil conj #{}) index))))))
(update-in [:workspace-local :edit-path id :selected-node] (fnil conj #{}) position))))))
(defn deselect-node [position]
(ptk/reify ::deselect-node
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace-local :edition])]
(-> state
(update-in [:workspace-local :edit-path id :selected-node] (fnil disj #{}) position))))))
(defn add-to-selection-handler [index type]
(ptk/reify ::add-to-selection-handler
@ -629,5 +694,4 @@
(rx/filter (ptk/type? ::finish-path))
(rx/take 1)
(rx/observe-on :async)
(rx/map #(handle-new-shape-result shape-id)))
)))))
(rx/map #(handle-new-shape-result shape-id))))))))

View file

@ -29,7 +29,6 @@
[app.main.ui.workspace.shapes.common :as common]
[app.util.geom.path :as ugp]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as gsp]
[app.main.ui.cursors :as cur]
[app.main.ui.icons :as i]))
@ -39,8 +38,6 @@
(def white-color "#FFFFFF")
(def gray-color "#B1B2B5")
(def current-edit-path-ref
(let [selfn (fn [local]
(let [id (:edition local)]
@ -85,7 +82,7 @@
content-modifiers (mf/deref content-modifiers-ref)
editing-id (mf/deref refs/selected-edition)
editing? (= editing-id (:id shape))
shape (update shape :content gsp/apply-content-modifiers content-modifiers)]
shape (update shape :content ugp/apply-content-modifiers content-modifiers)]
[:> shape-container {:shape shape
:pointer-events (when editing? "none")
@ -122,69 +119,83 @@
[:div.viewport-actions-entry {:class (when snap-toggled "is-toggled")} i/nodes-snap]]]))
(mf/defc path-preview [{:keys [zoom command from]}]
(when (not= :move-to (:command command))
[:path {:style {:fill "transparent"
:stroke secondary-color
:stroke-width (/ 1 zoom)}
:d (ugp/content->path [{:command :move-to
:params {:x (:x from)
:y (:y from)}}
command])}]))
(mf/defc path-point [{:keys [index position stroke-color fill-color zoom edit-mode selected]}]
(mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path?]}]
(let [{:keys [x y]} position
on-click (fn [event]
(cond
(= edit-mode :move)
(do
(dom/stop-propagation event)
(dom/prevent-default event)
(st/emit! (drp/select-node index)))))
on-mouse-down (fn [event]
(cond
(= edit-mode :move)
(do
(dom/stop-propagation event)
(dom/prevent-default event)
(st/emit! (drp/start-move-path-point index)))))]
on-enter
(fn [event]
(st/emit! (drp/path-pointer-enter position)))
on-leave
(fn [event]
(st/emit! (drp/path-pointer-leave position)))
on-click
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(cond
(and (= edit-mode :move) (not selected?))
(st/emit! (drp/select-node position))
(and (= edit-mode :move) selected?)
(st/emit! (drp/deselect-node position))))
on-mouse-down
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(cond
(= edit-mode :move)
(st/emit! (drp/start-move-path-point position))
(and (= edit-mode :draw) start-path?)
(st/emit! (drp/start-path-from-point position))
(and (= edit-mode :draw) (not start-path?))
(st/emit! (drp/close-path-drag-start position))))]
[:g.path-point
[:circle.path-point
{:cx x
:cy y
:r (/ 3 zoom)
:style { ;; :cursor cur/resize-alt
:style {:cursor (when (= edit-mode :draw) cur/pen-node)
:stroke-width (/ 1 zoom)
:stroke (or stroke-color black-color)
:fill (or fill-color white-color)}}]
:stroke (cond (or selected? hover?) black-color
preview? secondary-color
:else primary-color)
:fill (cond selected? primary-color
:else white-color)}}]
[:circle {:cx x
:cy y
:r (/ 10 zoom)
:on-click on-click
:on-mouse-down on-mouse-down
:style {:fill "transparent"}}]]
))
:style {:fill "transparent"}}]]))
(mf/defc path-handler [{:keys [index point handler zoom selected type edit-mode]}]
(mf/defc path-handler [{:keys [index prefix point handler zoom selected? hover? edit-mode]}]
(when (and point handler)
(let [{:keys [x y]} handler
on-click (fn [event]
(cond
(= edit-mode :move)
(do
(dom/stop-propagation event)
(dom/prevent-default event)
(drp/select-handler index type))))
on-click
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(cond
(= edit-mode :move)
(drp/select-handler index prefix)))
on-mouse-down (fn [event]
(cond
(= edit-mode :move)
(do
(dom/stop-propagation event)
(dom/prevent-default event)
(st/emit! (drp/start-move-handler index type)))))]
[:g.handler {:class (name type)}
on-mouse-down
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(cond
(= edit-mode :move)
(st/emit! (drp/start-move-handler index prefix))))]
[:g.handler {:pointer-events (when (= edit-mode :draw))}
[:line
{:x1 (:x point)
:y1 (:y point)
@ -198,11 +209,12 @@
:width (/ 6 zoom)
:height (/ 6 zoom)
:style {;; :cursor cur/resize-alt
:style {:cursor cur/pointer-move
:stroke-width (/ 1 zoom)
:stroke (if selected black-color primary-color)
:fill (if selected primary-color white-color)}}]
:stroke (cond (or selected? hover?) black-color
:else primary-color)
:fill (cond selected? primary-color
:else white-color)}}]
[:circle {:cx x
:cy y
:r (/ 10 zoom)
@ -210,76 +222,78 @@
:on-mouse-down on-mouse-down
:style {:fill "transparent"}}]])))
(mf/defc path-preview [{:keys [zoom command from]}]
[:g.preview {:style {:pointer-events "none"}}
(when (not= :move-to (:command command))
[:path {:style {:fill "transparent"
:stroke secondary-color
:stroke-width (/ 1 zoom)}
:d (ugp/content->path [{:command :move-to
:params {:x (:x from)
:y (:y from)}}
command])}])
[:& path-point {:position (:params command)
:preview? true
:zoom zoom}]])
(mf/defc path-editor
[{:keys [shape zoom]}]
(let [{:keys [content]} shape
edit-path-ref (make-edit-path-ref (:id shape))
{:keys [edit-mode selected drag-handler prev-handler preview content-modifiers]} (mf/deref edit-path-ref)
(let [edit-path-ref (make-edit-path-ref (:id shape))
{:keys [edit-mode selected drag-handler prev-handler preview content-modifiers last-point]} (mf/deref edit-path-ref)
{:keys [content]} shape
selected (or selected #{})
content (gsp/apply-content-modifiers content content-modifiers)
points (gsp/content->points content)
content (ugp/apply-content-modifiers content content-modifiers)
points (->> content ugp/content->points (into #{}))
last-command (last content)
last-p (last points)]
last-p (->> content last ugp/command->point)
handlers (ugp/content->handlers content)]
[:g.path-editor
(when (and preview (not drag-handler))
[:g.preview {:style {:pointer-events "none"}}
[:& path-preview {:command preview
:from last-p
:zoom zoom}]
[:& path-point {:position (:params preview)
:fill-color secondary-color
:zoom zoom}]])
[:& path-preview {:command preview
:from last-p
:zoom zoom}])
(for [[index [cmd next]] (d/enumerate (d/with-next content))]
(let [point (gpt/point (:params cmd))]
[:g.path-node
(when (= :curve-to (:command cmd))
[:& path-handler {:point point
:handler (gpt/point (-> cmd :params :c2x) (-> cmd :params :c2y))
:zoom zoom
:type :prev
:index index
:selected (selected [index :prev])
:edit-mode edit-mode}])
(for [position points]
[:g.path-node
[:& path-point {:position position
:selected? false
:zoom zoom
:edit-mode edit-mode
:start-path? (nil? last-point)}]
(when (= :curve-to (:command next))
[:& path-handler {:point point
:handler (gpt/point (-> next :params :c1x) (-> next :params :c1y))
:zoom zoom
:type :next
:index index
:selected (selected [index :next])
:edit-mode edit-mode}])
[:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
(for [[index prefix] (get handlers position)]
(let [command (get content index)
x (get-in command [:params (d/prefix-keyword prefix :x)])
y (get-in command [:params (d/prefix-keyword prefix :y)])
handler-position (gpt/point x y)]
[:& path-handler {:point position
:handler handler-position
:index index
:prefix prefix
:zoom zoom
:selected? false
:hover? false
:preview? false
:edit-mode edit-mode}]))]])
(when (and (= index (dec (count content)))
prev-handler (not drag-handler))
[:& path-handler {:point point
:handler prev-handler
:zoom zoom
:type :prev
:index index
:selected (selected index)
:edit-mode edit-mode}])
[:& path-point {:position point
:stroke-color (when-not (selected index) primary-color)
:fill-color (when (selected index) primary-color)
:index index
(when prev-handler
[:g.prev-handler {:pointer-events "none"}
[:& path-handler {:point last-p
:handler prev-handler
:zoom zoom
:edit-mode edit-mode}]]))
:selected false}]])
(when drag-handler
[:g.drag-handler
[:g.drag-handler {:pointer-events "none"}
(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}]])]))

View file

@ -248,4 +248,7 @@
;; nil
;; (throw e#)))))))
(defn prefix-keyword [prefix kw]
(let [prefix (if (keyword? prefix) (name prefix) prefix)
kw (if (keyword? kw) (name kw) kw)]
(keyword (str prefix kw))))

View file

@ -10,7 +10,8 @@
(ns app.util.geom.path
(:require
[cuerdas.core :as str]
[app.common.data :as d]
[app.util.data :as d]
[app.common.data :as cd]
[app.common.geom.point :as gpt]
[app.util.geom.path-impl-simplify :as impl-simplify]))
@ -208,8 +209,81 @@
:c2y (:y h2)}))
(defn opposite-handler
"Calculates the coordinates of the opposite handler"
[point handler]
(let [phv (gpt/to-vec point handler)
opposite (gpt/add point (gpt/negate phv))]
opposite))
(let [phv (gpt/to-vec point handler)]
(gpt/add point (gpt/negate phv))))
(defn opposite-handler-keep-distance
"Calculates the coordinates of the opposite handler but keeping the old distance"
[point handler old-opposite]
(let [old-distance (gpt/distance point old-opposite)
phv (gpt/to-vec point handler)
phv2 (gpt/multiply
(gpt/unit (gpt/negate phv))
(gpt/point old-distance))]
(gpt/add point phv2)))
(defn apply-content-modifiers [content modifiers]
(letfn [(apply-to-index [content [index params]]
(if (contains? content index)
(cond-> content
(and
(or (:c1x params) (:c1y params) (:c2x params) (:c2y params))
(= :line-to (get-in content [index :params :command])))
(-> (assoc-in [index :command] :curve-to)
(assoc-in [index :params] :curve-to) (make-curve-params
(get-in content [index :params])
(get-in content [(dec index) :params])))
(:x params) (update-in [index :params :x] + (:x params))
(:y params) (update-in [index :params :y] + (:y params))
(:c1x params) (update-in [index :params :c1x] + (:c1x params))
(:c1y params) (update-in [index :params :c1y] + (:c1y params))
(:c2x params) (update-in [index :params :c2x] + (:c2x params))
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
content))]
(reduce apply-to-index content modifiers)))
(defn command->point [{{:keys [x y]} :params}]
(gpt/point x y))
(defn content->points [content]
(->> content
(map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y))))
(remove nil?)
(into [])))
(defn content->handlers [content]
(->> (d/with-prev content) ;; [cmd, prev]
(d/enumerate) ;; [idx [cmd, prev]]
(mapcat (fn [[index [cur-cmd prev-cmd]]]
(if (and prev-cmd
(= :curve-to (:command cur-cmd)))
(let [cur-pos (command->point cur-cmd)
pre-pos (command->point prev-cmd)]
[[pre-pos [index :c1]]
[cur-pos [index :c2]]])
[])))
(group-by first)
(cd/mapm #(mapv second %2))))
(defn opposite-index [content index prefix]
(let [point (if (= prefix :c2)
(command->point (nth content index))
(command->point (nth content (dec index))))
handlers (-> (content->handlers content)
(get point))
opposite-prefix (if (= prefix :c1) :c2 :c1)
result (when (<= (count handlers) 2)
(->> handlers
(d/seek (fn [[index prefix]] (= prefix opposite-prefix)))
(first)))]
result))