diff --git a/common/app/common/geom/shapes/path.cljc b/common/app/common/geom/shapes/path.cljc index eaa177254..33c041cee 100644 --- a/common/app/common/geom/shapes/path.cljc +++ b/common/app/common/geom/shapes/path.cljc @@ -233,7 +233,9 @@ (loop [t1 0 t2 1] (if (<= (mth/abs (- t1 t2)) path-closest-point-accuracy) - (curve-values start end h1 h2 t1) + (-> (curve-values start end h1 h2 t1) + ;; store the segment info + (with-meta {:t t1 :from-p start :to-p end})) (let [ht (+ t1 (/ (- t2 t1) 2)) ht1 (+ t1 (/ (- t2 t1) 4)) @@ -260,21 +262,18 @@ "Point on line" [position from-p to-p] - (let [{v1x :x v1y :y} from-p - {v2x :x v2y :y} to-p - {px :x py :y} position - - e1 (gpt/point (- v2x v1x) (- v2y v1y)) - e2 (gpt/point (- px v1x) (- py v1y)) + (let [e1 (gpt/to-vec from-p to-p ) + e2 (gpt/to-vec from-p position) len2 (+ (mth/sq (:x e1)) (mth/sq (:y e1))) - val-dp (/ (gpt/dot e1 e2) len2)] + t (/ (gpt/dot e1 e2) len2)] + + (if (and (>= t 0) (<= t 1) (not (mth/almost-zero? len2))) + (-> (gpt/add from-p (gpt/scale e1 t)) + (with-meta {:t t + :from-p from-p + :to-p to-p})) - (if (and (>= val-dp 0) - (<= val-dp 1) - (not (mth/almost-zero? len2))) - (gpt/point (+ v1x (* val-dp (:x e1))) - (+ v1y (* val-dp (:y e1)))) ;; There is no perpendicular projection in the line so the closest ;; point will be one of the extremes (if (<= (gpt/distance position from-p) (gpt/distance position to-p)) @@ -286,20 +285,20 @@ [shape position] (let [point+distance (fn [[cur-cmd prev-cmd]] - (let [point + (let [from-p (command->point prev-cmd) + to-p (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 - (command->point prev-cmd) - (command->point cur-cmd)) - :curve-to (curve-closest-point - position - (command->point prev-cmd) - (command->point cur-cmd) - (gpt/point (get-in cur-cmd [:params :c1x]) - (get-in cur-cmd [:params :c1y])) - (gpt/point (get-in cur-cmd [:params :c2x]) - (get-in cur-cmd [:params :c2y]))) + :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)]))) diff --git a/frontend/src/app/main/data/workspace/path.cljs b/frontend/src/app/main/data/workspace/path.cljs index e4c04a5a7..7dd1a6d5d 100644 --- a/frontend/src/app/main/data/workspace/path.cljs +++ b/frontend/src/app/main/data/workspace/path.cljs @@ -22,6 +22,7 @@ (d/export edition/start-move-handler) (d/export edition/start-move-path-point) (d/export edition/start-path-edit) +(d/export edition/create-node-at-position) ;; Selection (d/export selection/handle-selection) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 0d07756ba..60335af17 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -234,3 +234,15 @@ (update [_ state] (let [id (get-in state [:workspace-local :edition])] (update state :workspace-local dissoc :edit-path id))))) + +(defn create-node-at-position + [{:keys [from-p to-p t]}] + (ptk/reify ::create-node-at-position + ptk/UpdateEvent + (update [_ state] + (let [id (st/get-path-id state)] + (update-in state (st/get-path state :content) ugp/split-segments #{from-p to-p} t))) + + ptk/WatchEvent + (watch [_ state stream] + (rx/of (changes/save-path-content))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index 150e66b31..ea81ec037 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -24,7 +24,7 @@ [rumext.alpha :as mf]) (:import goog.events.EventType)) -(mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path? last-p?]}] +(mf/defc path-point [{:keys [position zoom edit-mode hover? selected? preview? start-path? last-p? new-point?]}] (let [{:keys [x y]} position on-enter @@ -40,6 +40,9 @@ (dom/stop-propagation event) (dom/prevent-default event) + (when (and new-point? (some? (meta position))) + (st/emit! (drp/create-node-at-position (meta position)))) + (let [shift? (kbd/shift? event)] (cond (= edit-mode :move) @@ -190,6 +193,8 @@ last-p (->> content last ugp/command->point) handlers (ugp/content->handlers content) + start-p? (not (some? last-point)) + [snap-selected snap-points] (cond (some? drag-handler) [#{drag-handler} points] @@ -199,7 +204,9 @@ [(->> selected-points (map base->point) (into #{})) (->> points (remove selected-points) (into #{}))]) - show-snap? (and snap-toggled (or (some? drag-handler) (some? preview) (some? moving-handler) moving-nodes)) + show-snap? (and snap-toggled + (empty? hover-points) + (or (some? drag-handler) (some? preview) (some? moving-handler) moving-nodes)) handle-double-click-outside (fn [event] @@ -213,6 +220,13 @@ #(doseq [key keys] (events/unlistenByKey key))))) + (hooks/use-stream + ms/mouse-position + (mf/deps shape zoom) + (fn [position] + (when-let [point (gshp/path-closest-point shape position)] + (reset! hover-point (when (< (gpt/distance position point) (/ 10 zoom)) point))))) + [:g.path-editor {:ref editor-ref} (when (and preview (not drag-handler)) [:& path-preview {:command preview @@ -228,13 +242,15 @@ (when @hover-point [:g.hover-point [:& path-point {:position @hover-point + :edit-mode edit-mode + :new-point? true + :start-path? start-p? :zoom zoom}]]) (for [position points] (let [point-selected? (contains? selected-points (get point->base position)) point-hover? (contains? hover-points (get point->base position)) - last-p? (= last-point (get point->base position)) - start-p? (not (some? last-point))] + last-p? (= last-point (get point->base position))] [:g.path-node [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 3a0c42280..fd8b79170 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -98,6 +98,7 @@ drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode]))) (and (some? drawing-obj) (= :path (:type drawing-obj)))) text-editing? (and edition (= :text (get-in objects [edition :type]))) + path-editing? (and edition (= :path (get-in objects [edition :type]))) on-click (actions/on-click hover selected edition drawing-path? drawing-tool) on-context-menu (actions/on-context-menu hover) @@ -132,11 +133,12 @@ show-snap-distance? (and (contains? layout :dynamic-alignment) (= transform :move) (not (empty? selected))) show-snap-points? (and (contains? layout :dynamic-alignment) (or drawing-obj transform)) show-selrect? (and selrect (empty? drawing)) + show-measures? (and (not transform) (not path-editing?) show-distances?) ] (hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?) (hooks/setup-viewport-size viewport-ref) - (hooks/setup-cursor cursor alt? panning drawing-tool drawing-path?) + (hooks/setup-cursor cursor alt? panning drawing-tool drawing-path? path-editing?) (hooks/setup-resize layout viewport-ref) (hooks/setup-keyboard alt? ctrl?) (hooks/setup-hover-shapes page-id move-stream selected objects transform selected ctrl? hover hover-ids) @@ -224,7 +226,7 @@ :disable-handlers (or drawing-tool edition) :on-move-selected on-move-selected}]) - (when (and (not transform) show-distances?) + (when show-measures? [:& msr/measurement {:bounds vbox :selected-shapes selected-shapes diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index f2c3ae75b..12abb9853 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -58,23 +58,23 @@ ;; We schedule the event so it fires after `initialize-page` event (timers/schedule #(st/emit! (dw/initialize-viewport size))))))) -(defn setup-cursor [cursor alt? panning drawing-tool drawing-path?] +(defn setup-cursor [cursor alt? panning drawing-tool drawing-path? path-editing?] (mf/use-effect - (mf/deps @cursor @alt? panning drawing-tool drawing-path?) + (mf/deps @cursor @alt? panning drawing-tool drawing-path? path-editing?) (fn [] (let [new-cursor (cond - panning (utils/get-cursor :hand) - (= drawing-tool :comments) (utils/get-cursor :comments) - (= drawing-tool :frame) (utils/get-cursor :create-artboard) - (= drawing-tool :rect) (utils/get-cursor :create-rectangle) - (= drawing-tool :circle) (utils/get-cursor :create-ellipse) + panning (utils/get-cursor :hand) + (= drawing-tool :comments) (utils/get-cursor :comments) + (= drawing-tool :frame) (utils/get-cursor :create-artboard) + (= drawing-tool :rect) (utils/get-cursor :create-rectangle) + (= drawing-tool :circle) (utils/get-cursor :create-ellipse) (or (= drawing-tool :path) - drawing-path?) (utils/get-cursor :pen) - (= drawing-tool :curve) (utils/get-cursor :pencil) - drawing-tool (utils/get-cursor :create-shape) - @alt? (utils/get-cursor :duplicate) - :else (utils/get-cursor :pointer-inner))] + drawing-path?) (utils/get-cursor :pen) + (= drawing-tool :curve) (utils/get-cursor :pencil) + drawing-tool (utils/get-cursor :create-shape) + (and @alt? (not path-editing?)) (utils/get-cursor :duplicate) + :else (utils/get-cursor :pointer-inner))] (when (not= @cursor new-cursor) (reset! cursor new-cursor))))))