0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-03 04:49:03 -05:00

Merge pull request #399 from penpot/bezier-fixes

Bezier improvements
This commit is contained in:
Andrey Antukh 2020-11-26 14:12:19 +01:00 committed by GitHub
commit 24ea6a63c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 564 additions and 275 deletions

View file

@ -55,6 +55,11 @@
;;(assert (not (nil? y))) ;;(assert (not (nil? y)))
(Point. x y))) (Point. x y)))
(defn angle->point [{:keys [x y]} angle distance]
(point
(+ x (* distance (mth/cos angle)))
(- y (* distance (mth/sin angle)))))
(defn add (defn add
"Returns the addition of the supplied value to both "Returns the addition of the supplied value to both
coordinates of the point as a new point." coordinates of the point as a new point."

View file

@ -263,6 +263,7 @@
border-radius: 3px; border-radius: 3px;
svg { svg {
pointer-events: none;
width: 20px; width: 20px;
height: 20px; height: 20px;
} }

View file

@ -9,23 +9,63 @@
(ns app.main.data.workspace.drawing.path (ns app.main.data.workspace.drawing.path
(:require (:require
[clojure.spec.alpha :as s]
[app.common.spec :as us]
[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]
[app.common.data :as cd] [app.common.data :as cd]
[app.util.geom.path :as ugp] [app.util.geom.path :as ugp]
[app.main.streams :as ms] [app.main.streams :as ms]
[app.main.store :as st]
[app.main.data.workspace.common :as dwc] [app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing.common :as common] [app.main.data.workspace.drawing.common :as common]
[app.common.geom.shapes.path :as gsp])) [app.common.geom.shapes.path :as gsp]))
;; SCHEMAS
(s/def ::command #{:move-to
:line-to
:line-to-horizontal
:line-to-vertical
:curve-to
:smooth-curve-to
:quadratic-bezier-curve-to
:smooth-quadratic-bezier-curve-to
:elliptical-arc
:close-path})
(s/def :paths.params/x number?)
(s/def :paths.params/y number?)
(s/def :paths.params/c1x number?)
(s/def :paths.params/c1y number?)
(s/def :paths.params/c2x number?)
(s/def :paths.params/c2y number?)
(s/def ::relative? boolean?)
(s/def ::params
(s/keys :req-un [:path.params/x
:path.params/y]
:opt-un [:path.params/c1x
:path.params/c1y
:path.params/c2x
:path.params/c2y]))
(s/def ::content-entry
(s/keys :req-un [::command]
:req-opt [::params
::relative?]))
(s/def ::content
(s/coll-of ::content-entry :kind vector?))
;; CONSTANTS ;; CONSTANTS
(defonce enter-keycode 13) (defonce enter-keycode 13)
(defonce drag-threshold 5)
;; PRIVATE METHODS ;; PRIVATE METHODS
@ -54,6 +94,25 @@
points (gsh/rect->points selrect)] points (gsh/rect->points selrect)]
(assoc shape :points points :selrect selrect))) (assoc shape :points points :selrect selrect)))
(defn closest-angle [angle]
(cond
(or (> angle 337.5) (<= angle 22.5)) 0
(and (> angle 22.5) (<= angle 67.5)) 45
(and (> angle 67.5) (<= angle 112.5)) 90
(and (> angle 112.5) (<= angle 157.5)) 135
(and (> angle 157.5) (<= angle 202.5)) 180
(and (> angle 202.5) (<= angle 247.5)) 225
(and (> angle 247.5) (<= angle 292.5)) 270
(and (> angle 292.5) (<= angle 337.5)) 315))
(defn position-fixed-angle [point from-point]
(if (and from-point point)
(let [angle (mod (+ 360 (- (gpt/angle point from-point))) 360)
to-angle (closest-angle angle)
distance (gpt/distance point from-point)]
(gpt/angle->point from-point (mth/radians to-angle) distance))
point))
(defn next-node (defn next-node
"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]
@ -89,50 +148,111 @@
(update opposite-index assoc ocx (- dx) ocy (- dy))))) (update opposite-index assoc ocx (- dx) ocy (- dy)))))
(defn end-path-event? [{:keys [type shift] :as event}] (defn end-path-event? [{:keys [type shift] :as event}]
(or (= event ::end-path) (or (= (ptk/type event) ::finish-path)
(= (ptk/type event) :esc-pressed) (= (ptk/type event) :esc-pressed)
(= event :interrupt) ;; ESC (= event :interrupt) ;; ESC
(and (ms/mouse-double-click? event))
(and (ms/keyboard-event? event) (and (ms/keyboard-event? event)
(= type :down) (= type :down)
;; TODO: Enter now finish path but can finish drawing/editing as well ;; TODO: Enter now finish path but can finish drawing/editing as well
(= enter-keycode (:key event))))) (= enter-keycode (:key event)))))
(defn generate-path-changes [page-id shape-id old-content new-content]
(us/verify ::content old-content)
(us/verify ::content new-content)
(let [old-selrect (gsh/content->selrect old-content)
old-points (gsh/rect->points old-selrect)
new-selrect (gsh/content->selrect new-content)
new-points (gsh/rect->points new-selrect)
rch [{:type :mod-obj
:id shape-id
:page-id page-id
:operations [{:type :set :attr :content :val new-content}
{:type :set :attr :selrect :val new-selrect}
{:type :set :attr :points :val new-points}]}
{:type :reg-objects
:page-id page-id
:shapes [shape-id]}]
uch [{:type :mod-obj
:id shape-id
:page-id page-id
:operations [{:type :set :attr :content :val old-content}
{:type :set :attr :selrect :val old-selrect}
{:type :set :attr :points :val old-points}]}
{:type :reg-objects
:page-id page-id
:shapes [shape-id]}]]
[rch uch]))
(defn clean-edit-state
[state]
(dissoc state :last-point :prev-handler :drag-handler :preview))
(defn dragging? [start zoom]
(fn [current]
(>= (gpt/distance start current) (/ drag-threshold zoom))))
(defn drag-stream [to-stream]
(let [start @ms/mouse-position
zoom (get-in @st/state [:workspace-local :zoom] 1)
mouse-up (->> st/stream (rx/filter #(ms/mouse-up? %)))]
(->> ms/mouse-position
(rx/take-until mouse-up)
(rx/filter (dragging? start zoom))
(rx/take 1)
(rx/merge-map (fn [] to-stream)))))
(defn position-stream []
(->> ms/mouse-position
(rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %))))
(rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))))
;; EVENTS ;; EVENTS
(defn init-path [id] (defn init-path []
(ptk/reify ::init-path)) (ptk/reify ::init-path))
(defn finish-path [id] (defn finish-path [source]
(ptk/reify ::finish-path (ptk/reify ::finish-path
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(-> state (let [id (get-path-id state)]
(update-in [:workspace-local :edit-path id] dissoc :last-point :prev-handler :drag-handler :preview))))) (-> state
(update-in [:workspace-local :edit-path id] clean-edit-state))))))
(defn preview-next-point [{:keys [x y]}] (defn preview-next-point [{:keys [x y shift?]}]
(ptk/reify ::preview-next-point (ptk/reify ::preview-next-point
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) fix-angle? shift?
last-point (get-in state [:workspace-local :edit-path id :last-point])
position (cond-> (gpt/point x y)
fix-angle? (position-fixed-angle last-point))
shape (get-in state (get-path state)) shape (get-in state (get-path state))
{:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id]) {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])
command (next-node shape position last-point prev-handler)] command (next-node shape position last-point prev-handler)]
(assoc-in state [:workspace-local :edit-path id :preview] command))))) (assoc-in state [:workspace-local :edit-path id :preview] command)))))
(defn add-node [{:keys [x y]}] (defn add-node [{:keys [x y shift?]}]
(ptk/reify ::add-node (ptk/reify ::add-node
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) fix-angle? shift?
{:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])] {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])
(-> state position (cond-> (gpt/point x y)
(assoc-in [:workspace-local :edit-path id :last-point] position) fix-angle? (position-fixed-angle last-point))
(update-in [:workspace-local :edit-path id] dissoc :prev-handler) ]
(update-in (get-path state) append-node position last-point prev-handler)))))) (if-not (= last-point position)
(-> state
(assoc-in [:workspace-local :edit-path id :last-point] position)
(update-in [:workspace-local :edit-path id] dissoc :prev-handler)
(update-in [:workspace-local :edit-path id] dissoc :preview)
(update-in (get-path state) append-node position last-point prev-handler))
state)))))
(defn start-drag-handler [] (defn start-drag-handler []
(ptk/reify ::start-drag-handler (ptk/reify ::start-drag-handler
@ -154,22 +274,22 @@
(= command :line-to) (= command :line-to)
(update-in (get-path state :content index) make-curve)))))) (update-in (get-path state :content index) make-curve))))))
(defn drag-handler [{:keys [x y]}] (defn drag-handler [{:keys [x y alt? shift?]}]
(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)
handler-position (gpt/point x y)
shape (get-in state (get-path state)) shape (get-in state (get-path state))
content (:content shape) content (:content shape)
index (dec (count content)) index (dec (count content))
node-position (ugp/command->point (nth content index)) node-position (ugp/command->point (nth content index))
handler-position (cond-> (gpt/point x y)
shift? (position-fixed-angle node-position))
{dx :x dy :y} (gpt/subtract handler-position node-position) {dx :x dy :y} (gpt/subtract handler-position node-position)
match-opposite? true match-opposite? (not alt?)
modifiers (move-handler-modifiers content (inc index) :c1 match-opposite? dx dy)] modifiers (move-handler-modifiers content (inc index) :c1 match-opposite? dx dy)]
(-> state (-> state
(assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers) (update-in [:workspace-local :edit-path id :content-modifiers] merge modifiers)
(assoc-in [:workspace-local :edit-path id :prev-handler] handler-position) (assoc-in [:workspace-local :edit-path id :prev-handler] handler-position)
(assoc-in [:workspace-local :edit-path id :drag-handler] handler-position)))))) (assoc-in [:workspace-local :edit-path id :drag-handler] handler-position))))))
@ -194,114 +314,136 @@
;; 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] (declare close-path-drag-end)
(ptk/reify ::close-path
ptk/WatchEvent
(watch [_ state stream]
(rx/of (add-node position)
::end-path))))
(defn close-path-drag-start [position] (defn close-path-drag-start [position]
(ptk/reify ::close-path-drag-start (ptk/reify ::close-path-drag-start
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [zoom (get-in state [:workspace-local :zoom]) (let [id (get-path-id state)
threshold (/ 5 zoom) zoom (get-in state [:workspace-local :zoom])
check-if-dragging start-position @ms/mouse-position
(fn [current-position]
(let [start (gpt/point position)
current (gpt/point current-position)]
(>= (gpt/distance start current) 100)))
stop-stream stop-stream
(->> stream (rx/filter #(or (end-path-event? %) (->> stream (rx/filter #(or (end-path-event? %)
(ms/mouse-up? %)))) (ms/mouse-up? %))))
position-stream
(->> ms/mouse-position
(rx/take-until stop-stream)
(rx/throttle 50))
drag-events-stream drag-events-stream
(->> position-stream (->> (position-stream)
(rx/take-until stop-stream)
(rx/map #(drag-handler %)))] (rx/map #(drag-handler %)))]
(rx/concat (rx/concat
(rx/of (close-path position)) (rx/of (add-node position))
(drag-stream
(rx/concat
(rx/of (start-drag-handler))
drag-events-stream
(rx/of (finish-drag))
(rx/of (close-path-drag-end))))
(rx/of (finish-path "close-path")))))))
(->> position-stream (defn close-path-drag-end []
(rx/filter check-if-dragging) (ptk/reify ::close-path-drag-end
(rx/take 1) ptk/UpdateEvent
(rx/merge-map (update [_ state]
#(rx/concat (let [id (get-path-id state)]
(rx/of (start-drag-handler)) (update-in state [:workspace-local :edit-path id] dissoc :prev-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] (defn path-pointer-enter [position]
(ptk/reify ::path-pointer-enter)) (ptk/reify ::path-pointer-enter
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)]
(update-in state [:workspace-local :edit-path id :hover-points] (fnil conj #{}) position)))))
(defn path-pointer-leave [position] (defn path-pointer-leave [position]
(ptk/reify ::path-pointer-leave)) (ptk/reify ::path-pointer-leave
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)]
(update-in state [:workspace-local :edit-path id :hover-points] disj position)))))
(defn start-path-from-point [position] (defn start-path-from-point [position]
(ptk/reify ::start-path-from-point (ptk/reify ::start-path-from-point
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [mouse-up (->> stream (rx/filter #(or (end-path-event? %) (let [start-point @ms/mouse-position
zoom (get-in state [:workspace-local :zoom])
mouse-up (->> stream (rx/filter #(or (end-path-event? %)
(ms/mouse-up? %)))) (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/concat (rx/of (add-node position)) (rx/concat
(rx/of (start-drag-handler)) (rx/of (add-node position))
drag-events (drag-stream
(rx/of (finish-drag)))) (rx/concat
))) (rx/of (start-drag-handler))
drag-events
(rx/of (finish-drag)))))))))
(defn make-corner []
(ptk/reify ::make-corner
ptk/WatchEvent
(watch [_ state stream]
(let [id (get-path-id state)
page-id (:current-page-id state)
shape (get-in state (get-path state))
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
new-content (reduce ugp/make-corner-point (:content shape) selected-points)
[rch uch] (generate-path-changes page-id id (:content shape) new-content)]
(rx/of (dwc/commit-changes rch uch {:commit-local? true}))))))
(defn make-curve []
(ptk/reify ::make-curve
ptk/WatchEvent
(watch [_ state stream]
(let [id (get-path-id state)
page-id (:current-page-id state)
shape (get-in state (get-path state))
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
new-content (reduce ugp/make-curve-point (:content shape) selected-points)
[rch uch] (generate-path-changes page-id id (:content shape) new-content)]
(rx/of (dwc/commit-changes rch uch {:commit-local? true}))))))
(defn path-handler-enter [index prefix]
(ptk/reify ::path-handler-enter
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)]
(update-in state [:workspace-local :edit-path id :hover-handlers] (fnil conj #{}) [index prefix])))))
(defn path-handler-leave [index prefix]
(ptk/reify ::path-handler-leave
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)]
(update-in state [:workspace-local :edit-path id :hover-handlers] disj [index prefix])))))
;; EVENT STREAMS ;; EVENT STREAMS
(defn make-click-stream
[stream down-event]
(->> stream
(rx/filter ms/mouse-click?)
(rx/debounce 200)
(rx/first)
(rx/map #(add-node down-event))))
(defn make-drag-stream (defn make-drag-stream
[stream down-event] [stream down-event zoom]
(let [mouse-up (->> stream (rx/filter #(or (end-path-event? %) (let [mouse-up (->> stream (rx/filter #(or (end-path-event? %)
(ms/mouse-up? %)))) (ms/mouse-up? %))))
drag-events (->> ms/mouse-position drag-events (->> (position-stream)
(rx/take-until mouse-up) (rx/take-until mouse-up)
(rx/map #(drag-handler %)))] (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)))))))
(defn make-dbl-click-stream (rx/concat
[stream down-event] (rx/of (add-node down-event))
(->> stream (drag-stream
(rx/filter ms/mouse-double-click?) (rx/concat
(rx/first) (rx/of (start-drag-handler))
(rx/merge-map drag-events
#(rx/of (add-node down-event) (rx/of (finish-drag)))))))
::end-path))))
(defn make-node-events-stream (defn make-node-events-stream
[stream] [stream]
(->> (rx/merge (->> stream
(->> stream (rx/filter (ptk/type? ::close-path))) (rx/filter (ptk/type? ::close-path-drag-start))
(->> stream (rx/filter (ptk/type? ::close-path-drag-start))))
(rx/take 1) (rx/take 1)
(rx/merge-map #(rx/empty)))) (rx/merge-map #(rx/empty))))
@ -318,35 +460,32 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [mouse-down (->> stream (rx/filter ms/mouse-down?)) (let [zoom (get-in state [:workspace-local :zoom])
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?))
;; Mouse move preview ;; Mouse move preview
mousemove-events mousemove-events
(->> ms/mouse-position (->> (position-stream)
(rx/take-until end-path-events) (rx/take-until end-path-events)
(rx/throttle 50)
(rx/map #(preview-next-point %))) (rx/map #(preview-next-point %)))
;; From mouse down we can have: click, drag and double click ;; From mouse down we can have: click, drag and double click
mousedown-events mousedown-events
(->> mouse-down (->> mouse-down
(rx/take-until end-path-events) (rx/take-until end-path-events)
(rx/throttle 50) (rx/with-latest merge (position-stream))
(rx/with-latest merge ms/mouse-position)
;; 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-node-events-stream stream) #(rx/race (make-node-events-stream stream)
(make-click-stream stream %) (make-drag-stream stream % zoom))))]
(make-drag-stream stream %)
(make-dbl-click-stream stream %))))]
(rx/concat (rx/concat
(rx/of (init-path id)) (rx/of (init-path))
(rx/merge mousemove-events (rx/merge mousemove-events
mousedown-events) mousedown-events)
(rx/of (finish-path id))))))) (rx/of (finish-path "after-events")))))))
(defn stop-path-edit [] (defn stop-path-edit []
(ptk/reify ::stop-path-edit (ptk/reify ::stop-path-edit
@ -408,70 +547,33 @@
(ptk/reify ::apply-content-modifiers (ptk/reify ::apply-content-modifiers
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [id (get-in state [:workspace-local :edition]) (let [id (get-path-id state)
page-id (:current-page-id state) page-id (:current-page-id state)
shape (get-in state [:workspace-data :pages-index page-id :objects id]) shape (get-in state (get-path state))
{old-content :content old-selrect :selrect old-points :points} shape 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 (ugp/apply-content-modifiers (:content shape) content-modifiers)
new-content (ugp/apply-content-modifiers old-content content-modifiers) [rch uch] (generate-path-changes page-id (:id shape) (:content shape) new-content)]
new-selrect (gsh/content->selrect new-content)
new-points (gsh/rect->points new-selrect)
rch [{:type :mod-obj
:id id
:page-id page-id
:operations [{:type :set :attr :content :val new-content}
{:type :set :attr :selrect :val new-selrect}
{:type :set :attr :points :val new-points}]}
{:type :reg-objects
:page-id page-id
:shapes [id]}]
uch [{:type :mod-obj
:id id
:page-id page-id
:operations [{:type :set :attr :content :val old-content}
{:type :set :attr :selrect :val old-selrect}
{:type :set :attr :points :val old-points}]}
{:type :reg-objects
:page-id page-id
:shapes [id]}]]
(rx/of (dwc/commit-changes rch uch {:commit-local? true}) (rx/of (dwc/commit-changes rch uch {:commit-local? true})
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers))))))) (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers)))))))
(defn save-path-content [] (defn save-path-content []
(ptk/reify ::save-path-content (ptk/reify ::save-path-content
ptk/UpdateEvent
(update [_ state]
(let [content (get-in state (get-path state :content))
content (if (= (-> content last :command) :move-to)
(into [] (take (dec (count content)) content))
content)]
(assoc-in state (get-path state :content) content)))
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])
shape (get-in state (get-path state))
page-id (:current-page-id state) page-id (:current-page-id state)
old-content (get-in state [:workspace-local :edit-path id :old-content]) old-content (get-in state [:workspace-local :edit-path id :old-content])
old-selrect (gsh/content->selrect old-content) [rch uch] (generate-path-changes page-id id old-content (:content shape))]
old-points (gsh/rect->points old-content)
shape (get-in state [:workspace-data :pages-index page-id :objects id])
{new-content :content new-selrect :selrect new-points :points} shape
rch [{:type :mod-obj
:id id
:page-id page-id
:operations [{:type :set :attr :content :val new-content}
{:type :set :attr :selrect :val new-selrect}
{:type :set :attr :points :val new-points}]}
{:type :reg-objects
:page-id page-id
:shapes [id]}]
uch [{:type :mod-obj
:id id
:page-id page-id
:operations [{:type :set :attr :content :val old-content}
{:type :set :attr :selrect :val old-selrect}
{:type :set :attr :points :val old-points}]}
{:type :reg-objects
:page-id page-id
:shapes [id]}]]
(rx/of (dwc/commit-changes rch uch {:commit-local? true})))))) (rx/of (dwc/commit-changes rch uch {:commit-local? true}))))))
(declare start-draw-mode) (declare start-draw-mode)
@ -486,9 +588,9 @@
(cond (cond
(not= content old-content) (rx/of (save-path-content) (not= content old-content) (rx/of (save-path-content)
(start-draw-mode)) (start-draw-mode))
(= mode :draw) (rx/of :interrupt) (= mode :draw) (rx/of :interrupt)
:else (rx/of (finish-path id))))))) :else (rx/of (finish-path "changed-content")))))))
(defn move-path-point [start-point end-point] (defn move-path-point [start-point end-point]
(ptk/reify ::move-point (ptk/reify ::move-point
@ -506,7 +608,7 @@
(let [point (ugp/command->point command)] (let [point (ugp/command->point command)]
(= point start-point))) (= point start-point)))
point-indices (->> (d/enumerate content) point-indices (->> (cd/enumerate content)
(filter command-for-point) (filter command-for-point)
(map first)) (map first))
@ -533,14 +635,17 @@
[position] [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 [stopper (->> stream (rx/filter ms/mouse-up?))] (let [start-position @ms/mouse-position
(rx/concat stopper (->> stream (rx/filter ms/mouse-up?))
(->> ms/mouse-position zoom (get-in state [:workspace-local :zoom])]
(rx/take-until stopper)
(rx/map #(move-path-point position %))) (drag-stream
(rx/of (apply-content-modifiers))))))) (rx/concat
(->> ms/mouse-position
(rx/take-until stopper)
(rx/map #(move-path-point position %)))
(rx/of (apply-content-modifiers))))))))
(defn start-move-handler (defn start-move-handler
[index prefix] [index prefix]
@ -548,26 +653,40 @@
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 (= prefix :c1) [:c1x :c1y] [:c2x :c2y]) cx (ud/prefix-keyword prefix :x)
cy (ud/prefix-keyword prefix :y)
start-point @ms/mouse-position start-point @ms/mouse-position
start-delta-x (get-in state [:workspace-local :edit-path id :content-modifiers index cx] 0) modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])
start-delta-y (get-in state [:workspace-local :edit-path id :content-modifiers index cy] 0)] start-delta-x (get-in modifiers [index cx] 0)
start-delta-y (get-in modifiers [index cy] 0)
(rx/concat content (get-in state (get-path state :content))
(->> ms/mouse-position opposite-index (ugp/opposite-index content index prefix)
(rx/take-until (->> stream (rx/filter ms/mouse-up?))) opposite-prefix (if (= prefix :c1) :c2 :c1)
(rx/with-latest vector ms/mouse-position-alt) opposite-handler (-> content (get opposite-index) (ugp/get-handler opposite-prefix))
(rx/map
(fn [[pos alt?]] point (-> content (get (if (= prefix :c1) (dec index) index)) (ugp/command->point))
(modify-handler handler (-> content (get index) (ugp/get-handler prefix))
id
index current-distance (gpt/distance (ugp/opposite-handler point handler) opposite-handler)
prefix match-opposite? (mth/almost-zero? current-distance)]
(+ start-delta-x (- (:x pos) (:x start-point)))
(+ start-delta-y (- (:y pos) (:y start-point))) (drag-stream
(not alt?)))) (rx/concat
) (->> (position-stream)
(rx/concat (rx/of (apply-content-modifiers)))))))) (rx/take-until (->> stream (rx/filter ms/mouse-up?)))
(rx/map
(fn [{:keys [x y alt? shift?]}]
(let [pos (cond-> (gpt/point x y)
shift? (position-fixed-angle point))]
(modify-handler
id
index
prefix
(+ start-delta-x (- (:x pos) (:x start-point)))
(+ start-delta-y (- (:y pos) (:y start-point)))
(and (not alt?) match-opposite?))))))
(rx/concat (rx/of (apply-content-modifiers)))))))))
(defn start-draw-mode [] (defn start-draw-mode []
(ptk/reify ::start-draw-mode (ptk/reify ::start-draw-mode
@ -604,7 +723,7 @@
(watch [_ state stream] (watch [_ state stream]
(let [id (get-path-id state)] (let [id (get-path-id state)]
(cond (cond
(and id (= :move mode)) (rx/of ::end-path) (and id (= :move mode)) (rx/of (finish-path "change-edit-mode"))
(and id (= :draw mode)) (rx/of (start-draw-mode)) (and id (= :draw mode)) (rx/of (start-draw-mode))
:else (rx/empty)))))) :else (rx/empty))))))
@ -614,7 +733,7 @@
(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 type])))))) (update-in [:workspace-local :edit-path id :selected-handlers] (fnil conj #{}) [index type]))))))
(defn select-node [position] (defn select-node [position]
(ptk/reify ::select-node (ptk/reify ::select-node
@ -622,7 +741,7 @@
(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-node] (fnil conj #{}) position)))))) (assoc-in [:workspace-local :edit-path id :selected-points] #{position}))))))
(defn deselect-node [position] (defn deselect-node [position]
(ptk/reify ::deselect-node (ptk/reify ::deselect-node
@ -630,7 +749,7 @@
(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-node] (fnil disj #{}) position)))))) (update-in [:workspace-local :edit-path id :selected-points] (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
@ -656,11 +775,21 @@
(update [_ state] (update [_ state]
state))) state)))
(defn deselect-all []
(ptk/reify ::deselect-all
ptk/UpdateEvent
(update [_ state]
(let [id (get-path-id state)]
(-> state
(assoc-in [:workspace-local :edit-path id :selected-handlers] #{})
(assoc-in [:workspace-local :edit-path id :selected-points] #{}))))))
(defn handle-new-shape-result [shape-id] (defn handle-new-shape-result [shape-id]
(ptk/reify ::handle-new-shape-result (ptk/reify ::handle-new-shape-result
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [content (get-in state [:workspace-drawing :object :content] [])] (let [content (get-in state [:workspace-drawing :object :content] [])]
(us/verify ::content content)
(if (> (count content) 1) (if (> (count content) 1)
(assoc-in state [:workspace-drawing :object :initialized?] true) (assoc-in state [:workspace-drawing :object :initialized?] true)
state))) state)))

View file

@ -11,6 +11,7 @@
"The main logic for SVG export functionality." "The main logic for SVG export functionality."
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[cuerdas.core :as str]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.pages-helpers :as cph] [app.common.pages-helpers :as cph]
@ -42,10 +43,15 @@
(defn- calculate-dimensions (defn- calculate-dimensions
[{:keys [objects] :as data} vport] [{:keys [objects] :as data} vport]
(let [shapes (cph/select-toplevel-shapes objects {:include-frames? true})] (let [shapes (cph/select-toplevel-shapes objects {:include-frames? true})
(->> (gsh/selection-rect shapes) to-finite (fn [val fallback] (if (not (mth/finite? val)) fallback val))
(gal/adjust-to-viewport vport) rect (->> (gsh/selection-rect shapes)
#_(gsh/fix-invalid-rect-values)))) (gal/adjust-to-viewport vport))]
(-> rect
(update :x to-finite 0)
(update :y to-finite 0)
(update :width to-finite 10000)
(update :height to-finite 10000))))
(declare shape-wrapper-factory) (declare shape-wrapper-factory)
@ -93,21 +99,20 @@
:group [:> group-wrapper {:shape shape :frame frame}] :group [:> group-wrapper {:shape shape :frame frame}]
nil)]))))) nil)])))))
(defn get-viewbox [{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}]
(str/fmt "%s %s %s %s" x y width height))
(mf/defc page-svg (mf/defc page-svg
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [data width height] :as props}] [{:keys [data width height] :as props}]
(let [objects (:objects data) (let [objects (:objects data)
vport {:width width :height height}
dim (calculate-dimensions data vport)
root (get objects uuid/zero) root (get objects uuid/zero)
shapes (->> (:shapes root) shapes (->> (:shapes root)
(map #(get objects %))) (map #(get objects %)))
vbox (str (:x dim 0) " " vport {:width width :height height}
(:y dim 0) " " dim (calculate-dimensions data vport)
(:width dim 100) " " vbox (get-viewbox dim)
(:height dim 100))
background-color (get-in data [:options :background] default-color) background-color (get-in data [:options :background] default-color)
frame-wrapper frame-wrapper
(mf/use-memo (mf/use-memo

View file

@ -93,7 +93,7 @@
{:alt (t locale "workspace.toolbar.path") {:alt (t locale "workspace.toolbar.path")
:class (when (= selected-drawtool :path) "selected") :class (when (= selected-drawtool :path) "selected")
:on-click (partial select-drawtool :path)} :on-click (partial select-drawtool :path)}
i/curve] i/pen]
[:li.tooltip.tooltip-right [:li.tooltip.tooltip-right
{:alt (t locale "workspace.toolbar.comments") {:alt (t locale "workspace.toolbar.comments")

View file

@ -10,6 +10,7 @@
(ns app.main.ui.workspace.shapes.path (ns app.main.ui.workspace.shapes.path
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[goog.events :as events]
[okulary.core :as l] [okulary.core :as l]
[app.util.data :as d] [app.util.data :as d]
[app.util.dom :as dom] [app.util.dom :as dom]
@ -30,7 +31,8 @@
[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.main.ui.cursors :as cur] [app.main.ui.cursors :as cur]
[app.main.ui.icons :as i])) [app.main.ui.icons :as i])
(:import goog.events.EventType))
(def primary-color "#1FDEA7") (def primary-color "#1FDEA7")
(def secondary-color "#DB00FF") (def secondary-color "#DB00FF")
@ -94,7 +96,7 @@
(mf/defc path-actions [{:keys [shape]}] (mf/defc path-actions [{:keys [shape]}]
(let [id (mf/deref refs/selected-edition) (let [id (mf/deref refs/selected-edition)
{:keys [edit-mode selected snap-toggled] :as all} (mf/deref current-edit-path-ref)] {:keys [edit-mode selected-points snap-toggled] :as all} (mf/deref current-edit-path-ref)]
[:div.path-actions [:div.path-actions
[:div.viewport-actions-group [:div.viewport-actions-group
[:div.viewport-actions-entry {:class (when (= edit-mode :draw) "is-toggled") [:div.viewport-actions-entry {:class (when (= edit-mode :draw) "is-toggled")
@ -102,24 +104,28 @@
[:div.viewport-actions-entry {:class (when (= edit-mode :move) "is-toggled") [:div.viewport-actions-entry {:class (when (= edit-mode :move) "is-toggled")
:on-click #(st/emit! (drp/change-edit-mode :move))} i/pointer-inner]] :on-click #(st/emit! (drp/change-edit-mode :move))} i/pointer-inner]]
[:div.viewport-actions-group #_[:div.viewport-actions-group
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-add] [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-add]
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-remove]] [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-remove]]
[:div.viewport-actions-group #_[:div.viewport-actions-group
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-merge] [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-merge]
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-join] [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-join]
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-separate]] [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-separate]]
[:div.viewport-actions-group [:div.viewport-actions-group
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-corner] [:div.viewport-actions-entry {:class (when (empty? selected-points) "is-disabled")
[:div.viewport-actions-entry {:class "is-disabled"} i/nodes-curve]] :on-click #(when-not (empty? selected-points)
(st/emit! (drp/make-corner)))} i/nodes-corner]
[:div.viewport-actions-entry {:class (when (empty? selected-points) "is-disabled")
:on-click #(when-not (empty? selected-points)
(st/emit! (drp/make-curve)))} i/nodes-curve]]
[:div.viewport-actions-group #_[:div.viewport-actions-group
[: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-point [{:keys [position zoom edit-mode hover? selected? preview? start-path?]}] (mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path? last-p?]}]
(let [{:keys [x y]} position (let [{:keys [x y]} position
on-enter on-enter
@ -132,37 +138,38 @@
on-click on-click
(fn [event] (fn [event]
(dom/stop-propagation event) (when-not last-p?
(dom/prevent-default event) (do (dom/stop-propagation event)
(dom/prevent-default event)
(cond (cond
(and (= edit-mode :move) (not selected?)) (and (= edit-mode :move) (not selected?))
(st/emit! (drp/select-node position)) (st/emit! (drp/select-node position))
(and (= edit-mode :move) selected?) (and (= edit-mode :move) selected?)
(st/emit! (drp/deselect-node position)))) (st/emit! (drp/deselect-node position))))))
on-mouse-down on-mouse-down
(fn [event] (fn [event]
(dom/stop-propagation event) (when-not last-p?
(dom/prevent-default event) (do (dom/stop-propagation event)
(dom/prevent-default event)
(cond (cond
(= edit-mode :move) (= edit-mode :move)
(st/emit! (drp/start-move-path-point position)) (st/emit! (drp/start-move-path-point position))
(and (= edit-mode :draw) start-path?) (and (= edit-mode :draw) start-path?)
(st/emit! (drp/start-path-from-point position)) (st/emit! (drp/start-path-from-point position))
(and (= edit-mode :draw) (not start-path?)) (and (= edit-mode :draw) (not start-path?))
(st/emit! (drp/close-path-drag-start position))))] (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 (if (or selected? hover?) (/ 3.5 zoom) (/ 3 zoom))
:style {:cursor (when (= edit-mode :draw) cur/pen-node) :style {:stroke-width (/ 1 zoom)
:stroke-width (/ 1 zoom)
:stroke (cond (or selected? hover?) black-color :stroke (cond (or selected? hover?) black-color
preview? secondary-color preview? secondary-color
:else primary-color) :else primary-color)
@ -173,11 +180,24 @@
: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"}}]])) :on-mouse-enter on-enter
:on-mouse-leave on-leave
:style {:cursor (cond
(and (not last-p?) (= edit-mode :draw)) cur/pen-node
(= edit-mode :move) cur/pointer-node)
:fill "transparent"}}]]))
(mf/defc path-handler [{:keys [index prefix point handler zoom selected? hover? 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-enter
(fn [event]
(st/emit! (drp/path-handler-enter index prefix)))
on-leave
(fn [event]
(st/emit! (drp/path-handler-leave index prefix)))
on-click on-click
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
@ -201,7 +221,7 @@
:y1 (:y point) :y1 (:y point)
:x2 x :x2 x
:y2 y :y2 y
:style {:stroke gray-color :style {:stroke (if hover? black-color gray-color)
:stroke-width (/ 1 zoom)}}] :stroke-width (/ 1 zoom)}}]
[:rect [:rect
{:x (- x (/ 3 zoom)) {:x (- x (/ 3 zoom))
@ -209,8 +229,7 @@
:width (/ 6 zoom) :width (/ 6 zoom)
:height (/ 6 zoom) :height (/ 6 zoom)
:style {:cursor cur/pointer-move :style {:stroke-width (/ 1 zoom)
:stroke-width (/ 1 zoom)
:stroke (cond (or selected? hover?) black-color :stroke (cond (or selected? hover?) black-color
:else primary-color) :else primary-color)
:fill (cond selected? primary-color :fill (cond selected? primary-color
@ -220,7 +239,10 @@
: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"}}]]))) :on-mouse-enter on-enter
:on-mouse-leave on-leave
:style {:cursor (when (= edit-mode :move) cur/pointer-move)
:fill "transparent"}}]])))
(mf/defc path-preview [{:keys [zoom command from]}] (mf/defc path-preview [{:keys [zoom command from]}]
[:g.preview {:style {:pointer-events "none"}} [:g.preview {:style {:pointer-events "none"}}
@ -239,17 +261,40 @@
(mf/defc path-editor (mf/defc path-editor
[{:keys [shape zoom]}] [{:keys [shape zoom]}]
(let [edit-path-ref (make-edit-path-ref (:id shape)) (let [editor-ref (mf/use-ref nil)
{:keys [edit-mode selected drag-handler prev-handler preview content-modifiers last-point]} (mf/deref edit-path-ref) edit-path-ref (make-edit-path-ref (:id shape))
{:keys [edit-mode
drag-handler
prev-handler
preview
content-modifiers
last-point
selected-handlers
selected-points
hover-handlers
hover-points]} (mf/deref edit-path-ref)
{:keys [content]} shape {:keys [content]} shape
selected (or selected #{})
content (ugp/apply-content-modifiers content content-modifiers) content (ugp/apply-content-modifiers content content-modifiers)
points (->> content ugp/content->points (into #{})) points (->> content ugp/content->points (into #{}))
last-command (last content) last-command (last content)
last-p (->> content last ugp/command->point) last-p (->> content last ugp/command->point)
handlers (ugp/content->handlers content)] handlers (ugp/content->handlers content)
[:g.path-editor handle-click-outside
(fn [event]
(let [current (dom/get-target event)
editor-dom (mf/ref-val editor-ref)]
(when-not (or (.contains editor-dom current)
(dom/class? current "viewport-actions-entry"))
(st/emit! (drp/deselect-all)))))]
(mf/use-layout-effect
(fn []
(let [keys [(events/listen (dom/get-root) EventType.CLICK handle-click-outside)]]
#(doseq [key keys]
(events/unlistenByKey key)))))
[:g.path-editor {:ref editor-ref}
(when (and preview (not drag-handler)) (when (and preview (not drag-handler))
[:& path-preview {:command preview [:& path-preview {:command preview
:from last-p :from last-p
@ -257,43 +302,37 @@
(for [position points] (for [position points]
[:g.path-node [:g.path-node
[:& path-point {:position position
:selected? false
:zoom zoom
:edit-mode edit-mode
:start-path? (nil? last-point)}]
[:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
(for [[index prefix] (get handlers position)] (for [[index prefix] (get handlers position)]
(let [command (get content index) (let [command (get content index)
x (get-in command [:params (d/prefix-keyword prefix :x)]) x (get-in command [:params (d/prefix-keyword prefix :x)])
y (get-in command [:params (d/prefix-keyword prefix :y)]) y (get-in command [:params (d/prefix-keyword prefix :y)])
handler-position (gpt/point x y)] handler-position (gpt/point x y)]
[:& path-handler {:point position (when (not= position handler-position)
:handler handler-position [:& path-handler {:point position
:index index :handler handler-position
:prefix prefix :index index
:zoom zoom :prefix prefix
:selected? false :zoom zoom
:hover? false :selected? (contains? selected-handlers [index prefix])
:preview? false :hover? (contains? hover-handlers [index prefix])
:edit-mode edit-mode}]))]]) :edit-mode edit-mode}])))]
[:& path-point {:position position
:zoom zoom
:edit-mode edit-mode
:selected? (contains? selected-points position)
:hover? (contains? hover-points position)
:last-p? (= last-point position)
:start-path? (nil? last-point)}]])
(when prev-handler (when prev-handler
[:g.prev-handler {:pointer-events "none"} [:g.prev-handler {:pointer-events "none"}
[:& path-handler {:point last-p [:& path-handler {:point last-p
:handler prev-handler :handler prev-handler
:zoom zoom :zoom zoom}]])
:selected false}]])
(when drag-handler (when drag-handler
[:g.drag-handler {:pointer-events "none"} [: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
:selected false}])
[:& path-handler {:point last-p [:& path-handler {:point last-p
:handler drag-handler :handler drag-handler
:zoom zoom :zoom zoom}]])]))
:selected false}]])]))

View file

@ -294,7 +294,7 @@
on-double-click on-double-click
(mf/use-callback (mf/use-callback
(mf/deps edition edit-path) (mf/deps drawing-path?)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event) (let [ctrl? (kbd/ctrl? event)

View file

@ -236,3 +236,7 @@
(defn get-root [] (defn get-root []
(query js/document "#app")) (query js/document "#app"))
(defn ^boolean class? [node class-name]
(let [class-list (.-classList ^js node)]
(.contains ^js class-list class-name)))

View file

@ -10,6 +10,7 @@
(ns app.util.geom.path (ns app.util.geom.path
(:require (:require
[cuerdas.core :as str] [cuerdas.core :as str]
[app.common.data :as cd]
[app.util.data :as d] [app.util.data :as d]
[app.common.data :as cd] [app.common.data :as cd]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
@ -247,8 +248,10 @@
content))] content))]
(reduce apply-to-index content modifiers))) (reduce apply-to-index content modifiers)))
(defn command->point [{{:keys [x y]} :params}] (defn command->point [command]
(gpt/point x y)) (when-not (nil? command)
(let [{{:keys [x y]} :params} command]
(gpt/point x y))))
(defn content->points [content] (defn content->points [content]
(->> content (->> content
@ -256,23 +259,37 @@
(remove nil?) (remove nil?)
(into []))) (into [])))
(defn content->handlers [content] (defn get-handler [{:keys [params] :as command} prefix]
(->> (d/with-prev content) ;; [cmd, prev] (let [cx (d/prefix-keyword prefix :x)
(d/enumerate) ;; [idx [cmd, prev]] cy (d/prefix-keyword prefix :y)]
(when (and command
(contains? params cx)
(contains? params cy))
(gpt/point (get params cx)
(get params cy)))))
(mapcat (fn [[index [cur-cmd prev-cmd]]] (defn content->handlers
(if (and prev-cmd "Retrieve a map where for every point will retrieve a list of
(= :curve-to (:command cur-cmd))) the handlers that are associated with that point.
point -> [[index, prefix]]"
[content]
(->> (d/with-prev content)
(d/enumerate)
(mapcat (fn [[index [cur-cmd pre-cmd]]]
(if (and pre-cmd (= :curve-to (:command cur-cmd)))
(let [cur-pos (command->point cur-cmd) (let [cur-pos (command->point cur-cmd)
pre-pos (command->point prev-cmd)] pre-pos (command->point pre-cmd)]
[[pre-pos [index :c1]] (-> [[pre-pos [index :c1]]
[cur-pos [index :c2]]]) [cur-pos [index :c2]]]))
[]))) [])))
(group-by first) (group-by first)
(cd/mapm #(mapv second %2)))) (cd/mapm #(mapv second %2))))
(defn opposite-index [content index prefix] (defn opposite-index
"Calculate sthe opposite index given a prefix and an index"
[content index prefix]
(let [point (if (= prefix :c2) (let [point (if (= prefix :c2)
(command->point (nth content index)) (command->point (nth content index))
(command->point (nth content (dec index)))) (command->point (nth content (dec index))))
@ -280,10 +297,99 @@
handlers (-> (content->handlers content) handlers (-> (content->handlers content)
(get point)) (get point))
opposite-prefix (if (= prefix :c1) :c2 :c1) opposite-prefix (if (= prefix :c1) :c2 :c1)]
(when (<= (count handlers) 2)
(->> handlers
(d/seek (fn [[index prefix]] (= prefix opposite-prefix)))
(first)))))
result (when (<= (count handlers) 2) (defn remove-line-curves
(->> handlers "Remove all curves that have both handlers in the same position that the
(d/seek (fn [[index prefix]] (= prefix opposite-prefix))) beggining and end points. This makes them really line-to commands"
(first)))] [content]
result)) (let [with-prev (d/enumerate (d/with-prev content))
process-command
(fn [content [index [command prev]]]
(let [cur-point (command->point command)
pre-point (command->point prev)
handler-c1 (get-handler command :c1)
handler-c2 (get-handler command :c2)]
(if (and (= :curve-to (:command command))
(= cur-point handler-c2)
(= pre-point handler-c1))
(assoc content index {:command :line-to
:params cur-point})
content)))]
(reduce process-command content with-prev)))
(defn make-corner-point
"Changes the content to make a point a 'corner'"
[content point]
(let [handlers (-> (content->handlers content)
(get point))
change-content
(fn [content [index prefix]]
(let [cx (d/prefix-keyword prefix :x)
cy (d/prefix-keyword prefix :y)]
(-> content
(assoc-in [index :params cx] (:x point))
(assoc-in [index :params cy] (:y point)))))]
(as-> content $
(reduce change-content $ handlers)
(remove-line-curves $))))
(defn make-curve-point
"Changes the content to make the point a 'curve'. The handlers will be positioned
in the same vector that results from te previous->next points but with fixed length."
[content point]
(let [content-next (d/enumerate (d/with-prev-next content))
make-curve
(fn [command previous]
(if (= :line-to (:command command))
(let [cur-point (command->point command)
pre-point (command->point previous)]
(-> command
(assoc :command :curve-to)
(assoc :params (make-curve-params cur-point pre-point))))
command))
update-handler
(fn [command prefix handler]
(if (= :curve-to (:command command))
(let [cx (d/prefix-keyword prefix :x)
cy (d/prefix-keyword prefix :y)]
(-> command
(assoc-in [:params cx] (:x handler))
(assoc-in [:params cy] (:y handler))))
command))
calculate-vector
(fn [point next prev]
(let [base-vector (if (or (nil? next) (nil? prev) (= next prev))
(-> (gpt/to-vec point (or next prev))
(gpt/normal-left))
(gpt/to-vec next prev))]
(-> base-vector
(gpt/unit)
(gpt/multiply (gpt/point 100)))))
redfn (fn [content [index [command prev next]]]
(if (= point (command->point command))
(let [prev-point (if (= :move-to (:command command)) nil (command->point prev))
next-point (if (= :move-to (:command next)) nil (command->point next))
handler-vector (calculate-vector point next-point prev-point)
handler (gpt/add point handler-vector)
handler-opposite (gpt/add point (gpt/negate handler-vector))]
(-> content
(cd/update-when index make-curve prev)
(cd/update-when index update-handler :c2 handler)
(cd/update-when (inc index) make-curve command)
(cd/update-when (inc index) update-handler :c1 handler-opposite)))
content))]
(as-> content $
(reduce redfn $ content-next)
(remove-line-curves $))))