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.math :as mth]
[app.common.data :as d])) [app.common.data :as d]))
(defn segments->points [segments]
segments)
(defn content->points [content] (defn content->points [content]
(->> content (->> content
(map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y)))) (map #(when (-> % :params :x) (gpt/point (-> % :params :x) (-> % :params :y))))
@ -147,21 +144,6 @@
(mapv #(update % :params transform-params) content))) (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 (defn segments->content
([segments] ([segments]
(segments->content segments false)) (segments->content segments false))

View file

@ -12,6 +12,7 @@
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk] [potok.core :as ptk]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.data :as d]
[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.util.data :as ud] [app.util.data :as ud]
@ -28,11 +29,16 @@
;; PRIVATE METHODS ;; 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]) (or (get-in state [:workspace-local :edition])
(get-in state [:workspace-drawing :object :id]))) (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]) (let [edit-id (get-in state [:workspace-local :edition])
page-id (:current-page-id state)] page-id (:current-page-id state)]
(cd/concat (cd/concat
@ -41,13 +47,9 @@
[:workspace-drawing :object]) [:workspace-drawing :object])
path))) path)))
(defn last-start-path [content] (defn update-selrect
(->> content "Updates the selrect and points for a path"
reverse [shape]
(cd/seek (fn [{cmd :command}] (= cmd :move-to)))
:params))
(defn update-selrect [shape]
(let [selrect (gsh/content->selrect (:content shape)) (let [selrect (gsh/content->selrect (:content shape))
points (gsh/rect->points selrect)] points (gsh/rect->points selrect)]
(assoc shape :points points :selrect selrect))) (assoc shape :points points :selrect selrect)))
@ -56,8 +58,6 @@
"Calculates the next-node to be inserted." "Calculates the next-node to be inserted."
[shape position prev-point prev-handler] [shape position prev-point prev-handler]
(let [last-command (-> shape :content last :command) (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-line? (and prev-point (not prev-handler) (not= last-command :close-path))
add-curve? (and prev-point prev-handler (not= last-command :close-path))] add-curve? (and prev-point prev-handler (not= last-command :close-path))]
(cond (cond
@ -76,53 +76,17 @@
(update :content (fnil conj []) command) (update :content (fnil conj []) command)
(update-selrect)))) (update-selrect))))
(defn suffix-keyword (defn move-handler-modifiers [content index prefix match-opposite? dx dy]
[kw suffix] (let [[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
(let [strkw (if kw (name kw) "")] [ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y])
(keyword (str strkw suffix)))) opposite-index (ugp/opposite-index content index prefix)]
(defn move-handler (cond-> {}
[shape index handler-type match-opposite? position] :always
(let [content (:content shape) (update index assoc cx dx cy dy)
[command next-command] (-> (ud/with-next content) (nth index))
update-command (and match-opposite? opposite-index)
(fn [{cmd :command params :params :as command} param-prefix prev-command] (update opposite-index assoc ocx (- dx) ocy (- dy)))))
(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))))))
(defn end-path-event? [{:keys [type shift] :as event}] (defn end-path-event? [{:keys [type shift] :as event}]
(or (= event ::end-path) (or (= event ::end-path)
@ -175,29 +139,58 @@
(update-in [:workspace-local :edit-path id] dissoc :prev-handler) (update-in [:workspace-local :edit-path id] dissoc :prev-handler)
(update-in (get-path state) append-node position last-point 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]}] (defn drag-handler [{:keys [x y]}]
(ptk/reify ::drag-handler (ptk/reify ::drag-handler
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [id (get-path-id 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)) 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 (-> state
(update-in (get-path state) move-handler index :next true position) (assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers)
(assoc-in [:workspace-local :edit-path id :prev-handler] position) (assoc-in [:workspace-local :edit-path id :prev-handler] handler-position)
(assoc-in [:workspace-local :edit-path id :drag-handler] position)))))) (assoc-in [:workspace-local :edit-path id :drag-handler] handler-position))))))
(defn finish-drag [] (defn finish-drag []
(ptk/reify ::finish-drag (ptk/reify ::finish-drag
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [id (get-path-id 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])] handler (get-in state [:workspace-local :edit-path id :drag-handler])]
(-> state (-> state
(update-in (get-path state :content) ugp/apply-content-modifiers modifiers)
(update-in [:workspace-local :edit-path id] dissoc :drag-handler) (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 ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -206,6 +199,76 @@
;; Update the preview because can be outdated after the dragging ;; Update the preview because can be outdated after the dragging
(rx/of (preview-next-point handler)))))) (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 ;; EVENT STREAMS
(defn make-click-stream (defn make-click-stream
@ -218,13 +281,15 @@
(defn make-drag-stream (defn make-drag-stream
[stream down-event] [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 drag-events (->> ms/mouse-position
(rx/take-until mouse-up) (rx/take-until mouse-up)
(rx/map #(drag-handler %)))] (rx/map #(drag-handler %)))]
(->> (rx/timer 400) (->> (rx/timer 400)
(rx/merge-map #(rx/concat (rx/merge-map #(rx/concat
(rx/of (add-node down-event)) (rx/of (add-node down-event))
(rx/of (start-drag-handler))
drag-events drag-events
(rx/of (finish-drag))))))) (rx/of (finish-drag)))))))
@ -237,14 +302,27 @@
#(rx/of (add-node down-event) #(rx/of (add-node down-event)
::end-path)))) ::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 ;; MAIN ENTRIES
(defn handle-drawing-path (defn handle-drawing-path
[id] [id]
(ptk/reify ::handle-drawing-path (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 ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [mouse-down (->> stream (rx/filter ms/mouse-down?)) (let [mouse-down (->> stream (rx/filter ms/mouse-down?))
end-path-events (->> stream (rx/filter end-path-event?)) end-path-events (->> stream (rx/filter end-path-event?))
@ -264,7 +342,8 @@
;; We change to the stream that emits the first event ;; We change to the stream that emits the first event
(rx/switch-map (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-drag-stream stream %)
(make-dbl-click-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 [] (defn stop-path-edit []
(ptk/reify ::stop-path-edit (ptk/reify ::stop-path-edit
ptk/UpdateEvent ptk/UpdateEvent
@ -363,12 +381,12 @@
(rx/take 1) (rx/take 1)
(rx/map #(stop-path-edit)))))) (rx/map #(stop-path-edit))))))
(defn modify-point [index dx dy] (defn modify-point [index prefix dx dy]
(ptk/reify ::modify-point (ptk/reify ::modify-point
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (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 (-> state
(update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc (update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc
:c1x dx :c1y dy) :c1x dx :c1y dy)
@ -376,19 +394,22 @@
:x dx :y dy :c2x dx :c2y dy) :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/reify ::modify-point
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [id (get-in state [:workspace-local :edition])] (let [content (get-in state (get-path state :content))
(let [s1 (if (= type :prev) -1 1) [cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
s2 (if (= type :prev) 1 -1)] [ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y])
(-> state opposite-index (ugp/opposite-index content index prefix)]
(update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc (cond-> state
:c1x (* s1 dx) :c1y (* s1 dy)) :always
(update-in [:workspace-local :edit-path id :content-modifiers index] assoc (update-in [:workspace-local :edit-path id :content-modifiers index] assoc
:c2x (* s2 dx) :c2y (* s2 dy) )) 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 [] (defn apply-content-modifiers []
(ptk/reify ::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-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]) 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]) 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-selrect (gsh/content->selrect new-content)
new-points (gsh/rect->points new-selrect) new-points (gsh/rect->points new-selrect)
rch [{:type :mod-obj rch [{:type :mod-obj
@ -478,46 +499,82 @@
(= mode :draw) (rx/of :interrupt) (= mode :draw) (rx/of :interrupt)
:else (rx/of (finish-path id))))))) :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 (defn start-move-path-point
[index] [position]
(ptk/reify ::start-move-path-point (ptk/reify ::start-move-path-point
ptk/WatchEvent ptk/WatchEvent
;; TODO REWRITE
(watch [_ state stream] (watch [_ state stream]
(let [id (get-in state [:workspace-local :edition]) (let [stopper (->> stream (rx/filter ms/mouse-up?))]
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)]
(rx/concat (rx/concat
(->> ms/mouse-position (->> ms/mouse-position
(rx/take-until (->> stream (rx/filter ms/mouse-up?))) (rx/take-until stopper)
(rx/map #(modify-point (rx/map #(move-path-point position %)))
index (rx/of (apply-content-modifiers)))))))
(+ start-delta-x (- (:x %) (:x start-point)))
(+ start-delta-y (- (:y %) (:y start-point))))))
(rx/concat (rx/of (apply-content-modifiers)))
)))))
(defn start-move-handler (defn start-move-handler
[index type] [index prefix]
(ptk/reify ::start-move-handler (ptk/reify ::start-move-handler
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [id (get-in state [:workspace-local :edition]) (let [id (get-in state [:workspace-local :edition])
[cx cy] (if (= :prev type) [:c2x :c2y] [:c1x :c1y]) [cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
cidx (if (= :prev type) index (inc index))
start-point @ms/mouse-position start-point @ms/mouse-position
start-delta-x (get-in state [:workspace-local :edit-path id :content-modifiers cidx cx] 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 cidx cy] 0)] start-delta-y (get-in state [:workspace-local :edit-path id :content-modifiers index cy] 0)]
(rx/concat (rx/concat
(->> ms/mouse-position (->> ms/mouse-position
(rx/take-until (->> stream (rx/filter ms/mouse-up?))) (rx/take-until (->> stream (rx/filter ms/mouse-up?)))
(rx/map #(modify-handler (rx/with-latest vector ms/mouse-position-alt)
index (rx/map
type (fn [[pos alt?]]
(+ start-delta-x (- (:x %) (:x start-point))) (modify-handler
(+ start-delta-y (- (:y %) (:y start-point))))) 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)))))))) (rx/concat (rx/of (apply-content-modifiers))))))))
@ -568,13 +625,21 @@
(-> state (-> state
(update-in [:workspace-local :edit-path id :selected] (fnil conj #{}) [index type])))))) (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/reify ::select-node
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [id (get-in state [:workspace-local :edition])] (let [id (get-in state [:workspace-local :edition])]
(-> state (-> 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] (defn add-to-selection-handler [index type]
(ptk/reify ::add-to-selection-handler (ptk/reify ::add-to-selection-handler
@ -629,5 +694,4 @@
(rx/filter (ptk/type? ::finish-path)) (rx/filter (ptk/type? ::finish-path))
(rx/take 1) (rx/take 1)
(rx/observe-on :async) (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.main.ui.workspace.shapes.common :as common]
[app.util.geom.path :as ugp] [app.util.geom.path :as ugp]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as gsp]
[app.main.ui.cursors :as cur] [app.main.ui.cursors :as cur]
[app.main.ui.icons :as i])) [app.main.ui.icons :as i]))
@ -39,8 +38,6 @@
(def white-color "#FFFFFF") (def white-color "#FFFFFF")
(def gray-color "#B1B2B5") (def gray-color "#B1B2B5")
(def current-edit-path-ref (def current-edit-path-ref
(let [selfn (fn [local] (let [selfn (fn [local]
(let [id (:edition local)] (let [id (:edition local)]
@ -85,7 +82,7 @@
content-modifiers (mf/deref content-modifiers-ref) content-modifiers (mf/deref content-modifiers-ref)
editing-id (mf/deref refs/selected-edition) editing-id (mf/deref refs/selected-edition)
editing? (= editing-id (:id shape)) 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 [:> shape-container {:shape shape
:pointer-events (when editing? "none") :pointer-events (when editing? "none")
@ -122,69 +119,83 @@
[:div.viewport-actions-entry {:class (when snap-toggled "is-toggled")} i/nodes-snap]]])) [:div.viewport-actions-entry {:class (when snap-toggled "is-toggled")} i/nodes-snap]]]))
(mf/defc path-preview [{:keys [zoom command from]}] (mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path?]}]
(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]}]
(let [{:keys [x y]} position (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] on-enter
(cond (fn [event]
(= edit-mode :move) (st/emit! (drp/path-pointer-enter position)))
(do
(dom/stop-propagation event) on-leave
(dom/prevent-default event) (fn [event]
(st/emit! (drp/start-move-path-point index)))))] (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 [:g.path-point
[:circle.path-point [:circle.path-point
{:cx x {:cx x
:cy y :cy y
:r (/ 3 zoom) :r (/ 3 zoom)
:style { ;; :cursor cur/resize-alt :style {:cursor (when (= edit-mode :draw) cur/pen-node)
:stroke-width (/ 1 zoom) :stroke-width (/ 1 zoom)
:stroke (or stroke-color black-color) :stroke (cond (or selected? hover?) black-color
:fill (or fill-color white-color)}}] preview? secondary-color
:else primary-color)
:fill (cond selected? primary-color
:else white-color)}}]
[:circle {:cx x [:circle {:cx x
:cy y :cy y
:r (/ 10 zoom) :r (/ 10 zoom)
:on-click on-click :on-click on-click
:on-mouse-down on-mouse-down :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) (when (and point handler)
(let [{:keys [x y]} handler (let [{:keys [x y]} handler
on-click (fn [event] on-click
(cond (fn [event]
(= edit-mode :move) (dom/stop-propagation event)
(do (dom/prevent-default event)
(dom/stop-propagation event) (cond
(dom/prevent-default event) (= edit-mode :move)
(drp/select-handler index type)))) (drp/select-handler index prefix)))
on-mouse-down (fn [event] on-mouse-down
(cond (fn [event]
(= edit-mode :move) (dom/stop-propagation event)
(do (dom/prevent-default event)
(dom/stop-propagation event)
(dom/prevent-default event) (cond
(st/emit! (drp/start-move-handler index type)))))] (= edit-mode :move)
[:g.handler {:class (name type)} (st/emit! (drp/start-move-handler index prefix))))]
[:g.handler {:pointer-events (when (= edit-mode :draw))}
[:line [:line
{:x1 (:x point) {:x1 (:x point)
:y1 (:y point) :y1 (:y point)
@ -198,11 +209,12 @@
:width (/ 6 zoom) :width (/ 6 zoom)
:height (/ 6 zoom) :height (/ 6 zoom)
:style {;; :cursor cur/resize-alt :style {:cursor cur/pointer-move
:stroke-width (/ 1 zoom) :stroke-width (/ 1 zoom)
:stroke (if selected black-color primary-color) :stroke (cond (or selected? hover?) black-color
:fill (if selected primary-color white-color)}}] :else primary-color)
:fill (cond selected? primary-color
:else white-color)}}]
[:circle {:cx x [:circle {:cx x
:cy y :cy y
:r (/ 10 zoom) :r (/ 10 zoom)
@ -210,76 +222,78 @@
:on-mouse-down on-mouse-down :on-mouse-down on-mouse-down
:style {:fill "transparent"}}]]))) :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 (mf/defc path-editor
[{:keys [shape zoom]}] [{:keys [shape zoom]}]
(let [{:keys [content]} shape (let [edit-path-ref (make-edit-path-ref (:id shape))
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 [edit-mode selected drag-handler prev-handler preview content-modifiers]} (mf/deref edit-path-ref) {:keys [content]} shape
selected (or selected #{}) selected (or selected #{})
content (gsp/apply-content-modifiers content content-modifiers) content (ugp/apply-content-modifiers content content-modifiers)
points (gsp/content->points content) points (->> content ugp/content->points (into #{}))
last-command (last content) last-command (last content)
last-p (last points)] last-p (->> content last ugp/command->point)
handlers (ugp/content->handlers content)]
[:g.path-editor [:g.path-editor
(when (and preview (not drag-handler)) (when (and preview (not drag-handler))
[:g.preview {:style {:pointer-events "none"}} [:& path-preview {:command preview
[:& path-preview {:command preview :from last-p
:from last-p :zoom zoom}])
:zoom zoom}]
[:& path-point {:position (:params preview)
:fill-color secondary-color
:zoom zoom}]])
(for [[index [cmd next]] (d/enumerate (d/with-next content))] (for [position points]
(let [point (gpt/point (:params cmd))] [:g.path-node
[:g.path-node [:& path-point {:position position
(when (= :curve-to (:command cmd)) :selected? false
[:& path-handler {:point point :zoom zoom
:handler (gpt/point (-> cmd :params :c2x) (-> cmd :params :c2y)) :edit-mode edit-mode
:zoom zoom :start-path? (nil? last-point)}]
:type :prev
:index index
:selected (selected [index :prev])
:edit-mode edit-mode}])
(when (= :curve-to (:command next)) [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
[:& path-handler {:point point (for [[index prefix] (get handlers position)]
:handler (gpt/point (-> next :params :c1x) (-> next :params :c1y)) (let [command (get content index)
:zoom zoom x (get-in command [:params (d/prefix-keyword prefix :x)])
:type :next y (get-in command [:params (d/prefix-keyword prefix :y)])
:index index handler-position (gpt/point x y)]
:selected (selected [index :next]) [:& path-handler {:point position
:edit-mode edit-mode}]) :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))) (when prev-handler
prev-handler (not drag-handler)) [:g.prev-handler {:pointer-events "none"}
[:& path-handler {:point point [:& path-handler {:point last-p
:handler prev-handler :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
:zoom zoom :zoom zoom
:edit-mode edit-mode}]])) :selected false}]])
(when drag-handler (when drag-handler
[:g.drag-handler [:g.drag-handler {:pointer-events "none"}
(when (not= :move-to (:command last-command)) (when (not= :move-to (:command last-command))
[:& path-handler {:point last-p [:& path-handler {:point last-p
:handler (ugp/opposite-handler last-p drag-handler) :handler (ugp/opposite-handler last-p drag-handler)
:zoom zoom :zoom zoom
:type :drag-opposite
:selected false}]) :selected false}])
[:& path-handler {:point last-p [:& path-handler {:point last-p
:handler drag-handler :handler drag-handler
:zoom zoom :zoom zoom
:type :drag
:selected false}]])])) :selected false}]])]))

View file

@ -248,4 +248,7 @@
;; nil ;; nil
;; (throw e#))))))) ;; (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 (ns app.util.geom.path
(:require (:require
[cuerdas.core :as str] [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.common.geom.point :as gpt]
[app.util.geom.path-impl-simplify :as impl-simplify])) [app.util.geom.path-impl-simplify :as impl-simplify]))
@ -208,8 +209,81 @@
:c2y (:y h2)})) :c2y (:y h2)}))
(defn opposite-handler (defn opposite-handler
"Calculates the coordinates of the opposite handler"
[point handler] [point handler]
(let [phv (gpt/to-vec point handler) (let [phv (gpt/to-vec point handler)]
opposite (gpt/add point (gpt/negate phv))] (gpt/add point (gpt/negate phv))))
opposite))
(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))