mirror of
https://github.com/penpot/penpot.git
synced 2025-06-05 13:11:40 +02:00
🎉 Allow masked groups
This commit is contained in:
parent
ad66955a54
commit
ee89b2e7f4
14 changed files with 207 additions and 33 deletions
|
@ -9,6 +9,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.main.ui.shapes.group :refer [mask-id-ctx]]
|
||||
[app.util.object :as obj]))
|
||||
|
||||
; The SVG standard does not implement yet the 'stroke-alignment'
|
||||
|
@ -23,13 +24,16 @@
|
|||
base-props (unchecked-get props "base-props")
|
||||
elem-name (unchecked-get props "elem-name")
|
||||
{:keys [x y width height]} (geom/shape->rect-shape shape)
|
||||
mask-id (mf/use-ctx mask-id-ctx)
|
||||
stroke-id (mf/use-var (uuid/next))
|
||||
stroke-style (:stroke-style shape :none)
|
||||
stroke-position (:stroke-alignment shape :center)]
|
||||
(cond
|
||||
;; Center alignment (or no stroke): the default in SVG
|
||||
(or (= stroke-style :none) (= stroke-position :center))
|
||||
[:> elem-name base-props]
|
||||
[:> elem-name (cond-> (obj/merge! #js {} base-props)
|
||||
(some? mask-id)
|
||||
(obj/merge! #js {:mask mask-id}))]
|
||||
|
||||
;; Inner alignment: display the shape with double width stroke,
|
||||
;; and clip the result with the original shape without stroke.
|
||||
|
@ -49,10 +53,15 @@
|
|||
shape-props (-> (obj/merge! #js {} base-props)
|
||||
(obj/merge! #js {:strokeWidth (* stroke-width 2)
|
||||
:clipPath (str "url('#" clip-id "')")}))]
|
||||
[:*
|
||||
[:> "clipPath" #js {:id clip-id}
|
||||
[:> elem-name clip-props]]
|
||||
[:> elem-name shape-props]])
|
||||
(if (nil? mask-id)
|
||||
[:*
|
||||
[:> "clipPath" #js {:id clip-id}
|
||||
[:> elem-name clip-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
|
||||
;; without stroke (only fill), and another one only with stroke
|
||||
|
@ -61,7 +70,7 @@
|
|||
;; without stroke
|
||||
|
||||
(= stroke-position :outer)
|
||||
(let [mask-id (str "mask-" @stroke-id)
|
||||
(let [stroke-mask-id (str "mask-" @stroke-id)
|
||||
stroke-width (.-strokeWidth ^js base-props)
|
||||
mask-props1 (-> (obj/merge! #js {} base-props)
|
||||
(obj/merge! #js {:stroke "white"
|
||||
|
@ -89,11 +98,18 @@
|
|||
(obj/merge! #js {:strokeWidth (* stroke-width 2)
|
||||
:fill "none"
|
||||
:fillOpacity 0
|
||||
:mask (str "url('#" mask-id "')")}))]
|
||||
[:*
|
||||
[:mask {:id mask-id}
|
||||
[:> elem-name mask-props1]
|
||||
[:> elem-name mask-props2]]
|
||||
[:> elem-name shape-props1]
|
||||
[:> elem-name shape-props2]]))))
|
||||
:mask (str "url('#" stroke-mask-id "')")}))]
|
||||
(if (nil? mask-id)
|
||||
[:*
|
||||
[:mask {:id mask-id}
|
||||
[:> elem-name mask-props1]
|
||||
[:> elem-name mask-props2]]
|
||||
[:> elem-name shape-props1]
|
||||
[:> 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]])))))
|
||||
|
||||
|
|
|
@ -10,10 +10,13 @@
|
|||
(ns app.main.ui.shapes.group
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.util.debug :refer [debug?]]
|
||||
[app.common.geom.shapes :as geom]))
|
||||
|
||||
(def mask-id-ctx (mf/create-context nil))
|
||||
|
||||
(defn group-shape
|
||||
[shape-wrapper]
|
||||
(mf/fnc group-shape
|
||||
|
@ -22,14 +25,26 @@
|
|||
(let [frame (unchecked-get props "frame")
|
||||
shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
mask (if (:masked-group? shape)
|
||||
(first childs)
|
||||
nil)
|
||||
childs (if (:masked-group? shape)
|
||||
(rest childs)
|
||||
childs)
|
||||
is-child-selected? (unchecked-get props "is-child-selected?")
|
||||
{:keys [id x y width height]} shape
|
||||
transform (geom/transform-matrix shape)]
|
||||
[:g
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape item
|
||||
:key (:id item)}])
|
||||
(when mask
|
||||
[:defs
|
||||
[:mask {:id (:id mask)}
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape mask}]]])
|
||||
[:& (mf/provider mask-id-ctx) {:value (str/fmt "url(#%s)" (:id mask))}
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape item
|
||||
:key (:id item)}])]
|
||||
(when (not is-child-selected?)
|
||||
[:rect {:transform transform
|
||||
:x x
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.group :refer [mask-id-ctx]]
|
||||
[app.util.object :as obj]))
|
||||
|
||||
(mf/defc icon-shape
|
||||
|
@ -20,6 +21,7 @@
|
|||
(let [shape (unchecked-get props "shape")
|
||||
{:keys [id x y width height metadata rotation content]} shape
|
||||
|
||||
mask-id (mf/use-ctx mask-id-ctx)
|
||||
transform (geom/transform-matrix shape)
|
||||
vbox (apply str (interpose " " (:view-box metadata)))
|
||||
|
||||
|
@ -33,6 +35,7 @@
|
|||
:height height
|
||||
:viewBox vbox
|
||||
:preserveAspectRatio "none"
|
||||
:mask mask-id
|
||||
:dangerouslySetInnerHTML #js {:__html content}}))]
|
||||
[:g {:transform transform}
|
||||
[:> "svg" props]]))
|
||||
|
@ -41,7 +44,9 @@
|
|||
[{:keys [shape] :as props}]
|
||||
(let [{:keys [content id metadata]} shape
|
||||
view-box (apply str (interpose " " (:view-box metadata)))
|
||||
mask-id (mf/use-ctx mask-id-ctx)
|
||||
props {:viewBox view-box
|
||||
:id (str "shape-" id)
|
||||
:mask mask-id
|
||||
:dangerouslySetInnerHTML #js {:__html content}}]
|
||||
[:& "svg" props]))
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.config :as cfg]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.group :refer [mask-id-ctx]]
|
||||
[app.util.object :as obj]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.data.fetch :as df]
|
||||
|
@ -26,6 +27,7 @@
|
|||
{:keys [id x y width height rotation metadata]} shape
|
||||
uri (cfg/resolve-media-path (:path metadata))
|
||||
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))]
|
||||
|
||||
(mf/use-effect
|
||||
|
@ -44,7 +46,8 @@
|
|||
:id (str "shape-" id)
|
||||
:width width
|
||||
:height height
|
||||
:preserveAspectRatio "none"}))]
|
||||
:preserveAspectRatio "none"
|
||||
:mask mask-id}))]
|
||||
(if (nil? @data-uri)
|
||||
[:> "rect" (obj/merge!
|
||||
props
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[rumext.alpha :as mf]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[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.util.object :as obj]))
|
||||
|
||||
|
@ -45,6 +46,7 @@
|
|||
(let [shape (unchecked-get props "shape")
|
||||
background? (unchecked-get props "background?")
|
||||
{:keys [id x y width height]} (geom/shape->rect-shape shape)
|
||||
mask-id (mf/use-ctx mask-id-ctx)
|
||||
transform (geom/transform-matrix shape)
|
||||
pdata (render-path shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
|
@ -53,7 +55,7 @@
|
|||
:id (str "shape-" id)
|
||||
:d pdata}))]
|
||||
(if background?
|
||||
[:g
|
||||
[:g {:mask mask-id}
|
||||
[:path {:stroke "transparent"
|
||||
:fill "transparent"
|
||||
:stroke-width "20px"
|
||||
|
@ -63,5 +65,6 @@
|
|||
:elem-name "path"}]]
|
||||
[:& shape-custom-stroke {:shape shape
|
||||
:base-props props
|
||||
:mask mask-id
|
||||
:elem-name "path"}])))
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.main.data.fetch :as df]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.group :refer [mask-id-ctx]]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
|
@ -224,6 +225,7 @@
|
|||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
selected? (unchecked-get props "selected?")
|
||||
mask-id (mf/use-ctx mask-id-ctx)
|
||||
{:keys [id x y width height rotation content]} shape]
|
||||
[:foreignObject {:x x
|
||||
:y y
|
||||
|
@ -231,6 +233,7 @@
|
|||
:transform (geom/transform-matrix shape)
|
||||
:id (str id)
|
||||
:width width
|
||||
:height height}
|
||||
:height height
|
||||
:mask mask-id}
|
||||
[:& text-content {:content (:content shape)}]]))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue