mirror of
https://github.com/penpot/penpot.git
synced 2025-03-20 19:51:23 -05:00
✨ Snapping 180 angle with opposites handlers
This commit is contained in:
parent
961a7a2e03
commit
6331dff484
8 changed files with 108 additions and 56 deletions
common/app/common/geom
frontend/src/app
main
data/workspace/path
ui/workspace/shapes/path
util/path
|
@ -162,6 +162,8 @@
|
|||
(mth/precision 6))]
|
||||
(if (mth/nan? d) 0 d)))))
|
||||
|
||||
(defn angle-sign [v1 v2]
|
||||
(if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1))
|
||||
|
||||
(defn update-angle
|
||||
"Update the angle of the point."
|
||||
|
|
|
@ -161,7 +161,7 @@
|
|||
v2 (gpt/to-vec center p2)
|
||||
|
||||
rot-angle (gpt/angle-with-other v1 v2)
|
||||
rot-sign (if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1)]
|
||||
rot-sign (gpt/angle-sign v1 v2)]
|
||||
(* rot-sign rot-angle)))
|
||||
|
||||
(defn- calculate-dimensions
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
prefix (or prefix :c1)
|
||||
position (or position (upc/command->point (nth content (dec index))))
|
||||
|
||||
old-handler (helpers/handler->point content index prefix)
|
||||
old-handler (upc/handler->point content index prefix)
|
||||
|
||||
handler-position (cond-> (gpt/point x y)
|
||||
shift? (helpers/position-fixed-angle position))
|
||||
|
|
|
@ -163,11 +163,14 @@
|
|||
point (-> content (get (if (= prefix :c1) (dec index) index)) (upc/command->point))
|
||||
handler (-> content (get index) (upc/get-handler prefix))
|
||||
|
||||
[op-idx op-prefix] (upc/opposite-index content index prefix)
|
||||
opposite (upc/handler->point content op-idx op-prefix)
|
||||
|
||||
snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled])]
|
||||
|
||||
(streams/drag-stream
|
||||
(rx/concat
|
||||
(->> (streams/move-handler-stream snap-toggled start-point handler points)
|
||||
(->> (streams/move-handler-stream snap-toggled start-point point handler opposite points)
|
||||
(rx/take-until (->> stream (rx/filter ms/mouse-up?)))
|
||||
(rx/map
|
||||
(fn [{:keys [x y alt? shift?]}]
|
||||
|
|
|
@ -107,29 +107,6 @@
|
|||
(update :content (fnil conj []) command)
|
||||
(update-selrect))))
|
||||
|
||||
(defn prefix->coords [prefix]
|
||||
(case prefix
|
||||
:c1 [:c1x :c1y]
|
||||
:c2 [:c2x :c2y]
|
||||
nil))
|
||||
|
||||
(defn handler->point [content index prefix]
|
||||
(when (and (some? index)
|
||||
(some? prefix)
|
||||
(contains? content index))
|
||||
(let [[cx cy :as coords] (prefix->coords prefix)]
|
||||
(if (= :curve-to (get-in content [index :command]))
|
||||
(gpt/point (get-in content [index :params cx])
|
||||
(get-in content [index :params cy]))
|
||||
|
||||
(gpt/point (get-in content [index :params :x])
|
||||
(get-in content [index :params :y]))))))
|
||||
|
||||
(defn handler->node [content index prefix]
|
||||
(if (= prefix :c1)
|
||||
(upc/command->point (get content (dec index)))
|
||||
(upc/command->point (get content index))))
|
||||
|
||||
(defn angle-points [common p1 p2]
|
||||
(mth/abs
|
||||
(gpt/angle-with-other
|
||||
|
@ -153,7 +130,7 @@
|
|||
v2 (gpt/to-vec node new-handler)
|
||||
|
||||
delta-angle (gpt/angle-with-other v1 v2)
|
||||
delta-sign (if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1)
|
||||
delta-sign (gpt/angle-sign v1 v2)
|
||||
|
||||
distance-scale (/ (gpt/distance node handler)
|
||||
(gpt/distance node new-handler))
|
||||
|
@ -170,14 +147,14 @@
|
|||
(defn move-handler-modifiers
|
||||
[content index prefix match-distance? match-angle? dx dy]
|
||||
|
||||
(let [[cx cy] (prefix->coords prefix)
|
||||
(let [[cx cy] (upc/prefix->coords prefix)
|
||||
[op-idx op-prefix] (upc/opposite-index content index prefix)
|
||||
|
||||
node (handler->node content index prefix)
|
||||
handler (handler->point content index prefix)
|
||||
opposite (handler->point content op-idx op-prefix)
|
||||
node (upc/handler->node content index prefix)
|
||||
handler (upc/handler->point content index prefix)
|
||||
opposite (upc/handler->point content op-idx op-prefix)
|
||||
|
||||
[ocx ocy] (prefix->coords op-prefix)
|
||||
[ocx ocy] (upc/prefix->coords op-prefix)
|
||||
[odx ody] (calculate-opposite-delta node handler opposite match-angle? match-distance? dx dy)
|
||||
|
||||
hnv (if (some? handler)
|
||||
|
|
|
@ -73,24 +73,45 @@
|
|||
(rx/map check-path-snap))))
|
||||
|
||||
(defn move-handler-stream
|
||||
[snap-toggled start-point handler points]
|
||||
[snap-toggled start-point node handler opposite points]
|
||||
|
||||
(let [zoom (get-in @st/state [:workspace-local :zoom] 1)
|
||||
ranges (snap/create-ranges points)
|
||||
d-pos (/ snap/snap-path-accuracy zoom)
|
||||
|
||||
initial-angle (gpt/angle-with-other (gpt/to-vec node handler)
|
||||
(gpt/to-vec node opposite))
|
||||
|
||||
check-path-snap
|
||||
(fn [position]
|
||||
(if snap-toggled
|
||||
(let [delta (gpt/subtract position start-point)
|
||||
handler-position (gpt/add handler delta)
|
||||
snap (snap/get-snap-delta [handler-position] ranges d-pos)]
|
||||
(gpt/add position snap))
|
||||
handler (gpt/add handler delta)
|
||||
|
||||
v1 (gpt/to-vec node opposite)
|
||||
v2 (gpt/to-vec node handler)
|
||||
|
||||
rot-angle (gpt/angle-with-other v1 v2)
|
||||
rot-sign (gpt/angle-sign v1 v2)
|
||||
|
||||
snap-opposite-angle?
|
||||
(and (or (:alt? position) (> (- 180 initial-angle) 0.1))
|
||||
(<= (- 180 rot-angle) 5))]
|
||||
|
||||
(cond
|
||||
snap-opposite-angle?
|
||||
(let [rot-handler (gpt/rotate handler node (- 180 (* rot-sign rot-angle)))
|
||||
snap (gpt/to-vec handler rot-handler)]
|
||||
(merge position (gpt/add position snap)))
|
||||
|
||||
:else
|
||||
(let [snap (snap/get-snap-delta [handler] ranges d-pos)]
|
||||
(merge position (gpt/add position snap)))))
|
||||
position))]
|
||||
(->> ms/mouse-position
|
||||
(rx/map check-path-snap)
|
||||
(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? %)))))))
|
||||
(rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %))))
|
||||
(rx/map check-path-snap))))
|
||||
|
||||
(defn position-stream
|
||||
[snap-toggled points]
|
||||
|
@ -115,6 +136,5 @@
|
|||
(let [snap (snap/get-snap-delta [position] ranges d-pos)]
|
||||
(gpt/add position snap))
|
||||
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? %)))))))
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
(= 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 snap-angle?]}]
|
||||
(when (and point handler)
|
||||
(let [{:keys [x y]} handler
|
||||
on-enter
|
||||
|
@ -108,6 +108,16 @@
|
|||
:y2 y
|
||||
:style {:stroke (if hover? pc/black-color pc/gray-color)
|
||||
:stroke-width (/ 1 zoom)}}]
|
||||
|
||||
(when snap-angle?
|
||||
[:line
|
||||
{:x1 (:x point)
|
||||
:y1 (:y point)
|
||||
:x2 x
|
||||
:y2 y
|
||||
:style {:stroke pc/secondary-color
|
||||
:stroke-width (/ 1 zoom)}}])
|
||||
|
||||
[:rect
|
||||
{:x (- x (/ 3 zoom))
|
||||
:y (- y (/ 3 zoom))
|
||||
|
@ -157,6 +167,18 @@
|
|||
:style {:stroke pc/secondary-color
|
||||
:stroke-width (/ 1 zoom)}}])]))
|
||||
|
||||
(defn matching-handler? [content node handlers]
|
||||
(when (= 2 (count handlers))
|
||||
(let [[[i1 p1] [i2 p2]] handlers
|
||||
p1 (upc/handler->point content i1 p1)
|
||||
p2 (upc/handler->point content i2 p2)
|
||||
|
||||
v1 (gpt/to-vec node p1)
|
||||
v2 (gpt/to-vec node p2)
|
||||
|
||||
angle (gpt/angle-with-other v1 v2)]
|
||||
(<= (- 180 angle) 0.1))))
|
||||
|
||||
(mf/defc path-editor
|
||||
[{:keys [shape zoom]}]
|
||||
|
||||
|
@ -258,20 +280,24 @@
|
|||
|
||||
[:g.path-node
|
||||
[: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)
|
||||
handler-hover? (contains? hover-handlers [index prefix])]
|
||||
(when (not= position handler-position)
|
||||
[:& path-handler {:point position
|
||||
:handler handler-position
|
||||
:index index
|
||||
:prefix prefix
|
||||
:zoom zoom
|
||||
:hover? handler-hover?
|
||||
:edit-mode edit-mode}])))]
|
||||
(let [pos-handlers (get handlers position)]
|
||||
(for [[index prefix] pos-handlers]
|
||||
(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)
|
||||
handler-hover? (contains? hover-handlers [index prefix])
|
||||
moving-handler? (= handler-position moving-handler)
|
||||
matching-handler? (matching-handler? content position pos-handlers)]
|
||||
(when (not= position handler-position)
|
||||
[:& path-handler {:point position
|
||||
:handler handler-position
|
||||
:index index
|
||||
:prefix prefix
|
||||
:zoom zoom
|
||||
:hover? handler-hover?
|
||||
:snap-angle? (and moving-handler? matching-handler?)
|
||||
:edit-mode edit-mode}]))))]
|
||||
[:& path-point {:position position
|
||||
:zoom zoom
|
||||
:edit-mode edit-mode
|
||||
|
@ -289,6 +315,6 @@
|
|||
(when show-snap?
|
||||
[:g.path-snap {:pointer-events "none"}
|
||||
[:& path-snap {:selected snap-selected
|
||||
:points snap-points
|
||||
:zoom zoom}]])]))
|
||||
:points snap-points
|
||||
:zoom zoom}]])]))
|
||||
|
||||
|
|
|
@ -180,3 +180,27 @@
|
|||
[content point]
|
||||
(->> (d/enumerate content)
|
||||
(filterv (fn [[idx cmd]] (= (command->point cmd) point)))))
|
||||
|
||||
|
||||
(defn prefix->coords [prefix]
|
||||
(case prefix
|
||||
:c1 [:c1x :c1y]
|
||||
:c2 [:c2x :c2y]
|
||||
nil))
|
||||
|
||||
(defn handler->point [content index prefix]
|
||||
(when (and (some? index)
|
||||
(some? prefix)
|
||||
(contains? content index))
|
||||
(let [[cx cy :as coords] (prefix->coords prefix)]
|
||||
(if (= :curve-to (get-in content [index :command]))
|
||||
(gpt/point (get-in content [index :params cx])
|
||||
(get-in content [index :params cy]))
|
||||
|
||||
(gpt/point (get-in content [index :params :x])
|
||||
(get-in content [index :params :y]))))))
|
||||
|
||||
(defn handler->node [content index prefix]
|
||||
(if (= prefix :c1)
|
||||
(command->point (get content (dec index)))
|
||||
(command->point (get content index))))
|
||||
|
|
Loading…
Add table
Reference in a new issue