Multiple fills in text shapes

This commit is contained in:
alonso.torres 2022-02-22 18:06:48 +01:00
parent a3063eb46d
commit ec63d23666
19 changed files with 230 additions and 211 deletions

View file

@ -205,7 +205,7 @@
(= :image (:type shape))
(> (count (:fills shape)) 1)
(some #(some? (:fill-color-gradient %)) (:fills shape)))
(obj/set! styles "fill" (str "url(#fill-" 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")
@ -227,9 +227,8 @@
(add-style-attrs shape)))
(defn extract-fill-attrs
[shape index]
(let [render-id (mf/use-ctx muc/render-ctx)
fill-styles (-> (obj/get shape "style" (obj/new))
[shape render-id index]
(let [fill-styles (-> (obj/get shape "style" (obj/new))
(add-fill shape render-id index))]
(-> (obj/new)
(obj/set! "style" fill-styles))))

View file

@ -20,45 +20,54 @@
[props]
(let [shape (obj/get props "shape")
render-id (obj/get props "render-id")
{:keys [x y width height]} (:selrect shape)
{:keys [metadata]} shape
fill-id (str "fill-" render-id)
has-image (or metadata (:fill-image shape))
uri (if metadata
(cfg/resolve-file-media metadata)
(cfg/resolve-file-media (:fill-image shape)))
embed (embed/use-data-uris [uri])
transform (gsh/transform-matrix shape)
pattern-attrs (cond-> #js {:id fill-id
:patternUnits "userSpaceOnUse"
:x x
:y y
:height height
:width width
:data-loading (str (not (contains? embed uri)))}
(= :path (:type shape))
(obj/set! "patternTransform" transform))]
render-id (obj/get props "render-id")]
[:*
(for [[index value] (-> (d/enumerate (:fills shape [])) reverse)]
(cond (some? (:fill-color-gradient value))
(case (:type (:fill-color-gradient value))
:linear [:> grad/linear-gradient #js {:id (str (name :fill-color-gradient) "_" render-id "_" index)
:gradient (:fill-color-gradient value)
:shape shape}]
:radial [:> grad/radial-gradient #js {:id (str (name :fill-color-gradient) "_" render-id "_" index)
:gradient (:fill-color-gradient value)
:shape shape}])))
(when (or (some? (:fill-image shape))
(#{:image :text} (:type shape))
(> (count (:fills shape)) 1)
(some :fill-color-gradient (:fills shape)))
[:> :pattern pattern-attrs
[:g
(for [[index value] (-> (d/enumerate (:fills shape [])) reverse)]
[:> :rect (-> (attrs/extract-fill-attrs value index)
(obj/set! "width" width)
(obj/set! "height" height))])
(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)
(cfg/resolve-file-media (:fill-image shape)))
embed (embed/use-data-uris [uri])
transform (gsh/transform-matrix shape)
pattern-attrs (cond-> #js {:patternUnits "userSpaceOnUse"
:x x
:y y
:height height
:width width
:data-loading (str (not (contains? embed uri)))}
(= :path (:type shape))
(obj/set! "patternTransform" transform))]
(when has-image
[:image {:xlinkHref (get embed uri uri)
:width width
:height height}])]]]))
[:*
(for [[_shape-index shape] (d/enumerate (or (:position-data shape) [shape]))]
(for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)]
(cond (some? (:fill-color-gradient value))
(case (d/name (:type (:fill-color-gradient value)))
"linear" [:> grad/linear-gradient #js {:id (str "fill-color-gradient_" render-id "_" fill-index)
:gradient (:fill-color-gradient value)
:shape shape}]
"radial" [:> grad/radial-gradient #js {:id (str "fill-color-gradient_" render-id "_" fill-index)
:gradient (:fill-color-gradient value)
:shape shape}]))))
(for [[shape-index shape] (d/enumerate (or (:position-data shape) [shape]))]
(let [fill-id (str "fill-" shape-index "-" render-id)]
[:> :pattern (-> (obj/clone pattern-attrs)
(obj/set! "id" fill-id))
[:g
(for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)]
[:> :rect (-> (attrs/extract-fill-attrs value render-id fill-index)
(obj/set! "width" width)
(obj/set! "height" height))])
(when has-image
[:image {:xlinkHref (get embed uri uri)
:width width
:height height}])]]))]))))

View file

@ -37,7 +37,7 @@
(fn [data]
(-> data
(dissoc :fill-color :fill-opacity :fill-color-gradient)
(assoc :fill-color "#FFFFFF" :fill-opacity 1)))]
(assoc :fills [{:fill-color "#FFFFFF" :fill-opacity 1}])))]
(-> shape
(d/update-when :position-data #(mapv update-color %))
(assoc :stroke-color "#FFFFFF" :stroke-opacity 1))))

View file

