mirror of
https://github.com/penpot/penpot.git
synced 2025-07-13 17:17:17 +02:00
✨ Create grid from selection
This commit is contained in:
parent
02399add7a
commit
351f7fd1bb
17 changed files with 395 additions and 234 deletions
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.geom.shapes.modifiers
|
||||
(ns app.common.geom.modifiers
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
|
@ -15,7 +15,6 @@
|
|||
[app.common.geom.shapes.constraints :as gct]
|
||||
[app.common.geom.shapes.corners :as gsc]
|
||||
[app.common.geom.shapes.intersect :as gsi]
|
||||
[app.common.geom.shapes.modifiers :as gsm]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.geom.shapes.transforms :as gtr]
|
||||
[app.common.math :as mth]))
|
||||
|
@ -203,8 +202,5 @@
|
|||
(dm/export gsc/shape-corners-1)
|
||||
(dm/export gsc/shape-corners-4)
|
||||
|
||||
;; Modifiers
|
||||
(dm/export gsm/set-objects-modifiers)
|
||||
|
||||
;; Rect
|
||||
(dm/export grc/rect->points)
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
[app.common.geom.shapes.flex-layout.bounds :as fbo]
|
||||
[app.common.geom.shapes.flex-layout.drop-area :as fdr]
|
||||
[app.common.geom.shapes.flex-layout.layout-data :as fld]
|
||||
[app.common.geom.shapes.flex-layout.modifiers :as fmo]))
|
||||
[app.common.geom.shapes.flex-layout.modifiers :as fmo]
|
||||
[app.common.geom.shapes.flex-layout.params :as fp]))
|
||||
|
||||
(dm/export fbo/layout-content-bounds)
|
||||
(dm/export fbo/layout-content-points)
|
||||
|
@ -19,3 +20,4 @@
|
|||
(dm/export fdr/get-drop-areas)
|
||||
(dm/export fld/calc-layout-data)
|
||||
(dm/export fmo/layout-child-modifiers)
|
||||
(dm/export fp/calculate-params)
|
||||
|
|
97
common/src/app/common/geom/shapes/flex_layout/params.cljc
Normal file
97
common/src/app/common/geom/shapes/flex_layout/params.cljc
Normal file
|
@ -0,0 +1,97 @@
|
|||
;; 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.common.geom.shapes.flex-layout.params
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.shape-tree :as ctt]))
|
||||
|
||||
(defn calculate-params
|
||||
"Given the shapes calculate its flex parameters (horizontal vs vertical, gaps, etc)"
|
||||
([objects shapes]
|
||||
(calculate-params objects shapes nil))
|
||||
|
||||
([objects shapes parent]
|
||||
(when (d/not-empty? shapes)
|
||||
(let [points
|
||||
(->> shapes
|
||||
(map :id)
|
||||
(ctt/sort-z-index objects)
|
||||
(map (comp gco/shape->center (d/getf objects))))
|
||||
|
||||
start (first points)
|
||||
end (reduce (fn [acc p] (gpt/add acc (gpt/to-vec start p))) points)
|
||||
|
||||
angle (gpt/signed-angle-with-other
|
||||
(gpt/to-vec start end)
|
||||
(gpt/point 1 0))
|
||||
|
||||
angle (mod angle 360)
|
||||
|
||||
t1 (min (abs (- angle 0)) (abs (- angle 360)))
|
||||
t2 (abs (- angle 90))
|
||||
t3 (abs (- angle 180))
|
||||
t4 (abs (- angle 270))
|
||||
|
||||
tmin (min t1 t2 t3 t4)
|
||||
|
||||
direction
|
||||
(cond
|
||||
(mth/close? tmin t1) :row
|
||||
(mth/close? tmin t2) :column-reverse
|
||||
(mth/close? tmin t3) :row-reverse
|
||||
(mth/close? tmin t4) :column)
|
||||
|
||||
selrects (->> shapes
|
||||
(mapv :selrect))
|
||||
min-x (->> selrects
|
||||
(mapv #(min (:x1 %) (:x2 %)))
|
||||
(apply min))
|
||||
max-x (->> selrects
|
||||
(mapv #(max (:x1 %) (:x2 %)))
|
||||
(apply max))
|
||||
all-width (->> selrects
|
||||
(map :width)
|
||||
(reduce +))
|
||||
column-gap (if (and (> (count shapes) 1)
|
||||
(or (= direction :row) (= direction :row-reverse)))
|
||||
(/ (- (- max-x min-x) all-width)
|
||||
(dec (count shapes)))
|
||||
0)
|
||||
|
||||
min-y (->> selrects
|
||||
(mapv #(min (:y1 %) (:y2 %)))
|
||||
(apply min))
|
||||
max-y (->> selrects
|
||||
(mapv #(max (:y1 %) (:y2 %)))
|
||||
(apply max))
|
||||
all-height (->> selrects
|
||||
(map :height)
|
||||
(reduce +))
|
||||
row-gap (if (and (> (count shapes) 1)
|
||||
(or (= direction :column) (= direction :column-reverse)))
|
||||
(/ (- (- max-y min-y) all-height)
|
||||
(dec (count shapes)))
|
||||
0)
|
||||
|
||||
layout-gap {:row-gap (max row-gap 0)
|
||||
:column-gap (max column-gap 0)}
|
||||
|
||||
parent-selrect (:selrect parent)
|
||||
|
||||
padding (when (and (not (nil? parent)) (> (count shapes) 0))
|
||||
{:p1 (min (- min-y (:y1 parent-selrect)) (- (:y2 parent-selrect) max-y))
|
||||
:p2 (min (- min-x (:x1 parent-selrect)) (- (:x2 parent-selrect) max-x))})]
|
||||
|
||||
(cond-> {:layout-flex-dir direction :layout-gap layout-gap}
|
||||
(not (nil? padding))
|
||||
(assoc :layout-padding {:p1 (:p1 padding)
|
||||
:p2 (:p2 padding)
|
||||
:p3 (:p1 padding)
|
||||
:p4 (:p2 padding)}))))))
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes.grid-layout.bounds :as glpb]
|
||||
[app.common.geom.shapes.grid-layout.layout-data :as glld]
|
||||
[app.common.geom.shapes.grid-layout.params :as glpr]
|
||||
[app.common.geom.shapes.grid-layout.positions :as glp]))
|
||||
|
||||
(dm/export glld/calc-layout-data)
|
||||
|
@ -19,3 +20,4 @@
|
|||
(dm/export glp/cell-bounds)
|
||||
(dm/export glpb/layout-content-points)
|
||||
(dm/export glpb/layout-content-bounds)
|
||||
(dm/export glpr/calculate-params)
|
||||
|
|
|
@ -590,7 +590,7 @@
|
|||
[{:keys [origin row-tracks column-tracks shape-cells]} _transformed-parent-bounds [_ child]]
|
||||
|
||||
(let [grid-cell (get shape-cells (:id child))]
|
||||
(when (some? grid-cell)
|
||||
(when (and (some? grid-cell) (d/not-empty? grid-cell))
|
||||
(let [column (nth column-tracks (dec (:column grid-cell)) nil)
|
||||
row (nth row-tracks (dec (:row grid-cell)) nil)
|
||||
|
||||
|
|
166
common/src/app/common/geom/shapes/grid_layout/params.cljc
Normal file
166
common/src/app/common/geom/shapes/grid_layout/params.cljc
Normal file
|
@ -0,0 +1,166 @@
|
|||
;; 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.common.geom.shapes.grid-layout.params
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.geom.shapes.points :as gpo]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[clojure.set :as set]))
|
||||
|
||||
;; Small functions to help with ranges
|
||||
(defn rect->range
|
||||
"Creates ranges"
|
||||
[axis rect]
|
||||
(let [start (get (gpt/point rect) axis)]
|
||||
(if (= axis :x)
|
||||
[start (+ start (:width rect))]
|
||||
[start (+ start (:height rect))])))
|
||||
|
||||
(defn overlaps-range?
|
||||
"Return true if the ranges overlaps in the given axis"
|
||||
[axis [start-a end-a] rect]
|
||||
(let [[start-b end-b] (rect->range axis rect)]
|
||||
(or (< start-a start-b end-a)
|
||||
(< start-b start-a end-b)
|
||||
(mth/close? start-a start-b)
|
||||
(mth/close? end-a end-b))))
|
||||
|
||||
(defn join-range
|
||||
"Creates a new range given the rect"
|
||||
[axis [start-a end-a :as range] rect]
|
||||
(if (not range)
|
||||
(rect->range axis rect)
|
||||
(let [[start-b end-b] (rect->range axis rect)]
|
||||
[(min start-a start-b) (max end-a end-b)])))
|
||||
|
||||
(defn size-range
|
||||
[[start end]]
|
||||
(- end start))
|
||||
|
||||
(defn calculate-tracks
|
||||
"Given the geometry and the axis calculates the tracks for the given shapes"
|
||||
[axis shapes-by-axis]
|
||||
(loop [pending (seq shapes-by-axis)
|
||||
result []
|
||||
index 1
|
||||
current-track #{}
|
||||
current-range nil]
|
||||
(if pending
|
||||
(let [[next-shape rect :as next-shape+rects] (first pending)]
|
||||
|
||||
(if (or (not current-range) (overlaps-range? axis current-range rect))
|
||||
;; Add shape to current row
|
||||
(let [current-track (conj current-track (:id next-shape))
|
||||
current-range (join-range axis current-range rect)]
|
||||
(recur (next pending) result index current-track current-range))
|
||||
|
||||
;; New row
|
||||
(recur (next pending)
|
||||
(conj result {:index index
|
||||
:shapes current-track
|
||||
:size (size-range current-range)})
|
||||
(inc index)
|
||||
#{(:id next-shape)}
|
||||
(rect->range axis rect))))
|
||||
|
||||
;; Add the still ongoing row
|
||||
(conj result {:index index
|
||||
:shapes current-track
|
||||
:size (size-range current-range)}))))
|
||||
|
||||
(defn assign-shape-cells
|
||||
"Create cells for the defined tracks and assign the shapes to these cells"
|
||||
[params rows cols]
|
||||
|
||||
(let [assign-cell
|
||||
(fn [[params auto?] row column]
|
||||
(let [row-num (:index row)
|
||||
column-num (:index column)
|
||||
cell (ctl/cell-by-row-column params row-num column-num)
|
||||
shape (first (set/intersection (:shapes row) (:shapes column)))
|
||||
auto? (and auto? (some? shape))]
|
||||
|
||||
[(cond-> params
|
||||
(some? shape)
|
||||
(assoc-in [:layout-grid-cells (:id cell) :shapes] [shape])
|
||||
|
||||
(not auto?)
|
||||
(assoc-in [:layout-grid-cells (:id cell) :position] :manual))
|
||||
auto?]))
|
||||
|
||||
[params _]
|
||||
(->> rows
|
||||
(reduce
|
||||
(fn [result row]
|
||||
(->> cols
|
||||
(reduce
|
||||
#(assign-cell %1 row %2)
|
||||
result)))
|
||||
[params true]))]
|
||||
params))
|
||||
|
||||
(defn calculate-params
|
||||
"Given the shapes calculate its grid parameters (horizontal vs vertical, gaps, etc)"
|
||||
([objects shapes]
|
||||
(calculate-params objects shapes nil))
|
||||
|
||||
([_objects shapes parent]
|
||||
(if (empty? shapes)
|
||||
(-> {:layout-grid-columns [{:type :auto} {:type :auto}]
|
||||
:layout-grid-rows [{:type :auto} {:type :auto}]}
|
||||
(ctl/create-cells [1 1 2 2]))
|
||||
|
||||
(let [all-shapes-rect (gco/shapes->rect shapes)
|
||||
shapes+bounds
|
||||
(->> shapes
|
||||
(map #(vector % (grc/points->rect (get % :points)))))
|
||||
|
||||
shapes-by-x
|
||||
(->> shapes+bounds
|
||||
(sort-by (comp :x second)))
|
||||
|
||||
shapes-by-y
|
||||
(->> shapes+bounds
|
||||
(sort-by (comp :y second)))
|
||||
|
||||
cols (calculate-tracks :x shapes-by-x)
|
||||
rows (calculate-tracks :y shapes-by-y)
|
||||
|
||||
num-cols (count cols)
|
||||
num-rows (count rows)
|
||||
|
||||
total-cols-width (->> cols (reduce #(+ %1 (:size %2)) 0))
|
||||
total-rows-height (->> rows (reduce #(+ %1 (:size %2)) 0))
|
||||
|
||||
column-gap
|
||||
(if (= num-cols 1)
|
||||
0
|
||||
(/ (- (:width all-shapes-rect) total-cols-width) (dec num-cols)))
|
||||
|
||||
row-gap
|
||||
(if (= num-rows 1)
|
||||
0
|
||||
(/ (- (:height all-shapes-rect) total-rows-height) (dec num-rows)))
|
||||
|
||||
layout-grid-rows (mapv (constantly (array-map :type :auto)) rows)
|
||||
layout-grid-columns (mapv (constantly (array-map :type :auto)) cols)
|
||||
|
||||
parent-childs-vector (gpt/to-vec (gpo/origin (:points parent)) (gpt/point all-shapes-rect))
|
||||
p-left (:x parent-childs-vector)
|
||||
p-top (:y parent-childs-vector)]
|
||||
|
||||
(-> {:layout-grid-columns layout-grid-columns
|
||||
:layout-grid-rows layout-grid-rows
|
||||
:layout-gap {:row-gap row-gap
|
||||
:column-gap column-gap}
|
||||
:layout-padding {:p1 p-top :p2 p-left :p3 p-top :p4 p-left}
|
||||
:layout-grid-dir (if (> num-cols num-rows) :row :column)}
|
||||
(ctl/create-cells [1 1 num-cols num-rows])
|
||||
(assign-shape-cells rows cols))))))
|
|
@ -373,7 +373,7 @@
|
|||
|
||||
(cond-> objects
|
||||
@changed?
|
||||
(assoc-in [parent-id :shapes] new-shapes))))
|
||||
(d/assoc-in-when [parent-id :shapes] new-shapes))))
|
||||
|
||||
check-modify-component
|
||||
(fn [data]
|
||||
|
|
|
@ -227,8 +227,7 @@
|
|||
|
||||
([objects ids {:keys [bottom-frames?] :as options
|
||||
:or {bottom-frames? false}}]
|
||||
(letfn [
|
||||
(comp [id-a id-b]
|
||||
(letfn [(comp [id-a id-b]
|
||||
(cond
|
||||
(= id-a id-b)
|
||||
0
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue