Ability to add multiple strokes to a shape

This commit is contained in:
Alejandro Alonso 2022-02-17 15:19:36 +01:00 committed by Alonso Torres
parent 719aacd6f8
commit a73a393e26
30 changed files with 680 additions and 353 deletions

View file

@ -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)))

View file

@ -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]]))

View file

@ -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])))

View file

@ -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)]))

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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])]))

View file

@ -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]]))

View file

@ -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])]))

View file

@ -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]]))

View file

@ -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)]]))]]))