@ -63,10 +63,6 @@
[:& 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}]
(when (or (some? (:fill-image shape))
(= :image (:type shape))
(> (count (:fills shape)) 1)
(some :fill-color-gradient (:fills shape)))
[:& fills/fills {:shape shape :render-id render-id}])
[:& fills/fills {:shape shape :render-id render-id}]
[:& frame/frame-clip-def {:shape shape :render-id render-id}]]
children]]))

View file

@ -194,13 +194,16 @@
grow-type (obj/get props "grow-type") ;; This is only needed in workspace
;; We add 8px to add a padding for the exporter
;; width (+ width 8)
[colors color-mapping color-mapping-inverse] (retrieve-colors shape)
plain-colors? (mf/use-ctx muc/text-plain-colors-ctx)
content (cond-> content
plain-colors?
(remap-colors color-mapping))]
(remap-colors color-mapping))
]
[:foreignObject
{:x x

View file

@ -88,9 +88,10 @@
:overflowWrap "initial"}
base (-> base
(obj/set! "--fill-color" fill-color)
(obj/set! "--fill-color-gradient" (transit/encode-str (:fill-color-gradient data)))
(obj/set! "--fill-opacity" fill-opacity))]
(obj/set! "--fills" (transit/encode-str (:fills data)))
#_(obj/set! "--fill-color" fill-color)
#_(obj/set! "--fill-color-gradient" (transit/encode-str (:fill-color-gradient data)))
#_(obj/set! "--fill-opacity" fill-opacity))]
(when (and (string? letter-spacing)
(pos? (alength letter-spacing)))

View file

@ -59,7 +59,8 @@
:fontStyle (:font-style data)
:direction (if (:rtl? data) "rtl" "ltr")
:whiteSpace "pre"}
(attrs/add-fill data (get-gradient-id index)))})]
(obj/set! "fill" (str "url(#fill-" index "-" render-id ")"))
#_(attrs/add-fill data (get-gradient-id index)))})]
[:& shape-custom-stroke {:shape shape :index index}
[:> :text props (:text data)]]))]]))

View file

@ -155,6 +155,11 @@
show-svg-text? (or (some? position-data) (some? @local-position-data))
shape
(cond-> shape
(some? @local-position-data)
(assoc :position-data @local-position-data))
update-position-data
(fn []
(when (some? @local-position-data)
@ -171,7 +176,7 @@
(fn []
;; Timer to update the shape. We do this so a lot of changes won't produce
;; a lot of updates (kind of a debounce)
(let [sid (timers/schedule 100 update-position-data)]
(let [sid (timers/schedule 50 update-position-data)]
(fn []
(rx/dispose! sid)))))
@ -217,9 +222,5 @@
:key (str id edition?)}]]
(when show-svg-text?
(let [shape
(cond-> shape
(some? @local-position-data)
(assoc :position-data @local-position-data))]
[:g.text-svg {:pointer-events "none"}
[:& svg/text-shape {:shape shape}]]))]]))
[:g.text-svg {:pointer-events "none"}
[:& svg/text-shape {:shape shape}]])]]))

View file

