diff --git a/common/app/common/geom/point.cljc b/common/app/common/geom/point.cljc index 1f97b3174..ef0139f21 100644 --- a/common/app/common/geom/point.cljc +++ b/common/app/common/geom/point.cljc @@ -55,6 +55,11 @@ ;;(assert (not (nil? 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 "Returns the addition of the supplied value to both coordinates of the point as a new point." diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs index aed97a83e..9e31ee460 100644 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ b/frontend/src/app/main/data/workspace/drawing/path.cljs @@ -20,6 +20,7 @@ [app.common.data :as cd] [app.util.geom.path :as ugp] [app.main.streams :as ms] + [app.main.store :as st] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing.common :as common] [app.common.geom.shapes.path :as gsp])) @@ -67,7 +68,7 @@ ;; CONSTANTS (defonce enter-keycode 13) -(defonce drag-threshold 2) +(defonce drag-threshold 5) ;; PRIVATE METHODS @@ -96,6 +97,25 @@ points (gsh/rect->points 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 "Calculates the next-node to be inserted." [shape position prev-point prev-handler] @@ -177,6 +197,21 @@ (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 (defn init-path [] @@ -190,24 +225,31 @@ (-> 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/UpdateEvent (update [_ 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)) {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id]) command (next-node shape position last-point prev-handler)] (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/UpdateEvent (update [_ state] (let [id (get-path-id state) - position (gpt/point x y) - {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])] + fix-angle? shift? + {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id]) + position (cond-> (gpt/point x y) + fix-angle? (position-fixed-angle last-point)) + ] (if-not (= last-point position) (-> state (assoc-in [:workspace-local :edit-path id :last-point] position) @@ -301,16 +343,12 @@ (rx/concat (rx/of (add-node position)) - - (->> position-stream - (rx/filter (dragging? start-position zoom)) - (rx/take 1) - (rx/merge-map - #(rx/concat - (rx/of (start-drag-handler)) - drag-events-stream - (rx/of (finish-drag)) - (rx/of (close-path-drag-end))))) + (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"))))))) (defn close-path-drag-end [] @@ -338,16 +376,21 @@ (ptk/reify ::start-path-from-point ptk/WatchEvent (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? %)))) 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))))))) + (rx/concat + (rx/of (add-node position)) + (drag-stream + (rx/concat + (rx/of (start-drag-handler)) + drag-events + (rx/of (finish-drag))))))))) (defn make-corner [] (ptk/reify ::make-corner @@ -389,14 +432,6 @@ ;; 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 [stream down-event zoom] (let [mouse-up (->> stream (rx/filter #(or (end-path-event? %) @@ -407,16 +442,11 @@ (rx/concat (rx/of (add-node down-event)) - - (->> ms/mouse-position - (rx/take-until mouse-up) - (rx/filter (dragging? (gpt/point down-event) zoom)) - (rx/take 1) - (rx/merge-map - #(rx/concat - (rx/of (start-drag-handler)) - drag-events - (rx/of (finish-drag)))))))) + (drag-stream + (rx/concat + (rx/of (start-drag-handler)) + drag-events + (rx/of (finish-drag))))))) (defn make-node-events-stream [stream] @@ -444,7 +474,7 @@ ;; Mouse move preview mousemove-events - (->> ms/mouse-position + (->> (position-stream) (rx/take-until end-path-events) (rx/map #(preview-next-point %))) @@ -452,7 +482,7 @@ mousedown-events (->> mouse-down (rx/take-until end-path-events) - (rx/with-latest merge ms/mouse-position) + (rx/with-latest merge (position-stream)) ;; We change to the stream that emits the first event (rx/switch-map @@ -608,22 +638,14 @@ (watch [_ state stream] (let [start-position @ms/mouse-position stopper (->> stream (rx/filter ms/mouse-up?)) - zoom (get-in state [:workspace-local :zoom]) + zoom (get-in state [:workspace-local :zoom])] - move-point-stream - (fn [] (rx/concat - (->> ms/mouse-position - (rx/take-until stopper) - (rx/map #(move-path-point position %))) - (rx/of (apply-content-modifiers))))] - - (->> ms/mouse-position - (rx/take-until stopper) - (rx/filter (dragging? start-position zoom)) - (rx/take 1) - (rx/merge-map #(move-point-stream))) - - )))) + (drag-stream + (rx/concat + (->> ms/mouse-position + (rx/take-until stopper) + (rx/map #(move-path-point position %))) + (rx/of (apply-content-modifiers)))))))) (defn start-move-handler [index prefix] @@ -636,21 +658,21 @@ 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 index cy] 0)] - (rx/concat - (->> ms/mouse-position - (rx/take-until (->> stream (rx/filter ms/mouse-up?))) - (rx/with-latest vector ms/mouse-position-alt) - (rx/map - (fn [[pos alt?]] - (modify-handler - 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)))))))) + (drag-stream + (rx/concat + (->> ms/mouse-position + (rx/take-until (->> stream (rx/filter ms/mouse-up?))) + (rx/with-latest vector ms/mouse-position-alt) + (rx/map + (fn [[pos alt?]] + (modify-handler + 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))))))))) (defn start-draw-mode [] (ptk/reify ::start-draw-mode diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index d67f40e14..9308a5a1e 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -93,7 +93,7 @@ {:alt (t locale "workspace.toolbar.path") :class (when (= selected-drawtool :path) "selected") :on-click (partial select-drawtool :path)} - i/curve] + i/pen] [:li.tooltip.tooltip-right {:alt (t locale "workspace.toolbar.comments") diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index 9106df8ae..72a327f36 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -169,8 +169,7 @@ {:cx x :cy y :r (if (or selected? hover?) (/ 3.5 zoom) (/ 3 zoom)) - :style {:cursor (when (= edit-mode :draw) cur/pen-node) - :stroke-width (/ 1 zoom) + :style {:stroke-width (/ 1 zoom) :stroke (cond (or selected? hover?) black-color preview? secondary-color :else primary-color) @@ -183,7 +182,10 @@ :on-mouse-down on-mouse-down :on-mouse-enter on-enter :on-mouse-leave on-leave - :style {:fill "transparent"}}]])) + :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]}] (when (and point handler) @@ -227,8 +229,7 @@ :width (/ 6 zoom) :height (/ 6 zoom) - :style {:cursor cur/pointer-move - :stroke-width (/ 1 zoom) + :style {:stroke-width (/ 1 zoom) :stroke (cond (or selected? hover?) black-color :else primary-color) :fill (cond selected? primary-color @@ -240,7 +241,8 @@ :on-mouse-down on-mouse-down :on-mouse-enter on-enter :on-mouse-leave on-leave - :style {:fill "transparent"}}]]))) + :style {:cursor (when (= edit-mode :move) cur/pointer-move) + :fill "transparent"}}]]))) (mf/defc path-preview [{:keys [zoom command from]}] [:g.preview {:style {:pointer-events "none"}}