♻️ Refactor path-editor component

This commit is contained in:
Andrey Antukh 2025-04-04 11:19:02 +02:00
parent 93199e1a70
commit 3ca76c9ef7
2 changed files with 89 additions and 37 deletions

View file

@ -324,6 +324,43 @@
(reduce find-min-point) (reduce find-min-point)
(first)))) (first))))
(defn closest-point
"Given a path and a position"
[content position]
(let [point+distance
(fn [[cur-cmd prev-cmd]]
(let [from-p (helpers/command->point prev-cmd)
to-p (helpers/command->point cur-cmd)
h1 (gpt/point (get-in cur-cmd [:params :c1x])
(get-in cur-cmd [:params :c1y]))
h2 (gpt/point (get-in cur-cmd [:params :c2x])
(get-in cur-cmd [:params :c2y]))
point
(case (:command cur-cmd)
:line-to
(line-closest-point position from-p to-p)
:curve-to
(curve-closest-point position from-p to-p h1 h2)
nil)]
(when point
[point (gpt/distance point position)])))
find-min-point
(fn [[min-p min-dist :as acc] [cur-p cur-dist :as cur]]
(if (and (some? acc) (or (not cur) (<= min-dist cur-dist)))
[min-p min-dist]
[cur-p cur-dist]))]
(->> content
(d/with-prev)
(map point+distance)
(reduce find-min-point)
(first))))
(defn- remove-line-curves (defn- remove-line-curves
"Remove all curves that have both handlers in the same position that the "Remove all curves that have both handlers in the same position that the
beginning and end points. This makes them really line-to commands" beginning and end points. This makes them really line-to commands"

View file

@ -23,8 +23,7 @@
[app.util.path.format :as upf] [app.util.path.format :as upf]
[clojure.set :refer [map-invert]] [clojure.set :refer [map-invert]]
[goog.events :as events] [goog.events :as events]
[rumext.v2 :as mf]) [rumext.v2 :as mf]))
(:import goog.events.EventType))
(def point-radius 5) (def point-radius 5)
(def point-radius-selected 4) (def point-radius-selected 4)
@ -257,9 +256,9 @@
(mf/defc path-editor* (mf/defc path-editor*
[{:keys [shape zoom]}] [{:keys [shape zoom]}]
(let [editor-ref (mf/use-ref nil) (let [editor-ref (mf/use-ref nil)
edit-path-ref (pc/make-edit-path-ref (:id shape)) edit-path-ref (pc/make-edit-path-ref (:id shape))
hover-point (mf/use-state nil) hover-point (mf/use-state nil)
{:keys [edit-mode {:keys [edit-mode
drag-handler drag-handler
@ -286,7 +285,8 @@
(path.segment/content->points base-content)) (path.segment/content->points base-content))
content content
(path/apply-content-modifiers base-content content-modifiers) (mf/with-memo [base-content content-modifiers]
(path/apply-content-modifiers base-content content-modifiers))
content-points content-points
(mf/with-memo [content] (mf/with-memo [content]
@ -295,37 +295,37 @@
point->base (->> (map hash-map content-points base-points) (reduce merge)) point->base (->> (map hash-map content-points base-points) (reduce merge))
base->point (map-invert point->base) base->point (map-invert point->base)
points (into #{} content-points) points
(mf/with-memo [content-points]
(into #{} content-points))
last-p (->> content last path.segment/get-point) last-p (->> content last path.segment/get-point)
handlers (path.segment/content->handlers content)
is-path-start (not (some? last-point)) handlers
(mf/with-memo [content]
(path.segment/content->handlers content))
is-path-start
(not (some? last-point))
show-snap? show-snap?
(and ^boolean snap-toggled (and ^boolean snap-toggled
(or (some? drag-handler) (or (some? drag-handler)
(some? preview) (some? preview)
(some? moving-handler) (some? moving-handler)
moving-nodes)) moving-nodes))]
handle-double-click-outside (mf/with-layout-effect [edit-mode]
(fn [_] (let [key (events/listen (dom/get-root) "dblclick"
(when (= edit-mode :move) #(when (= edit-mode :move)
(st/emit! :interrupt)))] (st/emit! :interrupt)))]
#(events/unlistenByKey key)))
(mf/use-layout-effect
(mf/deps edit-mode)
(fn []
(let [keys [(events/listen (dom/get-root) EventType.DBLCLICK handle-double-click-outside)]]
#(doseq [key keys]
(events/unlistenByKey key)))))
(hooks/use-stream (hooks/use-stream
ms/mouse-position ms/mouse-position
(mf/deps shape zoom) (mf/deps base-content zoom)
(fn [position] (fn [position]
(when-let [point (path.segment/path-closest-point shape position)] (when-let [point (path.segment/closest-point base-content position)]
(reset! hover-point (when (< (gpt/distance position point) (/ 10 zoom)) point))))) (reset! hover-point (when (< (gpt/distance position point) (/ 10 zoom)) point)))))
[:g.path-editor {:ref editor-ref} [:g.path-editor {:ref editor-ref}
@ -353,31 +353,45 @@
:is-start-path is-path-start :is-start-path is-path-start
:zoom zoom}]]) :zoom zoom}]])
(for [[index position] (d/enumerate points)] (for [position points]
(let [show-handler? (let [pos-x (dm/get-prop position :x)
pos-y (dm/get-prop position :y)
show-handler?
(fn [[index prefix]] (fn [[index prefix]]
;; FIXME: handler->point is executed twice for each
;; render, this can be optimized
(let [handler-position (path.segment/handler->point content index prefix)] (let [handler-position (path.segment/handler->point content index prefix)]
(not= position handler-position))) (not= position handler-position)))
pos-handlers (get handlers position) position-handlers
point-selected? (contains? selected-points (get point->base position)) (->> (get handlers position)
point-hover? (contains? hover-points (get point->base position)) (filter show-handler?)
is-last (= last-point (get point->base position)) (not-empty))
pos-handlers (->> pos-handlers (filter show-handler?)) point-selected?
is-curve (boolean (seq pos-handlers))] (contains? selected-points (get point->base position))
[:g.path-node {:key (dm/str index "-" (:x position) "-" (:y position))} point-hover?
(contains? hover-points (get point->base position))
is-last
(= last-point (get point->base position))
is-curve
(boolean position-handlers)]
[:g.path-node {:key (dm/str pos-x "-" pos-y)}
[:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
(for [[hindex prefix] pos-handlers] (for [[hindex prefix] position-handlers]
(let [handler-position (path.segment/handler->point content hindex prefix) (let [handler-position (path.segment/handler->point content hindex prefix)
handler-hover? (contains? hover-handlers [hindex prefix]) handler-hover? (contains? hover-handlers [hindex prefix])
moving-handler? (= handler-position moving-handler) moving-handler? (= handler-position moving-handler)
matching-handler? (matching-handler? content position pos-handlers)] matching-handler? (matching-handler? content position position-handlers)]
(when (and position handler-position) (when (and position handler-position)
[:> path-handler* [:> path-handler*
{:key (dm/str (dm/str index "-" (:x position) "-" (:y position)) "-" hindex "-" (d/name prefix)) {:key (dm/str hindex "-" (d/name prefix))
:point position :point position
:handler handler-position :handler handler-position
:index hindex :index hindex
@ -386,6 +400,7 @@
:is-hover handler-hover? :is-hover handler-hover?
:snap-angle (and moving-handler? matching-handler?) :snap-angle (and moving-handler? matching-handler?)
:edit-mode edit-mode}])))] :edit-mode edit-mode}])))]
[:> path-point* {:position position [:> path-point* {:position position
:zoom zoom :zoom zoom
:edit-mode edit-mode :edit-mode edit-mode