@ -210,7 +210,11 @@
{:ref self-ref
:style {:cursor (cur/text (:rotation shape))
:width (:width shape)
:height (:height shape)}
:height (:height shape)
;; We hide the editor when is blurred because otherwise the selection won't let us see
;; the underlying text. Use opacity because display or visibility won't allow to recover
;; focus afterwards.
:opacity (when @blurred 0)}
:on-click on-click
:class (dom/classnames
:align-top (= (:vertical-align content "top") "top")

View file

@ -6,7 +6,6 @@
(ns app.main.ui.workspace.sidebar.options.menus.fill
(:require
[app.common.attrs :as attrs]
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.pages :as cp]
@ -17,7 +16,6 @@
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.alpha :as mf]))
(def fill-attrs
@ -51,40 +49,6 @@
;; Excluding nil values
values (d/without-nils values)
only-shapes? (and (contains? values :fills)
;; texts have :fill-* attributes, the rest of the shapes have :fills
(= (count (filter #(str/starts-with? (d/name %) "fill-") (keys values))) 0))
shapes-and-texts? (and (contains? values :fills)
;; texts have :fill-* attributes, the rest of the shapes have :fills
(> (count (filter #(str/starts-with? (d/name %) "fill-") (keys values))) 0))
;; Texts still have :fill-* attributes and the rest of the shapes just :fills so we need some extra calculation when multiple selection happens to detect them
plain-values (if (vector? (:fills values))
(concat (:fills values) [(dissoc values :fills)])
values)
plain-values (attrs/get-attrs-multi plain-values [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient])
plain-values (if (empty? plain-values)
values
plain-values)
;; We must control some rare situations like
;; - Selecting texts and shapes with different fills
;; - Selecting a text and a shape with empty fills
plain-values (if (and shapes-and-texts?
(or
(= (:fills values) :multiple)
(= 0 (count (:fills values)))))
{:fills :multiple
:fill-color :multiple
:fill-opacity :multiple
:fill-color-ref-id :multiple
:fill-color-ref-file :multiple
:fill-color-gradient :multiple}
plain-values)
hide-fill-on-export? (:hide-fill-on-export values false)
checkbox-ref (mf/use-ref)
@ -110,12 +74,6 @@
(fn [index]
(st/emit! (dc/reorder-fills ids index new-index)))))
on-change-mixed-shapes
(mf/use-callback
(mf/deps ids)
(fn [color]
(st/emit! (dc/change-fill-and-clear ids color))))
on-remove
(fn [index]
(fn []
@ -155,43 +113,33 @@
[:div.element-set
[:div.element-set-title
[:span label]
(when (and (not disable-remove?) (not (= :multiple (:fills values))) only-shapes?)
(when (and (not disable-remove?) (not (= :multiple (:fills values))))
[:div.add-page {:on-click on-add} i/close])]
[:div.element-set-content
(if only-shapes?
(cond
(= :multiple (:fills values))
[:div.element-set-options-group
[:div.element-set-label (tr "settings.multiple")]
[:div.element-set-actions
[:div.element-set-actions-button {:on-click on-remove-all}
i/minus]]]
(cond
(= :multiple (:fills values))
[:div.element-set-options-group
[:div.element-set-label (tr "settings.multiple")]
[:div.element-set-actions
[:div.element-set-actions-button {:on-click on-remove-all}
i/minus]]]
(seq (:fills values))
[:& h/sortable-container {}
(for [[index value] (d/enumerate (:fills values []))]
[:& color-row {:color {:color (:fill-color value)
:opacity (:fill-opacity value)
:id (:fill-color-ref-id value)
:file-id (:fill-color-ref-file value)
:gradient (:fill-color-gradient value)}
:index index
:title (tr "workspace.options.fill")
:on-change (on-change index)
:on-reorder (on-reorder index)
:on-detach (on-detach index)
:on-remove (on-remove index)}])])
[:& color-row {:color {:color (:fill-color plain-values)
:opacity (:fill-opacity plain-values)
:id (:fill-color-ref-id plain-values)
:file-id (:fill-color-ref-file plain-values)
:gradient (:fill-color-gradient plain-values)}
:title (tr "workspace.options.fill")
:on-change on-change-mixed-shapes
:on-detach (on-detach 0)}])
(seq (:fills values))
[:& h/sortable-container {}
(for [[index value] (d/enumerate (:fills values []))]
[:& color-row {:color {:color (:fill-color value)
:opacity (:fill-opacity value)
:id (:fill-color-ref-id value)
:file-id (:fill-color-ref-file value)
:gradient (:fill-color-gradient value)}
:index index
:title (tr "workspace.options.fill")
:on-change (on-change index)
:on-reorder (on-reorder index)
:on-detach (on-detach index)
:on-remove (on-remove index)}])])
(when (or (= type :frame)
(and (= type :multiple) (some? hide-fill-on-export?)))

View file

@ -347,5 +347,3 @@
[:div.row-flex
[:> grow-options opts]
[:div.align-icons]]]]))

View file

@ -6,7 +6,6 @@
(ns app.main.ui.workspace.sidebar.options.shapes.text
(:require
[app.common.colors :as clr]
[app.common.data :as d]
[app.main.data.workspace.texts :as dwt]
[app.main.refs :as refs]
@ -30,19 +29,16 @@
layer-values (select-keys shape layer-attrs)
fill-values (dwt/current-text-values
{:editor-state editor-state
:shape shape
:attrs text-fill-attrs})
fill-values (-> (dwt/current-text-values
{:editor-state editor-state
:shape shape
:attrs (conj text-fill-attrs :fills)})
(d/update-in-when [:fill-color-gradient :type] keyword))
fill-values (d/update-in-when fill-values [:fill-color-gradient :type] keyword)
fill-values (cond-> fill-values
(not (contains? fill-values :fill-color)) (assoc :fill-color clr/black)
(not (contains? fill-values :fill-opacity)) (assoc :fill-opacity 1)
;; Keep for backwards compatibility
(:fill fill-values) (assoc :fill-color (:fill fill-values))
(:opacity fill-values) (assoc :fill-opacity (:fill fill-values)))
fill-values (if (not (contains? fill-values :fills))
;; Old fill format
{:fills [fill-values]}
fill-values)
stroke-values (select-keys shape stroke-attrs)
@ -79,8 +75,7 @@
[:& fill-menu
{:ids ids
:type type
:values fill-values
:disable-remove? true}]
:values fill-values}]
[:& stroke-menu {:ids ids
:type type