🐛 Fixes problems with masks rotation and some clipping problems

This commit is contained in:
alonso.torres 2020-12-08 11:35:14 +01:00 committed by Hirunatan
parent 34af5e4563
commit 3a8a212432
10 changed files with 95 additions and 104 deletions

View file

@ -860,11 +860,16 @@
(distinct)) (distinct))
shapes))) shapes)))
(update-group [group objects] (update-group [group objects]
(let [children (->> (if (:masked-group? group) (let [children (->> group :shapes (map #(get objects %)))]
[(first (:shapes group))] (if (:masked-group? group)
(:shapes group)) (let [mask (first children)]
(map #(get objects %)))] (-> group
(gsh/update-group-selrect group children)))] (merge (select-keys mask [:selrect :points]))
(assoc :x (-> mask :selrect :x)
:y (-> mask :selrect :y)
:width (-> mask :selrect :width)
:height (-> mask :selrect :height))))
(gsh/update-group-selrect group children))))]
(if page-id (if page-id
(d/update-in-when data [:pages-index page-id :objects] reg-objects) (d/update-in-when data [:pages-index page-id :objects] reg-objects)

View file

@ -1146,8 +1146,11 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(rx/of (dws/deselect-all) (let [selected (get-in state [:workspace-local :selected])]
(dws/select-shape (:id shape)))))) (if (selected (:id shape))
(rx/empty)
(rx/of (dws/deselect-all)
(dws/select-shape (:id shape))))))))
(def hide-context-menu (def hide-context-menu
(ptk/reify ::hide-context-menu (ptk/reify ::hide-context-menu
@ -1478,14 +1481,20 @@
:id (:id group) :id (:id group)
:operations [{:type :set :operations [{:type :set
:attr :masked-group? :attr :masked-group?
:val nil}]}] :val nil}]}
{:type :reg-objects
:page-id page-id
:shapes [(:id group)]}]
uchanges [{:type :mod-obj uchanges [{:type :mod-obj
:page-id page-id :page-id page-id
:id (:id group) :id (:id group)
:operations [{:type :set :operations [{:type :set
:attr :masked-group? :attr :masked-group?
:val (:masked-group? group)}]}]] :val (:masked-group? group)}]}
{:type :reg-objects
:page-id page-id
:shapes [(:id group)]}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(dwc/select-shapes (d/ordered-set (:id group)))))))))) (dwc/select-shapes (d/ordered-set (:id group))))))))))

View file

@ -77,7 +77,10 @@
(fn [] (fn []
(let [keys [(events/listen js/window EventType.POPSTATE on-pop-state) (let [keys [(events/listen js/window EventType.POPSTATE on-pop-state)
(events/listen js/document EventType.KEYDOWN handle-keydown) (events/listen js/document EventType.KEYDOWN handle-keydown)
(events/listen js/document EventType.CLICK handle-click-outside)
;; Changing to js/document breaks the color picker
(events/listen (dom/get-root) EventType.CLICK handle-click-outside)
(events/listen js/document EventType.CONTEXTMENU handle-click-outside)]] (events/listen js/document EventType.CONTEXTMENU handle-click-outside)]]
#(doseq [key keys] #(doseq [key keys]
(events/unlistenByKey key))))) (events/unlistenByKey key)))))

View file

@ -9,7 +9,6 @@
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.common.geom.shapes :as geom] [app.common.geom.shapes :as geom]
[app.main.ui.shapes.group :refer [mask-id-ctx]]
[app.util.object :as obj])) [app.util.object :as obj]))
; The SVG standard does not implement yet the 'stroke-alignment' ; The SVG standard does not implement yet the 'stroke-alignment'
@ -25,16 +24,13 @@
elem-name (unchecked-get props "elem-name") elem-name (unchecked-get props "elem-name")
;; {:keys [x y width height]} (geom/shape->rect-shape shape) ;; {:keys [x y width height]} (geom/shape->rect-shape shape)
{:keys [x y width height]} (:selrect shape) {:keys [x y width height]} (:selrect shape)
mask-id (mf/use-ctx mask-id-ctx)
stroke-id (mf/use-var (uuid/next)) stroke-id (mf/use-var (uuid/next))
stroke-style (:stroke-style shape :none) stroke-style (:stroke-style shape :none)
stroke-position (:stroke-alignment shape :center)] stroke-position (:stroke-alignment shape :center)]
(cond (cond
;; Center alignment (or no stroke): the default in SVG ;; Center alignment (or no stroke): the default in SVG
(or (= stroke-style :none) (= stroke-position :center)) (or (= stroke-style :none) (= stroke-position :center))
[:> elem-name (cond-> (obj/merge! #js {} base-props) [:> elem-name (obj/merge! #js {} base-props)]
(some? mask-id)
(obj/merge! #js {:mask mask-id}))]
;; Inner alignment: display the shape with double width stroke, ;; Inner alignment: display the shape with double width stroke,
;; and clip the result with the original shape without stroke. ;; and clip the result with the original shape without stroke.
@ -54,15 +50,10 @@
shape-props (-> (obj/merge! #js {} base-props) shape-props (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:strokeWidth (* stroke-width 2) (obj/merge! #js {:strokeWidth (* stroke-width 2)
:clipPath (str "url('#" clip-id "')")}))] :clipPath (str "url('#" clip-id "')")}))]
(if (nil? mask-id) [:*
[:* [:> "clipPath" #js {:id clip-id}
[:> "clipPath" #js {:id clip-id} [:> elem-name clip-props]]
[:> elem-name clip-props]] [:> elem-name shape-props]])
[:> elem-name shape-props]]
[:g {:mask mask-id}
[:> "clipPath" #js {:id clip-id}
[:> elem-name clip-props]]
[:> elem-name shape-props]]))
;; Outer alingmnent: display the shape in two layers. One ;; Outer alingmnent: display the shape in two layers. One
;; without stroke (only fill), and another one only with stroke ;; without stroke (only fill), and another one only with stroke
@ -100,17 +91,10 @@
:fill "none" :fill "none"
:fillOpacity 0 :fillOpacity 0
:mask (str "url('#" stroke-mask-id "')")}))] :mask (str "url('#" stroke-mask-id "')")}))]
(if (nil? mask-id) [:*
[:* [:mask {:id stroke-mask-id}
[:mask {:id mask-id} [:> elem-name mask-props1]
[:> elem-name mask-props1] [:> elem-name mask-props2]]
[:> elem-name mask-props2]] [:> elem-name shape-props1]
[:> elem-name shape-props1] [:> elem-name shape-props2]]))))
[:> elem-name shape-props2]]
[:g {:mask mask-id}
[:mask {:id stroke-mask-id}
[:> elem-name mask-props1]
[:> elem-name mask-props2]]
[:> elem-name shape-props1]
[:> elem-name shape-props2]])))))

View file

@ -18,56 +18,54 @@
(mf/defc linear-gradient [{:keys [id gradient shape]}] (mf/defc linear-gradient [{:keys [id gradient shape]}]
(let [{:keys [x y width height]} shape] (let [{:keys [x y width height]} shape]
[:defs [:linearGradient {:id id
[:linearGradient {:id id :x1 (:start-x gradient)
:x1 (:start-x gradient) :y1 (:start-y gradient)
:y1 (:start-y gradient) :x2 (:end-x gradient)
:x2 (:end-x gradient) :y2 (:end-y gradient)}
:y2 (:end-y gradient)} (for [{:keys [offset color opacity]} (:stops gradient)]
(for [{:keys [offset color opacity]} (:stops gradient)] [:stop {:key (str id "-stop-" offset)
[:stop {:key (str id "-stop-" offset) :offset (or offset 0)
:offset (or offset 0) :stop-color color
:stop-color color :stop-opacity opacity}])]))
:stop-opacity opacity}])]]))
(mf/defc radial-gradient [{:keys [id gradient shape]}] (mf/defc radial-gradient [{:keys [id gradient shape]}]
(let [{:keys [x y width height]} shape] (let [{:keys [x y width height]} shape]
[:defs (let [[x y] (if (= (:type shape) :frame) [0 0] [x y])
(let [[x y] (if (= (:type shape) :frame) [0 0] [x y]) translate-vec (gpt/point (+ x (* width (:start-x gradient)))
translate-vec (gpt/point (+ x (* width (:start-x gradient))) (+ y (* height (:start-y gradient))))
(+ y (* height (:start-y gradient))))
gradient-vec (gpt/to-vec (gpt/point (* width (:start-x gradient))
(* height (:start-y gradient)))
(gpt/point (* width (:end-x gradient))
(* height (:end-y gradient))))
angle (gpt/angle gradient-vec gradient-vec (gpt/to-vec (gpt/point (* width (:start-x gradient))
(gpt/point 1 0)) (* height (:start-y gradient)))
(gpt/point (* width (:end-x gradient))
(* height (:end-y gradient))))
shape-height-vec (gpt/point 0 (/ height 2)) angle (gpt/angle gradient-vec
(gpt/point 1 0))
scale-factor-y (/ (gpt/length gradient-vec) (/ height 2)) shape-height-vec (gpt/point 0 (/ height 2))
scale-factor-x (* scale-factor-y (:width gradient))
scale-vec (gpt/point (* scale-factor-y (/ height 2)) scale-factor-y (/ (gpt/length gradient-vec) (/ height 2))
(* scale-factor-x (/ width 2))) scale-factor-x (* scale-factor-y (:width gradient))
tr-translate (str/fmt "translate(%s, %s)" (:x translate-vec) (:y translate-vec)) scale-vec (gpt/point (* scale-factor-y (/ height 2))
tr-rotate (str/fmt "rotate(%s)" angle) (* scale-factor-x (/ width 2)))
tr-scale (str/fmt "scale(%s, %s)" (:x scale-vec) (:y scale-vec))
transform (str/fmt "%s %s %s" tr-translate tr-rotate tr-scale)] tr-translate (str/fmt "translate(%s, %s)" (:x translate-vec) (:y translate-vec))
[:radialGradient {:id id tr-rotate (str/fmt "rotate(%s)" angle)
:cx 0 tr-scale (str/fmt "scale(%s, %s)" (:x scale-vec) (:y scale-vec))
:cy 0 transform (str/fmt "%s %s %s" tr-translate tr-rotate tr-scale)]
:r 1 [:radialGradient {:id id
:gradientUnits "userSpaceOnUse" :cx 0
:gradientTransform transform} :cy 0
(for [{:keys [offset color opacity]} (:stops gradient)] :r 1
[:stop {:key (str id "-stop-" offset) :gradientUnits "userSpaceOnUse"
:offset (or offset 0) :gradientTransform transform}
:stop-color color (for [{:keys [offset color opacity]} (:stops gradient)]
:stop-opacity opacity}])])])) [:stop {:key (str id "-stop-" offset)
:offset (or offset 0)
:stop-color color
:stop-opacity opacity}])])))
(mf/defc gradient (mf/defc gradient
{::mf/wrap-props false} {::mf/wrap-props false}

View file

@ -14,17 +14,16 @@
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.common.geom.shapes :as geom])) [app.common.geom.shapes :as geom]))
(def mask-id-ctx (mf/create-context nil))
(defn group-shape (defn group-shape
[shape-wrapper] [shape-wrapper]
(mf/fnc group-shape (mf/fnc group-shape
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [frame (unchecked-get props "frame") (let [frame (unchecked-get props "frame")
shape (unchecked-get props "shape") shape (unchecked-get props "shape")
childs (unchecked-get props "childs") childs (unchecked-get props "childs")
expand-mask (unchecked-get props "expand-mask") expand-mask (unchecked-get props "expand-mask")
pointer-events (unchecked-get props "pointer-events")
mask (if (and (:masked-group? shape) (not expand-mask)) mask (if (and (:masked-group? shape) (not expand-mask))
(first childs) (first childs)
nil) nil)
@ -33,7 +32,9 @@
childs) childs)
{:keys [id x y width height]} shape {:keys [id x y width height]} shape
transform (geom/transform-matrix shape)] transform (geom/transform-matrix shape)]
[:g [:g.group {:pointer-events pointer-events
:mask (when (and mask (not expand-mask))
(str/fmt "url(#%s)" (:id mask)))}
(when mask (when mask
[:defs [:defs
[:mask {:id (:id mask) [:mask {:id (:id mask)
@ -41,11 +42,10 @@
:height height} :height height}
[:& shape-wrapper {:frame frame [:& shape-wrapper {:frame frame
:shape mask}]]]) :shape mask}]]])
[:& (mf/provider mask-id-ctx) {:value (str/fmt "url(#%s)" (:id mask))} (for [item childs]
(for [item childs] [:& shape-wrapper {:frame frame
[:& shape-wrapper {:frame frame :shape item
:shape item :key (:id item)}])])))
:key (:id item)}])]
])))

View file

