diff --git a/common/app/common/geom/shapes/path.cljc b/common/app/common/geom/shapes/path.cljc index 5f8e2d8f8..1c9f26466 100644 --- a/common/app/common/geom/shapes/path.cljc +++ b/common/app/common/geom/shapes/path.cljc @@ -146,3 +146,18 @@ (not (nil? c2x)) (set-tr :c2x :c2y)))] (mapv #(update % :params transform-params) content))) + +(defn apply-content-modifiers [content modifiers] + (let [red-fn (fn [content [index params]] + (if (contains? content index) + (cond-> content + (:x params) (update-in [index :params :x] + (:x params)) + (:y params) (update-in [index :params :y] + (:y params)) + + (:c1x params) (update-in [index :params :c1x] + (:c1x params)) + (:c1y params) (update-in [index :params :c1y] + (:c1y params)) + + (:c2x params) (update-in [index :params :c2x] + (:c2x params)) + (:c2y params) (update-in [index :params :c2y] + (:c2y params))) + content))] + (reduce red-fn content modifiers))) diff --git a/frontend/resources/images/icons/nodes-add.svg b/frontend/resources/images/icons/nodes-add.svg new file mode 100644 index 000000000..9c5ecf93a --- /dev/null +++ b/frontend/resources/images/icons/nodes-add.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/nodes-corner.svg b/frontend/resources/images/icons/nodes-corner.svg new file mode 100644 index 000000000..295e316ab --- /dev/null +++ b/frontend/resources/images/icons/nodes-corner.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/nodes-curve.svg b/frontend/resources/images/icons/nodes-curve.svg new file mode 100644 index 000000000..b12913fc5 --- /dev/null +++ b/frontend/resources/images/icons/nodes-curve.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/nodes-join.svg b/frontend/resources/images/icons/nodes-join.svg new file mode 100644 index 000000000..551451cb9 --- /dev/null +++ b/frontend/resources/images/icons/nodes-join.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/nodes-merge.svg b/frontend/resources/images/icons/nodes-merge.svg new file mode 100644 index 000000000..5e0d9c336 --- /dev/null +++ b/frontend/resources/images/icons/nodes-merge.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/nodes-remove.svg b/frontend/resources/images/icons/nodes-remove.svg new file mode 100644 index 000000000..e00ecd534 --- /dev/null +++ b/frontend/resources/images/icons/nodes-remove.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/nodes-separate.svg b/frontend/resources/images/icons/nodes-separate.svg new file mode 100644 index 000000000..4e188e3cb --- /dev/null +++ b/frontend/resources/images/icons/nodes-separate.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/nodes-snap.svg b/frontend/resources/images/icons/nodes-snap.svg new file mode 100644 index 000000000..1bd5edac4 --- /dev/null +++ b/frontend/resources/images/icons/nodes-snap.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index ccccec01e..69282ab40 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -225,3 +225,76 @@ padding: $x-small; } } + +.viewport-actions { + position: absolute; + margin-left: auto; + width: 100%; + margin-top: 2rem; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + .path-actions { + display: flex; + flex-direction: row; + background: white; + border-radius: 3px; + padding: 0.5rem; + border: 1px solid $color-gray-20; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + } + + .viewport-actions-group { + display: flex; + flex-direction: row; + border-right: 1px solid $color-gray-20; + } + + .viewport-actions-entry { + width: 27px; + height: 20px; + margin: 0 0.25rem; + cursor: pointer; + + svg { + width: 27px; + height: 20px; + } + + &:hover svg { + fill: $color-primary; + } + + &.disabled { + opacity: 0.3; + + &:hover svg { + fill: initial; + } + } + } + + .viewport-actions-entry-wide { + width: 27px; + height: 20px; + + svg { + width: 27px; + height: 20px; + } + } + + .path-actions > :first-child .viewport-actions-entry { + margin-left: 0; + } + + .path-actions > :last-child { + border: none; + } + + .path-actions > :last-child .viewport-actions-entry { + margin-right: 0; + } +} diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 70cf54a20..112f98afa 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -32,6 +32,7 @@ [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.drawing :as dwd] + [app.main.data.workspace.drawing.path :as dwdp] [app.main.repo :as rp] [app.main.store :as st] [app.main.streams :as ms] @@ -1629,6 +1630,8 @@ (def add-shape dwc/add-shape) (def start-edition-mode dwc/start-edition-mode) +(defn start-path-edit [id] (dwdp/start-path-edit id)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Shortcuts diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs index 63b0668fc..7987488a2 100644 --- a/frontend/src/app/main/data/workspace/drawing.cljs +++ b/frontend/src/app/main/data/workspace/drawing.cljs @@ -99,6 +99,6 @@ (box/handle-drawing-box)))))) ;; Export -(def close-drawing-path path/close-drawing-path) +#_(def close-drawing-path path/close-drawing-path) diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs index 420ddda6f..4d956d7b6 100644 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ b/frontend/src/app/main/data/workspace/drawing/path.cljs @@ -17,7 +17,9 @@ [app.util.data :as d] [app.util.geom.path :as ugp] [app.main.streams :as ms] - [app.main.data.workspace.drawing.common :as common])) + [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.drawing.common :as common] + [app.common.geom.shapes.path :as gsp])) ;;;; @@ -149,14 +151,14 @@ (update [_ state] (-> state (assoc-in [:workspace-drawing :object :initialized?] true) - (assoc-in [:workspace-drawing :object :last-point] nil))))) + (assoc-in [:workspace-local :edit-path :last-point] nil))))) (defn finish-path [] (ptk/reify ::finish-path ptk/UpdateEvent (update [_ state] (-> state - (assoc-in [:workspace-drawing :object :last-point] nil) + (update :workspace-local dissoc :edit-path) (update-in [:workspace-drawing :object] calculate-selrect))))) (defn preview-next-point [{:keys [x y]}] @@ -164,9 +166,9 @@ ptk/UpdateEvent (update [_ state] (let [position (gpt/point x y) - {:keys [last-point prev-handler] :as shape} (get-in state [:workspace-drawing :object]) + {:keys [last-point prev-handler] :as shape} (get-in state [:workspace-local :edit-path]) command (next-node shape position last-point prev-handler)] - (assoc-in state [:workspace-drawing :object :preview] command))))) + (assoc-in state [:workspace-local :edit-path :preview] command))))) (defn add-node [{:keys [x y]}] (ptk/reify ::add-node @@ -174,14 +176,11 @@ (update [_ state] (let [position (gpt/point x y) - {:keys [last-point prev-handler]} (get-in state [:workspace-drawing :object])] - (update-in - state - [:workspace-drawing :object] - #(-> % - (append-node position last-point prev-handler) - (assoc :last-point position) - (dissoc :prev-handler))))))) + {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path])] + (-> state + (assoc-in [:workspace-local :edit-path :last-point] position) + (update-in [:workspace-local :edit-path] dissoc :prev-handler) + (update-in [:workspace-drawing :object] append-node position last-point prev-handler)))))) (defn drag-handler [{:keys [x y]}] (ptk/reify ::drag-handler @@ -193,16 +192,16 @@ index (dec (count (:content shape)))] (-> state (update-in [:workspace-drawing :object] move-handler index :next true position) - (assoc-in [:workspace-drawing :object :drag-handler] position)))))) + (assoc-in [:workspace-local :edit-path :drag-handler] position)))))) (defn finish-drag [] (ptk/reify ::finish-drag ptk/UpdateEvent (update [_ state] - (let [handler (get-in state [:workspace-drawing :object :drag-handler])] + (let [handler (get-in state [:workspace-local :edit-path :drag-handler])] (-> state - (update-in [:workspace-drawing :object] dissoc :drag-handler) - (assoc-in [:workspace-drawing :object :prev-handler] handler)))))) + (update-in [:workspace-local :edit-path] dissoc :drag-handler) + (assoc-in [:workspace-local :edit-path :prev-handler] handler)))))) (defn make-click-stream [stream down-event] @@ -238,7 +237,6 @@ ptk/WatchEvent (watch [_ state stream] - ;; clicks stream<[MouseEvent, Position]> (let [mouse-down (->> stream (rx/filter ms/mouse-down?)) finish-events (->> stream (rx/filter finish-event?)) @@ -266,10 +264,7 @@ (rx/merge mousemove-events mousedown-events) (rx/of (finish-path)) - (rx/of common/handle-finish-drawing))) - - - ))) + (rx/of common/handle-finish-drawing)))))) #_(def handle-drawing-path (ptk/reify ::handle-drawing-path @@ -331,7 +326,7 @@ (rx/of finish-drawing-path common/handle-finish-drawing))))))) -(defn close-drawing-path [] +#_(defn close-drawing-path [] (ptk/reify ::close-drawing-path ptk/UpdateEvent (update [_ state] @@ -340,3 +335,121 @@ ptk/WatchEvent (watch [_ state stream] (rx/of ::end-path-drawing)))) + + +(defn stop-path-edit [] + (ptk/reify ::stop-path-edit + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local dissoc :edit-path)))) + +(defn start-path-edit + [id] + (ptk/reify ::start-path-edit + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :edit-path] {})) + + ptk/WatchEvent + (watch [_ state stream] + (->> stream + (rx/filter #(= % :interrupt)) + (rx/take 1) + (rx/map #(stop-path-edit)))))) + +(defn modify-point [index dx dy] + (ptk/reify ::modify-point + + ptk/UpdateEvent + (update [_ state] + + (-> state + (update-in [:workspace-local :edit-path :content-modifiers (inc index)] assoc + :c1x dx :c1y dy) + (update-in [:workspace-local :edit-path :content-modifiers index] assoc + :x dx :y dy :c2x dx :c2y dy) + )))) + +(defn modify-handler [index type dx dy] + (ptk/reify ::modify-point + ptk/UpdateEvent + (update [_ state] + (let [s1 (if (= type :prev) -1 1) + s2 (if (= type :prev) 1 -1)] + (-> state + (update-in [:workspace-local :edit-path :content-modifiers (inc index)] assoc + :c1x (* s1 dx) :c1y (* s1 dy)) + (update-in [:workspace-local :edit-path :content-modifiers index] assoc + :c2x (* s2 dx) :c2y (* s2 dy) )) + )))) + +(defn apply-content-modifiers [] + (ptk/reify ::apply-content-modifiers + ;;ptk/UpdateEvent + ;;(update [_ state] + ;; (update-in state [:workspace-local :edit-path] dissoc :content-modifiers)) + + ptk/WatchEvent + (watch [_ state stream] + (let [id (get-in state [:workspace-local :edition]) + page-id (:current-page-id state) + old-content (get-in state [:workspace-data :pages-index page-id :objects id :content]) + old-selrect (get-in state [:workspace-data :pages-index page-id :objects id :selrect]) + content-modifiers (get-in state [:workspace-local :edit-path :content-modifiers]) + new-content (gsp/apply-content-modifiers old-content content-modifiers) + new-selrect (gsh/content->selrect new-content) + 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}]}] + + 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}]}]] + + (rx/of (dwc/commit-changes rch uch {:commit-local? true}) + (fn [state] (update-in state [:workspace-local :edit-path] dissoc :content-modifiers))))))) + +(defn start-move-path-point + [index] + (ptk/reify ::start-move-path-point + ptk/WatchEvent + (watch [_ state stream] + (let [start-point @ms/mouse-position + start-delta-x (get-in state [:workspace-local :edit-path :content-modifiers index :x] 0) + start-delta-y (get-in state [:workspace-local :edit-path :content-modifiers index :y] 0)] + (rx/concat + (->> ms/mouse-position + (rx/take-until (->> stream (rx/filter ms/mouse-up?))) + (rx/map #(modify-point + index + (+ start-delta-x (- (:x %) (:x start-point))) + (+ start-delta-y (- (:y %) (:y start-point)))))) + (rx/concat (rx/of (apply-content-modifiers))) + ))))) + +(defn start-move-handler + [index type] + (ptk/reify ::start-move-handler + ptk/WatchEvent + (watch [_ state stream] + (let [[cx cy] (if (= :prev type) [:c2x :c2y] [:c1x :c1y]) + cidx (if (= :prev type) index (inc index)) + + start-point @ms/mouse-position + start-delta-x (get-in state [:workspace-local :edit-path :content-modifiers cidx cx] 0) + start-delta-y (get-in state [:workspace-local :edit-path :content-modifiers cidx cy] 0)] + + (rx/concat + (->> ms/mouse-position + (rx/take-until (->> stream (rx/filter ms/mouse-up?))) + (rx/map #(modify-handler + index + type + (+ start-delta-x (- (:x %) (:x start-point))) + (+ start-delta-y (- (:y %) (:y start-point))))) + ) + (rx/concat (rx/of (apply-content-modifiers)))))))) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 5f9fc0705..0550c4a4a 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -128,6 +128,14 @@ (def checkbox-checked (icon-xref :checkbox-checked)) (def checkbox-unchecked (icon-xref :checkbox-unchecked)) (def code (icon-xref :code)) +(def nodes-add (icon-xref :nodes-add)) +(def nodes-corner (icon-xref :nodes-corner)) +(def nodes-curve (icon-xref :nodes-curve)) +(def nodes-join (icon-xref :nodes-join)) +(def nodes-merge (icon-xref :nodes-merge)) +(def nodes-remove (icon-xref :nodes-remove)) +(def nodes-separate (icon-xref :nodes-separate)) +(def nodes-snap (icon-xref :nodes-snap)) (def loader-pencil (mf/html diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index ea77a68b9..dfca19099 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -29,7 +29,7 @@ [app.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]] [app.main.ui.workspace.scroll :as scroll] [app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]] - [app.main.ui.workspace.viewport :refer [viewport coordinates]] + [app.main.ui.workspace.viewport :refer [viewport viewport-actions coordinates]] [app.util.dom :as dom] [beicon.core :as rx] [cuerdas.core :as str] @@ -65,6 +65,7 @@ (when (contains? layout :rules) [:& workspace-rules {:local local}]) + [:& viewport-actions] [:& viewport {:file file :local local :layout layout}]]] diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/selection.cljs index 120bcd543..5f9708a58 100644 --- a/frontend/src/app/main/ui/workspace/selection.cljs +++ b/frontend/src/app/main/ui/workspace/selection.cljs @@ -31,7 +31,8 @@ [app.common.geom.matrix :as gmt] [app.util.debug :refer [debug?]] [app.main.ui.workspace.shapes.outline :refer [outline]] - [app.main.ui.measurements :as msr])) + [app.main.ui.measurements :as msr] + [app.main.ui.workspace.shapes.path :refer [path-editor]])) (def rotation-handler-size 25) (def resize-point-radius 4) @@ -366,8 +367,13 @@ [:& text-edition-selection-handlers {:shape shape :zoom zoom :color color}] - (and (or (= type :path) - (= type :curve)) + + (= (= type :path) + (= edition (:id shape))) + [:& path-editor {:zoom zoom + :shape shape}] + + (and (= type :curve) (= edition (:id shape))) [:& path-edition-selection-handlers {:shape shape :zoom zoom diff --git a/frontend/src/app/main/ui/workspace/shapes/common.cljs b/frontend/src/app/main/ui/workspace/shapes/common.cljs index 302c0cf48..af268cd69 100644 --- a/frontend/src/app/main/ui/workspace/shapes/common.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/common.cljs @@ -23,6 +23,7 @@ (defn- on-mouse-down [event {:keys [id type] :as shape}] (let [selected @refs/selected-shapes + edition @refs/selected-edition selected? (contains? selected id) drawing? @refs/selected-drawing-tool button (.-which (.-nativeEvent event))] @@ -35,9 +36,8 @@ nil (= type :frame) - (when selected? - (dom/stop-propagation event) - (st/emit! (dw/start-move-selected))) + (do (dom/stop-propagation event) + (st/emit! (dw/start-move-selected))) :else (do @@ -50,7 +50,8 @@ (st/emit! (dw/deselect-all))) (st/emit! (dw/select-shape id)))) - (st/emit! (dw/start-move-selected))))))) + (when (not= edition id) + (st/emit! (dw/start-move-selected)))))))) (defn on-context-menu [event shape] diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index d1f761ab5..338a45f79 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -10,22 +10,40 @@ (ns app.main.ui.workspace.shapes.path (:require [rumext.alpha :as mf] - [app.common.data :as d] + [okulary.core :as l] + [app.util.data :as d] [app.util.dom :as dom] [app.util.timers :as ts] + [app.main.refs :as refs] [app.main.streams :as ms] [app.main.constants :as c] [app.main.refs :as refs] [app.main.store :as st] [app.main.data.workspace :as dw] [app.main.data.workspace.drawing :as dr] + [app.main.data.workspace.drawing.path :as drp] [app.main.ui.keyboard :as kbd] [app.main.ui.shapes.path :as path] [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.workspace.shapes.common :as common] [app.util.geom.path :as ugp] - [app.common.geom.shapes.path :as gsp])) + [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as gsp] + [app.main.ui.cursors :as cur] + [app.main.ui.icons :as i])) + +(def primary-color "#1FDEA7") +(def secondary-color "#DB00FF") +(def black-color "#000000") +(def white-color "#FFFFFF") +(def gray-color "#B1B2B5") + +(def edit-path-ref + (l/derived :edit-path refs/workspace-local)) + +(def content-modifiers-ref + (l/derived :content-modifiers edit-path-ref)) (mf/defc path-wrapper {::mf/wrap-props false} @@ -43,12 +61,15 @@ on-double-click (mf/use-callback (mf/deps shape) (fn [event] - (prn "?? PATH") - (when (and (not (::dr/initialized? shape)) (hover? (:id shape))) + (when (not (::dr/initialized? shape)) (do (dom/stop-propagation event) (dom/prevent-default event) - (st/emit! (dw/start-edition-mode (:id shape)))))))] + (st/emit! (dw/start-edition-mode (:id shape)) + (dw/start-path-edit (:id shape))))))) + + content-modifiers (mf/deref content-modifiers-ref) + shape (update shape :content gsp/apply-content-modifiers content-modifiers)] [:> shape-container {:shape shape :on-double-click on-double-click @@ -57,75 +78,142 @@ [:& path/path-shape {:shape shape :background? true}]])) +(mf/defc path-actions [{:keys [shape]}] + [:div.path-actions + [:div.viewport-actions-group + [:div.viewport-actions-entry i/nodes-add] + [:div.viewport-actions-entry i/nodes-remove]] -(mf/defc path-handler [{:keys [point handler zoom selected]}] + [:div.viewport-actions-group + [:div.viewport-actions-entry i/nodes-merge] + [:div.viewport-actions-entry i/nodes-join] + [:div.viewport-actions-entry i/nodes-separate]] + + [:div.viewport-actions-group + [:div.viewport-actions-entry i/nodes-corner] + [:div.viewport-actions-entry i/nodes-curve]] + + [:div.viewport-actions-group + [:div.viewport-actions-entry i/nodes-snap]]]) + + +(mf/defc path-preview [{:keys [zoom command from]}] + (when (not= :move-to (:command command)) + [:path {:style {:fill "transparent" + :stroke secondary-color + :stroke-width (/ 1 zoom)} + :d (ugp/content->path [{:command :move-to + :params {:x (:x from) + :y (:y from)}} + command])}])) + +(mf/defc path-point [{:keys [index position stroke-color fill-color zoom]}] + (let [{:keys [x y]} position + on-click (fn [event] + (dom/stop-propagation event) + (dom/prevent-default event)) + on-mouse-down (fn [event] + (dom/stop-propagation event) + (dom/prevent-default event) + (st/emit! (drp/start-move-path-point index)))] + [:circle + {:cx x + :cy y + :r (/ 3 zoom) + :on-click on-click + :on-mouse-down on-mouse-down + :style {:cursor cur/resize-alt + :stroke-width (/ 1 zoom) + :stroke (or stroke-color black-color) + :fill (or fill-color white-color)}}])) + +(mf/defc path-handler [{:keys [index point handler zoom selected type]}] (when (and point handler) - (let [{:keys [x y]} handler] - [:g.handler + (let [{:keys [x y]} handler + on-click (fn [event] + (dom/stop-propagation event) + (dom/prevent-default event)) + on-mouse-down (fn [event] + (dom/stop-propagation event) + (dom/prevent-default event) + (st/emit! (drp/start-move-handler index type)))] + [:g.handler {:class (name type)} [:line {:x1 (:x point) :y1 (:y point) :x2 x :y2 y - :style {:stroke "#B1B2B5" + :style {:stroke gray-color :stroke-width (/ 1 zoom)}}] [:rect {:x (- x (/ 3 zoom)) :y (- y (/ 3 zoom)) :width (/ 6 zoom) :height (/ 6 zoom) - :style {:stroke-width (/ 1 zoom) - :stroke (if selected "#000000" "#1FDEA7") - :fill (if selected "#1FDEA7" "#FFFFFF")}}]]))) + :on-click on-click + :on-mouse-down on-mouse-down + :style {:cursor cur/resize-alt + :stroke-width (/ 1 zoom) + :stroke (if selected black-color primary-color) + :fill (if selected primary-color white-color)}}]]))) (mf/defc path-editor [{:keys [shape zoom]}] - (let [points (gsp/content->points (:content shape)) - drag-handler (:drag-handler shape) - prev-handler (:prev-handler shape) - last-command (last (:content shape)) - selected false + (let [{:keys [content]} shape + {:keys [drag-handler prev-handler preview content-modifiers]} (mf/deref edit-path-ref) + content (gsp/apply-content-modifiers content content-modifiers) + points (gsp/content->points content) + last-command (last content) last-p (last points) - handlers (ugp/extract-handlers (:content shape)) - handlers (if (and prev-handler (not drag-handler)) - (conj handlers {:point last-p :prev prev-handler}) - handlers) - ] + selected false] [:g.path-editor - (when (and (:preview shape) (not (:drag-handler shape))) - [:* - [:path {:style {:fill "transparent" - :stroke "#DB00FF" - :stroke-width (/ 1 zoom)} - :d (ugp/content->path [{:command :move-to - :params {:x (:x last-p) - :y (:y last-p)}} - (:preview shape)])}] - [:circle - {:cx (-> shape :preview :params :x) - :cy (-> shape :preview :params :y) - :r (/ 3 zoom) - :style {:stroke-width (/ 1 zoom) - :stroke "#DB00FF" - :fill "#FFFFFF"}}]]) + (when (and preview (not drag-handler)) + [:g.preview {:style {:pointer-events "none"}} + [:& path-preview {:command preview + :from last-p + :zoom zoom}] + [:& path-point {:position (:params preview) + :fill-color secondary-color + :zoom zoom}]]) - (for [{:keys [point prev next]} handlers] - [:* - [:& path-handler {:point point - :handler prev - :zoom zoom - :type :prev - :selected false}] - [:& path-handler {:point point - :handler next - :zoom zoom - :type :next - :selected false}]]) + (for [[index [cmd next]] (d/enumerate (d/with-next content))] + (let [point (gpt/point (:params cmd))] + [:g.path-node + (when (= :curve-to (:command cmd)) + [:& path-handler {:point point + :handler (gpt/point (-> cmd :params :c2x) (-> cmd :params :c2y)) + :zoom zoom + :type :prev + :index index + :selected false}]) + + (when (= :curve-to (:command next)) + [:& path-handler {:point point + :handler (gpt/point (-> next :params :c1x) (-> next :params :c1y)) + :zoom zoom + :type :next + :index index + :selected false}]) + + (when (and (= index (dec (count content))) + prev-handler (not drag-handler)) + [:& path-handler {:point point + :handler prev-handler + :zoom zoom + :type :prev + :index index + :selected false}]) + + [:& path-point {:position point + :stroke-color (when-not selected primary-color) + :fill-color (when selected primary-color) + :index index + :zoom zoom}]])) (when drag-handler - [:* + [:g.drag-handler (when (not= :move-to (:command last-command)) [:& path-handler {:point last-p :handler (ugp/opposite-handler last-p drag-handler) @@ -136,14 +224,4 @@ :handler drag-handler :zoom zoom :type :drag - :selected false}]]) - - (for [{:keys [x y] :as point} points] - [:circle - {:cx x - :cy y - :r (/ 3 zoom) - :style {:stroke-width (/ 1 zoom) - :stroke (if selected "#000000" "#1FDEA7") - :fill (if selected "#1FDEA7" "#FFFFFF")} - }])])) + :selected false}]])])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a2e7461ac..547ceb36c 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -1,4 +1,4 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public +; This Source Code Form is subject to the terms of the Mozilla Public ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; @@ -52,7 +52,8 @@ [goog.events :as events] [potok.core :as ptk] [promesa.core :as p] - [rumext.alpha :as mf]) + [rumext.alpha :as mf] + [app.main.ui.workspace.shapes.path :refer [path-actions]]) (:import goog.events.EventType)) ;; --- Coordinates Widget @@ -222,7 +223,6 @@ drawing-obj (:object drawing) zoom (or zoom 1) - on-mouse-down (mf/use-callback (mf/deps drawing-tool edition) @@ -234,15 +234,13 @@ alt? (kbd/alt? event)] (st/emit! (ms/->MouseEvent :down ctrl? shift? alt?)) (cond - (and (= 1 (.-which event))) - + (and (= 1 (.-which event)) (not edition)) (if drawing-tool (when (not (#{:comments :path} drawing-tool)) (st/emit! (dd/start-drawing drawing-tool))) (st/emit! dw/handle-selection)) - (and (not edition) - (= 2 (.-which event))) + (and (= 2 (.-which event))) (handle-viewport-positioning viewport-ref))))) on-context-menu @@ -294,12 +292,16 @@ on-double-click (mf/use-callback + (mf/deps edition) (fn [event] (dom/stop-propagation event) (let [ctrl? (kbd/ctrl? event) shift? (kbd/shift? event) alt? (kbd/alt? event)] - (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?))))) + (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?)) + + (if edition + (st/emit! dw/clear-edition-mode))))) on-key-down (mf/use-callback @@ -610,3 +612,13 @@ (when (= options-mode :prototype) [:& interactions {:selected selected}])]])) + +(mf/defc viewport-actions [] + (let [edition (mf/deref refs/selected-edition) + selected (mf/deref refs/selected-objects) + shape (-> selected first)] + (when (and (= (count selected) 1) + (= (:id shape) edition) + (= :path (:type shape))) + [:div.viewport-actions + [:& path-actions {:shape shape}]]))) diff --git a/frontend/src/app/util/geom/path.cljs b/frontend/src/app/util/geom/path.cljs index 1da9ce3a0..e8d40fff9 100644 --- a/frontend/src/app/util/geom/path.cljs +++ b/frontend/src/app/util/geom/path.cljs @@ -213,9 +213,3 @@ opposite (gpt/add point (gpt/negate phv))] opposite)) -(defn extract-handlers [content] - (let [extract (fn [{param1 :params :as cmd1} {param2 :params :as cmd2}] - {:point (gpt/point (:x param1) (:y param1)) - :prev (when (:c2x param1) (gpt/point (:c2x param1) (:c2y param1))) - :next (when (:c1x param2) (gpt/point (:c1x param2) (:c1y param2)))})] - (map extract content (d/concat [] (rest content) [nil]))))