penpot/frontend/src/app/main/ui/shapes/gradients.cljs
2023-11-16 11:07:36 +01:00

140 lines
4.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.gradients
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.main.ui.context :as muc]
[app.main.ui.shapes.export :as ed]
[app.util.object :as obj]
[rumext.v2 :as mf]))
(defn- add-metadata!
[props gradient]
(-> props
(obj/set! "penpot:gradient" "true")
(obj/set! "penpot:start-x" (:start-x gradient))
(obj/set! "penpot:start-y" (:start-y gradient))
(obj/set! "penpot:end-x" (:end-x gradient))
(obj/set! "penpot:end-y" (:end-y gradient))
(obj/set! "penpot:width" (:width gradient))))
(mf/defc linear-gradient
{::mf/wrap-props false}
[{:keys [id gradient shape]}]
(let [transform (mf/with-memo [shape]
(when (cfh/frame-shape? shape)
(gsh/transform-matrix shape nil (gpt/point 0.5 0.5))))
metadata? (mf/use-ctx ed/include-metadata-ctx)
props #js {:id id
:x1 (:start-x gradient)
:y1 (:start-y gradient)
:x2 (:end-x gradient)
:y2 (:end-y gradient)
:gradientTransform (dm/str transform)}]
(when ^boolean metadata?
(add-metadata! props gradient))
[:> :linearGradient props
(for [{:keys [offset color opacity]} (:stops gradient)]
[:stop {:key (dm/str id "-stop-" offset)
:offset (d/nilv offset 0)
:stop-color color
:stop-opacity opacity}])]))
(mf/defc radial-gradient
{::mf/wrap-props false}
[{:keys [id gradient shape]}]
(let [path? (cfh/path-shape? shape)
transform (when ^boolean path?
(dm/get-prop shape :transform))
transform (d/nilv transform gmt/base)
transform-inv (when ^boolean path?
(dm/get-prop shape :transform-inverse))
transform-inv (d/nilv transform-inv gmt/base)
{:keys [start-x start-y end-x end-y] gwidth :width} gradient
gstart-pt (gpt/point start-x start-y)
gend-pt (gpt/point end-x end-y)
gradient-vec (gpt/to-vec gstart-pt gend-pt)
angle (+ (gpt/angle gradient-vec) 90)
points (dm/get-prop shape :points)
bounds (mf/with-memo [points]
(grc/points->rect points))
selrect (dm/get-prop shape :selrect)
;; Paths don't have a transform in SVG because we transform
;; the points we need to compensate the difference between the
;; original rectangle and the transformed one. This factor is
;; that calculation.
factor (if ^boolean path?
(/ (dm/get-prop selrect :height)
(dm/get-prop bounds :height))
1.0)
transform (mf/with-memo [gradient transform transform-inv factor]
(-> (gmt/matrix)
(gmt/translate gstart-pt)
(gmt/multiply transform)
(gmt/rotate angle)
(gmt/scale (gpt/point gwidth factor))
(gmt/multiply transform-inv)
(gmt/translate (gpt/negate gstart-pt))))
metadata? (mf/use-ctx ed/include-metadata-ctx)
props #js {:id id
:cx start-x
:cy start-y
:r (gpt/length gradient-vec)
:gradientTransform transform}]
(when ^boolean metadata?
(add-metadata! props gradient))
[:> :radialGradient props
(for [{:keys [offset color opacity]} (:stops gradient)]
[:stop {:key (dm/str id "-stop-" offset)
:offset (d/nilv offset 0)
:stop-color color
:stop-opacity opacity}])]))
(mf/defc gradient
{::mf/wrap-props false}
[props]
(let [attr (unchecked-get props "attr")
shape (unchecked-get props "shape")
id (unchecked-get props "id")
rid (mf/use-ctx muc/render-id)
id (if (some? id)
id
(dm/str (name attr) "-" rid))
gradient (get shape attr)
props #js {:id id
:gradient gradient
:shape shape}]
(when (some? gradient)
(case (:type gradient)
:linear [:> linear-gradient props]
:radial [:> radial-gradient props]
nil))))