mirror of
https://github.com/penpot/penpot.git
synced 2025-06-13 18:51:39 +02:00
✨ Ability to add multiple strokes to a shape
This commit is contained in:
parent
719aacd6f8
commit
a73a393e26
30 changed files with 680 additions and 353 deletions
|
@ -113,9 +113,9 @@
|
|||
|
||||
(obj/merge! attrs (clj->js fill-attrs)))))
|
||||
|
||||
(defn add-stroke [attrs shape render-id]
|
||||
(defn add-stroke [attrs shape render-id index]
|
||||
(let [stroke-style (:stroke-style shape :none)
|
||||
stroke-color-gradient-id (str "stroke-color-gradient_" render-id)
|
||||
stroke-color-gradient-id (str "stroke-color-gradient_" render-id "_" index)
|
||||
stroke-width (:stroke-width shape 1)]
|
||||
(if (not= stroke-style :none)
|
||||
(let [stroke-attrs
|
||||
|
@ -198,14 +198,13 @@
|
|||
|
||||
styles (-> (obj/get props "style" (obj/new))
|
||||
(obj/merge! svg-styles)
|
||||
(add-stroke shape render-id)
|
||||
(add-layer-props shape))
|
||||
|
||||
styles (cond (or (some? (:fill-image shape))
|
||||
(= :image (:type shape))
|
||||
(> (count (:fills shape)) 1)
|
||||
(some #(some? (:fill-color-gradient %)) (:fills shape)))
|
||||
(obj/set! styles "fill" (str "url(#fill-0-" render-id ")"))
|
||||
(obj/set! styles "fill" (str "url(#fill-0-" render-id ")"))
|
||||
|
||||
;; imported svgs can have fill and fill-opacity attributes
|
||||
(obj/contains? svg-styles "fill")
|
||||
|
@ -233,7 +232,15 @@
|
|||
(-> (obj/new)
|
||||
(obj/set! "style" fill-styles))))
|
||||
|
||||
(defn extract-stroke-attrs
|
||||
[shape index]
|
||||
(let [render-id (mf/use-ctx muc/render-ctx)
|
||||
stroke-styles (-> (obj/get shape "style" (obj/new))
|
||||
(add-stroke shape render-id index))]
|
||||
(-> (obj/new)
|
||||
(obj/set! "style" stroke-styles))))
|
||||
|
||||
(defn extract-border-radius-attrs
|
||||
[shape]
|
||||
(-> (obj/new)
|
||||
(add-border-radius shape)))
|
||||
(-> (obj/new)
|
||||
(add-border-radius shape)))
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.geom.shapes :as geom]
|
||||
[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-strokes]]
|
||||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
|
@ -32,5 +32,5 @@
|
|||
:ry ry
|
||||
:transform transform}))]
|
||||
|
||||
[:& shape-custom-stroke {:shape shape}
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
[:> :ellipse props]]))
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
(ns app.main.ui.shapes.custom-stroke
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.gradients :as grad]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -39,8 +42,20 @@
|
|||
stroke-width (case (:stroke-alignment shape :center)
|
||||
:center (/ (:stroke-width shape 0) 2)
|
||||
:outer (:stroke-width shape 0)
|
||||
0)]
|
||||
[:mask {:id stroke-mask-id}
|
||||
0)
|
||||
margin (gsh/shape-stroke-margin shape stroke-width)
|
||||
bounding-box (-> (gsh/points->selrect (:points shape))
|
||||
(update :x - (+ stroke-width margin))
|
||||
(update :y - (+ stroke-width margin))
|
||||
(update :width + (* 2 (+ stroke-width margin)))
|
||||
(update :height + (* 2 (+ stroke-width margin))))]
|
||||
|
||||
[:mask {:id stroke-mask-id
|
||||
:x (:x bounding-box)
|
||||
:y (:y bounding-box)
|
||||
:width (:width bounding-box)
|
||||
:height (:height bounding-box)
|
||||
:maskUnits "userSpaceOnUse"}
|
||||
[:use {:xlinkHref (str "#" shape-id)
|
||||
:style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}]
|
||||
|
||||
|
@ -49,13 +64,13 @@
|
|||
:stroke "none"}}]]))
|
||||
|
||||
(mf/defc cap-markers
|
||||
[{:keys [shape render-id]}]
|
||||
[{:keys [shape render-id index]}]
|
||||
(let [marker-id-prefix (str "marker-" render-id)
|
||||
cap-start (:stroke-cap-start shape)
|
||||
cap-end (:stroke-cap-end shape)
|
||||
|
||||
stroke-color (if (:stroke-color-gradient shape)
|
||||
(str/format "url(#%s)" (str "stroke-color-gradient_" render-id))
|
||||
(str/format "url(#%s)" (str "stroke-color-gradient_" render-id "_" index))
|
||||
(:stroke-color shape))
|
||||
|
||||
stroke-opacity (when-not (:stroke-color-gradient shape)
|
||||
|
@ -154,26 +169,35 @@
|
|||
[{:keys [shape render-id index]}]
|
||||
|
||||
(let [open-path? (and (= :path (:type shape)) (gsh/open-path? shape))]
|
||||
(cond
|
||||
(and (not open-path?)
|
||||
(= :inner (:stroke-alignment shape :center))
|
||||
(> (:stroke-width shape 0) 0))
|
||||
[:& inner-stroke-clip-path {:shape shape
|
||||
:render-id render-id
|
||||
:index index}]
|
||||
[:*
|
||||
(cond (some? (:stroke-color-gradient shape))
|
||||
(case (:type (:stroke-color-gradient shape))
|
||||
:linear [:> grad/linear-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index)
|
||||
:gradient (:stroke-color-gradient shape)
|
||||
:shape shape}]
|
||||
:radial [:> grad/radial-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index)
|
||||
:gradient (:stroke-color-gradient shape)
|
||||
:shape shape}]))
|
||||
(cond
|
||||
(and (not open-path?)
|
||||
(= :inner (:stroke-alignment shape :center))
|
||||
(> (:stroke-width shape 0) 0))
|
||||
[:& inner-stroke-clip-path {:shape shape
|
||||
:render-id render-id
|
||||
:index index}]
|
||||
|
||||
(and (not open-path?)
|
||||
(= :outer (:stroke-alignment shape :center))
|
||||
(> (:stroke-width shape 0) 0))
|
||||
[:& outer-stroke-mask {:shape shape
|
||||
:render-id render-id
|
||||
:index index}]
|
||||
(and (not open-path?)
|
||||
(= :outer (:stroke-alignment shape :center))
|
||||
(> (:stroke-width shape 0) 0))
|
||||
[:& outer-stroke-mask {:shape shape
|
||||
:render-id render-id
|
||||
:index index}]
|
||||
|
||||
(or (some? (:stroke-cap-start shape))
|
||||
(some? (:stroke-cap-end shape)))
|
||||
[:& cap-markers {:shape shape
|
||||
:render-id render-id
|
||||
:index index}])))
|
||||
(or (some? (:stroke-cap-start shape))
|
||||
(some? (:stroke-cap-end shape)))
|
||||
[:& cap-markers {:shape shape
|
||||
:render-id render-id
|
||||
:index index}])]))
|
||||
|
||||
;; Outer alignment: display the shape in two layers. One
|
||||
;; without stroke (only fill), and another one only with stroke
|
||||
|
@ -265,6 +289,7 @@
|
|||
|
||||
(let [child (obj/get props "children")
|
||||
shape (obj/get props "shape")
|
||||
render-id (mf/use-ctx muc/render-ctx)
|
||||
index (obj/get props "index")
|
||||
stroke-width (:stroke-width shape 0)
|
||||
stroke-style (:stroke-style shape :none)
|
||||
|
@ -286,5 +311,62 @@
|
|||
child]
|
||||
|
||||
:else
|
||||
child)))
|
||||
[:g.stroke-shape
|
||||
[:defs
|
||||
[:& stroke-defs {:shape shape :render-id render-id :index index}]]
|
||||
child])))
|
||||
|
||||
(defn build-stroke-props [position shape child value]
|
||||
(let [render-id (mf/use-ctx muc/render-ctx)
|
||||
url-fill? (or (some? (:fill-image shape))
|
||||
(= :image (:type shape))
|
||||
(> (count (:fills shape)) 1)
|
||||
(some :fill-color-gradient (:fills shape)))
|
||||
one-fill? (= (count (:fills shape)) 1)
|
||||
no-fills? (= (count (:fills shape)) 0)
|
||||
last-stroke? (= position (- (count (:strokes shape)) 1))
|
||||
|
||||
props (-> (obj/get child "props")
|
||||
(obj/clone))
|
||||
|
||||
props (cond
|
||||
(and last-stroke? url-fill?)
|
||||
;; TODO: check this zero
|
||||
(obj/set! props "fill" (str "url(#fill-0-" render-id ")"))
|
||||
|
||||
(and last-stroke? one-fill?)
|
||||
(obj/merge!
|
||||
props
|
||||
(attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0))
|
||||
|
||||
:else
|
||||
(-> props
|
||||
(obj/without ["fill" "fillOpacity"])
|
||||
(obj/set!
|
||||
"style"
|
||||
(-> (obj/get props "style")
|
||||
(obj/set! "fill" "none")
|
||||
(obj/set! "fillOpacity" "none")))))
|
||||
|
||||
props (-> props
|
||||
(add-style
|
||||
(obj/get (attrs/extract-stroke-attrs value position) "style")))]
|
||||
props))
|
||||
|
||||
(mf/defc shape-custom-strokes
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [child (obj/get props "children")
|
||||
shape (obj/get props "shape")
|
||||
elem-name (obj/get child "type")]
|
||||
|
||||
(cond
|
||||
(seq (:strokes shape))
|
||||
[:*
|
||||
(for [[index value] (-> (d/enumerate (:strokes shape)) reverse)]
|
||||
[:& shape-custom-stroke {:shape (assoc value :points (:points shape)) :index index}
|
||||
[:> elem-name (build-stroke-props index shape child value)]])]
|
||||
|
||||
:else
|
||||
[:& shape-custom-stroke {:shape shape :index 0}
|
||||
child])))
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.util.json :as json]
|
||||
[app.util.object :as obj]
|
||||
[app.util.svg :as usvg]
|
||||
|
@ -272,6 +273,39 @@
|
|||
(for [leaf (->> shape :content :content (filter string?))]
|
||||
[:> "penpot:svg-child" {} leaf])]))]))
|
||||
|
||||
|
||||
(defn- export-fills-data [{:keys [fills]}]
|
||||
(when-let [fills (seq fills)]
|
||||
(mf/html
|
||||
[:> "penpot:fills" #js {}
|
||||
(for [[index fill] (d/enumerate fills)]
|
||||
[:> "penpot:fill"
|
||||
#js {:penpot:fill-color (if (some? (:fill-color-gradient fill))
|
||||
(str/format "url(#%s)" (str "fill-color-gradient_" (mf/use-ctx muc/render-ctx) "_" index))
|
||||
(d/name (:fill-color fill)))
|
||||
:penpot:fill-color-ref-file (d/name (:fill-color-ref-file fill))
|
||||
:penpot:fill-color-ref-id (d/name (:fill-color-ref-id fill))
|
||||
:penpot:fill-opacity (d/name (:fill-opacity fill))}])])))
|
||||
|
||||
(defn- export-strokes-data [{:keys [strokes]}]
|
||||
(when-let [strokes (seq strokes)]
|
||||
(mf/html
|
||||
[:> "penpot:strokes" #js {}
|
||||
(for [[index stroke] (d/enumerate strokes)]
|
||||
[:> "penpot:stroke"
|
||||
#js {:penpot:stroke-color (if (some? (:stroke-color-gradient stroke))
|
||||
(str/format "url(#%s)" (str "stroke-color-gradient_" (mf/use-ctx muc/render-ctx) "_" index))
|
||||
(d/name (:stroke-color stroke)))
|
||||
:penpot:stroke-color-ref-file (d/name (:stroke-color-ref-file stroke))
|
||||
:penpot:stroke-color-ref-id (d/name (:stroke-color-ref-id stroke))
|
||||
:penpot:stroke-opacity (d/name (:stroke-opacity stroke))
|
||||
:penpot:stroke-style (d/name (:stroke-style stroke))
|
||||
:penpot:stroke-width (d/name (:stroke-width stroke))
|
||||
:penpot:stroke-alignment (d/name (:stroke-alignment stroke))
|
||||
:penpot:stroke-cap-start (d/name (:stroke-cap-start stroke))
|
||||
:penpot:stroke-cap-end (d/name (:stroke-cap-end stroke))}])])))
|
||||
|
||||
|
||||
(defn- export-interactions-data [{:keys [interactions]}]
|
||||
(when-let [interactions (seq interactions)]
|
||||
(mf/html
|
||||
|
@ -300,5 +334,7 @@
|
|||
(export-exports-data shape)
|
||||
(export-svg-data shape)
|
||||
(export-interactions-data shape)
|
||||
(export-fills-data shape)
|
||||
(export-strokes-data shape)
|
||||
(export-grid-data shape)]))
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
(let [{:keys [x y width height]} (:selrect shape)
|
||||
{:keys [metadata]} shape
|
||||
|
||||
|
||||
has-image (or metadata (:fill-image shape))
|
||||
uri (if metadata
|
||||
(cfg/resolve-file-media metadata)
|
||||
|
|
|
@ -201,11 +201,11 @@
|
|||
:height (- y2 y1)})))))
|
||||
|
||||
(defn calculate-padding [shape]
|
||||
(let [stroke-width (case (:stroke-alignment shape :center)
|
||||
:center (/ (:stroke-width shape 0) 2)
|
||||
:outer (:stroke-width shape 0)
|
||||
0)
|
||||
margin (gsh/shape-stroke-margin shape stroke-width)]
|
||||
(let [stroke-width (apply max 0 (map #(case (:stroke-alignment % :center)
|
||||
:center (/ (:stroke-width % 0) 2)
|
||||
:outer (:stroke-width % 0)
|
||||
0) (:strokes shape)))
|
||||
margin (apply max 0 (map #(gsh/shape-stroke-margin % stroke-width) (:strokes shape)))]
|
||||
(+ stroke-width margin)))
|
||||
|
||||
(defn change-filter-in
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.util.object :as obj]
|
||||
[debug :refer [debug?]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -24,9 +26,10 @@
|
|||
(mf/defc frame-clip-def
|
||||
[{:keys [shape render-id]}]
|
||||
(when (= :frame (:type shape))
|
||||
(let [{:keys [x y width height]} shape]
|
||||
(let [{:keys [x y width height]} shape
|
||||
padding (filters/calculate-padding shape)]
|
||||
[:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"}
|
||||
[:rect {:x x :y y :width width :height height}]])))
|
||||
[:rect {:x (- x padding) :y (- y padding) :width (+ width (* 2 padding)) :height (+ height (* 2 padding))}]])))
|
||||
|
||||
(mf/defc frame-thumbnail
|
||||
{::mf/wrap-props false}
|
||||
|
@ -59,8 +62,10 @@
|
|||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))]
|
||||
|
||||
[:*
|
||||
[:> :rect props]
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
[:> :rect props]]
|
||||
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape item
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[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-strokes]]
|
||||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
|
@ -29,8 +29,7 @@
|
|||
:height height}))
|
||||
path? (some? (.-d props))]
|
||||
|
||||
[:g
|
||||
[:& shape-custom-stroke {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]]))
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]))
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.logging :as log]
|
||||
[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-strokes]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.path.format :as upf]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -31,5 +31,5 @@
|
|||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/set! "d" pdata))]
|
||||
|
||||
[:& shape-custom-stroke {:shape shape}
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
[:> :path props]]))
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[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-strokes]]
|
||||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
|||
|
||||
path? (some? (.-d props))]
|
||||
|
||||
[:& shape-custom-stroke {:shape shape}
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
(if path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]))
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
[app.main.ui.shapes.fills :as fills]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.gradients :as grad]
|
||||
[app.main.ui.shapes.svg-defs :as defs]
|
||||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -62,7 +61,6 @@
|
|||
[:defs
|
||||
[:& defs/svg-defs {:shape shape :render-id render-id}]
|
||||
[:& filters/filters {:shape shape :filter-id filter-id}]
|
||||
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]
|
||||
[:& fills/fills {:shape shape :render-id render-id}]
|
||||
[:& frame/frame-clip-def {:shape shape :render-id render-id}]]
|
||||
children]]))
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.context :as muc]
|
||||
[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-strokes]]
|
||||
[app.main.ui.shapes.gradients :as grad]
|
||||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -21,7 +21,7 @@
|
|||
{::mf/wrap-props false
|
||||
::mf/wrap [mf/memo]}
|
||||
[props]
|
||||
|
||||
|
||||
(let [render-id (mf/use-ctx muc/render-ctx)
|
||||
{:keys [x y width height position-data] :as shape} (obj/get props "shape")
|
||||
transform (str (gsh/transform-matrix shape))
|
||||
|
@ -60,7 +60,7 @@
|
|||
:direction (if (:rtl data) "rtl" "ltr")
|
||||
:whiteSpace "pre"}
|
||||
(obj/set! "fill" (str "url(#fill-" index "-" render-id ")")))})]
|
||||
[:& shape-custom-stroke {:shape shape :index index}
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
[:> :text props (:text data)]]))]]))
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue