mirror of
https://github.com/penpot/penpot.git
synced 2025-05-29 06:26:12 +02:00
🎉 Add full integration with path data type feature
This commit is contained in:
parent
f545d7b3ea
commit
1fc0203c38
64 changed files with 2622 additions and 2237 deletions
|
@ -11,7 +11,7 @@
|
|||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.svg.path.command :as upc]))
|
||||
[app.common.types.path :as path]))
|
||||
|
||||
(defn lookup-profile
|
||||
([state]
|
||||
|
@ -157,7 +157,7 @@
|
|||
shape)
|
||||
modifiers (dm/get-in content-modifiers [id :content-modifiers])
|
||||
shape (if (some? modifiers)
|
||||
(update shape :content upc/apply-content-modifiers modifiers)
|
||||
(update shape :content path/apply-content-modifiers modifiers)
|
||||
shape)]
|
||||
(assoc result id shape))
|
||||
result))
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.helpers :as cph]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.svg.path.shapes-to-path :as stp]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.bool :as bool]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -30,7 +31,7 @@
|
|||
(or id (uuid/next))
|
||||
|
||||
shapes
|
||||
(mapv #(stp/convert-to-path % objects) shapes)
|
||||
(mapv #(path/convert-to-path % objects) shapes)
|
||||
|
||||
head
|
||||
(if (= type :difference) (first shapes) (last shapes))
|
||||
|
@ -38,7 +39,7 @@
|
|||
head
|
||||
(cond-> head
|
||||
(and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
(assoc :fills stp/default-bool-fills))
|
||||
(assoc :fills bool/default-fills))
|
||||
|
||||
shape
|
||||
{:id shape-id
|
||||
|
@ -51,7 +52,7 @@
|
|||
|
||||
shape
|
||||
(-> shape
|
||||
(merge (select-keys head stp/style-properties))
|
||||
(merge (select-keys head bool/style-properties))
|
||||
(cts/setup-shape)
|
||||
(gsh/update-bool-selrect shapes objects))]
|
||||
|
||||
|
@ -108,12 +109,12 @@
|
|||
[type group objects]
|
||||
(let [shapes (->> (:shapes group)
|
||||
(map #(get objects %))
|
||||
(mapv #(stp/convert-to-path % objects)))
|
||||
(mapv #(path/convert-to-path % objects)))
|
||||
head (if (= type :difference) (first shapes) (last shapes))
|
||||
head (cond-> head
|
||||
(and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
(assoc :fills stp/default-bool-fills))
|
||||
head-data (select-keys head stp/style-properties)]
|
||||
(assoc :fills bool/default-fills))
|
||||
head-data (select-keys head bool/style-properties)]
|
||||
|
||||
(-> group
|
||||
(assoc :type :bool)
|
||||
|
@ -136,7 +137,7 @@
|
|||
(-> shape
|
||||
(assoc :type :group)
|
||||
(dissoc :bool-type)
|
||||
(d/without-keys stp/style-group-properties)
|
||||
(d/without-keys bool/style-group-properties)
|
||||
(gsh/update-group-selrect
|
||||
(mapv (d/getf objects)
|
||||
(:shapes shape)))))
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
|
@ -65,6 +66,10 @@
|
|||
(-> (assoc :height 17 :width 4 :grow-type :auto-width)
|
||||
(cts/setup-shape))
|
||||
|
||||
(or (cfh/path-shape? shape)
|
||||
(cfh/bool-shape? shape))
|
||||
(update :content path/content)
|
||||
|
||||
:always
|
||||
(dissoc :initialized? :click-draw?))]
|
||||
|
||||
|
|
|
@ -9,11 +9,10 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.flex-layout :as gslf]
|
||||
[app.common.geom.shapes.grid-layout :as gslg]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.path.segment :as path.segm]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
|
@ -37,8 +36,8 @@
|
|||
(fn [object]
|
||||
(let [segments (-> (:segments object)
|
||||
(conj point))
|
||||
content (gsp/segments->content segments)
|
||||
selrect (gsh/content->selrect content)
|
||||
content (path.segm/segments->content segments)
|
||||
selrect (path.segm/content->selrect content)
|
||||
points (grc/rect->points selrect)]
|
||||
(-> object
|
||||
(assoc :segments segments)
|
||||
|
@ -81,8 +80,8 @@
|
|||
(update-in state [:workspace-drawing :object]
|
||||
(fn [{:keys [segments] :as shape}]
|
||||
(let [segments (ups/simplify segments simplify-tolerance)
|
||||
content (gsp/segments->content segments)
|
||||
selrect (gsh/content->selrect content)
|
||||
content (path.segm/segments->content segments)
|
||||
selrect (path.segm/content->selrect content)
|
||||
points (grc/rect->points selrect)]
|
||||
(-> shape
|
||||
(dissoc :segments)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.attrs :refer [editable-attrs]]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
|
@ -705,6 +706,9 @@
|
|||
(gsh/transform-shape modifiers)
|
||||
(cond-> (d/not-empty? pos-data)
|
||||
(assoc-position-data pos-data shape))
|
||||
(cond-> (or (cfh/path-shape? shape)
|
||||
(cfh/bool-shape? shape))
|
||||
(update :content path/content))
|
||||
(cond-> text-shape?
|
||||
(update-grow-type shape)))))]
|
||||
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
|
||||
(ns app.main.data.workspace.path.changes
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.types.path :as path]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace.path.common :refer [check-path-content!]]
|
||||
[app.main.data.workspace.path.helpers :as helpers]
|
||||
[app.main.data.workspace.path.state :as st]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
@ -20,31 +18,25 @@
|
|||
"Generates changes to update the new content of the shape"
|
||||
[it objects page-id shape old-content new-content]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid path content"
|
||||
(and (check-path-content! old-content)
|
||||
(check-path-content! new-content)))
|
||||
(assert (path/check-path-content old-content))
|
||||
(assert (path/check-path-content new-content))
|
||||
|
||||
(let [shape-id (:id shape)
|
||||
|
||||
[old-points old-selrect]
|
||||
(helpers/content->points+selrect shape old-content)
|
||||
|
||||
[new-points new-selrect]
|
||||
(helpers/content->points+selrect shape new-content)
|
||||
|
||||
;; We set the old values so the update-shapes works
|
||||
objects
|
||||
(-> objects
|
||||
(update
|
||||
shape-id
|
||||
assoc
|
||||
:content old-content
|
||||
:selrect old-selrect
|
||||
:points old-points))
|
||||
(update objects shape-id
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(assoc :content old-content)
|
||||
(path/update-geometry))))
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects))]
|
||||
changes
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects))
|
||||
|
||||
new-content
|
||||
(path/content new-content)]
|
||||
|
||||
(cond
|
||||
;; https://tree.taiga.io/project/penpot/issue/2366
|
||||
|
@ -60,10 +52,9 @@
|
|||
(-> changes
|
||||
(pcb/update-shapes [shape-id]
|
||||
(fn [shape]
|
||||
(assoc shape
|
||||
:content new-content
|
||||
:selrect new-selrect
|
||||
:points new-points)))
|
||||
(-> shape
|
||||
(assoc :content new-content)
|
||||
(path/update-geometry))))
|
||||
(pcb/resize-parents [shape-id])))))
|
||||
|
||||
(defn save-path-content
|
||||
|
@ -88,6 +79,7 @@
|
|||
id (get-in state [:workspace-local :edition])
|
||||
old-content (get-in state [:workspace-local :edit-path id :old-content])
|
||||
shape (st/get-path state)]
|
||||
|
||||
(if (and (some? old-content) (some? (:id shape)))
|
||||
(let [changes (generate-path-changes it objects page-id shape old-content (:content shape))]
|
||||
(rx/of (dch/commit-changes changes)))
|
||||
|
|
|
@ -6,44 +6,10 @@
|
|||
|
||||
(ns app.main.data.workspace.path.common
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.svg.path.subpath :as ups]
|
||||
[app.common.types.path :as path]
|
||||
[app.main.data.workspace.path.state :as st]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(def valid-commands
|
||||
#{:move-to
|
||||
:line-to
|
||||
:line-to-horizontal
|
||||
:line-to-vertical
|
||||
:curve-to
|
||||
:smooth-curve-to
|
||||
:quadratic-bezier-curve-to
|
||||
:smooth-quadratic-bezier-curve-to
|
||||
:elliptical-arc
|
||||
:close-path})
|
||||
|
||||
;; FIXME: should this schema be defined on common.types ?
|
||||
|
||||
(def ^:private
|
||||
schema:path-content
|
||||
[:vector {:title "PathContent"}
|
||||
[:map {:title "PathContentEntry"}
|
||||
[:command [::sm/one-of valid-commands]]
|
||||
;; FIXME: remove the `?` from prop name
|
||||
[:relative? {:optional true} :boolean]
|
||||
[:params {:optional true}
|
||||
[:map {:title "PathContentEntryParams"}
|
||||
[:x :double]
|
||||
[:y :double]
|
||||
[:c1x {:optional true} :double]
|
||||
[:c1y {:optional true} :double]
|
||||
[:c2x {:optional true} :double]
|
||||
[:c2y {:optional true} :double]]]]])
|
||||
|
||||
(def check-path-content!
|
||||
(sm/check-fn schema:path-content))
|
||||
|
||||
(defn init-path []
|
||||
(ptk/reify ::init-path))
|
||||
|
||||
|
@ -59,4 +25,4 @@
|
|||
(let [id (st/get-path-id state)]
|
||||
(-> state
|
||||
(update-in [:workspace-local :edit-path id] clean-edit-state)
|
||||
(update-in (st/get-path-location state :content) ups/close-subpaths))))))
|
||||
(update-in (st/get-path-location state :content) path/close-subpaths))))))
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.flex-layout :as gsl]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.shapes-to-path :as upsp]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.helpers :as path.helpers]
|
||||
[app.common.types.path.segment :as path.segment]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
|
@ -19,7 +20,7 @@
|
|||
[app.main.data.workspace.drawing.common :as dwdc]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.path.changes :as changes]
|
||||
[app.main.data.workspace.path.common :as common :refer [check-path-content!]]
|
||||
[app.main.data.workspace.path.common :as common]
|
||||
[app.main.data.workspace.path.helpers :as helpers]
|
||||
[app.main.data.workspace.path.state :as st]
|
||||
[app.main.data.workspace.path.streams :as streams]
|
||||
|
@ -39,10 +40,10 @@
|
|||
fix-angle? shift?
|
||||
last-point (get-in state [:workspace-local :edit-path id :last-point])
|
||||
position (cond-> (gpt/point x y)
|
||||
fix-angle? (helpers/position-fixed-angle last-point))
|
||||
fix-angle? (path.helpers/position-fixed-angle last-point))
|
||||
shape (st/get-path state)
|
||||
{:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])
|
||||
command (helpers/next-node shape position last-point prev-handler)]
|
||||
command (path.segment/next-node shape position last-point prev-handler)]
|
||||
(assoc-in state [:workspace-local :edit-path id :preview] command)))))
|
||||
|
||||
(defn add-node
|
||||
|
@ -54,7 +55,7 @@
|
|||
fix-angle? shift?
|
||||
{:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id])
|
||||
position (cond-> (gpt/point x y)
|
||||
fix-angle? (helpers/position-fixed-angle last-point))]
|
||||
fix-angle? (path.helpers/position-fixed-angle last-point))]
|
||||
(if-not (= last-point position)
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :edit-path id :last-point] position)
|
||||
|
@ -75,12 +76,12 @@
|
|||
|
||||
index (or index (count content))
|
||||
prefix (or prefix :c1)
|
||||
position (or position (upc/command->point (nth content (dec index))))
|
||||
position (or position (path.segment/get-point (nth content (dec index))))
|
||||
|
||||
old-handler (upc/handler->point content index prefix)
|
||||
old-handler (path.segment/handler->point content index prefix)
|
||||
|
||||
handler-position (cond-> (gpt/point x y)
|
||||
shift? (helpers/position-fixed-angle position))
|
||||
shift? (path.helpers/position-fixed-angle position))
|
||||
|
||||
{dx :x dy :y} (if (some? old-handler)
|
||||
(gpt/add (gpt/to-vec old-handler position)
|
||||
|
@ -102,7 +103,7 @@
|
|||
|
||||
modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])
|
||||
content (-> (st/get-path state :content)
|
||||
(upc/apply-content-modifiers modifiers))
|
||||
(path/apply-content-modifiers modifiers))
|
||||
|
||||
handler (get-in state [:workspace-local :edit-path id :drag-handler])]
|
||||
(-> state
|
||||
|
@ -110,7 +111,7 @@
|
|||
(update-in [:workspace-local :edit-path id] dissoc :drag-handler)
|
||||
(update-in [:workspace-local :edit-path id] dissoc :content-modifiers)
|
||||
(assoc-in [:workspace-local :edit-path id :prev-handler] handler)
|
||||
(update-in (st/get-path-location state) helpers/update-selrect))))
|
||||
(update-in (st/get-path-location state) path/update-geometry))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -128,7 +129,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [content (st/get-path state :content)
|
||||
handlers (-> (upc/content->handlers content)
|
||||
handlers (-> (path.segment/content->handlers content)
|
||||
(get position))
|
||||
|
||||
[idx prefix] (when (= (count handlers) 1)
|
||||
|
@ -254,7 +255,12 @@
|
|||
(update [_ state]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
content (get-in state [:workspace-drawing :object :content] [])
|
||||
position (gpt/point (get-in content [0 :params] nil))
|
||||
|
||||
;; FIXME: use native operation for retrieve the first position
|
||||
position (-> (nth content 0)
|
||||
(get :params)
|
||||
(gpt/point))
|
||||
|
||||
frame-id (->> (ctst/top-nested-frame objects position)
|
||||
(ctn/get-first-not-copy-parent objects) ;; We don't want to change the structure of component copies
|
||||
:id)
|
||||
|
@ -274,11 +280,10 @@
|
|||
(ptk/reify ::handle-new-shape-result
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [content (get-in state [:workspace-drawing :object :content] [])]
|
||||
(let [content (dm/get-in state [:workspace-drawing :object :content])]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid path content"
|
||||
(check-path-content! content))
|
||||
(assert (path/check-path-content content)
|
||||
"expected valid path content instance")
|
||||
|
||||
(if (> (count content) 1)
|
||||
(assoc-in state [:workspace-drawing :object :initialized?] true)
|
||||
|
@ -286,8 +291,8 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [content (get-in state [:workspace-drawing :object :content] [])]
|
||||
(if (and (seq content) (> (count content) 1))
|
||||
(when-let [content (dm/get-in state [:workspace-drawing :object :content])]
|
||||
(if (> (count content) 1)
|
||||
(rx/of (setup-frame)
|
||||
(dwdc/handle-finish-drawing)
|
||||
(dwe/start-edition-mode shape-id)
|
||||
|
@ -300,9 +305,8 @@
|
|||
(ptk/reify ::handle-new-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [shape (cts/setup-shape {:type :path})]
|
||||
(-> state
|
||||
(update :workspace-drawing assoc :object shape))))
|
||||
(let [shape (cts/setup-shape {:type :path :content (path/content nil)})]
|
||||
(update state :workspace-drawing assoc :object shape)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -334,12 +338,12 @@
|
|||
edit-mode (get-in state [:workspace-local :edit-path id :edit-mode])]
|
||||
(if (= :draw edit-mode)
|
||||
(rx/concat
|
||||
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path))
|
||||
(rx/of (dwsh/update-shapes [id] path/convert-to-path))
|
||||
(rx/of (handle-drawing id))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::common/finish-path))
|
||||
(rx/take 1)
|
||||
(rx/merge-map #(rx/of (check-changed-content)))))
|
||||
(rx/map check-changed-content)))
|
||||
(rx/empty))))))
|
||||
|
||||
(defn check-changed-content []
|
||||
|
|
|
@ -10,10 +10,9 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as upg]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.shapes-to-path :as upsp]
|
||||
[app.common.svg.path.subpath :as ups]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.helpers :as path.helpers]
|
||||
[app.common.types.path.segment :as path.segment]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
|
@ -27,7 +26,6 @@
|
|||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.mouse :as mse]
|
||||
[app.util.path.tools :as upt]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
|
@ -58,10 +56,10 @@
|
|||
content-modifiers (dm/get-in state [:workspace-local :edit-path id :content-modifiers])
|
||||
|
||||
content (:content shape)
|
||||
new-content (upc/apply-content-modifiers content content-modifiers)
|
||||
new-content (path/apply-content-modifiers content content-modifiers)
|
||||
|
||||
old-points (->> content upg/content->points)
|
||||
new-points (->> new-content upg/content->points)
|
||||
old-points (->> content path.segment/content->points)
|
||||
new-points (->> new-content path.segment/content->points)
|
||||
point-change (->> (map hash-map old-points new-points) (reduce merge))]
|
||||
|
||||
(when (and (some? new-content) (some? shape))
|
||||
|
@ -75,8 +73,8 @@
|
|||
|
||||
(defn modify-content-point
|
||||
[content {dx :x dy :y} modifiers point]
|
||||
(let [point-indices (upc/point-indices content point) ;; [indices]
|
||||
handler-indices (upc/handler-indices content point) ;; [[index prefix]]
|
||||
(let [point-indices (path.segment/point-indices content point) ;; [indices]
|
||||
handler-indices (path.segment/handler-indices content point) ;; [[index prefix]]
|
||||
|
||||
modify-point
|
||||
(fn [modifiers index]
|
||||
|
@ -116,7 +114,7 @@
|
|||
(let [id (st/get-path-id state)
|
||||
content (st/get-path state :content)
|
||||
to-point (cond-> to-point
|
||||
(:shift? to-point) (helpers/position-fixed-angle from-point))
|
||||
(:shift? to-point) (path.helpers/position-fixed-angle from-point))
|
||||
|
||||
delta (gpt/subtract to-point from-point)
|
||||
|
||||
|
@ -144,7 +142,7 @@
|
|||
selected? (contains? selected-points position)]
|
||||
(streams/drag-stream
|
||||
(rx/of
|
||||
(dwsh/update-shapes [id] upsp/convert-to-path)
|
||||
(dwsh/update-shapes [id] path/convert-to-path)
|
||||
(when-not selected? (selection/select-node position shift?))
|
||||
(drag-selected-points @ms/mouse-position))
|
||||
(rx/of (selection/select-node position shift?)))))))
|
||||
|
@ -163,7 +161,7 @@
|
|||
start-position (apply min-key #(gpt/distance start-position %) selected-points)
|
||||
|
||||
content (st/get-path state :content)
|
||||
points (upg/content->points content)]
|
||||
points (path.segment/content->points content)]
|
||||
|
||||
(rx/concat
|
||||
;; This stream checks the consecutive mouse positions to do the dragging
|
||||
|
@ -228,7 +226,7 @@
|
|||
mov-vec (gpt/multiply (get-displacement direction) scale)]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path))
|
||||
(rx/of (dwsh/update-shapes [id] path/convert-to-path))
|
||||
(rx/merge
|
||||
(->> move-events
|
||||
(rx/take-until stopper)
|
||||
|
@ -256,22 +254,22 @@
|
|||
start-delta-y (dm/get-in modifiers [index cy] 0)
|
||||
|
||||
content (st/get-path state :content)
|
||||
points (upg/content->points content)
|
||||
points (path.segment/content->points content)
|
||||
|
||||
point (-> content (get (if (= prefix :c1) (dec index) index)) (upc/command->point))
|
||||
handler (-> content (get index) (upc/get-handler prefix))
|
||||
point (-> content (nth (if (= prefix :c1) (dec index) index)) (path.segment/get-point))
|
||||
handler (-> content (nth index) (path.segment/get-handler prefix))
|
||||
|
||||
[op-idx op-prefix] (upc/opposite-index content index prefix)
|
||||
opposite (upc/handler->point content op-idx op-prefix)]
|
||||
[op-idx op-prefix] (path.segment/opposite-index content index prefix)
|
||||
opposite (path.segment/handler->point content op-idx op-prefix)]
|
||||
|
||||
(streams/drag-stream
|
||||
(rx/concat
|
||||
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path))
|
||||
(rx/of (dwsh/update-shapes [id] path/convert-to-path))
|
||||
(->> (streams/move-handler-stream handler point handler opposite points)
|
||||
(rx/map
|
||||
(fn [{:keys [x y alt? shift?]}]
|
||||
(let [pos (cond-> (gpt/point x y)
|
||||
shift? (helpers/position-fixed-angle point))]
|
||||
shift? (path.helpers/position-fixed-angle point))]
|
||||
(modify-handler
|
||||
id
|
||||
index
|
||||
|
@ -294,19 +292,22 @@
|
|||
(ptk/reify ::start-path-edit
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
edit-path (dm/get-in state [:workspace-local :edit-path id])
|
||||
content (st/get-path state :content)
|
||||
state (cond-> state
|
||||
(cfh/path-shape? objects id)
|
||||
(st/set-content (ups/close-subpaths content)))]
|
||||
content (st/get-path state :content)
|
||||
state (cond-> state
|
||||
(cfh/path-shape? objects id)
|
||||
(st/set-content (path/close-subpaths content)))]
|
||||
|
||||
(cond-> state
|
||||
(or (not edit-path) (= :draw (:edit-mode edit-path)))
|
||||
(or (not edit-path)
|
||||
(= :draw (:edit-mode edit-path)))
|
||||
(assoc-in [:workspace-local :edit-path id] {:edit-mode :move
|
||||
:selected #{}
|
||||
:snap-toggled false})
|
||||
|
||||
(and (some? edit-path) (= :move (:edit-mode edit-path)))
|
||||
(and (some? edit-path)
|
||||
(= :move (:edit-mode edit-path)))
|
||||
(assoc-in [:workspace-local :edit-path id :edit-mode] :draw))))
|
||||
|
||||
ptk/WatchEvent
|
||||
|
@ -343,7 +344,9 @@
|
|||
content (st/get-path state :content)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :edit-path id :old-content] content)
|
||||
(st/set-content (-> content (upt/split-segments #{from-p to-p} t))))))
|
||||
(st/set-content (-> content
|
||||
(path.segment/split-segments #{from-p to-p} t)
|
||||
(path/content))))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -355,5 +358,5 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [id (st/get-path-id state)]
|
||||
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path)
|
||||
(rx/of (dwsh/update-shapes [id] path/convert-to-path)
|
||||
(split-segments event))))))
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
|
||||
(ns app.main.data.workspace.path.helpers
|
||||
(:require
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.helpers :as path.helpers]
|
||||
[app.common.types.path.segment :as path.segment]
|
||||
[app.main.data.workspace.path.common :as common]
|
||||
[app.util.mouse :as mse]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
@ -28,96 +27,13 @@
|
|||
(and ^boolean (mse/mouse-event? event)
|
||||
^boolean (mse/mouse-double-click-event? event)))))
|
||||
|
||||
(defn content-center
|
||||
[content]
|
||||
(-> content
|
||||
gsh/content->selrect
|
||||
grc/rect->center))
|
||||
|
||||
(defn content->points+selrect
|
||||
"Given the content of a shape, calculate its points and selrect"
|
||||
[shape content]
|
||||
(let [{:keys [flip-x flip-y]} shape
|
||||
transform
|
||||
(cond-> (:transform shape (gmt/matrix))
|
||||
flip-x (gmt/scale (gpt/point -1 1))
|
||||
flip-y (gmt/scale (gpt/point 1 -1)))
|
||||
|
||||
transform-inverse
|
||||
(cond-> (gmt/matrix)
|
||||
flip-x (gmt/scale (gpt/point -1 1))
|
||||
flip-y (gmt/scale (gpt/point 1 -1))
|
||||
:always (gmt/multiply (:transform-inverse shape (gmt/matrix))))
|
||||
|
||||
center (or (gsh/shape->center shape)
|
||||
(content-center content))
|
||||
|
||||
base-content (gsh/transform-content
|
||||
content
|
||||
(gmt/transform-in center transform-inverse))
|
||||
|
||||
;; Calculates the new selrect with points given the old center
|
||||
points (-> (gsh/content->selrect base-content)
|
||||
(grc/rect->points)
|
||||
(gsh/transform-points center transform))
|
||||
|
||||
points-center (gsh/points->center points)
|
||||
|
||||
;; Points is now the selrect but the center is different so we can create the selrect
|
||||
;; through points
|
||||
selrect (-> points
|
||||
(gsh/transform-points points-center transform-inverse)
|
||||
(grc/points->rect))]
|
||||
[points selrect]))
|
||||
|
||||
(defn update-selrect
|
||||
"Updates the selrect and points for a path"
|
||||
[shape]
|
||||
(let [[points selrect] (content->points+selrect shape (:content shape))]
|
||||
(assoc shape :points points :selrect selrect)))
|
||||
|
||||
(defn closest-angle
|
||||
[angle]
|
||||
(cond
|
||||
(or (> angle 337.5) (<= angle 22.5)) 0
|
||||
(and (> angle 22.5) (<= angle 67.5)) 45
|
||||
(and (> angle 67.5) (<= angle 112.5)) 90
|
||||
(and (> angle 112.5) (<= angle 157.5)) 135
|
||||
(and (> angle 157.5) (<= angle 202.5)) 180
|
||||
(and (> angle 202.5) (<= angle 247.5)) 225
|
||||
(and (> angle 247.5) (<= angle 292.5)) 270
|
||||
(and (> angle 292.5) (<= angle 337.5)) 315))
|
||||
|
||||
(defn position-fixed-angle [point from-point]
|
||||
(if (and from-point point)
|
||||
(let [angle (mod (+ 360 (- (gpt/angle point from-point))) 360)
|
||||
to-angle (closest-angle angle)
|
||||
distance (gpt/distance point from-point)]
|
||||
(gpt/angle->point from-point (mth/radians to-angle) distance))
|
||||
point))
|
||||
|
||||
(defn next-node
|
||||
"Calculates the next-node to be inserted."
|
||||
[shape position prev-point prev-handler]
|
||||
(let [position (select-keys position [:x :y])
|
||||
last-command (-> shape :content last :command)
|
||||
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))]
|
||||
(cond
|
||||
add-line? {:command :line-to
|
||||
:params position}
|
||||
add-curve? {:command :curve-to
|
||||
:params (upc/make-curve-params position prev-handler)}
|
||||
:else {:command :move-to
|
||||
:params position})))
|
||||
|
||||
(defn append-node
|
||||
"Creates a new node in the path. Usually used when drawing."
|
||||
[shape position prev-point prev-handler]
|
||||
(let [command (next-node shape position prev-point prev-handler)]
|
||||
(let [segment (path.segment/next-node (:content shape) position prev-point prev-handler)]
|
||||
(-> shape
|
||||
(update :content (fnil conj []) command)
|
||||
(update-selrect))))
|
||||
(update :content path.segment/append-segment segment)
|
||||
(path/update-geometry))))
|
||||
|
||||
(defn angle-points [common p1 p2]
|
||||
(mth/abs
|
||||
|
@ -125,7 +41,7 @@
|
|||
(gpt/to-vec common p1)
|
||||
(gpt/to-vec common p2))))
|
||||
|
||||
(defn calculate-opposite-delta [node handler opposite match-angle? match-distance? dx dy]
|
||||
(defn- calculate-opposite-delta [node handler opposite match-angle? match-distance? dx dy]
|
||||
(when (and (some? handler) (some? opposite))
|
||||
(let [;; To match the angle, the angle should be matching (angle between points 180deg)
|
||||
angle-handlers (angle-points node handler opposite)
|
||||
|
@ -159,14 +75,14 @@
|
|||
(defn move-handler-modifiers
|
||||
[content index prefix match-distance? match-angle? dx dy]
|
||||
|
||||
(let [[cx cy] (upc/prefix->coords prefix)
|
||||
[op-idx op-prefix] (upc/opposite-index content index prefix)
|
||||
(let [[cx cy] (path.helpers/prefix->coords prefix)
|
||||
[op-idx op-prefix] (path.segment/opposite-index content index prefix)
|
||||
|
||||
node (upc/handler->node content index prefix)
|
||||
handler (upc/handler->point content index prefix)
|
||||
opposite (upc/handler->point content op-idx op-prefix)
|
||||
node (path.segment/handler->node content index prefix)
|
||||
handler (path.segment/handler->point content index prefix)
|
||||
opposite (path.segment/handler->point content op-idx op-prefix)
|
||||
|
||||
[ocx ocy] (upc/prefix->coords op-prefix)
|
||||
[ocx ocy] (path.helpers/prefix->coords op-prefix)
|
||||
[odx ody] (calculate-opposite-delta node handler opposite match-angle? match-distance? dx dy)
|
||||
|
||||
hnv (if (some? handler)
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.helpers :as cph]
|
||||
[app.common.svg.path.shapes-to-path :as upsp]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.path :as path]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[beicon.v2.core :as rx]
|
||||
|
@ -35,7 +35,8 @@
|
|||
changes
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/update-shapes selected #(upsp/convert-to-path % objects))
|
||||
;; FIXME: use with-objects? true
|
||||
(pcb/update-shapes selected #(path/convert-to-path % objects))
|
||||
(pcb/remove-objects children-ids))]
|
||||
|
||||
(rx/of (dch/commit-changes changes)))))))
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cph]
|
||||
[app.common.svg.path.shapes-to-path :as upsp]))
|
||||
[app.common.types.path.shape-to-path :as stp]))
|
||||
|
||||
(defn path-editing?
|
||||
"Returns true if we're editing a path or creating a new one."
|
||||
|
@ -63,8 +63,7 @@
|
|||
[state & ks]
|
||||
(let [path-loc (get-path-location state)
|
||||
shape (-> (get-in state path-loc)
|
||||
;; Empty map because we know the current shape will not have children
|
||||
(upsp/convert-to-path {}))]
|
||||
(stp/convert-to-path {}))]
|
||||
(if (empty? ks)
|
||||
shape
|
||||
(get-in shape ks))))
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as upg]
|
||||
[app.common.types.path.segment :as path.segm]
|
||||
[app.main.constants :refer [zoom-half-pixel-precision]]
|
||||
[app.main.data.workspace.path.state :as pst]
|
||||
[app.main.snap :as snap]
|
||||
|
@ -170,7 +170,7 @@
|
|||
|
||||
ranges-stream
|
||||
(->> content-stream
|
||||
(rx/map upg/content->points)
|
||||
(rx/map path.segm/content->points)
|
||||
(rx/map snap/create-ranges))]
|
||||
|
||||
(->> ms/mouse-position
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
|
||||
(ns app.main.data.workspace.path.tools
|
||||
(:require
|
||||
[app.common.svg.path.shapes-to-path :as upsp]
|
||||
[app.common.svg.path.subpath :as ups]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.segment :as path.segment]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.path.changes :as changes]
|
||||
[app.main.data.workspace.path.state :as st]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.util.path.tools :as upt]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
|
@ -26,19 +27,30 @@
|
|||
(ptk/reify ::process-path-tool
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
id (st/get-path-id state)
|
||||
page-id (:current-page-id state)
|
||||
shape (st/get-path state)
|
||||
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
|
||||
points (or points selected-points)]
|
||||
(let [page-id (get state :current-page-id)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
|
||||
shape (st/get-path state)
|
||||
id (st/get-path-id state)
|
||||
|
||||
selected-points
|
||||
(dm/get-in state [:workspace-local :edit-path id :selected-points] #{})
|
||||
|
||||
points
|
||||
(or points selected-points)]
|
||||
|
||||
(when (and (seq points) (some? shape))
|
||||
(let [new-content (-> (tool-fn (:content shape) points)
|
||||
(ups/close-subpaths))
|
||||
changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
|
||||
(let [new-content
|
||||
(-> (tool-fn (:content shape) points)
|
||||
(path/close-subpaths))
|
||||
|
||||
changes
|
||||
(changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (dwsh/update-shapes [id] upsp/convert-to-path))
|
||||
(if (cfh/path-shape? shape)
|
||||
(rx/empty)
|
||||
(rx/of (dwsh/update-shapes [id] path/convert-to-path)))
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(when (empty? new-content)
|
||||
(dwe/clear-edition-mode)))))))))))
|
||||
|
@ -50,7 +62,7 @@
|
|||
(process-path-tool
|
||||
(when point #{point})
|
||||
(fn [content points]
|
||||
(reduce upt/make-corner-point content points)))))
|
||||
(reduce path.segment/make-corner-point content points)))))
|
||||
|
||||
(defn make-curve
|
||||
([]
|
||||
|
@ -59,22 +71,22 @@
|
|||
(process-path-tool
|
||||
(when point #{point})
|
||||
(fn [content points]
|
||||
(reduce upt/make-curve-point content points)))))
|
||||
(reduce path.segment/make-curve-point content points)))))
|
||||
|
||||
(defn add-node []
|
||||
(process-path-tool (fn [content points] (upt/split-segments content points 0.5))))
|
||||
(process-path-tool (fn [content points] (path.segment/split-segments content points 0.5))))
|
||||
|
||||
(defn remove-node []
|
||||
(process-path-tool upt/remove-nodes))
|
||||
(process-path-tool path.segment/remove-nodes))
|
||||
|
||||
(defn merge-nodes []
|
||||
(process-path-tool upt/merge-nodes))
|
||||
(process-path-tool path.segment/merge-nodes))
|
||||
|
||||
(defn join-nodes []
|
||||
(process-path-tool upt/join-nodes))
|
||||
(process-path-tool path.segment/join-nodes))
|
||||
|
||||
(defn separate-nodes []
|
||||
(process-path-tool upt/separate-nodes))
|
||||
(process-path-tool path.segment/separate-nodes))
|
||||
|
||||
(defn toggle-snap []
|
||||
(ptk/reify ::toggle-snap
|
||||
|
|
|
@ -47,40 +47,53 @@
|
|||
|
||||
(defn update-shapes
|
||||
([ids update-fn] (update-shapes ids update-fn nil))
|
||||
([ids update-fn {:keys [reg-objects? save-undo? stack-undo? attrs ignore-tree page-id ignore-touched undo-group with-objects? changed-sub-attr]
|
||||
:or {reg-objects? false save-undo? true stack-undo? false ignore-touched false with-objects? false}}]
|
||||
([ids update-fn
|
||||
{:keys [reg-objects? save-undo? stack-undo? attrs ignore-tree page-id
|
||||
ignore-touched undo-group with-objects? changed-sub-attr]
|
||||
:or {reg-objects? false
|
||||
save-undo? true
|
||||
stack-undo? false
|
||||
ignore-touched false
|
||||
with-objects? false}}]
|
||||
|
||||
(assert (sm/check-coll-of-uuid ids))
|
||||
(assert (fn? update-fn))
|
||||
(assert (every? uuid? ids) "expect a coll of uuid for `ids`")
|
||||
(assert (fn? update-fn) "the `update-fn` should be a valid function")
|
||||
|
||||
(ptk/reify ::update-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page-id (or page-id (:current-page-id state))
|
||||
(let [page-id (or page-id (get state :current-page-id))
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
ids (into [] (filter some?) ids)
|
||||
|
||||
xf-update-layout
|
||||
(comp
|
||||
(map (d/getf objects))
|
||||
(filter #(some update-layout-attr? (pcb/changed-attrs % objects update-fn {:attrs attrs :with-objects? with-objects?})))
|
||||
(map :id))
|
||||
|
||||
update-layout-ids
|
||||
(->> ids
|
||||
(map (d/getf objects))
|
||||
(filter #(some update-layout-attr? (pcb/changed-attrs % objects update-fn {:attrs attrs :with-objects? with-objects?})))
|
||||
(map :id))
|
||||
(->> (into [] xf-update-layout ids)
|
||||
(not-empty))
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/set-save-undo? save-undo?)
|
||||
(pcb/set-stack-undo? stack-undo?)
|
||||
(cls/generate-update-shapes ids
|
||||
update-fn
|
||||
objects
|
||||
{:attrs attrs
|
||||
:changed-sub-attr changed-sub-attr
|
||||
:ignore-tree ignore-tree
|
||||
:ignore-touched ignore-touched
|
||||
:with-objects? with-objects?})
|
||||
(cond-> undo-group
|
||||
(pcb/set-undo-group undo-group)))
|
||||
changes
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/set-save-undo? save-undo?)
|
||||
(pcb/set-stack-undo? stack-undo?)
|
||||
(cls/generate-update-shapes ids
|
||||
update-fn
|
||||
objects
|
||||
{:attrs attrs
|
||||
:changed-sub-attr changed-sub-attr
|
||||
:ignore-tree ignore-tree
|
||||
:ignore-touched ignore-touched
|
||||
:with-objects? with-objects?})
|
||||
(cond-> undo-group
|
||||
(pcb/set-undo-group undo-group)))
|
||||
|
||||
changes
|
||||
(add-undo-group changes state)]
|
||||
|
||||
changes (add-undo-group changes state)]
|
||||
(rx/concat
|
||||
(if (seq (:redo-changes changes))
|
||||
(let [changes (cond-> changes reg-objects? (pcb/resize-parents ids))]
|
||||
|
@ -88,7 +101,7 @@
|
|||
(rx/empty))
|
||||
|
||||
;; Update layouts for properties marked
|
||||
(if (d/not-empty? update-layout-ids)
|
||||
(if update-layout-ids
|
||||
(rx/of (ptk/data-event :layout/update {:ids update-layout-ids}))
|
||||
(rx/empty))))))))
|
||||
|
||||
|
@ -112,11 +125,13 @@
|
|||
(pcb/with-objects objects)
|
||||
(cfsh/prepare-add-shape shape objects))
|
||||
|
||||
changes (cond-> changes
|
||||
(cfh/text-shape? shape)
|
||||
(pcb/set-undo-group (:id shape)))
|
||||
changes
|
||||
(cond-> changes
|
||||
(cfh/text-shape? shape)
|
||||
(pcb/set-undo-group (:id shape)))
|
||||
|
||||
undo-id (js/Symbol)]
|
||||
undo-id
|
||||
(js/Symbol)]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
|
|
|
@ -110,4 +110,3 @@
|
|||
(log/inf :hint "initialized"
|
||||
:enabled (str/join "," features)
|
||||
:runtime (str/join "," (:features-runtime state)))))))
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
(ns app.main.ui.shapes.bool
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.types.path :as path]
|
||||
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.shapes.export :as use]
|
||||
[app.main.ui.shapes.path :refer [path-shape]]
|
||||
|
@ -30,7 +31,7 @@
|
|||
content
|
||||
|
||||
(some? child-objs)
|
||||
(gsh/calc-bool-content shape child-objs))))
|
||||
(path/calc-bool-content shape child-objs))))
|
||||
|
||||
shape (mf/with-memo [shape content]
|
||||
(assoc shape :content content))]
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.bounds :as gsb]
|
||||
[app.common.geom.shapes.text :as gst]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.ui.context :as muc]
|
||||
|
@ -204,7 +205,7 @@
|
|||
{::mf/wrap-props false}
|
||||
[{:keys [shape stroke render-id index]}]
|
||||
(let [open-path? (and ^boolean (cfh/path-shape? shape)
|
||||
^boolean (gsh/open-path? shape))
|
||||
^boolean (path/shape-with-open-path? shape))
|
||||
gradient (:stroke-color-gradient stroke)
|
||||
alignment (:stroke-alignment stroke :center)
|
||||
width (:stroke-width stroke 0)
|
||||
|
@ -397,7 +398,7 @@
|
|||
has-stroke? (and (> stroke-width 0)
|
||||
(not= stroke-style :none))
|
||||
closed? (or (not ^boolean (cfh/path-shape? shape))
|
||||
(not ^boolean (gsh/open-path? shape)))
|
||||
(not ^boolean (path/shape-with-open-path? shape)))
|
||||
inner? (= :inner stroke-position)
|
||||
outer? (= :outer stroke-position)]
|
||||
|
||||
|
@ -496,7 +497,7 @@
|
|||
:style style})
|
||||
|
||||
open-path? (and ^boolean (cfh/path-shape? shape)
|
||||
^boolean (gsh/open-path? shape))]
|
||||
^boolean (path/shape-with-open-path? shape))]
|
||||
(when-not ^boolean (cfh/frame-shape? shape)
|
||||
(when (and (some? shape-blur)
|
||||
(not ^boolean (:hidden shape-blur)))
|
||||
|
|
|
@ -7,28 +7,35 @@
|
|||
(ns app.main.ui.shapes.path
|
||||
(:require
|
||||
[app.common.logging :as log]
|
||||
[app.common.types.path :as path]
|
||||
[app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.path.format :as upf]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- content->string
|
||||
[content]
|
||||
(cond
|
||||
(nil? content)
|
||||
""
|
||||
|
||||
(path/content? content)
|
||||
(.toString content)
|
||||
|
||||
:else
|
||||
(let [content (path/content content)]
|
||||
(.toString content))))
|
||||
|
||||
(mf/defc path-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
content (:content shape)
|
||||
{::mf/props :obj}
|
||||
[{:keys [shape]}]
|
||||
(let [content (get shape :content)
|
||||
pdata (mf/with-memo [content]
|
||||
(try
|
||||
(upf/format-path content)
|
||||
(catch :default e
|
||||
(content->string content)
|
||||
(catch :default cause
|
||||
(log/error :hint "unexpected error on formatting path"
|
||||
:shape-name (:name shape)
|
||||
:shape-id (:id shape)
|
||||
:cause e)
|
||||
"")))
|
||||
|
||||
props (-> #js {}
|
||||
(obj/set! "d" pdata))]
|
||||
|
||||
:cause cause)
|
||||
"")))]
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
[:> :path props]]))
|
||||
[:path {:d pdata}]]))
|
||||
|
|
|
@ -10,12 +10,13 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.geom.shapes.text :as gst]
|
||||
[app.common.math :as mth]
|
||||
[app.common.svg.path.bool :as pb]
|
||||
[app.common.svg.path.shapes-to-path :as stp]
|
||||
[app.common.svg.path.subpath :as ups]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.bool :as path.bool]
|
||||
[app.common.types.path.helpers :as path.helpers]
|
||||
[app.common.types.path.segment :as path.segment]
|
||||
[app.common.types.path.subpath :as path.subpath]
|
||||
[app.main.refs :as refs]
|
||||
[app.util.color :as uc]
|
||||
[app.util.debug :as dbg]
|
||||
|
@ -101,49 +102,49 @@
|
|||
radius (/ 3 zoom)
|
||||
|
||||
c1 (-> (get objects (first (:shapes shape)))
|
||||
(stp/convert-to-path objects))
|
||||
(path/convert-to-path objects))
|
||||
c2 (-> (get objects (second (:shapes shape)))
|
||||
(stp/convert-to-path objects))
|
||||
(path/convert-to-path objects))
|
||||
|
||||
content-a (:content c1)
|
||||
content-b (:content c2)
|
||||
|
||||
bool-type (:bool-type shape)
|
||||
should-reverse? (and (not= :union bool-type)
|
||||
(= (ups/clockwise? content-b)
|
||||
(ups/clockwise? content-a)))
|
||||
(= (path.subpath/clockwise? content-b)
|
||||
(path.subpath/clockwise? content-a)))
|
||||
|
||||
content-a (-> (:content c1)
|
||||
(pb/close-paths)
|
||||
(pb/add-previous))
|
||||
(path.bool/close-paths)
|
||||
(path.bool/add-previous))
|
||||
|
||||
content-b (-> (:content c2)
|
||||
(pb/close-paths)
|
||||
(cond-> should-reverse? (ups/reverse-content))
|
||||
(pb/add-previous))
|
||||
(path.bool/close-paths)
|
||||
(cond-> should-reverse? (path.subpath/reverse-content))
|
||||
(path.bool/add-previous))
|
||||
|
||||
|
||||
sr-a (gsp/content->selrect content-a)
|
||||
sr-b (gsp/content->selrect content-b)
|
||||
sr-a (path.segment/content->selrect content-a)
|
||||
sr-b (path.segment/content->selrect content-b)
|
||||
|
||||
[content-a-split content-b-split] (pb/content-intersect-split content-a content-b sr-a sr-b)
|
||||
[content-a-split content-b-split] (path.bool/content-intersect-split content-a content-b sr-a sr-b)
|
||||
|
||||
;;content-a-geom (gsp/content->geom-data content-a)
|
||||
;;content-b-geom (gsp/content->geom-data content-b)
|
||||
;;content-a-split (->> content-a-split #_(filter #(pb/contains-segment? % content-b sr-b content-b-geom)))
|
||||
;;content-b-split (->> content-b-split #_(filter #(pb/contains-segment? % content-a sr-a content-a-geom)))
|
||||
;;content-a-geom (path.segment/content->geom-data content-a)
|
||||
;;content-b-geom (path.segment/content->geom-data content-b)
|
||||
;;content-a-split (->> content-a-split #_(filter #(path.bool/contains-segment? % content-b sr-b content-b-geom)))
|
||||
;;content-b-split (->> content-b-split #_(filter #(path.bool/contains-segment? % content-a sr-a content-a-geom)))
|
||||
]
|
||||
[:*
|
||||
(for [[i cmd] (d/enumerate content-a-split)]
|
||||
(let [p1 (:prev cmd)
|
||||
p2 (gsp/command->point cmd)
|
||||
p2 (path.helpers/command->point cmd)
|
||||
|
||||
hp (case (:command cmd)
|
||||
:line-to (-> (gsp/command->line cmd)
|
||||
(gsp/line-values 0.5))
|
||||
:line-to (-> (path.helpers/command->line cmd)
|
||||
(path.helpers/line-values 0.5))
|
||||
|
||||
:curve-to (-> (gsp/command->bezier cmd)
|
||||
(gsp/curve-values 0.5))
|
||||
:curve-to (-> (path.helpers/command->bezier cmd)
|
||||
(path.helpers/curve-values 0.5))
|
||||
nil)]
|
||||
[:*
|
||||
(when p1
|
||||
|
@ -155,14 +156,14 @@
|
|||
|
||||
(for [[i cmd] (d/enumerate content-b-split)]
|
||||
(let [p1 (:prev cmd)
|
||||
p2 (gsp/command->point cmd)
|
||||
p2 (path.helpers/command->point cmd)
|
||||
|
||||
hp (case (:command cmd)
|
||||
:line-to (-> (gsp/command->line cmd)
|
||||
(gsp/line-values 0.5))
|
||||
:line-to (-> (path.helpers/command->line cmd)
|
||||
(path.helpers/line-values 0.5))
|
||||
|
||||
:curve-to (-> (gsp/command->bezier cmd)
|
||||
(gsp/curve-values 0.5))
|
||||
:curve-to (-> (path.helpers/command->bezier cmd)
|
||||
(path.helpers/curve-values 0.5))
|
||||
nil)]
|
||||
[:*
|
||||
(when p1
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
(ns app.main.ui.workspace.shapes.path
|
||||
(:require
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.main.data.workspace.path.helpers :as helpers]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.path :as types.path]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.shapes.path :as path]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
|
@ -15,25 +15,31 @@
|
|||
[app.main.ui.workspace.shapes.path.common :as pc]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn apply-content-modifiers
|
||||
(defn- apply-content-modifiers
|
||||
[shape content-modifiers]
|
||||
(let [shape (update shape :content upc/apply-content-modifiers content-modifiers)
|
||||
[_ new-selrect] (helpers/content->points+selrect shape (:content shape))]
|
||||
(assoc shape :selrect new-selrect)))
|
||||
(let [shape (update shape :content types.path/apply-content-modifiers content-modifiers)]
|
||||
(types.path/update-geometry shape)))
|
||||
|
||||
(mf/defc path-wrapper
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
content-modifiers-ref (pc/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))
|
||||
[{:keys [shape]}]
|
||||
(let [shape-id (dm/get-prop shape :id)
|
||||
|
||||
content-modifiers-ref
|
||||
(pc/make-content-modifiers-ref shape-id)
|
||||
|
||||
content-modifiers
|
||||
(mf/deref content-modifiers-ref)
|
||||
|
||||
editing-id
|
||||
(mf/deref refs/selected-edition)
|
||||
|
||||
editing?
|
||||
(= editing-id shape-id)
|
||||
|
||||
shape
|
||||
(mf/use-memo
|
||||
(mf/deps shape content-modifiers)
|
||||
#(cond-> shape
|
||||
(mf/with-memo [shape content-modifiers]
|
||||
(cond-> shape
|
||||
(some? content-modifiers)
|
||||
(apply-content-modifiers content-modifiers)))]
|
||||
|
||||
|
|
|
@ -9,9 +9,8 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.shapes-to-path :as ups]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.segment :as path.segment]
|
||||
[app.main.data.workspace.path :as drp]
|
||||
[app.main.snap :as snap]
|
||||
[app.main.store :as st]
|
||||
|
@ -196,8 +195,8 @@
|
|||
(defn matching-handler? [content node handlers]
|
||||
(when (= 2 (count handlers))
|
||||
(let [[[i1 p1] [i2 p2]] handlers
|
||||
p1 (upc/handler->point content i1 p1)
|
||||
p2 (upc/handler->point content i2 p2)
|
||||
p1 (path.segment/handler->point content i1 p1)
|
||||
p2 (path.segment/handler->point content i2 p2)
|
||||
|
||||
v1 (gpt/to-vec node p1)
|
||||
v2 (gpt/to-vec node p2)
|
||||
|
@ -227,34 +226,36 @@
|
|||
:as edit-path} (mf/deref edit-path-ref)
|
||||
|
||||
selected-points (or selected-points #{})
|
||||
shape (hooks/use-equal-memo shape)
|
||||
|
||||
shape (cond-> shape
|
||||
(not= :path (:type shape))
|
||||
(ups/convert-to-path {})
|
||||
base-content
|
||||
(get shape :content)
|
||||
|
||||
:always
|
||||
hooks/use-equal-memo)
|
||||
base-points
|
||||
(mf/with-memo [base-content]
|
||||
(path.segment/content->points base-content))
|
||||
|
||||
base-content (:content shape)
|
||||
base-points (mf/use-memo (mf/deps base-content) #(->> base-content gsp/content->points))
|
||||
content
|
||||
(path/apply-content-modifiers base-content content-modifiers)
|
||||
|
||||
content (upc/apply-content-modifiers base-content content-modifiers)
|
||||
content-points (mf/use-memo (mf/deps content) #(->> content gsp/content->points))
|
||||
content-points
|
||||
(mf/with-memo [content]
|
||||
(path.segment/content->points content))
|
||||
|
||||
point->base (->> (map hash-map content-points base-points) (reduce merge))
|
||||
base->point (map-invert point->base)
|
||||
|
||||
points (into #{} content-points)
|
||||
|
||||
last-p (->> content last upc/command->point)
|
||||
handlers (upc/content->handlers content)
|
||||
last-p (->> content last path.segment/get-point)
|
||||
handlers (path.segment/content->handlers content)
|
||||
|
||||
start-p? (not (some? last-point))
|
||||
|
||||
[snap-selected snap-points]
|
||||
(cond
|
||||
(some? drag-handler) [#{drag-handler} points]
|
||||
(some? preview) [#{(upc/command->point preview)} points]
|
||||
(some? preview) [#{(path.segment/get-point preview)} points]
|
||||
(some? moving-handler) [#{moving-handler} points]
|
||||
:else
|
||||
[(->> selected-points (map base->point) (into #{}))
|
||||
|
@ -282,7 +283,7 @@
|
|||
ms/mouse-position
|
||||
(mf/deps shape zoom)
|
||||
(fn [position]
|
||||
(when-let [point (gsp/path-closest-point shape position)]
|
||||
(when-let [point (path.segment/path-closest-point shape position)]
|
||||
(reset! hover-point (when (< (gpt/distance position point) (/ 10 zoom)) point)))))
|
||||
|
||||
[:g.path-editor {:ref editor-ref}
|
||||
|
@ -313,7 +314,7 @@
|
|||
(for [[index position] (d/enumerate points)]
|
||||
(let [show-handler?
|
||||
(fn [[index prefix]]
|
||||
(let [handler-position (upc/handler->point content index prefix)]
|
||||
(let [handler-position (path.segment/handler->point content index prefix)]
|
||||
(not= position handler-position)))
|
||||
|
||||
pos-handlers (get handlers position)
|
||||
|
@ -327,7 +328,7 @@
|
|||
[:g.path-node {:key (dm/str index "-" (:x position) "-" (:y position))}
|
||||
[:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
|
||||
(for [[hindex prefix] pos-handlers]
|
||||
(let [handler-position (upc/handler->point content hindex prefix)
|
||||
(let [handler-position (path.segment/handler->point content hindex prefix)
|
||||
handler-hover? (contains? hover-handlers [hindex prefix])
|
||||
moving-handler? (= handler-position moving-handler)
|
||||
matching-handler? (matching-handler? content position pos-handlers)]
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape.attrs :refer [editable-attrs]]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -294,7 +295,7 @@
|
|||
file-id (unchecked-get props "file-id")
|
||||
shared-libs (unchecked-get props "libraries")
|
||||
|
||||
show-caps (some #(and (= :path (:type %)) (gsh/open-path? %)) shapes)
|
||||
show-caps (some #(and (= :path (:type %)) (path/shape-with-open-path? %)) shapes)
|
||||
|
||||
;; Selrect/points only used for measures and it's the one that changes the most. We separate it
|
||||
;; so we can memoize it
|
||||
|
|
|
@ -7,16 +7,15 @@
|
|||
(ns app.main.ui.workspace.viewport.path-actions
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.types.path.segment :as path.segm]
|
||||
[app.main.data.workspace.path :as drp]
|
||||
[app.main.data.workspace.path.shortcuts :as sc]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.shapes.path.common :as pc]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.path.tools :as upt]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
||||
(def ^:private pentool-icon
|
||||
(i/icon-xref :pentool (stl/css :pentool-icon :pathbar-icon)))
|
||||
|
||||
|
@ -49,7 +48,7 @@
|
|||
|
||||
|
||||
(defn check-enabled [content selected-points]
|
||||
(let [segments (upt/get-segments content selected-points)
|
||||
(let [segments (path.segm/get-segments content selected-points)
|
||||
num-segments (count segments)
|
||||
num-points (count selected-points)
|
||||
points-selected? (seq selected-points)
|
||||
|
@ -58,7 +57,7 @@
|
|||
max-segments (-> num-points
|
||||
(* (- num-points 1))
|
||||
(/ 2))
|
||||
is-curve? (some #(upt/is-curve? content %) selected-points)]
|
||||
is-curve? (some #(path.segm/is-curve? content %) selected-points)]
|
||||
|
||||
{:make-corner (and points-selected? is-curve?)
|
||||
:make-curve (and points-selected? (not is-curve?))
|
||||
|
|
|
@ -15,18 +15,19 @@
|
|||
[app.common.record :as crc]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.svg.path :as path]
|
||||
[app.common.svg.path :as svg.path]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.grid :as ctg]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.segment :as path.segm]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.blur :as ctsb]
|
||||
[app.common.types.shape.export :as ctse]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.shape.path :as ctsp]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -1312,18 +1313,19 @@
|
|||
:get #(-> % u/proxy->shape :content upf/format-path)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [content (->> (path/parse value))]
|
||||
(let [content (svg.path/parse value)]
|
||||
(cond
|
||||
(not (cfh/path-shape? data))
|
||||
(u/display-not-valid :content-type type)
|
||||
|
||||
(not (sm/validate ::ctsp/content content))
|
||||
;; FIXME: revisit path content validation
|
||||
(not (sm/validate ::path/content content))
|
||||
(u/display-not-valid :content value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/display-not-valid :content "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [selrect (gsh/content->selrect content)
|
||||
(let [selrect (path.segm/content->selrect content)
|
||||
points (grc/rect->points selrect)]
|
||||
(st/emit! (dwsh/update-shapes [id] (fn [shape] (assoc shape :content content :selrect selrect :points points))))))))}))))))
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
["react-dom/server" :as rds]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.shape.path :as path]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.fonts :as fonts]
|
||||
|
@ -309,13 +309,14 @@
|
|||
(h/call wasm/internal-module "stringToUTF8" str offset size)
|
||||
(h/call wasm/internal-module "_set_shape_path_attrs" (count attrs))))
|
||||
|
||||
;; FIXME: revisit on heap refactor is merged to use u32 instead u8
|
||||
(defn set-shape-path-content
|
||||
[content]
|
||||
(let [pdata (path/path-data content)
|
||||
size (* (count pdata) path/SEGMENT-BYTE-SIZE)
|
||||
(let [pdata (path/content content)
|
||||
size (path/get-byte-size content)
|
||||
offset (mem/alloc-bytes size)
|
||||
heap (mem/get-heap-u8)]
|
||||
(path/-write-to pdata (.-buffer heap) offset)
|
||||
(path/write-to pdata (.-buffer heap) offset)
|
||||
(h/call wasm/internal-module "_set_shape_path_content")))
|
||||
|
||||
(defn set-shape-svg-raw-content
|
||||
|
|
|
@ -5,12 +5,19 @@
|
|||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.util.path.format
|
||||
"Legacy path data formater, replaced by
|
||||
app.common.types.path.PathData type.
|
||||
|
||||
WARNING: Pending to be removed from codebase once completly unused"
|
||||
(:require
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.subpath :refer [pt=]]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.path.segment :as path.segm]
|
||||
[app.util.array :as arr]))
|
||||
|
||||
;; TODO: move to common
|
||||
(defn pt=
|
||||
"Check if two points are close"
|
||||
[p1 p2]
|
||||
(< (gpt/distance p1 p2) 0.1))
|
||||
|
||||
(def path-precision 3)
|
||||
|
||||
|
@ -115,7 +122,7 @@
|
|||
(try
|
||||
(let [result (make-array (count content))]
|
||||
(reduce (fn [last-move current]
|
||||
(let [point (upc/command->point current)
|
||||
(let [point (path.segm/get-point current)
|
||||
current-move? (= :move-to (:command current))
|
||||
last-move (if current-move? point last-move)]
|
||||
|
||||
|
|
|
@ -1,461 +0,0 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.util.path.tools
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as upg]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[clojure.set :as set]))
|
||||
|
||||
;; FIXME: move to common, there are nothing tied to frontend
|
||||
|
||||
(defn remove-line-curves
|
||||
"Remove all curves that have both handlers in the same position that the
|
||||
beginning and end points. This makes them really line-to commands"
|
||||
[content]
|
||||
(let [with-prev (d/enumerate (d/with-prev content))
|
||||
process-command
|
||||
(fn [content [index [command prev]]]
|
||||
|
||||
(let [cur-point (upc/command->point command)
|
||||
pre-point (upc/command->point prev)
|
||||
handler-c1 (upc/get-handler command :c1)
|
||||
handler-c2 (upc/get-handler command :c2)]
|
||||
(if (and (= :curve-to (:command command))
|
||||
(= cur-point handler-c2)
|
||||
(= pre-point handler-c1))
|
||||
(assoc content index {:command :line-to
|
||||
:params (into {} cur-point)})
|
||||
content)))]
|
||||
|
||||
(reduce process-command content with-prev)))
|
||||
|
||||
(defn make-corner-point
|
||||
"Changes the content to make a point a 'corner'"
|
||||
[content point]
|
||||
(let [handlers (-> (upc/content->handlers content)
|
||||
(get point))
|
||||
change-content
|
||||
(fn [content [index prefix]]
|
||||
(let [cx (d/prefix-keyword prefix :x)
|
||||
cy (d/prefix-keyword prefix :y)]
|
||||
(-> content
|
||||
(assoc-in [index :params cx] (:x point))
|
||||
(assoc-in [index :params cy] (:y point)))))]
|
||||
(as-> content $
|
||||
(reduce change-content $ handlers)
|
||||
(remove-line-curves $))))
|
||||
|
||||
(defn line->curve
|
||||
[from-p cmd]
|
||||
|
||||
(let [to-p (upc/command->point cmd)
|
||||
|
||||
v (gpt/to-vec from-p to-p)
|
||||
d (gpt/distance from-p to-p)
|
||||
|
||||
dv1 (-> (gpt/normal-left v)
|
||||
(gpt/scale (/ d 3)))
|
||||
|
||||
h1 (gpt/add from-p dv1)
|
||||
|
||||
dv2 (-> (gpt/to-vec to-p h1)
|
||||
(gpt/unit)
|
||||
(gpt/scale (/ d 3)))
|
||||
|
||||
h2 (gpt/add to-p dv2)]
|
||||
(-> cmd
|
||||
(assoc :command :curve-to)
|
||||
(update :params (fn [params]
|
||||
;; ensure plain map
|
||||
(-> (into {} params)
|
||||
(assoc :c1x (:x h1))
|
||||
(assoc :c1y (:y h1))
|
||||
(assoc :c2x (:x h2))
|
||||
(assoc :c2y (:y h2))))))))
|
||||
|
||||
(defn is-curve?
|
||||
[content point]
|
||||
(let [handlers (-> (upc/content->handlers content)
|
||||
(get point))
|
||||
handler-points (map #(upc/handler->point content (first %) (second %)) handlers)]
|
||||
(some #(not= point %) handler-points)))
|
||||
|
||||
(def ^:private xf:mapcat-points
|
||||
(comp
|
||||
(mapcat #(vector (:next-p %) (:prev-p %)))
|
||||
(remove nil?)))
|
||||
|
||||
(defn make-curve-point
|
||||
"Changes the content to make the point a 'curve'. The handlers will be positioned
|
||||
in the same vector that results from the previous->next points but with fixed length."
|
||||
[content point]
|
||||
|
||||
(let [indices (upc/point-indices content point)
|
||||
vectors (map (fn [index]
|
||||
(let [cmd (nth content index)
|
||||
prev-i (dec index)
|
||||
prev (when (not (= :move-to (:command cmd)))
|
||||
(get content prev-i))
|
||||
next-i (inc index)
|
||||
next (get content next-i)
|
||||
|
||||
next (when (not (= :move-to (:command next)))
|
||||
next)]
|
||||
{:index index
|
||||
:prev-i (when (some? prev) prev-i)
|
||||
:prev-c prev
|
||||
:prev-p (upc/command->point prev)
|
||||
:next-i (when (some? next) next-i)
|
||||
:next-c next
|
||||
:next-p (upc/command->point next)
|
||||
:command cmd}))
|
||||
indices)
|
||||
|
||||
points (into #{} xf:mapcat-points vectors)]
|
||||
|
||||
(if (= (count points) 2)
|
||||
(let [v1 (gpt/to-vec (first points) point)
|
||||
v2 (gpt/to-vec (first points) (second points))
|
||||
vp (gpt/project v1 v2)
|
||||
vh (gpt/subtract v1 vp)
|
||||
|
||||
add-curve
|
||||
(fn [content {:keys [index prev-p next-p next-i]}]
|
||||
(let [cur-cmd (get content index)
|
||||
next-cmd (get content next-i)
|
||||
|
||||
;; New handlers for prev-point and next-point
|
||||
prev-h (when (some? prev-p) (gpt/add prev-p vh))
|
||||
next-h (when (some? next-p) (gpt/add next-p vh))
|
||||
|
||||
;; Correct 1/3 to the point improves the curve
|
||||
prev-correction (when (some? prev-h) (gpt/scale (gpt/to-vec prev-h point) (/ 1 3)))
|
||||
next-correction (when (some? next-h) (gpt/scale (gpt/to-vec next-h point) (/ 1 3)))
|
||||
|
||||
prev-h (when (some? prev-h) (gpt/add prev-h prev-correction))
|
||||
next-h (when (some? next-h) (gpt/add next-h next-correction))]
|
||||
(cond-> content
|
||||
(and (= :line-to (:command cur-cmd)) (some? prev-p))
|
||||
(update index upc/update-curve-to prev-p prev-h)
|
||||
|
||||
(and (= :line-to (:command next-cmd)) (some? next-p))
|
||||
(update next-i upc/update-curve-to next-h next-p)
|
||||
|
||||
(and (= :curve-to (:command cur-cmd)) (some? prev-p))
|
||||
(update index upc/update-handler :c2 prev-h)
|
||||
|
||||
(and (= :curve-to (:command next-cmd)) (some? next-p))
|
||||
(update next-i upc/update-handler :c1 next-h))))]
|
||||
|
||||
(reduce add-curve content vectors))
|
||||
|
||||
(let [add-curve
|
||||
(fn [content {:keys [index command prev-p next-c next-i]}]
|
||||
(cond-> content
|
||||
(= :line-to (:command command))
|
||||
(update index #(line->curve prev-p %))
|
||||
|
||||
(= :curve-to (:command command))
|
||||
(update index #(line->curve prev-p %))
|
||||
|
||||
(= :line-to (:command next-c))
|
||||
(update next-i #(line->curve point %))
|
||||
|
||||
(= :curve-to (:command next-c))
|
||||
(update next-i #(line->curve point %))))]
|
||||
(reduce add-curve content vectors)))))
|
||||
|
||||
(defn get-segments
|
||||
"Given a content and a set of points return all the segments in the path
|
||||
that uses the points"
|
||||
[content points]
|
||||
(let [point-set (set points)]
|
||||
|
||||
(loop [segments []
|
||||
prev-point nil
|
||||
start-point nil
|
||||
index 0
|
||||
cur-cmd (first content)
|
||||
content (rest content)]
|
||||
|
||||
(let [command (:command cur-cmd)
|
||||
close-path? (= command :close-path)
|
||||
move-to? (= command :move-to)
|
||||
|
||||
;; Close-path makes a segment from the last point to the initial path point
|
||||
cur-point (if close-path?
|
||||
start-point
|
||||
(upc/command->point cur-cmd))
|
||||
|
||||
;; If there is a move-to we don't have a segment
|
||||
prev-point (if move-to?
|
||||
nil
|
||||
prev-point)
|
||||
|
||||
;; We update the start point
|
||||
start-point (if move-to?
|
||||
cur-point
|
||||
start-point)
|
||||
|
||||
is-segment? (and (some? prev-point)
|
||||
(contains? point-set prev-point)
|
||||
(contains? point-set cur-point))
|
||||
|
||||
segments (cond-> segments
|
||||
is-segment?
|
||||
(conj {:start prev-point
|
||||
:end cur-point
|
||||
:cmd cur-cmd
|
||||
:index index}))]
|
||||
|
||||
(if (some? cur-cmd)
|
||||
(recur segments
|
||||
cur-point
|
||||
start-point
|
||||
(inc index)
|
||||
(first content)
|
||||
(rest content))
|
||||
|
||||
segments)))))
|
||||
|
||||
(defn split-segments
|
||||
"Given a content creates splits commands between points with new segments"
|
||||
[content points value]
|
||||
|
||||
(let [split-command
|
||||
(fn [{:keys [start end cmd index]}]
|
||||
(case (:command cmd)
|
||||
:line-to [index (upg/split-line-to start cmd value)]
|
||||
:curve-to [index (upg/split-curve-to start cmd value)]
|
||||
:close-path [index [(upc/make-line-to (gpt/lerp start end value)) cmd]]
|
||||
nil))
|
||||
|
||||
cmd-changes
|
||||
(->> (get-segments content points)
|
||||
(into {} (comp (map split-command)
|
||||
(filter (comp not nil?)))))
|
||||
|
||||
process-segments
|
||||
(fn [[index command]]
|
||||
(if (contains? cmd-changes index)
|
||||
(get cmd-changes index)
|
||||
[command]))]
|
||||
|
||||
(into [] (mapcat process-segments) (d/enumerate content))))
|
||||
|
||||
(defn remove-nodes
|
||||
"Removes from content the points given. Will try to reconstruct the paths
|
||||
to keep everything consistent"
|
||||
[content points]
|
||||
|
||||
(if (empty? points)
|
||||
content
|
||||
|
||||
(let [content (d/with-prev content)]
|
||||
|
||||
(loop [result []
|
||||
last-handler nil
|
||||
[cur-cmd prev-cmd] (first content)
|
||||
content (rest content)]
|
||||
|
||||
(if (nil? cur-cmd)
|
||||
;; The result with be an array of arrays were every entry is a subpath
|
||||
(->> result
|
||||
;; remove empty and only 1 node subpaths
|
||||
(filter #(> (count %) 1))
|
||||
;; flatten array-of-arrays plain array
|
||||
(flatten)
|
||||
(into []))
|
||||
|
||||
(let [move? (= :move-to (:command cur-cmd))
|
||||
curve? (= :curve-to (:command cur-cmd))
|
||||
|
||||
;; When the old command was a move we start a subpath
|
||||
result (if move? (conj result []) result)
|
||||
|
||||
subpath (peek result)
|
||||
|
||||
point (upc/command->point cur-cmd)
|
||||
|
||||
old-prev-point (upc/command->point prev-cmd)
|
||||
new-prev-point (upc/command->point (peek subpath))
|
||||
|
||||
remove? (contains? points point)
|
||||
|
||||
|
||||
;; We store the first handler for the first curve to be removed to
|
||||
;; use it for the first handler of the regenerated path
|
||||
cur-handler (cond
|
||||
(and (not last-handler) remove? curve?)
|
||||
(select-keys (:params cur-cmd) [:c1x :c1y])
|
||||
|
||||
(not remove?)
|
||||
nil
|
||||
|
||||
:else
|
||||
last-handler)
|
||||
|
||||
cur-cmd (cond-> cur-cmd
|
||||
;; If we're starting a subpath and it's not a move make it a move
|
||||
(and (not move?) (empty? subpath))
|
||||
(assoc :command :move-to
|
||||
:params (select-keys (:params cur-cmd) [:x :y]))
|
||||
|
||||
;; If have a curve the first handler will be relative to the previous
|
||||
;; point. We change the handler to the new previous point
|
||||
(and curve? (seq subpath) (not= old-prev-point new-prev-point))
|
||||
(update :params merge last-handler))
|
||||
|
||||
head-idx (dec (count result))
|
||||
|
||||
result (cond-> result
|
||||
(not remove?)
|
||||
(update head-idx conj cur-cmd))]
|
||||
(recur result
|
||||
cur-handler
|
||||
(first content)
|
||||
(rest content))))))))
|
||||
|
||||
(defn join-nodes
|
||||
"Creates new segments between points that weren't previously"
|
||||
[content points]
|
||||
|
||||
(let [segments-set (into #{}
|
||||
(map (juxt :start :end))
|
||||
(get-segments content points))
|
||||
|
||||
create-line-command (fn [point other]
|
||||
[(upc/make-move-to point)
|
||||
(upc/make-line-to other)])
|
||||
|
||||
not-segment? (fn [point other] (and (not (contains? segments-set [point other]))
|
||||
(not (contains? segments-set [other point]))))
|
||||
|
||||
new-content (->> (d/map-perm create-line-command not-segment? points)
|
||||
(flatten)
|
||||
(into []))]
|
||||
|
||||
(into content new-content)))
|
||||
|
||||
|
||||
(defn separate-nodes
|
||||
"Removes the segments between the points given"
|
||||
[content points]
|
||||
|
||||
(let [content (d/with-prev content)]
|
||||
(loop [result []
|
||||
[cur-cmd prev-cmd] (first content)
|
||||
content (rest content)]
|
||||
|
||||
(if (nil? cur-cmd)
|
||||
(->> result
|
||||
(filter #(> (count %) 1))
|
||||
(flatten)
|
||||
(into []))
|
||||
|
||||
(let [prev-point (upc/command->point prev-cmd)
|
||||
cur-point (upc/command->point cur-cmd)
|
||||
|
||||
cur-cmd (cond-> cur-cmd
|
||||
(and (contains? points prev-point)
|
||||
(contains? points cur-point))
|
||||
|
||||
(assoc :command :move-to
|
||||
:params (select-keys (:params cur-cmd) [:x :y])))
|
||||
|
||||
move? (= :move-to (:command cur-cmd))
|
||||
|
||||
result (if move? (conj result []) result)
|
||||
head-idx (dec (count result))
|
||||
|
||||
result (-> result
|
||||
(update head-idx conj cur-cmd))]
|
||||
(recur result
|
||||
(first content)
|
||||
(rest content)))))))
|
||||
|
||||
|
||||
(defn- add-to-set
|
||||
"Given a list of sets adds the value to the target set"
|
||||
[set-list target value]
|
||||
(->> set-list
|
||||
(mapv (fn [it]
|
||||
(cond-> it
|
||||
(= it target) (conj value))))))
|
||||
|
||||
(defn- join-sets
|
||||
"Given a list of sets join two sets in the list into a new one"
|
||||
[set-list target other]
|
||||
(conj (->> set-list
|
||||
(filterv #(and (not= % target)
|
||||
(not= % other))))
|
||||
(set/union target other)))
|
||||
|
||||
(defn group-segments [segments]
|
||||
(loop [result []
|
||||
{point-a :start point-b :end :as segment} (first segments)
|
||||
segments (rest segments)]
|
||||
|
||||
(if (nil? segment)
|
||||
result
|
||||
|
||||
(let [set-a (d/seek #(contains? % point-a) result)
|
||||
set-b (d/seek #(contains? % point-b) result)
|
||||
|
||||
result (cond-> result
|
||||
(and (nil? set-a) (nil? set-b))
|
||||
(conj #{point-a point-b})
|
||||
|
||||
(and (some? set-a) (nil? set-b))
|
||||
(add-to-set set-a point-b)
|
||||
|
||||
(and (nil? set-a) (some? set-b))
|
||||
(add-to-set set-b point-a)
|
||||
|
||||
(and (some? set-a) (some? set-b) (not= set-a set-b))
|
||||
(join-sets set-a set-b))]
|
||||
(recur result
|
||||
(first segments)
|
||||
(rest segments))))))
|
||||
|
||||
(defn calculate-merge-points [group-segments points]
|
||||
(let [index-merge-point (fn [group] (vector group (gpt/center-points group)))
|
||||
index-group (fn [point] (vector point (d/seek #(contains? % point) group-segments)))
|
||||
|
||||
group->merge-point (into {} (map index-merge-point) group-segments)
|
||||
point->group (into {} (map index-group) points)]
|
||||
(d/mapm #(group->merge-point %2) point->group)))
|
||||
|
||||
;; TODO: Improve the replace for curves
|
||||
(defn replace-points
|
||||
"Replaces the points in a path for its merge-point"
|
||||
[content point->merge-point]
|
||||
(let [replace-command
|
||||
(fn [cmd]
|
||||
(let [point (upc/command->point cmd)]
|
||||
(if (contains? point->merge-point point)
|
||||
(let [merge-point (get point->merge-point point)]
|
||||
(-> cmd (update :params assoc :x (:x merge-point) :y (:y merge-point))))
|
||||
cmd)))]
|
||||
(->> content
|
||||
(mapv replace-command))))
|
||||
|
||||
(defn merge-nodes
|
||||
"Reduces the contiguous segments in points to a single point"
|
||||
[content points]
|
||||
(let [segments (get-segments content points)]
|
||||
(if (seq segments)
|
||||
(let [point->merge-point (-> segments
|
||||
(group-segments)
|
||||
(calculate-merge-points points))]
|
||||
(-> content
|
||||
(separate-nodes points)
|
||||
(replace-points point->merge-point)))
|
||||
content)))
|
||||
|
|
@ -11,13 +11,13 @@
|
|||
[app.common.exceptions :as ex]
|
||||
[app.common.files.builder :as fb]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as gpa]
|
||||
[app.common.json :as json]
|
||||
[app.common.logging :as log]
|
||||
[app.common.media :as cm]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.text :as ct]
|
||||
[app.common.time :as tm]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.http :as http]
|
||||
|
@ -330,7 +330,7 @@
|
|||
(d/update-when :x + (:x frame))
|
||||
(d/update-when :y + (:y frame))
|
||||
(cond-> (= :path type)
|
||||
(update :content gpa/move-content (gpt/point (:x frame) (:y frame)))))
|
||||
(update :content path/move-content (gpt/point (:x frame) (:y frame)))))
|
||||
|
||||
data)))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue