mirror of
https://github.com/penpot/penpot.git
synced 2025-05-02 22:15:54 +02:00
✨ Path create-edit workflow
This commit is contained in:
parent
8db7078ce8
commit
b1c786077b
18 changed files with 564 additions and 231 deletions
|
@ -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]
|
||||
|
|
4
frontend/resources/images/cursors/pen-node.svg
Normal file
4
frontend/resources/images/cursors/pen-node.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16.621 21.506">
|
||||
<path fill="#fff" d="M.719.719l.5 1.8v.1a45.985 45.985 0 011 4.7c.2 1.7.9 3.2 1.9 4.2 1 1.1 2.4 1.7 4.1 1.7.7 0-.001-.101.699-.301l3 3 4-4.1-3-3 .3-.6c0-1.7-.6-3.099-1.8-4.099-1-1.1-2.5-1.7-4.2-1.9l-3.4-.6c-.438-.116-.87-.25-1.298-.4L.719.719zm3.629 13.808a3.49 3.49 0 000 6.979 3.49 3.49 0 000-6.979zm0 1.866a1.624 1.624 0 11.002 3.248 1.624 1.624 0 01-.002-3.248z" color="#000" font-family="sans-serif" font-weight="400" overflow="visible" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||
<path fill="#000" fill-rule="evenodd" d="M.719.719l.5 1.8v.1a45.985 45.985 0 011 4.7c.2 1.7.9 3.2 1.9 4.2 1 1.1 2.4 1.7 4.1 1.7.333 0 .639-.028.937-.063l2.762 2.762 4-4.1-2.729-2.728c.018-.28.03-.562.03-.871 0-1.7-.601-3.1-1.801-4.1-1-1.1-2.5-1.7-4.2-1.9l-3.4-.6c-.438-.116-.87-.25-1.298-.4L.719.719zm2.537 1.736c1.31.306 2.63.573 3.963.764 3 .4 5 2.1 5 5l-.1 1.3.1.1.699.7 1.602 1.599-2.602 2.602-1.6-1.602-.004-.004-.695-.695-.047.047-.052-.047H8.219c-2.9 0-4.6-2-5-5-.2-1.7-.5-2.999-.7-3.899L6.33 7.13c-.34.884.12 2.089 1.389 2.089 2 0 2-3 0-3-.224 0-.419.04-.592.107l-3.871-3.87zM4.348 14.95a3.067 3.067 0 100 6.135 3.067 3.067 0 100-6.135zm0 1.024a2.045 2.045 0 110 4.09 2.045 2.045 0 010-4.09z" clip-rule="evenodd" color="#000" font-family="sans-serif" font-weight="400" overflow="visible" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
6
frontend/resources/images/cursors/pointer-move.svg
Normal file
6
frontend/resources/images/cursors/pointer-move.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 26" width="14.516" height="26.17">
|
||||
<path d="M1 13.6V2.4l8.2 8.2H4z" fill="#fff"/>
|
||||
<path d="M0 16V0l11.6 11.6H4.4zm4-5.4h5.2L1 2.4v11.2z" clip-rule="evenodd" fill="#231f20" fill-rule="evenodd"/>
|
||||
<path d="M8.937 14.17l-2.842 2.842 1.263 1.263.526-.526v1.263h-.947l.526-.527L6.2 17.222l-2.842 2.842L6.2 22.907l1.263-1.264-.526-.526h.947v1.474l-.526-.632-1.263 1.369 2.842 2.842 2.842-2.842-1.263-1.264-.527.527v-1.474h.948l-.527.526 1.264 1.264 2.842-2.843-2.842-2.842-1.264 1.263.527.527h-.948v-1.263l.527.526 1.263-1.263z" fill="#fff"/>
|
||||
<path d="M8.937 14.907l-2.105 2.105.526.526 1.052-1.053v3.053H5.674l1.052-1.053-.526-.526-2.105 2.105L6.2 22.17l.526-.527-1.052-1.052H8.41v3.263L7.358 22.8l-.526.527 2.105 2.105 2.105-2.105-.526-.527-1.053 1.053V20.59H12.2l-1.053 1.052.527.527 2.105-2.106-2.105-2.105-.527.526 1.053 1.053H9.463v-3.053l1.053 1.053.526-.526z" fill="#000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 946 B |
1
frontend/resources/images/cursors/pointer-node.svg
Normal file
1
frontend/resources/images/cursors/pointer-node.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12.632" height="21.293" fill="none"><path d="M1 13.6V2.4l8.2 8.2H4z" fill="#fff"/><path d="M0 16V0l11.6 11.6H4.4zm4-5.4h5.2L1 2.4v11.2z" clip-rule="evenodd" fill="#231f20" fill-rule="evenodd"/><path d="M9.143 19.429a1.624 1.624 0 100-3.248 1.624 1.624 0 000 3.248zm3.489-1.624a3.489 3.489 0 11-6.978 0 3.489 3.489 0 016.978 0z" clip-rule="evenodd" fill="#fff" fill-rule="evenodd"/><path d="M12.21 17.805a3.068 3.068 0 11-6.135 0 3.068 3.068 0 016.135 0zm-1.022 0a2.045 2.045 0 11-4.09 0 2.045 2.045 0 014.09 0z" clip-rule="evenodd" fill="#000" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 618 B |
56
frontend/resources/images/icons/pen.svg
Normal file
56
frontend/resources/images/icons/pen.svg
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="500"
|
||||
viewBox="0 0 500.00001 500.00001"
|
||||
width="500"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="pen.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2086"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3350176"
|
||||
inkscape:cx="551.31385"
|
||||
inkscape:cy="371.25461"
|
||||
inkscape:window-x="3840"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
style="clip-rule:evenodd;fill:#000000;fill-rule:evenodd;stroke:none;stroke-width:7.82213879;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="M 1.75e-5,-0.0344246 1.8524271,6.6323446 C 21.707431,76.98556 39.775647,154.86535 49.997926,218.44692 c -0.005,-0.0421 -0.01033,-0.0839 -0.01528,-0.12604 l 0.02101,0.1566 c -0.0017,-0.0103 -0.0041,-0.0203 -0.0058,-0.0305 6.542466,55.39121 29.322431,104.35363 62.177964,137.22926 32.90376,36.13782 79.12263,55.8626 134.67028,55.8626 10.16195,0 19.79295,-0.54966 28.99882,-1.54495 l 90.04053,90.04053 134.11453,-137.46988 -88.9329,-88.93482 c 0.46511,-8.60602 0.50799,-17.51305 0.50799,-26.81795 0,-55.50516 -19.75987,-101.81434 -59.11101,-134.70654 C 319.51383,75.993429 270.47749,56.500337 215.36282,49.978756 137.69366,41.205798 73.142832,20.282665 1.75e-5,-0.0344246 Z M 99.23386,67.935688 c 37.99513,8.774093 76.30079,16.144787 114.86856,21.925279 l 0.0325,0.0039 0.0305,0.0059 c 47.63845,6.351798 87.01754,22.988383 114.40641,49.203453 27.3689,26.19596 42.99135,61.9504 43.01413,107.64035 l -3.34007,43.4018 4.48971,4.4897 22.51347,22.51348 48.69546,48.69549 -78.09341,78.09343 -48.69739,-48.6974 -0.0134,-0.0134 -25.26535,-25.26535 -1.67863,1.67862 -0.0669,-0.0573 h -43.28338 c -45.73783,0 -81.52124,-15.63099 -107.73583,-43.01986 C 112.89574,301.14472 96.25867,261.76757 89.906869,214.12908 84.324003,166.70136 76.438965,129.4996 70.223571,101.44904 L 181.85519,213.08066 c -12.12343,33.53406 12.70563,69.75968 48.90364,69.80533 h 0.004 0.002 c 28.75911,0 52.15587,-23.39869 52.15587,-52.15777 0,-28.75909 -23.39676,-52.15587 -52.15587,-52.15587 h -0.002 c -6.05426,0.003 -12.03463,1.12108 -17.71439,3.17774 z"
|
||||
id="path821"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccsccccscccccccscccccccccccsccccccsssccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
3
frontend/resources/images/icons/pointer-inner.svg
Normal file
3
frontend/resources/images/icons/pointer-inner.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M108.957 0v500l8.142-8.142 129.769-129.77h224.175zm39.35 94.576l228.162 228.163H230.994l-1.398 1.395-81.29 81.29z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 198 B |
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
)))))
|
||||
|
|
|
@ -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#)))))
|
||||
|
|
|
@ -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)]])))]))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}]
|
||||
|
||||
|
|
|
@ -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)))))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue