penpot/frontend/src/app/util/code_gen/style_css_values.cljs
2026-03-18 17:43:27 +01:00

591 lines
18 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.util.code-gen.style-css-values
(: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.shapes :as gsh]
[app.common.types.shape.layout :as ctl]
[app.main.ui.formats :as fmt]
[app.util.code-gen.common :as cgc]
[cuerdas.core :as str]))
(defn fill->color
[{:keys [fill-color fill-opacity fill-color-gradient fill-image]}]
{:color fill-color
:opacity fill-opacity
:gradient fill-color-gradient
:image fill-image})
(defn get-shape-position
[shape objects coord]
(when (and (not (cfh/root-frame? shape))
(or (not (ctl/any-layout-immediate-child? objects shape))
(ctl/position-absolute? shape)))
(let [parent (get objects (:parent-id shape))
parent-value (dm/get-in parent [:selrect coord])
[selrect _ _]
(-> (:points shape)
(gsh/transform-points (gsh/shape->center parent) (:transform-inverse parent (gmt/matrix)))
(gsh/calculate-geometry))
shape-value (get selrect coord)]
(- shape-value parent-value))))
(defn get-shape-size
[shape objects type]
(let [parent (cfh/get-parent objects (:id shape))
sizing (if (= type :width)
(:layout-item-h-sizing shape)
(:layout-item-v-sizing shape))]
(cond
(and (ctl/flex-layout-immediate-child? objects shape)
(or (and (= type :height)
(contains? #{:row :row-reverse} (:layout-flex-dir parent))
(= :fill (:layout-item-v-sizing shape)))
(and (= type :width)
(contains? #{:column :column-reverse} (:layout-flex-dir parent))
(= :fill (:layout-item-h-sizing shape)))))
:fill
(and (ctl/flex-layout-immediate-child? objects shape) (= sizing :fill))
nil
(or (and (ctl/any-layout? shape) (= sizing :auto) (not (cgc/svg-markup? shape)))
(and (ctl/grid-layout-immediate-child? objects shape) (= sizing :fill)))
sizing
(some? (:selrect shape))
(-> shape :selrect type)
(some? (get shape type))
(get shape type))))
(defn get-stroke-data
[stroke]
(let [width (:stroke-width stroke)
style (:stroke-style stroke)
color {:color (:stroke-color stroke)
:opacity (:stroke-opacity stroke)
:gradient (:stroke-color-gradient stroke)}]
(when (and (some? stroke) (not= :none (:stroke-style stroke)))
{:color color
:style style
:width width})))
(defn area-cell?
[{:keys [position area-name]}]
(and (= position :area) (d/not-empty? area-name)))
(defn get-grid-coord
[shape objects prop span-prop]
(when (and (ctl/grid-layout-immediate-child? objects shape)
(not (ctl/position-absolute? shape)))
(let [parent (get objects (:parent-id shape))
cell (ctl/get-cell-by-shape-id parent (:id shape))]
(when (and
(not (and (= (:position cell) :area) (d/not-empty? (:area-name cell))))
(or (= (:position cell) :manual)
(> (:row-span cell) 1)
(> (:column-span cell) 1)))
(if (> (get cell span-prop) 1)
(dm/str (get cell prop) " / " (+ (get cell prop) (get cell span-prop)))
(get cell prop))))))
;; SHAPE VALUES
(defn- get-position
[shape objects]
(cond
(or (and (ctl/any-layout-immediate-child? objects shape)
(not (ctl/position-absolute? shape))
(or (cfh/group-like-shape? shape)
(cfh/frame-shape? shape)
(cgc/svg-markup? shape)))
(cfh/root-frame? shape))
:relative
(and (ctl/any-layout-immediate-child? objects shape)
(not (ctl/position-absolute? shape)))
nil
:else
:absolute))
(defn- get-left-position
[shape objects]
(get-shape-position shape objects :x))
(defn- get-top-position
[shape objects]
(get-shape-position shape objects :y))
(defn- get-flex
[shape objects]
(let [parent (cfh/get-parent objects (:id shape))]
(when (and (ctl/flex-layout-immediate-child? objects shape)
(or (and (contains? #{:row :row-reverse} (:layout-flex-dir parent))
(= :fill (:layout-item-h-sizing shape)))
(and (contains? #{:column :column-reverse} (:layout-flex-dir parent))
(= :fill (:layout-item-v-sizing shape)))))
1)))
(defn- get-width
[shape objects options]
(let [root? (contains? (:root-shapes options) (:id shape))]
(if (and root? (ctl/any-layout? shape))
:fill
;; Don't set fixed width for auto-width text shapes
(when-not (and (cfh/text-shape? shape) (= (:grow-type shape) :auto-width))
(get-shape-size shape objects :width)))))
(defn- get-height
[shape objects options]
(let [root? (contains? (:root-shapes options) (:id shape))]
(when-not (and root? (ctl/any-layout? shape))
;; Don't set fixed height for auto-height text shapes
(when-not (and (cfh/text-shape? shape) (= (:grow-type shape) :auto-height))
(get-shape-size shape objects :height)))))
(defn- get-flex-grow
[shape options]
(let [root? (contains? (:root-shapes options) (:id shape))]
(when (and root? (ctl/any-layout? shape))
1)))
(defn- get-transform
[shape objects]
(if (cgc/svg-markup? shape)
(let [parent (get objects (:parent-id shape))
transform
(:transform-inverse parent (gmt/matrix))
transform-str (when-not (gmt/unit? transform) (fmt/format-matrix transform))]
(if (cgc/has-wrapper? objects shape)
(dm/str "translate(-50%, -50%) " (d/nilv transform-str ""))
transform-str))
(let [parent (get objects (:parent-id shape))
transform
(gmt/multiply (:transform shape (gmt/matrix))
(:transform-inverse parent (gmt/matrix)))
transform-str (when-not (gmt/unit? transform) (fmt/format-matrix transform))]
(if (cgc/has-wrapper? objects shape)
(dm/str "translate(-50%, -50%) " (d/nilv transform-str ""))
transform-str))))
(defn- get-background
[{:keys [fills] :as shape}]
(let [single-fill? (= (count fills) 1)]
(when (and (not (cgc/svg-markup? shape)) (not (cfh/group-shape? shape)) single-fill?)
(fill->color (first fills)))))
(defn- get-border
[shape]
(when-not (cgc/svg-markup? shape)
(get-stroke-data (first (:strokes shape)))))
(defn- get-border-radius
[{:keys [rx r1 r2 r3 r4] :as shape}]
(cond
(cfh/circle-shape? shape)
"50%"
(some? rx)
[rx]
(every? some? [r1 r2 r3 r4])
[r1 r2 r3 r4]))
(defn- get-border-start-start-radius
[{:keys [_ r1 _ _ _] :as shape}]
(when (and r1 (not= r1 0))
[r1]))
(defn- get-border-start-end-radius
[{:keys [_ _ r2 _ _] :as shape}]
(when (and r2 (not= r2 0))
[r2]))
(defn- get-border-end-start-radius
[{:keys [_ _ _ r3 _] :as shape}]
(when (and r3 (not= r3 0))
[r3]))
(defn- get-border-end-end-radius
[{:keys [_ _ _ _ r4] :as shape}]
(when (and r4 (not= r4 0))
[r4]))
(defn- get-border-style
[stroke]
(when-not (cgc/svg-markup? stroke)
(get-stroke-data stroke)))
(defn- get-border-width
[stroke]
(when-not (cgc/svg-markup? stroke)
(get-stroke-data stroke)))
(defn- get-border-color
[stroke]
(when-not (cgc/svg-markup? stroke)
(get-stroke-data stroke)))
(defn- get-box-shadow
[shape]
(when-not (cgc/svg-markup? shape)
(:shadow shape)))
(defn- get-filter
[shape]
(when-not (cgc/svg-markup? shape)
(when (= :layer-blur (get-in shape [:blur :type]))
(get-in shape [:blur :value]))))
(defn- get-backdrop-filter
[shape]
(when-not (cgc/svg-markup? shape)
(when (= :background-blur (get-in shape [:blur :type]))
(get-in shape [:blur :value]))))
(defn- get-display
[shape]
(cond
(:hidden shape) "none"
(ctl/flex-layout? shape) "flex"
(ctl/grid-layout? shape) "grid"))
(defn- get-opacity
[shape]
(when (< (:opacity shape) 1)
(:opacity shape)))
(defn- get-overflow
[shape]
(when (and (cfh/frame-shape? shape)
(not (cgc/svg-markup? shape))
(not (:show-content shape)))
"hidden"))
(defn- get-flex-direction
[shape]
(:layout-flex-dir shape))
(defn- get-align-items
[shape]
(:layout-align-items shape))
(defn- get-align-content
[shape]
(:layout-align-content shape))
(defn- get-justify-items
[shape]
(:layout-justify-items shape))
(defn- get-justify-content
[shape]
(:layout-justify-content shape))
(defn- get-flex-wrap
[shape]
(:layout-wrap-type shape))
(defn- get-gap
[shape]
(let [[g1 g2] (ctl/gaps shape)]
(when (and (= g1 g2) (or (not= g1 0) (not= g2 0)))
[g1])))
(defn- get-row-gap
[shape]
(let [[g1 g2] (ctl/gaps shape)]
(when (and (not= g1 g2) (not= g1 0)) [g1])))
(defn- get-column-gap
[shape]
(let [[g1 g2] (ctl/gaps shape)]
(when (and (not= g1 g2) (not= g2 0)) [g2])))
(defn- get-padding
[{:keys [layout-padding]}]
(when (some? layout-padding)
(let [default-padding {:p1 0 :p2 0 :p3 0 :p4 0}
{:keys [p1 p2 p3 p4]} (merge default-padding layout-padding)]
(when (or (not= p1 0) (not= p2 0) (not= p3 0) (not= p4 0))
[p1 p2 p3 p4]))))
(defn- get-padding-block-start
[{:keys [layout-padding]}]
(when (and (:p1 layout-padding) (not= (:p1 layout-padding) 0))
[(:p1 layout-padding)]))
(defn- get-padding-inline-end
[{:keys [layout-padding]}]
(when (and (:p2 layout-padding) (not= (:p2 layout-padding) 0))
[(:p2 layout-padding)]))
(defn- get-padding-block-end
[{:keys [layout-padding]}]
(when (and (:p3 layout-padding) (not= (:p3 layout-padding) 0))
[(:p3 layout-padding)]))
(defn- get-padding-inline-start
[{:keys [layout-padding]}]
(when (and (:p4 layout-padding) (not= (:p4 layout-padding) 0))
[(:p4 layout-padding)]))
(defn- get-grid-template-rows
[shape]
(:layout-grid-rows shape))
(defn- get-grid-template-columns
[shape]
(:layout-grid-columns shape))
(defn- get-grid-template-areas
[shape]
(when (and (ctl/grid-layout? shape)
(some area-cell? (vals (:layout-grid-cells shape))))
(let [result
(->> (d/enumerate (:layout-grid-rows shape))
(map
(fn [[row _]]
(dm/str
"\""
(->> (d/enumerate (:layout-grid-columns shape))
(map (fn [[column _]]
(let [cell (ctl/get-cell-by-position shape (inc row) (inc column))]
(str/replace (:area-name cell ".") " " "-"))))
(str/join " "))
"\"")))
(str/join "\n"))]
result)))
(defn- get-grid-column
[shape objects]
(get-grid-coord shape objects :column :column-span))
(defn- get-grid-row
[shape objects]
(get-grid-coord shape objects :row :row-span))
(defn- get-grid-area
[shape objects]
(when (and (ctl/grid-layout-immediate-child? objects shape)
(not (ctl/position-absolute? shape)))
(let [parent (get objects (:parent-id shape))
cell (ctl/get-cell-by-shape-id parent (:id shape))]
(when (and (= (:position cell) :area) (d/not-empty? (:area-name cell)))
(str/replace (:area-name cell) " " "-")))))
(defn- get-flex-shrink
[shape objects]
(when (and (ctl/flex-layout-immediate-child? objects shape)
(not (and (contains? #{:row :reverse-row} (:layout-flex-dir shape))
(= :fill (:layout-item-h-sizing shape))))
(not (and (contains? #{:column :column-row} (:layout-flex-dir shape))
(= :fill (:layout-item-v-sizing shape))))
;;(not= :fill (:layout-item-h-sizing shape))
;;(not= :fill (:layout-item-v-sizing shape))
(not= :auto (:layout-item-h-sizing shape))
(not= :auto (:layout-item-v-sizing shape)))
0))
(defn- get-margin
[{:keys [layout-item-margin] :as shape} objects]
(when (ctl/any-layout-immediate-child? objects shape)
(let [default-margin {:m1 0 :m2 0 :m3 0 :m4 0}
{:keys [m1 m2 m3 m4]} (merge default-margin layout-item-margin)]
(when (or (not= m1 0) (not= m2 0) (not= m3 0) (not= m4 0))
[m1 m2 m3 m4]))))
(defn- get-margin-block-start
[{:keys [layout-item-margin] :as shape} objects]
(when (and (ctl/any-layout-immediate-child? objects shape) (:m1 layout-item-margin) (not= (:m1 layout-item-margin) 0))
[(:m1 layout-item-margin)]))
(defn- get-margin-inline-end
[{:keys [layout-item-margin] :as shape} objects]
(when (and (ctl/any-layout-immediate-child? objects shape) (:m2 layout-item-margin) (not= (:m2 layout-item-margin) 0))
[(:m2 layout-item-margin)]))
(defn- get-margin-block-end
[{:keys [layout-item-margin] :as shape} objects]
(when (and (ctl/any-layout-immediate-child? objects shape) (:m3 layout-item-margin) (not= (:m3 layout-item-margin) 0))
[(:m3 layout-item-margin)]))
(defn- get-margin-inline-start
[{:keys [layout-item-margin] :as shape} objects]
(when (and (ctl/any-layout-immediate-child? objects shape) (:m4 layout-item-margin) (not= (:m4 layout-item-margin) 0))
[(:m4 layout-item-margin)]))
(defn- get-z-index
[{:keys [layout-item-z-index] :as shape} objects]
(cond
(cfh/root-frame? shape)
0
(ctl/any-layout-immediate-child? objects shape)
layout-item-z-index))
(defn- get-max-height
[shape objects]
(cond
(ctl/any-layout-immediate-child? objects shape)
(:layout-item-max-h shape)))
(defn- get-min-height
[shape objects]
(cond
(and (ctl/any-layout-immediate-child? objects shape) (some? (:layout-item-min-h shape)))
(:layout-item-min-h shape)
(and (ctl/auto-height? shape) (cfh/frame-shape? shape) (not (:show-content shape)))
(-> shape :selrect :height)))
(defn- get-max-width
[shape objects]
(cond
(ctl/any-layout-immediate-child? objects shape)
(:layout-item-max-w shape)))
(defn- get-min-width
[shape objects]
(cond
(and (ctl/any-layout-immediate-child? objects shape) (some? (:layout-item-min-w shape)))
(:layout-item-min-w shape)
(and (ctl/auto-width? shape) (cfh/frame-shape? shape) (not (:show-content shape)))
(-> shape :selrect :width)))
(defn- get-align-self
[shape objects]
(cond
(ctl/flex-layout-immediate-child? objects shape)
(:layout-item-align-self shape)
(ctl/grid-layout-immediate-child? objects shape)
(let [parent (get objects (:parent-id shape))
cell (ctl/get-cell-by-shape-id parent (:id shape))
align-self (:align-self cell)]
(when (not= align-self :auto) align-self))))
(defn- get-justify-self
[shape objects]
(cond
(ctl/grid-layout-immediate-child? objects shape)
(let [parent (get objects (:parent-id shape))
cell (ctl/get-cell-by-shape-id parent (:id shape))
justify-self (:justify-self cell)]
(when (not= justify-self :auto) justify-self))))
(defn- get-grid-auto-flow
[shape]
(when (and (ctl/grid-layout? shape) (= (:layout-grid-dir shape) :column))
"column"))
(defn get-value
"Get the value for a given CSS property from a shape"
[property shape objects options]
(case property
;; Positioning
:position (get-position shape objects)
:left (get-left-position shape objects)
:top (get-top-position shape objects)
:z-index (get-z-index shape objects)
:transform (get-transform shape objects)
;; Size
:width (get-width shape objects options)
:height (get-height shape objects options)
(:max-width :max-inline-size) (get-max-width shape objects)
(:min-width :min-inline-size) (get-min-width shape objects)
(:max-height :max-block-size) (get-max-height shape objects)
(:min-height :min-block-size) (get-min-height shape objects)
;; Spacing
:margin (get-margin shape objects)
:margin-block-start (get-margin-block-start shape objects)
:margin-inline-end (get-margin-inline-end shape objects)
:margin-block-end (get-margin-block-end shape objects)
:margin-inline-start (get-margin-inline-start shape objects)
:padding (get-padding shape)
:padding-block-start (get-padding-block-start shape)
:padding-inline-end (get-padding-inline-end shape)
:padding-block-end (get-padding-block-end shape)
:padding-inline-start (get-padding-inline-start shape)
;; Border & Background
:border (get-border shape)
:border-style (get-border-style shape)
:border-width (get-border-width shape)
:border-color (get-border-color shape)
:border-radius (get-border-radius shape)
:border-start-start-radius (get-border-start-start-radius shape)
:border-start-end-radius (get-border-start-end-radius shape)
:border-end-start-radius (get-border-end-start-radius shape)
:border-end-end-radius (get-border-end-end-radius shape)
:background (get-background shape)
;; Visual Effects
:opacity (get-opacity shape)
:box-shadow (get-box-shadow shape)
:filter (get-filter shape)
:backdrop-filter (get-backdrop-filter shape)
:overflow (get-overflow shape)
;; Display
:display (get-display shape)
;; Flexbox
:flex (get-flex shape objects)
:flex-grow (get-flex-grow shape options)
:flex-shrink (get-flex-shrink shape objects)
:flex-direction (get-flex-direction shape)
:flex-wrap (get-flex-wrap shape)
:align-items (get-align-items shape)
:align-content (get-align-content shape)
:align-self (get-align-self shape objects)
:justify-content (get-justify-content shape)
:justify-items (get-justify-items shape)
:justify-self (get-justify-self shape objects)
;; Grid
:grid-template-rows (get-grid-template-rows shape)
:grid-template-columns (get-grid-template-columns shape)
:grid-template-areas (get-grid-template-areas shape)
:grid-column (get-grid-column shape objects)
:grid-row (get-grid-row shape objects)
:grid-area (get-grid-area shape objects)
:grid-auto-flow (get-grid-auto-flow shape)
;; Spacing (Flex/Grid)
:gap (get-gap shape)
:row-gap (get-row-gap shape)
:column-gap (get-column-gap shape)
;; Default
(get shape property)))