diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc
index ae5a91983..491b1fc78 100644
--- a/common/app/common/pages.cljc
+++ b/common/app/common/pages.cljc
@@ -611,8 +611,7 @@
:stroke-alignment :center
:stroke-width 2
:stroke-color "#000000"
- :stroke-opacity 1
- :segments []}
+ :stroke-opacity 1}
{:type :frame
:name "Artboard"
@@ -632,8 +631,7 @@
:stroke-alignment :center
:stroke-width 2
:stroke-color "#000000"
- :stroke-opacity 1
- :segments []}
+ :stroke-opacity 1}
{:type :text
:name "Text"
@@ -646,22 +644,24 @@
(ex/raise :type :assertion
:code :shape-type-not-implemented
:context {:type type}))
- (assoc shape
- :id (uuid/next)
- :x 0
- :y 0
- :width 1
- :height 1
- :selrect {:x 0
- :x1 0
- :x2 1
- :y 0
- :y1 0
- :y2 1
- :width 1
- :height 1}
- :points []
- :segments [])))
+
+ (cond-> shape
+ :always
+ (assoc :id (uuid/next))
+
+ (not #{:path :curve})
+ (assoc :x 0
+ :y 0
+ :width 1
+ :height 1
+ :selrect {:x 0
+ :y 0
+ :x1 0
+ :y1 0
+ :x2 1
+ :y2 1
+ :width 1
+ :height 1}))))
(defn make-minimal-group
[frame-id selection-rect group-name]
diff --git a/frontend/resources/images/cursors/pen-node.svg b/frontend/resources/images/cursors/pen-node.svg
new file mode 100644
index 000000000..ba03c12c6
--- /dev/null
+++ b/frontend/resources/images/cursors/pen-node.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/resources/images/cursors/pointer-move.svg b/frontend/resources/images/cursors/pointer-move.svg
new file mode 100644
index 000000000..895bbd8ee
--- /dev/null
+++ b/frontend/resources/images/cursors/pointer-move.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/cursors/pointer-node.svg b/frontend/resources/images/cursors/pointer-node.svg
new file mode 100644
index 000000000..185862c1d
--- /dev/null
+++ b/frontend/resources/images/cursors/pointer-node.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/resources/images/icons/pen.svg b/frontend/resources/images/icons/pen.svg
new file mode 100644
index 000000000..9cd783355
--- /dev/null
+++ b/frontend/resources/images/icons/pen.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/frontend/resources/images/icons/pointer-inner.svg b/frontend/resources/images/icons/pointer-inner.svg
new file mode 100644
index 000000000..50798578b
--- /dev/null
+++ b/frontend/resources/images/icons/pointer-inner.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss
index 69282ab40..d6119cd4a 100644
--- a/frontend/resources/styles/main/partials/workspace.scss
+++ b/frontend/resources/styles/main/partials/workspace.scss
@@ -253,13 +253,17 @@
}
.viewport-actions-entry {
- width: 27px;
- height: 20px;
+ width: 28px;
+ height: 28px;
margin: 0 0.25rem;
cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 3px;
svg {
- width: 27px;
+ width: 20px;
height: 20px;
}
@@ -267,13 +271,21 @@
fill: $color-primary;
}
- &.disabled {
+ &.is-disabled {
opacity: 0.3;
&:hover svg {
fill: initial;
}
}
+
+ &.is-toggled {
+ background: $color-black;
+
+ svg {
+ fill: $color-primary;
+ }
+ }
}
.viewport-actions-entry-wide {
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 112f98afa..a8fc65d4d 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -1632,13 +1632,24 @@
(defn start-path-edit [id] (dwdp/start-path-edit id))
-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts impl https://github.com/ccampbell/mousetrap
+(defn esc-pressed []
+ (ptk/reify :esc-pressed
+ ptk/WatchEvent
+ (watch [_ state stream]
+ ;; Not interrupt when we're editing a path
+ (let [edition-id (get-in state [:workspace-local :edition])
+ path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])]
+ (if-not (= :draw path-edit-mode)
+ (rx/of :interrupt
+ (deselect-all true))
+ (rx/empty))))))
+
(def shortcuts
{"ctrl+i" #(st/emit! (toggle-layout-flags :assets))
"ctrl+l" #(st/emit! (toggle-layout-flags :sitemap :layers))
@@ -1671,7 +1682,7 @@
"ctrl+c" #(st/emit! copy-selected)
"ctrl+v" #(st/emit! paste)
"ctrl+x" #(st/emit! copy-selected delete-selected)
- "escape" #(st/emit! :interrupt (deselect-all true))
+ "escape" #(st/emit! (esc-pressed))
"del" #(st/emit! delete-selected)
"backspace" #(st/emit! delete-selected)
"ctrl+up" #(st/emit! (vertical-order-selected :up))
diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs
index 3275799b5..594c00f03 100644
--- a/frontend/src/app/main/data/workspace/common.cljs
+++ b/frontend/src/app/main/data/workspace/common.cljs
@@ -461,7 +461,14 @@
(ptk/reify ::start-edition-mode
ptk/UpdateEvent
(update [_ state]
- (assoc-in state [:workspace-local :edition] id))
+ (let [page-id (:current-page-id state)
+ objects (get-in state [:workspace-data :pages-index page-id :objects])]
+ ;; Can only edit objects that exist
+ (if (contains? objects id)
+ (-> state
+ (assoc-in [:workspace-local :selected] #{id})
+ (assoc-in [:workspace-local :edition] id))
+ state)))
ptk/WatchEvent
(watch [_ state stream]
@@ -486,7 +493,7 @@
(let [page-id (:current-page-id state)
objects (lookup-page-objects state page-id)
- id (uuid/next)
+ id (or (:id attrs) (uuid/next))
shape (gpr/setup-proportions attrs)
unames (retrieve-used-names objects)
diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs
index 7987488a2..9e792a797 100644
--- a/frontend/src/app/main/data/workspace/drawing.cljs
+++ b/frontend/src/app/main/data/workspace/drawing.cljs
@@ -45,8 +45,9 @@
(rx/of (start-drawing :path)))
;; NOTE: comments are a special case and they manage they
- ;; own interrupt cycle.
- (when (not= tool :comments)
+ ;; own interrupt cycle.q
+ (when (and (not= tool :comments)
+ (not= tool :path))
(->> stream
(rx/filter dwc/interrupt?)
(rx/take 1)
@@ -90,7 +91,7 @@
(watch [_ state stream]
(rx/of (case type
:path
- (path/handle-drawing-path)
+ (path/handle-new-shape)
:curve
(curve/handle-drawing-curve)
diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs
index 4d956d7b6..2ac40bf29 100644
--- a/frontend/src/app/main/data/workspace/drawing/path.cljs
+++ b/frontend/src/app/main/data/workspace/drawing/path.cljs
@@ -14,38 +14,48 @@
[app.common.math :as mth]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
- [app.util.data :as d]
+ [app.util.data :as ud]
+ [app.common.data :as cd]
[app.util.geom.path :as ugp]
[app.main.streams :as ms]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing.common :as common]
[app.common.geom.shapes.path :as gsp]))
-;;;;
+;; CONSTANTS
+(defonce enter-keycode 13)
-(def close-path-distance 5)
-(defn seek-start-path [content]
+;; PRIVATE METHODS
+
+(defn get-path-id [state]
+ (or (get-in state [:workspace-local :edition])
+ (get-in state [:workspace-drawing :object :id])))
+
+(defn get-path [state & path]
+ (let [edit-id (get-in state [:workspace-local :edition])
+ page-id (:current-page-id state)]
+ (cd/concat
+ (if edit-id
+ [:workspace-data :pages-index page-id :objects edit-id]
+ [:workspace-drawing :object])
+ path)))
+
+(defn last-start-path [content]
(->> content
reverse
- (d/seek (fn [{cmd :command}] (= cmd :move-to)))
+ (cd/seek (fn [{cmd :command}] (= cmd :move-to)))
:params))
(defn next-node
"Calculates the next-node to be inserted."
[shape position prev-point prev-handler]
(let [last-command (-> shape :content last :command)
- start-point (-> shape :content seek-start-path)
+ start-point (-> shape :content last-start-path)
add-line? (and prev-point (not prev-handler) (not= last-command :close-path))
- add-curve? (and prev-point prev-handler (not= last-command :close-path))
- close-path? (and start-point
- (< (mth/abs (gpt/distance (gpt/point start-point)
- (gpt/point position)))
- close-path-distance))]
+ add-curve? (and prev-point prev-handler (not= last-command :close-path))]
(cond
- close-path? {:command :close-path
- :params []}
add-line? {:command :line-to
:params position}
add-curve? {:command :curve-to
@@ -56,19 +66,24 @@
(defn append-node
"Creates a new node in the path. Usualy used when drawing."
[shape position prev-point prev-handler]
- (let [command (next-node shape position prev-point prev-handler)]
- (as-> shape $
- (update $ :content (fnil conj []) command)
- (update $ :selrect (gsh/content->selrect (:content $))))))
+ (let [command (next-node shape position prev-point prev-handler)
+ content (:content shape [])
+ content (conj content command)]
+ (-> shape
+ (assoc :content content)
+ (assoc :selrect (gsh/content->selrect content))
+ ;; TODO: REMOVE POINTS
+ (assoc :points (gsh/content->points content)))))
-(defn suffix-keyword [kw suffix]
+(defn suffix-keyword
+ [kw suffix]
(let [strkw (if kw (name kw) "")]
(keyword (str strkw suffix))))
-;; handler-type => :prev :next
-(defn move-handler [shape index handler-type match-opposite? position]
+(defn move-handler
+ [shape index handler-type match-opposite? position]
(let [content (:content shape)
- [command next-command] (-> (d/with-next content) (nth index))
+ [command next-command] (-> (ud/with-next content) (nth index))
update-command
(fn [{cmd :command params :params :as command} param-prefix prev-command]
@@ -86,9 +101,11 @@
update-content
(fn [shape index prefix]
(if (contains? (:content shape) index)
- (let [prev-command (get-in shape [:content (dec index)])]
- (update-in shape [:content index] update-command prefix prev-command))
-
+ (let [prev-command (get-in shape [:content (dec index)])
+ content (-> shape :content (update index update-command prefix prev-command))]
+ (-> shape
+ (assoc :content content)
+ (assoc :selrect (gsh/content->selrect content))))
shape))]
(cond-> shape
@@ -106,102 +123,94 @@
(ugp/opposite-handler (gpt/point (:params command))
(gpt/point position))))))
-
-;;;;
-(defn finish-event? [{:keys [type shift] :as event}]
- (or (= event ::end-path-drawing)
- (= event :interrupt)
+(defn end-path-event? [{:keys [type shift] :as event}]
+ (or (= event ::end-path)
+ (= (ptk/type event) :esc-pressed)
+ (= event :interrupt) ;; ESC
(and (ms/keyboard-event? event)
(= type :down)
- (= 13 (:key event)))))
-
-#_(defn init-path []
- (fn [state]
- (update-in state [:workspace-drawing :object]
- assoc :content []
- :initialized? true)))
-
-#_(defn add-path-command [command]
- (fn [state]
- (update-in state [:workspace-drawing :object :content] conj command)))
-
-#_(defn update-point-segment [state index point]
- (let [segments (count (get-in state [:workspace-drawing :object :segments]))
- exists? (< -1 index segments)]
- (cond-> state
- exists? (assoc-in [:workspace-drawing :object :segments index] point))))
-
-#_(defn finish-drawing-path []
- (fn [state]
- (update-in
- state [:workspace-drawing :object]
- (fn [shape] (-> shape
- (update :segments #(vec (butlast %)))
- (gsh/update-path-selrect))))))
-
+ ;; TODO: Enter now finish path but can finish drawing/editing as well
+ (= enter-keycode (:key event)))))
(defn calculate-selrect [shape]
(assoc shape
:points (gsh/content->points (:content shape))
:selrect (gsh/content->selrect (:content shape))))
-(defn init-path []
+
+;; EVENTS
+
+(defn init-path [id]
(ptk/reify ::init-path
ptk/UpdateEvent
(update [_ state]
(-> state
- (assoc-in [:workspace-drawing :object :initialized?] true)
- (assoc-in [:workspace-local :edit-path :last-point] nil)))))
+ #_(assoc-in [:workspace-drawing :object :initialized?] true)
+ #_(assoc-in [:workspace-local :edit-path :last-point] nil)))))
-(defn finish-path []
+(defn finish-path [id]
(ptk/reify ::finish-path
ptk/UpdateEvent
(update [_ state]
(-> state
- (update :workspace-local dissoc :edit-path)
- (update-in [:workspace-drawing :object] calculate-selrect)))))
+ (update-in [:workspace-local :edit-path id] dissoc :last-point :prev-handler :drag-handler :preview)))))
(defn preview-next-point [{:keys [x y]}]
- (ptk/reify ::add-node
+ (ptk/reify ::preview-next-point
ptk/UpdateEvent
(update [_ state]
- (let [position (gpt/point x y)
- {:keys [last-point prev-handler] :as shape} (get-in state [:workspace-local :edit-path])
+ (let [id (get-path-id state)
+ position (gpt/point x y)
+ 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 :preview] command)))))
+ (assoc-in state [:workspace-local :edit-path id :preview] command)))))
(defn add-node [{:keys [x y]}]
(ptk/reify ::add-node
ptk/UpdateEvent
(update [_ state]
-
- (let [position (gpt/point x y)
- {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path])]
+ (let [id (get-path-id state)
+ position (gpt/point x y)
+ {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])]
(-> 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))))))
+ (assoc-in [:workspace-local :edit-path id :last-point] position)
+ (update-in [:workspace-local :edit-path id] dissoc :prev-handler)
+ (update-in (get-path state) append-node position last-point prev-handler))))))
(defn drag-handler [{:keys [x y]}]
(ptk/reify ::drag-handler
ptk/UpdateEvent
(update [_ state]
- (let [position (gpt/point x y)
- shape (get-in state [:workspace-drawing :object])
+ (let [id (get-path-id state)
+ position (gpt/point x y)
+ shape (get-in state (get-path state))
index (dec (count (:content shape)))]
(-> state
- (update-in [:workspace-drawing :object] move-handler index :next true position)
- (assoc-in [:workspace-local :edit-path :drag-handler] position))))))
+ (update-in (get-path state) move-handler index :next true position)
+ (assoc-in [:workspace-local :edit-path id :prev-handler] position)
+ (assoc-in [:workspace-local :edit-path id :drag-handler] position))))))
(defn finish-drag []
(ptk/reify ::finish-drag
ptk/UpdateEvent
(update [_ state]
- (let [handler (get-in state [:workspace-local :edit-path :drag-handler])]
+ (let [id (get-path-id state)
+ handler (get-in state [:workspace-local :edit-path id :drag-handler])]
(-> state
- (update-in [:workspace-local :edit-path] dissoc :drag-handler)
- (assoc-in [:workspace-local :edit-path :prev-handler] handler))))))
+ (update-in [:workspace-local :edit-path id] dissoc :drag-handler)
+ (assoc-in [:workspace-local :edit-path id :prev-handler] handler))))
+
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (let [id (get-path-id state)
+ handler (get-in state [:workspace-local :edit-path id :prev-handler])]
+ ;; Update the preview because can be outdated after the dragging
+ (rx/of (preview-next-point handler))))))
+
+;; EVENT STREAMS
(defn make-click-stream
[stream down-event]
@@ -230,26 +239,31 @@
(rx/first)
(rx/merge-map
#(rx/of (add-node down-event)
- ::end-path-drawing))))
+ ::end-path))))
-(defn handle-drawing-path []
+;; MAIN ENTRIES
+
+(defn handle-drawing-path
+ [id]
(ptk/reify ::handle-drawing-path
ptk/WatchEvent
(watch [_ state stream]
(let [mouse-down (->> stream (rx/filter ms/mouse-down?))
- finish-events (->> stream (rx/filter finish-event?))
+ end-path-events (->> stream (rx/filter end-path-event?))
+ ;; Mouse move preview
mousemove-events
(->> ms/mouse-position
- (rx/take-until finish-events)
- (rx/throttle 100)
+ (rx/take-until end-path-events)
+ (rx/throttle 50)
(rx/map #(preview-next-point %)))
+ ;; From mouse down we can have: click, drag and double click
mousedown-events
(->> mouse-down
- (rx/take-until finish-events)
- (rx/throttle 100)
+ (rx/take-until end-path-events)
+ (rx/throttle 50)
(rx/with-latest merge ms/mouse-position)
;; We change to the stream that emits the first event
@@ -258,13 +272,13 @@
(make-drag-stream stream %)
(make-dbl-click-stream stream %))))]
+ (->> (rx/concat
+ (rx/of (init-path id))
+ (rx/merge mousemove-events
+ mousedown-events)
+ (rx/of (finish-path id))))))))
+
- (rx/concat
- (rx/of (init-path))
- (rx/merge mousemove-events
- mousedown-events)
- (rx/of (finish-path))
- (rx/of common/handle-finish-drawing))))))
#_(def handle-drawing-path
(ptk/reify ::handle-drawing-path
@@ -326,29 +340,25 @@
(rx/of finish-drawing-path
common/handle-finish-drawing)))))))
-#_(defn close-drawing-path []
- (ptk/reify ::close-drawing-path
- ptk/UpdateEvent
- (update [_ state]
- (assoc-in state [:workspace-drawing :object :close?] true))
-
- 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))))
+ (let [id (get-in state [:workspace-local :edition])]
+ (update state :workspace-local dissoc :edit-path id)))))
(defn start-path-edit
[id]
(ptk/reify ::start-path-edit
ptk/UpdateEvent
(update [_ state]
- (assoc-in state [:workspace-local :edit-path] {}))
+ ;; Only edit if the object has been created
+ (if-let [id (get-in state [:workspace-local :edition])]
+ (assoc-in state [:workspace-local :edit-path id] {:edit-mode :move
+ :selected #{}
+ :snap-toggled true})
+ state))
ptk/WatchEvent
(watch [_ state stream]
@@ -362,40 +372,37 @@
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)
- ))))
+ (let [id (get-in state [:workspace-local :edition])]
+ (-> state
+ (update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc
+ :c1x dx :c1y dy)
+ (update-in [:workspace-local :edit-path id :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) ))
- ))))
+ (let [id (get-in state [:workspace-local :edition])]
+ (let [s1 (if (= type :prev) -1 1)
+ s2 (if (= type :prev) 1 -1)]
+ (-> state
+ (update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc
+ :c1x (* s1 dx) :c1y (* s1 dy))
+ (update-in [:workspace-local :edit-path id :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])
+ content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])
new-content (gsp/apply-content-modifiers old-content content-modifiers)
new-selrect (gsh/content->selrect new-content)
rch [{:type :mod-obj
@@ -411,16 +418,59 @@
{: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)))))))
+ (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers)))))))
+
+(defn save-path-content []
+ (ptk/reify ::save-path-content
+ 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-local :edit-path id :old-content])
+ old-selrect (gsh/content->selrect old-content)
+ new-content (get-in state [:workspace-data :pages-index page-id :objects id :content])
+ new-selrect (get-in state [:workspace-data :pages-index page-id :objects id :selrect])
+
+ 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}))))))
+
+(declare start-draw-mode)
+(defn check-changed-content []
+ (ptk/reify ::check-changed-content
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (let [id (get-path-id state)
+ content (get-in state (get-path state :content))
+ old-content (get-in state [:workspace-local :edit-path id :old-content])
+ mode (get-in state [:workspace-local :edit-path id :edit-mode])]
+
+
+ (cond
+ (not= content old-content) (rx/of (save-path-content)
+ (start-draw-mode))
+ (= mode :draw) (rx/of :interrupt)
+ :else (rx/of (finish-path id)))))))
(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)]
+ (let [id (get-in state [:workspace-local :edition])
+ start-point @ms/mouse-position
+ start-delta-x (get-in state [:workspace-local :edit-path id :content-modifiers index :x] 0)
+ start-delta-y (get-in state [:workspace-local :edit-path id :content-modifiers index :y] 0)]
(rx/concat
(->> ms/mouse-position
(rx/take-until (->> stream (rx/filter ms/mouse-up?)))
@@ -436,12 +486,13 @@
(ptk/reify ::start-move-handler
ptk/WatchEvent
(watch [_ state stream]
- (let [[cx cy] (if (= :prev type) [:c2x :c2y] [:c1x :c1y])
+ (let [id (get-in state [:workspace-local :edition])
+ [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)]
+ start-delta-x (get-in state [:workspace-local :edit-path id :content-modifiers cidx cx] 0)
+ start-delta-y (get-in state [:workspace-local :edit-path id :content-modifiers cidx cy] 0)]
(rx/concat
(->> ms/mouse-position
@@ -453,3 +504,114 @@
(+ start-delta-y (- (:y %) (:y start-point)))))
)
(rx/concat (rx/of (apply-content-modifiers))))))))
+
+(defn start-draw-mode []
+ (ptk/reify ::start-draw-mode
+ ptk/UpdateEvent
+ (update [_ state]
+ (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])]
+ (-> state
+ (assoc-in [:workspace-local :edit-path id :old-content] old-content))))
+
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (let [id (get-in state [:workspace-local :edition])
+ edit-mode (get-in state [:workspace-local :edit-path id :edit-mode])]
+ (if (= :draw edit-mode)
+ (rx/concat
+ (rx/of (handle-drawing-path id))
+ (->> stream
+ (rx/filter (ptk/type? ::finish-path))
+ (rx/take 1)
+ (rx/merge-map #(rx/of (check-changed-content)))))
+ (rx/empty))))))
+
+(defn change-edit-mode [mode]
+ (ptk/reify ::change-edit-mode
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [id (get-in state [:workspace-local :edition])]
+ (cond-> state
+ id (assoc-in [:workspace-local :edit-path id :edit-mode] mode))))
+
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (let [id (get-path-id state)]
+ (cond
+ (and id (= :move mode)) (rx/of ::end-path)
+ (and id (= :draw mode)) (rx/of (start-draw-mode))
+ :else (rx/empty))))))
+
+(defn select-handler [index type]
+ (ptk/reify ::select-handler
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [id (get-in state [:workspace-local :edition])]
+ (-> state
+ (update-in [:workspace-local :edit-path id :selected] (fnil conj #{}) [index type]))))))
+
+(defn select-node [index]
+ (ptk/reify ::select-node
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [id (get-in state [:workspace-local :edition])]
+ (-> state
+ (update-in [:workspace-local :edit-path id :selected] (fnil conj #{}) index))))))
+
+(defn add-to-selection-handler [index type]
+ (ptk/reify ::add-to-selection-handler
+ ptk/UpdateEvent
+ (update [_ state]
+ state)))
+
+(defn add-to-selection-node [index]
+ (ptk/reify ::add-to-selection-node
+ ptk/UpdateEvent
+ (update [_ state]
+ state)))
+
+(defn remove-from-selection-handler [index]
+ (ptk/reify ::remove-from-selection-handler
+ ptk/UpdateEvent
+ (update [_ state]
+ state)))
+
+(defn remove-from-selection-node [index]
+ (ptk/reify ::remove-from-selection-handler
+ ptk/UpdateEvent
+ (update [_ state]
+ state)))
+
+(defn handle-new-shape-result [shape-id]
+ (ptk/reify ::handle-new-shape-result
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [content (get-in state [:workspace-drawing :object :content] [])]
+ (if (> (count content) 1)
+ (assoc-in state [:workspace-drawing :object :initialized?] true)
+ state)))
+
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (->> (rx/of common/handle-finish-drawing
+ (dwc/start-edition-mode shape-id)
+ (start-path-edit shape-id)
+ (change-edit-mode :draw))))))
+
+(defn handle-new-shape
+ "Creates a new path shape"
+ []
+ (ptk/reify ::handle-new-shape
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (let [shape-id (get-in state [:workspace-drawing :object :id])]
+ (rx/concat
+ (rx/of (handle-drawing-path shape-id))
+ (->> stream
+ (rx/filter (ptk/type? ::finish-path))
+ (rx/take 1)
+ (rx/observe-on :async)
+ (rx/map #(handle-new-shape-result shape-id)))
+ )))))
diff --git a/frontend/src/app/main/ui/cursors.clj b/frontend/src/app/main/ui/cursors.clj
index 414297d11..d7ed83b71 100644
--- a/frontend/src/app/main/ui/cursors.clj
+++ b/frontend/src/app/main/ui/cursors.clj
@@ -19,6 +19,7 @@
(def default-hotspot-x 12)
(def default-hotspot-y 12)
(def default-rotation 0)
+(def default-height 20)
(defn parse-svg [svg-data]
(-> svg-data
@@ -53,25 +54,27 @@
(str/replace #"\s+$" "")))
(defn encode-svg-cursor
- [id rotation x y]
+ [id rotation x y height]
(let [svg-path (str cursor-folder "/" (name id) ".svg")
data (-> svg-path io/resource slurp parse-svg uri/percent-encode)
transform (if rotation (str " transform='rotate(" rotation ")'") "")
data (clojure.pprint/cl-format
nil
- "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='20px' height='20px'~A%3E~A%3C/svg%3E\") ~A ~A, auto"
- transform data x y)]
+ "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='20px' height='~Apx'~A%3E~A%3C/svg%3E\") ~A ~A, auto"
+ height transform data x y )]
data))
(defmacro cursor-ref
"Creates a static cursor given its name, rotation and x/y hotspot"
- ([id] (encode-svg-cursor id default-rotation default-hotspot-x default-hotspot-y))
- ([id rotation] (encode-svg-cursor id rotation default-hotspot-x default-hotspot-y))
- ([id rotation x y] (encode-svg-cursor id rotation x y)))
+ ([id] (encode-svg-cursor id default-rotation default-hotspot-x default-hotspot-y default-height))
+ ([id rotation] (encode-svg-cursor id rotation default-hotspot-x default-hotspot-y default-height))
+ ([id rotation x y] (encode-svg-cursor id rotation x y default-height))
+ ([id rotation x y height] (encode-svg-cursor id rotation x y height))
+ )
(defmacro cursor-fn
"Creates a dynamic cursor that can be rotated in runtime"
[id initial]
- (let [cursor (encode-svg-cursor id "{{rotation}}" default-hotspot-x default-hotspot-y)]
+ (let [cursor (encode-svg-cursor id "{{rotation}}" default-hotspot-x default-hotspot-y default-height)]
`(fn [rot#]
(str/replace ~cursor "{{rotation}}" (+ ~initial rot#)))))
diff --git a/frontend/src/app/main/ui/cursors.cljs b/frontend/src/app/main/ui/cursors.cljs
index f7dd21fed..bc60deba4 100644
--- a/frontend/src/app/main/ui/cursors.cljs
+++ b/frontend/src/app/main/ui/cursors.cljs
@@ -8,8 +8,7 @@
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.cursors
- (:require-macros [app.main.ui.cursors :refer [cursor-ref
- cursor-fn]])
+ (:require-macros [app.main.ui.cursors :refer [cursor-ref cursor-fn]])
(:require [rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.timers :as ts]))
@@ -33,6 +32,9 @@
(def rotate (cursor-fn :rotate 90))
(def text (cursor-ref :text))
(def picker (cursor-ref :picker 0 0 24))
+(def pointer-node (cursor-ref :pointer-node 0 0 10 32))
+(def pointer-move (cursor-ref :pointer-move 0 0 10 42))
+(def pen-node (cursor-ref :pen-node 0 0 10 36))
(mf/defc debug-preview
{::mf/wrap-props false}
@@ -49,8 +51,11 @@
[:div {:style {:width "100px"
:height "100px"
:background-image (-> value (str/replace #"(url\(.*\)).*" "$1"))
- :background-size "cover"
+ :background-size "contain"
+ :background-repeat "no-repeat"
+ :background-position "center"
:cursor value}}]
[:span {:style {:white-space "nowrap"
:margin-right "1rem"}} (pr-str key)]])))]))
+
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index 0550c4a4a..f620afd84 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -136,6 +136,8 @@
(def nodes-remove (icon-xref :nodes-remove))
(def nodes-separate (icon-xref :nodes-separate))
(def nodes-snap (icon-xref :nodes-snap))
+(def pen (icon-xref :pen))
+(def pointer-inner (icon-xref :pointer-inner))
(def loader-pencil
(mf/html
diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/selection.cljs
index 5f9708a58..cdd104091 100644
--- a/frontend/src/app/main/ui/workspace/selection.cljs
+++ b/frontend/src/app/main/ui/workspace/selection.cljs
@@ -368,8 +368,8 @@
:zoom zoom
:color color}]
- (= (= type :path)
- (= edition (:id shape)))
+ (and (= type :path)
+ (= edition (:id shape)))
[:& path-editor {:zoom zoom
:shape shape}]
diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs
index d1665317b..1d84fa620 100644
--- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs
@@ -110,7 +110,6 @@
(mf/use-callback
(mf/deps (:id shape))
(fn [event]
- (prn "?? FRAME")
(dom/prevent-default event)
(st/emit! (dw/deselect-all)
(dw/select-shape (:id shape)))))
diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs
index 338a45f79..9396aca1f 100644
--- a/frontend/src/app/main/ui/workspace/shapes/path.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs
@@ -39,11 +39,25 @@
(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))
+
+(def current-edit-path-ref
+ (let [selfn (fn [local]
+ (let [id (:edition local)]
+ (get-in local [:edit-path id])))]
+ (l/derived selfn refs/workspace-local)))
+
+(defn make-edit-path-ref [id]
+ (mf/use-memo
+ (mf/deps id)
+ (let [selfn #(get-in % [:edit-path id])]
+ #(l/derived selfn refs/workspace-local))))
+
+(defn make-content-modifiers-ref [id]
+ (mf/use-memo
+ (mf/deps id)
+ (let [selfn #(get-in % [:edit-path id :content-modifiers])]
+ #(l/derived selfn refs/workspace-local))))
(mf/defc path-wrapper
{::mf/wrap-props false}
@@ -67,11 +81,14 @@
(dom/prevent-default event)
(st/emit! (dw/start-edition-mode (:id shape))
(dw/start-path-edit (:id shape)))))))
-
+ content-modifiers-ref (make-content-modifiers-ref (:id shape))
content-modifiers (mf/deref content-modifiers-ref)
+ editing-id (mf/deref refs/selected-edition)
+ editing? (= editing-id (:id shape))
shape (update shape :content gsp/apply-content-modifiers content-modifiers)]
[:> shape-container {:shape shape
+ :pointer-events (when editing? "none")
:on-double-click on-double-click
:on-mouse-down on-mouse-down
:on-context-menu on-context-menu}
@@ -79,22 +96,30 @@
: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]]
+ (let [id (mf/deref refs/selected-edition)
+ {:keys [edit-mode selected snap-toggled] :as all} (mf/deref current-edit-path-ref)]
+ [:div.path-actions
+ [:div.viewport-actions-group
+ [:div.viewport-actions-entry {:class (when (= edit-mode :draw) "is-toggled")
+ :on-click #(st/emit! (drp/change-edit-mode :draw))} i/pen]
+ [:div.viewport-actions-entry {:class (when (= edit-mode :move) "is-toggled")
+ :on-click #(st/emit! (drp/change-edit-mode :move))} i/pointer-inner]]
+
+ [:div.viewport-actions-group
+ [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-add]
+ [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-remove]]
- [: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 {:class "is-disabled"} i/nodes-merge]
+ [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-join]
+ [:div.viewport-actions-entry {:class "is-disabled"} 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 {:class "is-disabled"} i/nodes-corner]
+ [:div.viewport-actions-entry {:class "is-disabled"} i/nodes-curve]]
- [:div.viewport-actions-group
- [:div.viewport-actions-entry i/nodes-snap]]])
+ [:div.viewport-actions-group
+ [:div.viewport-actions-entry {:class (when snap-toggled "is-toggled")} i/nodes-snap]]]))
(mf/defc path-preview [{:keys [zoom command from]}]
@@ -107,36 +132,58 @@
:y (:y from)}}
command])}]))
-(mf/defc path-point [{:keys [index position stroke-color fill-color zoom]}]
+(mf/defc path-point [{:keys [index position stroke-color fill-color zoom edit-mode selected]}]
(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)}}]))
+ (cond
+ (= edit-mode :move)
+ (do
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (st/emit! (drp/select-node index)))))
-(mf/defc path-handler [{:keys [index point handler zoom selected type]}]
+ on-mouse-down (fn [event]
+ (cond
+ (= edit-mode :move)
+ (do
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (st/emit! (drp/start-move-path-point index)))))]
+ [:g.path-point
+ [:circle.path-point
+ {:cx x
+ :cy y
+ :r (/ 3 zoom)
+ :style { ;; :cursor cur/resize-alt
+ :stroke-width (/ 1 zoom)
+ :stroke (or stroke-color black-color)
+ :fill (or fill-color white-color)}}]
+ [:circle {:cx x
+ :cy y
+ :r (/ 10 zoom)
+ :on-click on-click
+ :on-mouse-down on-mouse-down
+ :style {:fill "transparent"}}]]
+ ))
+
+(mf/defc path-handler [{:keys [index point handler zoom selected type edit-mode]}]
(when (and point handler)
(let [{:keys [x y]} handler
on-click (fn [event]
- (dom/stop-propagation event)
- (dom/prevent-default event))
+ (cond
+ (= edit-mode :move)
+ (do
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (drp/select-handler index type))))
+
on-mouse-down (fn [event]
- (dom/stop-propagation event)
- (dom/prevent-default event)
- (st/emit! (drp/start-move-handler index type)))]
+ (cond
+ (= edit-mode :move)
+ (do
+ (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)
@@ -150,23 +197,30 @@
:y (- y (/ 3 zoom))
:width (/ 6 zoom)
:height (/ 6 zoom)
- :on-click on-click
- :on-mouse-down on-mouse-down
- :style {:cursor cur/resize-alt
+
+ :style {;; :cursor cur/resize-alt
:stroke-width (/ 1 zoom)
:stroke (if selected black-color primary-color)
- :fill (if selected primary-color white-color)}}]])))
+ :fill (if selected primary-color white-color)}}]
+
+ [:circle {:cx x
+ :cy y
+ :r (/ 10 zoom)
+ :on-click on-click
+ :on-mouse-down on-mouse-down
+ :style {:fill "transparent"}}]])))
(mf/defc path-editor
[{:keys [shape zoom]}]
(let [{:keys [content]} shape
- {:keys [drag-handler prev-handler preview content-modifiers]} (mf/deref edit-path-ref)
+ edit-path-ref (make-edit-path-ref (:id shape))
+ {:keys [edit-mode selected drag-handler prev-handler preview content-modifiers]} (mf/deref edit-path-ref)
+ selected (or selected #{})
content (gsp/apply-content-modifiers content content-modifiers)
points (gsp/content->points content)
last-command (last content)
- last-p (last points)
- selected false]
+ last-p (last points)]
[:g.path-editor
(when (and preview (not drag-handler))
@@ -187,7 +241,8 @@
:zoom zoom
:type :prev
:index index
- :selected false}])
+ :selected (selected [index :prev])
+ :edit-mode edit-mode}])
(when (= :curve-to (:command next))
[:& path-handler {:point point
@@ -195,7 +250,8 @@
:zoom zoom
:type :next
:index index
- :selected false}])
+ :selected (selected [index :next])
+ :edit-mode edit-mode}])
(when (and (= index (dec (count content)))
prev-handler (not drag-handler))
@@ -204,13 +260,15 @@
:zoom zoom
:type :prev
:index index
- :selected false}])
+ :selected (selected index)
+ :edit-mode edit-mode}])
[:& path-point {:position point
- :stroke-color (when-not selected primary-color)
- :fill-color (when selected primary-color)
+ :stroke-color (when-not (selected index) primary-color)
+ :fill-color (when (selected index) primary-color)
:index index
- :zoom zoom}]]))
+ :zoom zoom
+ :edit-mode edit-mode}]]))
(when drag-handler
[:g.drag-handler
diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs
index 547ceb36c..fc07ba6db 100644
--- a/frontend/src/app/main/ui/workspace/viewport.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport.cljs
@@ -199,6 +199,7 @@
vport
vbox
edition
+ edit-path
tooltip
selected
panning
@@ -221,6 +222,7 @@
drawing (mf/deref refs/workspace-drawing)
drawing-tool (:tool drawing)
drawing-obj (:object drawing)
+ drawing-path? (and edition (= :draw (get-in edit-path [edition :edit-mode])))
zoom (or zoom 1)
on-mouse-down
@@ -292,7 +294,7 @@
on-double-click
(mf/use-callback
- (mf/deps edition)
+ (mf/deps edition edit-path)
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
@@ -300,7 +302,7 @@
alt? (kbd/alt? event)]
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?))
- (if edition
+ (if (not drawing-path?)
(st/emit! dw/clear-edition-mode)))))
on-key-down
@@ -431,6 +433,7 @@
final-x (- (:x viewport-coord) (/ (:width shape) 2))
final-y (- (:y viewport-coord) (/ (:height shape) 2))]
(st/emit! (dw/add-shape (-> shape
+ (assoc :id (uuid/next))
(assoc :x final-x)
(assoc :y final-y)))))
@@ -537,7 +540,7 @@
(= drawing-tool :frame) cur/create-artboard
(= drawing-tool :rect) cur/create-rectangle
(= drawing-tool :circle) cur/create-ellipse
- (= drawing-tool :path) cur/pen
+ (or (= drawing-tool :path) drawing-path?) cur/pen
(= drawing-tool :curve)cur/pencil
drawing-tool cur/create-shape
:else cur/pointer-inner)