@ -13,7 +13,6 @@
[app.config :as cfg] [app.config :as cfg]
[app.common.geom.shapes :as geom] [app.common.geom.shapes :as geom]
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.group :refer [mask-id-ctx]]
[app.util.object :as obj] [app.util.object :as obj]
[app.main.ui.context :as muc] [app.main.ui.context :as muc]
[app.main.data.fetch :as df] [app.main.data.fetch :as df]
@ -27,7 +26,6 @@
{:keys [id x y width height rotation metadata]} shape {:keys [id x y width height rotation metadata]} shape
uri (cfg/resolve-media-path (:path metadata)) uri (cfg/resolve-media-path (:path metadata))
embed-resources? (mf/use-ctx muc/embed-ctx) embed-resources? (mf/use-ctx muc/embed-ctx)
mask-id (mf/use-ctx mask-id-ctx)
data-uri (mf/use-state (when (not embed-resources?) uri))] data-uri (mf/use-state (when (not embed-resources?) uri))]
(mf/use-effect (mf/use-effect
@ -45,8 +43,7 @@
:transform transform :transform transform
:width width :width width
:height height :height height
:preserveAspectRatio "none" :preserveAspectRatio "none"}))]
:mask mask-id}))]
(if (nil? @data-uri) (if (nil? @data-uri)
[:> "rect" (obj/merge! [:> "rect" (obj/merge!
props props

View file

@ -13,7 +13,6 @@
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
[app.main.ui.shapes.group :refer [mask-id-ctx]]
[app.common.geom.shapes :as geom] [app.common.geom.shapes :as geom]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.geom.path :as ugp])) [app.util.geom.path :as ugp]))
@ -27,7 +26,6 @@
background? (unchecked-get props "background?") background? (unchecked-get props "background?")
;; {:keys [id x y width height]} (geom/shape->rect-shape shape) ;; {:keys [id x y width height]} (geom/shape->rect-shape shape)
{:keys [id x y width height]} (:selrect shape) {:keys [id x y width height]} (:selrect shape)
mask-id (mf/use-ctx mask-id-ctx)
transform (geom/transform-matrix shape) transform (geom/transform-matrix shape)
pdata (ugp/content->path (:content shape)) pdata (ugp/content->path (:content shape))
props (-> (attrs/extract-style-attrs shape) props (-> (attrs/extract-style-attrs shape)
@ -35,7 +33,7 @@
#js {:transform transform #js {:transform transform
:d pdata}))] :d pdata}))]
(if background? (if background?
[:g {:mask mask-id} [:g
[:path {:stroke "transparent" [:path {:stroke "transparent"
:fill "transparent" :fill "transparent"
:stroke-width "20px" :stroke-width "20px"
@ -45,6 +43,5 @@
:elem-name "path"}]] :elem-name "path"}]]
[:& shape-custom-stroke {:shape shape [:& shape-custom-stroke {:shape shape
:base-props props :base-props props
:mask mask-id
:elem-name "path"}]))) :elem-name "path"}])))

View file

@ -12,7 +12,6 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.main.ui.context :as muc] [app.main.ui.context :as muc]
[app.main.ui.shapes.group :refer [mask-id-ctx]]
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as geom] [app.common.geom.shapes :as geom]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
@ -90,7 +89,6 @@
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
selected? (unchecked-get props "selected?") selected? (unchecked-get props "selected?")
grow-type (:grow-type shape) grow-type (:grow-type shape)
mask-id (mf/use-ctx mask-id-ctx)
{:keys [id x y width height content]} shape] {:keys [id x y width height content]} shape]
[:foreignObject {:x x [:foreignObject {:x x
:y y :y y
@ -99,7 +97,6 @@
:transform (geom/transform-matrix shape) :transform (geom/transform-matrix shape)
:width (if (#{:auto-width} grow-type) 10000 width) :width (if (#{:auto-width} grow-type) 10000 width)
:height (if (#{:auto-height :auto-width} grow-type) 10000 height) :height (if (#{:auto-height :auto-width} grow-type) 10000 height)
:mask mask-id
:ref ref :ref ref
:pointer-events "none"} :pointer-events "none"}
[:& text-content {:shape shape [:& text-content {:shape shape

View file

@ -80,7 +80,8 @@
{:frame frame {:frame frame
:shape shape :shape shape
:childs childs :childs childs
:expand-mask is-mask-selected?}] :expand-mask is-mask-selected?
:pointer-events (when (not is-child-selected?) "none")}]
(when-not is-child-selected? (when-not is-child-selected?
[:rect.group-actions [:rect.group-actions