penpot/frontend/src/app/main/ui/shapes/mask.cljs
2024-01-08 09:32:50 +01:00

114 lines
3.9 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.shapes.mask
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.rect :as grc]
[app.main.ui.context :as muc]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn mask-id [render-id mask]
(dm/str render-id "-" (:id mask) "-mask"))
(defn mask-url [render-id mask]
(dm/str "url(#" (mask-id render-id mask) ")"))
(defn clip-id [render-id mask]
(dm/str render-id "-" (:id mask) "-clip"))
(defn clip-url [render-id mask]
(dm/str "url(#" (clip-id render-id mask) ")"))
(defn filter-id [render-id mask]
(dm/str render-id "-" (:id mask) "-filter"))
(defn filter-url [render-id mask]
(dm/str "url(#" (filter-id render-id mask) ")"))
(defn set-white-fill
[shape]
(let [update-color
(fn [data]
(-> data
(dissoc :fill-color :fill-opacity :fill-color-gradient)
(assoc :fills [{:fill-color "#FFFFFF" :fill-opacity 1}])))]
(-> shape
(d/update-when :position-data #(mapv update-color %))
(assoc :stroke-color "#FFFFFF" :stroke-opacity 1))))
(defn- point->str
[point]
(dm/str (dm/get-prop point :x) "," (dm/get-prop point :y)))
(defn mask-factory
[shape-wrapper]
(mf/fnc mask-shape
{::mf/wrap-props false}
[props]
(let [mask (unchecked-get props "mask")
render-id (mf/use-ctx muc/render-id)
svg-text? (and ^boolean (cfh/text-shape? mask)
^boolean (some? (:position-data mask)))
points (dm/get-prop mask :points)
points-str (mf/with-memo [points]
(->> (map point->str points)
(str/join " ")))
bounds (mf/with-memo [points]
(grc/points->rect points))
bx (dm/get-prop bounds :x)
by (dm/get-prop bounds :y)
bw (dm/get-prop bounds :width)
bh (dm/get-prop bounds :height)
shape (mf/with-memo [mask]
(-> mask
(dissoc :shadow :blur)
(assoc :is-mask? true)))]
[:defs
[:filter {:id (filter-id render-id mask)}
[:feFlood {:flood-color "white"
:result "FloodResult"}]
[:feComposite {:in "FloodResult"
:in2 "SourceGraphic"
:operator "in"
:result "comp"}]]
;; Clip path is necessary so the elements inside the mask won't affect
;; the events outside. Clip hides the elements but mask doesn't (like display vs visibility)
;; we cannot use clips instead of mask because clips can only be simple shapes
[:clipPath {:class "mask-clip-path"
:id (clip-id render-id mask)}
[:polyline {:points points-str}]]
;; When te shape is a text we pass to the shape the info and disable the filter.
;; There is a bug in Firefox with filters and texts. We change the text to white at shape level
[:mask {:class "mask-shape"
:id (mask-id render-id mask)
:x bx
:y by
:width bw
:height bh
;; This is necesary to prevent a race condition in the dynamic-modifiers whether the modifier
;; triggers afte the render
:data-old-x bx
:data-old-y by
:data-old-width bw
:data-old-height bh
:mask-units "userSpaceOnUse"}
[:g {:filter (when-not ^boolean svg-text?
(filter-url render-id mask))}
[:& shape-wrapper {:shape shape}]]]])))