0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 02:28:18 -05:00

Add min/max width/height for elements

This commit is contained in:
alonso.torres 2022-10-25 15:23:01 +02:00
parent b8c90fdcf3
commit 4b61e3228f
5 changed files with 203 additions and 129 deletions

View file

@ -6,11 +6,15 @@
(ns app.common.geom.shapes.flex-layout.lines
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.points :as gpo]
[app.common.geom.shapes.transforms :as gst]
[app.common.math :as mth]
[app.common.types.shape.layout :as ctl]))
(def conjv (fnil conj []))
(defn layout-bounds
[{:keys [layout-padding layout-padding-type] :as shape}]
(let [;; Add padding to the bounds
@ -38,53 +42,65 @@
layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
reduce-fn
(fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child]
calculate-line-data
(fn [[{:keys [line-min-width line-min-height
line-max-width line-max-height
num-children
children-data] :as line-data} result] child]
(let [child-bounds (gst/parent-coords-points child shape)
child-width (gpo/width-points child-bounds)
child-height (gpo/height-points child-bounds)
child-min-width (ctl/child-min-width child)
child-min-height (ctl/child-min-height child)
child-max-width (ctl/child-max-width child)
child-max-height (ctl/child-max-height child)
fill-width? (ctl/fill-width? child)
fill-height? (ctl/fill-height? child)
cur-child-fill? (or (and row? fill-width?) (and col? fill-height?))
cur-line-fill? (or (and col? fill-width?) (and row? fill-height?))
;; We need this info later to calculate the child resizes when fill
child-data {:id (:id child)
:child-min-width (if fill-width? child-min-width child-width)
:child-min-height (if fill-height? child-min-height child-height)
:child-max-width (if fill-width? child-max-width child-width)
:child-max-height (if fill-height? child-max-height child-height)}
next-width (if fill-width? child-min-width child-width)
next-height (if fill-height? child-min-height child-height)
next-line-width (+ line-width next-width (* layout-gap-row (dec num-children)))
next-line-height (+ line-height next-height (* layout-gap-col (dec num-children)))]
next-min-width (if fill-width? child-min-width child-width)
next-min-height (if fill-height? child-min-height child-height)
next-max-width (if fill-width? child-max-width child-width)
next-max-height (if fill-height? child-max-height child-height)
next-line-min-width (+ line-min-width next-min-width (* layout-gap-row num-children))
next-line-min-height (+ line-min-height next-min-height (* layout-gap-col num-children))]
(if (and (some? line-data)
(or (not wrap?)
(and row? (<= next-line-width layout-width))
(and col? (<= next-line-height layout-height))))
(and row? (<= next-line-min-width layout-width))
(and col? (<= next-line-min-height layout-height))))
[{:line-width (if row? (+ line-width next-width) (max line-width next-width))
:line-height (if col? (+ line-height next-height) (max line-height next-height))
[{:line-min-width (if row? (+ line-min-width next-min-width) (max line-min-width next-min-width))
:line-max-width (if row? (+ line-max-width next-max-width) (max line-max-width next-max-width))
:line-min-height (if col? (+ line-min-height next-min-height) (max line-min-height next-min-height))
:line-max-height (if col? (+ line-max-height next-max-height) (max line-max-height next-max-height))
:num-children (inc num-children)
:child-fill? (or cur-child-fill? child-fill?)
:line-fill? (or cur-line-fill? line-fill?)
:num-child-fill (cond-> num-child-fill cur-child-fill? inc)}
:children-data (conjv children-data child-data)}
result]
[{:line-width next-width
:line-height next-height
[{:line-min-width next-min-width
:line-min-height next-min-height
:line-max-width next-max-width
:line-max-height next-max-height
:num-children 1
:child-fill? cur-child-fill?
:line-fill? cur-line-fill?
:num-child-fill (if cur-child-fill? 1 0)}
:children-data [child-data]}
(cond-> result (some? line-data) (conj line-data))])))
[line-data layout-lines] (reduce reduce-fn [nil []] children)]
[line-data layout-lines] (reduce calculate-line-data [nil []] children)]
(cond-> layout-lines (some? line-data) (conj line-data))))
(defn get-base-line
[parent layout-bounds total-width total-height]
"Main axis line"
[parent layout-bounds total-width total-height num-lines]
(let [layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
@ -95,20 +111,25 @@
v-center? (ctl/v-center? parent)
v-end? (ctl/v-end? parent)
hv (partial gpo/start-hv layout-bounds)
vv (partial gpo/start-vv layout-bounds)]
vv (partial gpo/start-vv layout-bounds)
;; Adjust the totals so it takes into account the gaps
[layout-gap-row layout-gap-col] (ctl/gaps parent)
lines-gap-row (* (dec num-lines) layout-gap-row)
lines-gap-col (* (dec num-lines) layout-gap-col)]
(cond-> (gpo/origin layout-bounds)
(and col? h-center?)
(gpt/add (hv (/ (- layout-width total-width) 2)))
(and col? h-end?)
(gpt/add (hv (- layout-width total-width)))
(and row? v-center?)
(gpt/add (vv (/ (- layout-height total-height) 2)))
(gpt/add (vv (/ (- layout-height total-height lines-gap-col) 2)))
(and row? v-end?)
(gpt/add (vv (- layout-height total-height))))))
(gpt/add (vv (- layout-height total-height lines-gap-col)))
(and col? h-center?)
(gpt/add (hv (/ (- layout-width total-width lines-gap-row) 2)))
(and col? h-end?)
(gpt/add (hv (- layout-width total-width lines-gap-row))))))
(defn get-next-line
[parent layout-bounds {:keys [line-width line-height]} base-p]
@ -129,6 +150,7 @@
(gpt/add (vv (+ line-height layout-gap-col))))))
(defn get-start-line
"Cross axis line. It's position is fixed along the different lines"
[parent layout-bounds {:keys [line-width line-height num-children child-fill? ]} base-p]
(let [layout-width (gpo/width-points layout-bounds)
@ -150,14 +172,6 @@
children-gap-width (* layout-gap-row (dec num-children))
children-gap-height (* layout-gap-col (dec num-children))
line-width (if (and row? child-fill?)
(- layout-width (* layout-gap-row (dec num-children)))
line-width)
line-height (if (and col? child-fill?)
(- layout-height (* layout-gap-col (dec num-children)))
line-height)
start-p
(cond-> base-p
;; X AXIS
@ -192,20 +206,60 @@
start-p))
(defn add-space-to-items
;; Distributes the remainder space between the lines
[prop prop-min prop-max to-share items]
(let [num-items (->> items (remove #(mth/close? (get % prop) (get % prop-max))) count)
per-line-target (/ to-share num-items)]
(loop [current (first items)
items (rest items)
remainder to-share
result []]
(if (nil? current)
[result remainder]
(let [cur-val (or (get current prop) (get current prop-min) 0)
max-val (get current prop-max)
cur-inc (if (> (+ cur-val per-line-target) max-val)
(- max-val cur-val)
per-line-target)
current (assoc current prop (+ cur-val cur-inc))
remainder (- remainder cur-inc)
result (conj result current)]
(recur (first items) (rest items) remainder result))))))
(defn distribute-space
[prop prop-min prop-max min-value bound-value items]
(loop [to-share (- bound-value min-value)
items items]
(if (<= to-share 0)
items
(let [[items remainder] (add-space-to-items prop prop-min prop-max to-share items)]
(assert (<= remainder to-share) (str remainder ">" to-share))
(if (or (<= remainder 0) (= remainder to-share))
items
(recur remainder items))))))
(defn add-lines-positions
[parent layout-bounds layout-lines]
(let [layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
row? (ctl/row? parent)
col? (ctl/col? parent)]
layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)]
(letfn [(add-lines [[total-width total-height] {:keys [line-width line-height]}]
[(+ total-width line-width)
(+ total-height line-height)])
(letfn [(add-lines [[total-width total-height]
{:keys [line-width line-height]}]
[(+ total-width line-width) (+ total-height line-height)])
(add-ranges [[total-min-width total-min-height total-max-width total-max-height]
{:keys [line-min-width line-min-height line-max-width line-max-height]}]
[(+ total-min-width line-min-width)
(+ total-min-height line-min-height)
(+ total-max-width line-max-width)
(+ total-max-height line-max-height)])
(add-starts [[result base-p] layout-line]
(let [start-p (get-start-line parent layout-bounds layout-line base-p)
@ -215,29 +269,44 @@
(assoc layout-line :start-p start-p))
next-p]))]
(let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0]))
(let [[total-min-width total-min-height total-max-width total-max-height]
(->> layout-lines (reduce add-ranges [0 0 0 0]))
total-width (+ total-width (* layout-gap-row (dec (count layout-lines))))
total-height (+ total-height (* layout-gap-col (dec (count layout-lines))))
get-layout-width (fn [{:keys [num-children]}] (- layout-width (* layout-gap-row (dec num-children))))
get-layout-height (fn [{:keys [num-children]}] (- layout-height (* layout-gap-col (dec num-children))))
vertical-fill-space (- layout-height total-height)
horizontal-fill-space (- layout-width total-width)
num-line-fill (count (->> layout-lines (filter :line-fill?)))
num-lines (count layout-lines)
;; Distributes the space between the layout lines based on its max/min constraints
layout-lines
(->> layout-lines
(mapv #(cond-> %
(and row? (:line-fill? %))
(update :line-height + (/ vertical-fill-space num-line-fill))
(cond->> layout-lines
row?
(map #(assoc % :line-width (max (:line-min-width %) (min (get-layout-width %) (:line-max-width %)))))
(and col? (:line-fill? %))
(update :line-width + (/ horizontal-fill-space num-line-fill)))))
col?
(map #(assoc % :line-height (max (:line-min-height %) (min (get-layout-height %) (:line-max-height %)))))
total-width (if (and col? (> num-line-fill 0)) layout-width total-width)
total-height (if (and row? (> num-line-fill 0)) layout-height total-height)
(and row? (>= total-min-height layout-height))
(map #(assoc % :line-height (:line-min-height %)))
base-p (get-base-line parent layout-bounds total-width total-height)]
(and row? (<= total-max-height layout-height))
(map #(assoc % :line-height (:line-max-height %)))
(and row? (< total-min-height layout-height total-max-height))
(distribute-space :line-height :line-min-height :line-max-height total-min-height (- layout-height (* (dec num-lines) layout-gap-col)))
(and col? (>= total-min-width layout-width))
(map #(assoc % :line-width (:line-min-width %)))
(and col? (<= total-max-width layout-width))
(map #(assoc % :line-width (:line-max-width %)))
(and col? (< total-min-width layout-width total-max-width))
(distribute-space :line-width :line-min-width :line-max-width total-min-width (- layout-width (* (dec num-lines) layout-gap-row))))
[total-width total-height] (->> layout-lines (reduce add-lines [0 0]))
base-p (get-base-line parent layout-bounds total-width total-height num-lines)]
(first (reduce add-starts [[] base-p] layout-lines))))))
@ -291,6 +360,23 @@
:margin-x margin-x
:margin-y margin-y)))
(defn add-children-resizes
[shape {:keys [line-min-width line-width line-min-height line-height] :as line-data}]
(let [row? (ctl/row? shape)
col? (ctl/col? shape)]
(update line-data :children-data
(fn [children-data]
(cond->> children-data
row?
(distribute-space :child-width :child-min-width :child-max-width line-min-width line-width)
col?
(distribute-space :child-height :child-min-height :child-max-height line-min-height line-height)
:always
(d/index-by :id))))))
(defn calc-layout-data
"Digest the layout data to pass it to the constrains"
[shape children]
@ -303,7 +389,8 @@
layout-lines
(->> (init-layout-lines shape children layout-bounds)
(add-lines-positions shape layout-bounds)
(mapv (partial add-line-spacing shape layout-bounds)))]
(mapv (partial add-line-spacing shape layout-bounds))
(mapv (partial add-children-resizes shape)))]
{:layout-lines layout-lines
:reverse? reverse?}))

View file

@ -33,47 +33,40 @@
(defn calc-fill-width-data
"Calculates the size and modifiers for the width of an auto-fill child"
[{:keys [transform transform-inverse] :as parent}
{:keys [layout-h-behavior] :as child}
child
child-origin child-width
{:keys [num-children line-width line-fill? child-fill? layout-bounds] :as layout-data}]
{:keys [children-data line-width] :as layout-data}]
(let [[layout-gap-row _] (ctl/gaps parent)]
(cond
(and (ctl/row? parent) (= :fill layout-h-behavior) child-fill?)
(let [layout-width (gpo/width-points layout-bounds)
fill-space (- layout-width line-width (* layout-gap-row (dec num-children)))
fill-width (/ fill-space (:num-child-fill layout-data))
fill-scale (/ fill-width child-width)]
{:width fill-width
(and (ctl/row? parent) (ctl/fill-width? child))
(let [target-width (get-in children-data [(:id child) :child-width])
fill-scale (/ target-width child-width)]
{:width target-width
:modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)})
(and (ctl/col? parent) (= :fill layout-h-behavior) line-fill?)
(and (ctl/col? parent) (ctl/fill-width? child))
(let [fill-scale (/ line-width child-width)]
{:width line-width
:modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))))
:modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)})))
(defn calc-fill-height-data
"Calculates the size and modifiers for the height of an auto-fill child"
[{:keys [transform transform-inverse] :as parent}
{:keys [layout-v-behavior] :as child}
child
child-origin child-height
{:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}]
{:keys [children-data line-height] :as layout-data}]
(let [[_ layout-gap-col] (ctl/gaps parent)]
(cond
(and (ctl/col? parent) (= :fill layout-v-behavior) child-fill?)
(let [layout-height (gpo/height-points layout-bounds)
fill-space (- layout-height line-height (* layout-gap-col (dec num-children)))
fill-height (/ fill-space (:num-child-fill layout-data))
fill-scale (/ fill-height child-height)]
{:height fill-height
(and (ctl/col? parent) (ctl/fill-height? child))
(let [target-height (get-in children-data [(:id child) :child-height])
fill-scale (/ target-height child-height)]
{:height target-height
:modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)})
(and (ctl/row? parent) (= :fill layout-v-behavior) line-fill?)
(and (ctl/row? parent) (ctl/fill-height? child))
(let [fill-scale (/ line-height child-height)]
{:height line-height
:modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))))
:modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)})))
(defn calc-layout-modifiers
"Calculates the modifiers for the layout"

View file

@ -76,22 +76,17 @@
children (map (d/getf objects) (:shapes transformed-parent))
modif-tree (reduce (partial normalize-child transformed-parent _snap-pixel?) modif-tree children)
children (->> children (map (partial apply-modifiers modif-tree)))
layout-data (gcl/calc-layout-data transformed-parent children)
children (into [] (cond-> children (:reverse? layout-data) reverse))
max-idx (dec (count children))
layout-lines (:layout-lines layout-data)]
(loop [modif-tree modif-tree
layout-line (first layout-lines)
pending (rest layout-lines)
from-idx 0]
(if (and (some? layout-line) (<= from-idx max-idx))
(let [to-idx (+ from-idx (:num-children layout-line))
children (subvec children from-idx to-idx)

View file

@ -80,7 +80,7 @@
(s/def ::layout-margin-type #{:simple :multiple})
(s/def ::layout-h-behavior #{:fill :fix :auto})
(s/def ::layout-v-behavior #{:fill :fix :auto})
(s/def ::layout-align-self #{:start :end :center :strech :baseline})
(s/def ::layout-align-self #{:start :end :center :strech})
(s/def ::layout-max-h ::us/safe-number)
(s/def ::layout-min-h ::us/safe-number)
(s/def ::layout-max-w ::us/safe-number)
@ -126,30 +126,30 @@
(defn child-min-width
[child]
(if (and (fill-width? child)
(some? (:layout-min-h child)))
(max 0 (:layout-min-h child))
(some? (:layout-min-w child)))
(max 0 (:layout-min-w child))
0))
(defn child-max-width
[child]
(if (and (fill-width? child)
(some? (:layout-max-w child)))
(max 0 (:layout-max-w child))
##Inf))
(defn child-min-height
[child]
(if (and (fill-height? child)
(some? (:layout-min-h child)))
(max 0 (:layout-min-h child))
0))
(defn child-min-height
[child]
(if (and (fill-width? child)
(some? (:layout-min-v child)))
(max 0 (:layout-min-v child))
0))
(defn child-max-height
[child]
(if (and (fill-width? child)
(some? (:layout-min-v child)))
(max 0 (:layout-min-v child))
0))
(if (and (fill-height? child)
(some? (:layout-max-h child)))
(max 0 (:layout-max-h child))
##Inf))
(defn h-start?
[{:keys [layout-align-items layout-justify-content] :as shape}]

View file

@ -218,6 +218,5 @@
:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-size-change item)
;; :value (get values item)
:value 100}]]])]])]]
))
:value (get values item)
:nillable true}]]])]])]]))