mirror of
https://github.com/penpot/penpot.git
synced 2025-05-06 09:55:53 +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
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
(ns app.common.geom.shapes.modifiers
|
(ns app.common.geom.modifiers
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
|
@ -15,7 +15,6 @@
|
||||||
[app.common.geom.shapes.constraints :as gct]
|
[app.common.geom.shapes.constraints :as gct]
|
||||||
[app.common.geom.shapes.corners :as gsc]
|
[app.common.geom.shapes.corners :as gsc]
|
||||||
[app.common.geom.shapes.intersect :as gsi]
|
[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.path :as gsp]
|
||||||
[app.common.geom.shapes.transforms :as gtr]
|
[app.common.geom.shapes.transforms :as gtr]
|
||||||
[app.common.math :as mth]))
|
[app.common.math :as mth]))
|
||||||
|
@ -203,8 +202,5 @@
|
||||||
(dm/export gsc/shape-corners-1)
|
(dm/export gsc/shape-corners-1)
|
||||||
(dm/export gsc/shape-corners-4)
|
(dm/export gsc/shape-corners-4)
|
||||||
|
|
||||||
;; Modifiers
|
|
||||||
(dm/export gsm/set-objects-modifiers)
|
|
||||||
|
|
||||||
;; Rect
|
;; Rect
|
||||||
(dm/export grc/rect->points)
|
(dm/export grc/rect->points)
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
[app.common.geom.shapes.flex-layout.bounds :as fbo]
|
[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.drop-area :as fdr]
|
||||||
[app.common.geom.shapes.flex-layout.layout-data :as fld]
|
[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-bounds)
|
||||||
(dm/export fbo/layout-content-points)
|
(dm/export fbo/layout-content-points)
|
||||||
|
@ -19,3 +20,4 @@
|
||||||
(dm/export fdr/get-drop-areas)
|
(dm/export fdr/get-drop-areas)
|
||||||
(dm/export fld/calc-layout-data)
|
(dm/export fld/calc-layout-data)
|
||||||
(dm/export fmo/layout-child-modifiers)
|
(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.data.macros :as dm]
|
||||||
[app.common.geom.shapes.grid-layout.bounds :as glpb]
|
[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.layout-data :as glld]
|
||||||
|
[app.common.geom.shapes.grid-layout.params :as glpr]
|
||||||
[app.common.geom.shapes.grid-layout.positions :as glp]))
|
[app.common.geom.shapes.grid-layout.positions :as glp]))
|
||||||
|
|
||||||
(dm/export glld/calc-layout-data)
|
(dm/export glld/calc-layout-data)
|
||||||
|
@ -19,3 +20,4 @@
|
||||||
(dm/export glp/cell-bounds)
|
(dm/export glp/cell-bounds)
|
||||||
(dm/export glpb/layout-content-points)
|
(dm/export glpb/layout-content-points)
|
||||||
(dm/export glpb/layout-content-bounds)
|
(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]]
|
[{:keys [origin row-tracks column-tracks shape-cells]} _transformed-parent-bounds [_ child]]
|
||||||
|
|
||||||
(let [grid-cell (get shape-cells (:id 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)
|
(let [column (nth column-tracks (dec (:column grid-cell)) nil)
|
||||||
row (nth row-tracks (dec (:row 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
|
(cond-> objects
|
||||||
@changed?
|
@changed?
|
||||||
(assoc-in [parent-id :shapes] new-shapes))))
|
(d/assoc-in-when [parent-id :shapes] new-shapes))))
|
||||||
|
|
||||||
check-modify-component
|
check-modify-component
|
||||||
(fn [data]
|
(fn [data]
|
||||||
|
|
|
@ -227,8 +227,7 @@
|
||||||
|
|
||||||
([objects ids {:keys [bottom-frames?] :as options
|
([objects ids {:keys [bottom-frames?] :as options
|
||||||
:or {bottom-frames? false}}]
|
:or {bottom-frames? false}}]
|
||||||
(letfn [
|
(letfn [(comp [id-a id-b]
|
||||||
(comp [id-a id-b]
|
|
||||||
(cond
|
(cond
|
||||||
(= id-a id-b)
|
(= id-a id-b)
|
||||||
0
|
0
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.geom.modifiers :as gm]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.rect :as grc]
|
[app.common.geom.rect :as grc]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
@ -328,7 +329,7 @@
|
||||||
(as-> objects $
|
(as-> objects $
|
||||||
(apply-text-modifiers $ (get state :workspace-text-modifier))
|
(apply-text-modifiers $ (get state :workspace-text-modifier))
|
||||||
;;(apply-path-modifiers $ (get-in state [:workspace-local :edit-path]))
|
;;(apply-path-modifiers $ (get-in state [:workspace-local :edit-path]))
|
||||||
(gsh/set-objects-modifiers modif-tree $ (merge
|
(gm/set-objects-modifiers modif-tree $ (merge
|
||||||
params
|
params
|
||||||
{:ignore-constraints ignore-constraints
|
{:ignore-constraints ignore-constraints
|
||||||
:snap-pixel? snap-pixel?
|
:snap-pixel? snap-pixel?
|
||||||
|
@ -348,7 +349,14 @@
|
||||||
objects
|
objects
|
||||||
(-> objects
|
(-> objects
|
||||||
(apply-text-modifiers (get state :workspace-text-modifier)))]
|
(apply-text-modifiers (get state :workspace-text-modifier)))]
|
||||||
(gsh/set-objects-modifiers old-modif-tree modif-tree objects {:ignore-constraints ignore-constraints :snap-pixel? snap-pixel? :snap-precision snap-precision})))
|
|
||||||
|
(gm/set-objects-modifiers
|
||||||
|
old-modif-tree
|
||||||
|
modif-tree
|
||||||
|
objects
|
||||||
|
{:ignore-constraints ignore-constraints
|
||||||
|
:snap-pixel? snap-pixel?
|
||||||
|
:snap-precision snap-precision})))
|
||||||
|
|
||||||
(defn update-modifiers
|
(defn update-modifiers
|
||||||
([modif-tree]
|
([modif-tree]
|
||||||
|
@ -405,7 +413,7 @@
|
||||||
|
|
||||||
modif-tree
|
modif-tree
|
||||||
(-> (build-modif-tree ids objects get-modifier)
|
(-> (build-modif-tree ids objects get-modifier)
|
||||||
(gsh/set-objects-modifiers objects))]
|
(gm/set-objects-modifiers objects))]
|
||||||
|
|
||||||
(assoc state :workspace-modifiers modif-tree))))))
|
(assoc state :workspace-modifiers modif-tree))))))
|
||||||
|
|
||||||
|
@ -432,7 +440,7 @@
|
||||||
|
|
||||||
modif-tree
|
modif-tree
|
||||||
(-> (build-modif-tree ids objects get-modifier)
|
(-> (build-modif-tree ids objects get-modifier)
|
||||||
(gsh/set-objects-modifiers objects))]
|
(gm/set-objects-modifiers objects))]
|
||||||
|
|
||||||
(assoc state :workspace-modifiers modif-tree))))))
|
(assoc state :workspace-modifiers modif-tree))))))
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,11 @@
|
||||||
[app.common.colors :as clr]
|
[app.common.colors :as clr]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.shapes.flex-layout :as flex]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes.grid-layout :as grid]
|
||||||
[app.common.math :as mth]
|
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.types.component :as ctc]
|
[app.common.types.component :as ctc]
|
||||||
[app.common.types.modifiers :as ctm]
|
[app.common.types.modifiers :as ctm]
|
||||||
[app.common.types.shape-tree :as ctt]
|
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.workspace.changes :as dwc]
|
[app.main.data.workspace.changes :as dwc]
|
||||||
|
@ -70,140 +68,27 @@
|
||||||
:layout-grid-columns []})
|
:layout-grid-columns []})
|
||||||
|
|
||||||
(defn get-layout-initializer
|
(defn get-layout-initializer
|
||||||
[type from-frame?]
|
[type from-frame? objects]
|
||||||
(let [initial-layout-data
|
(let [[initial-layout-data calculate-params]
|
||||||
(case type
|
(case type
|
||||||
:flex initial-flex-layout
|
:flex [initial-flex-layout flex/calculate-params]
|
||||||
:grid initial-grid-layout)]
|
:grid [initial-grid-layout grid/calculate-params])]
|
||||||
|
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
|
(let [shape
|
||||||
(-> shape
|
(-> shape
|
||||||
(merge initial-layout-data)
|
(merge initial-layout-data)
|
||||||
(cond-> (= type :grid) ctl/assign-cells)
|
|
||||||
|
;; (cond-> (= type :grid) (-> ctl/assign-cells ctl/reorder-grid-children))
|
||||||
;; If the original shape is not a frame we set clip content and show-viewer to false
|
;; If the original shape is not a frame we set clip content and show-viewer to false
|
||||||
(cond-> (not from-frame?)
|
(cond-> (not from-frame?)
|
||||||
(assoc :show-content true :hide-in-viewer true))))))
|
(assoc :show-content true :hide-in-viewer true)))
|
||||||
|
|
||||||
|
params (calculate-params objects (cph/get-immediate-children objects (:id shape)) shape)]
|
||||||
|
|
||||||
(defn shapes->flex-params
|
(cond-> (merge shape params)
|
||||||
"Given the shapes calculate its flex parameters (horizontal vs vertical, gaps, etc)"
|
(= type :grid) (-> ctl/assign-cells ctl/reorder-grid-children)))
|
||||||
([objects shapes]
|
)))
|
||||||
(shapes->flex-params objects shapes nil))
|
|
||||||
([objects shapes parent]
|
|
||||||
(when (d/not-empty? shapes)
|
|
||||||
(let [points
|
|
||||||
(->> shapes
|
|
||||||
(map :id)
|
|
||||||
(ctt/sort-z-index objects)
|
|
||||||
(map (comp gsh/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)}))))))
|
|
||||||
|
|
||||||
(defn shapes->grid-params
|
|
||||||
"Given the shapes calculate its flex parameters (horizontal vs vertical, gaps, etc)"
|
|
||||||
([objects shapes]
|
|
||||||
(shapes->flex-params objects shapes nil))
|
|
||||||
([_objects shapes _parent]
|
|
||||||
(if (empty? shapes)
|
|
||||||
(ctl/create-cells
|
|
||||||
{:layout-grid-columns [{:type :auto} {:type :auto}]
|
|
||||||
:layout-grid-rows [{:type :auto} {:type :auto}]}
|
|
||||||
[1 1 2 2])
|
|
||||||
{})))
|
|
||||||
|
|
||||||
(defn create-layout-from-id
|
|
||||||
[ids type from-frame?]
|
|
||||||
(ptk/reify ::create-layout-from-id
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state _]
|
|
||||||
(let [objects (wsh/lookup-page-objects state)
|
|
||||||
children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids)
|
|
||||||
children-shapes (map (d/getf objects) children-ids)
|
|
||||||
parent (get objects (first ids))
|
|
||||||
layout-params (case type
|
|
||||||
:flex (shapes->flex-params objects children-shapes parent)
|
|
||||||
:grid (shapes->grid-params objects children-shapes parent))
|
|
||||||
undo-id (js/Symbol)]
|
|
||||||
(rx/of (dwu/start-undo-transaction undo-id)
|
|
||||||
(dwc/update-shapes ids (get-layout-initializer type from-frame?))
|
|
||||||
(dwc/update-shapes
|
|
||||||
ids
|
|
||||||
(fn [shape]
|
|
||||||
(-> shape
|
|
||||||
(cond-> (not from-frame?)
|
|
||||||
(assoc :layout-item-h-sizing :auto
|
|
||||||
:layout-item-v-sizing :auto))
|
|
||||||
(merge layout-params)
|
|
||||||
(cond-> (= type :grid)
|
|
||||||
(-> (ctl/assign-cells)
|
|
||||||
(ctl/reorder-grid-children))))))
|
|
||||||
(ptk/data-event :layout/update ids)
|
|
||||||
(dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v))
|
|
||||||
(dwu/commit-undo-transaction undo-id))))))
|
|
||||||
|
|
||||||
|
|
||||||
;; Never call this directly but through the data-event `:layout/update`
|
;; Never call this directly but through the data-event `:layout/update`
|
||||||
;; Otherwise a lot of cycle dependencies could be generated
|
;; Otherwise a lot of cycle dependencies could be generated
|
||||||
|
@ -236,6 +121,22 @@
|
||||||
[]
|
[]
|
||||||
(ptk/reify ::finalize))
|
(ptk/reify ::finalize))
|
||||||
|
|
||||||
|
(defn create-layout-from-id
|
||||||
|
[id type from-frame?]
|
||||||
|
(assert (uuid? id) (str id))
|
||||||
|
(ptk/reify ::create-layout-from-id
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [objects (wsh/lookup-page-objects state)
|
||||||
|
parent (get objects id)
|
||||||
|
undo-id (js/Symbol)
|
||||||
|
layout-initializer (get-layout-initializer type from-frame? objects)]
|
||||||
|
|
||||||
|
(rx/of (dwu/start-undo-transaction undo-id)
|
||||||
|
(dwc/update-shapes [id] layout-initializer)
|
||||||
|
(dwc/update-shapes (dm/get-prop parent :shapes) #(dissoc % :constraints-h :constraints-v))
|
||||||
|
(ptk/data-event :layout/update [id])
|
||||||
|
(dwu/commit-undo-transaction undo-id))))))
|
||||||
|
|
||||||
(defn create-layout-from-selection
|
(defn create-layout-from-selection
|
||||||
[type]
|
[type]
|
||||||
|
@ -253,66 +154,41 @@
|
||||||
has-mask? (->> selected-shapes (d/seek cph/mask-shape?))
|
has-mask? (->> selected-shapes (d/seek cph/mask-shape?))
|
||||||
is-mask? (and single? has-mask?)
|
is-mask? (and single? has-mask?)
|
||||||
has-component? (some true? (map ctc/instance-root? selected-shapes))
|
has-component? (some true? (map ctc/instance-root? selected-shapes))
|
||||||
is-component? (and single? has-component?)]
|
is-component? (and single? has-component?)
|
||||||
|
|
||||||
(if (and (not is-component?) is-group? (not is-mask?))
|
new-shape-id (uuid/next)
|
||||||
(let [new-shape-id (uuid/next)
|
undo-id (js/Symbol)]
|
||||||
parent-id (:parent-id (first selected-shapes))
|
|
||||||
|
(rx/concat
|
||||||
|
(rx/of (dwu/start-undo-transaction undo-id))
|
||||||
|
(if (and is-group? (not is-component?) (not is-mask?))
|
||||||
|
;; Create layout from a group:
|
||||||
|
;; When creating a layout from a group we remove the group and create the layout with its children
|
||||||
|
(let [parent-id (:parent-id (first selected-shapes))
|
||||||
shapes-ids (:shapes (first selected-shapes))
|
shapes-ids (:shapes (first selected-shapes))
|
||||||
ordered-ids (into (d/ordered-set) shapes-ids)
|
ordered-ids (into (d/ordered-set) shapes-ids)
|
||||||
undo-id (js/Symbol)
|
|
||||||
group-index (cph/get-index-replacement selected objects)]
|
group-index (cph/get-index-replacement selected objects)]
|
||||||
(rx/of
|
(rx/of
|
||||||
(dwu/start-undo-transaction undo-id)
|
|
||||||
(dwse/select-shapes ordered-ids)
|
(dwse/select-shapes ordered-ids)
|
||||||
(dws/create-artboard-from-selection new-shape-id parent-id group-index)
|
(dws/create-artboard-from-selection new-shape-id parent-id group-index)
|
||||||
(cl/remove-all-fills [new-shape-id] {:color clr/black
|
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
|
||||||
:opacity 1})
|
(create-layout-from-id new-shape-id type false)
|
||||||
(create-layout-from-id [new-shape-id] type false)
|
(dwc/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
|
||||||
(dwc/update-shapes
|
(dwc/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))
|
||||||
[new-shape-id]
|
|
||||||
(fn [shape]
|
|
||||||
(-> shape
|
|
||||||
(assoc :layout-item-h-sizing :auto
|
|
||||||
:layout-item-v-sizing :auto))))
|
|
||||||
;; Set the children to fixed to remove strange interactions
|
|
||||||
(dwc/update-shapes
|
|
||||||
selected
|
|
||||||
(fn [shape]
|
|
||||||
(-> shape
|
|
||||||
(assoc :layout-item-h-sizing :fix
|
|
||||||
:layout-item-v-sizing :fix))))
|
|
||||||
|
|
||||||
(ptk/data-event :layout/update [new-shape-id])
|
|
||||||
(dws/delete-shapes page-id selected)
|
(dws/delete-shapes page-id selected)
|
||||||
|
(ptk/data-event :layout/update [new-shape-id])
|
||||||
(dwu/commit-undo-transaction undo-id)))
|
(dwu/commit-undo-transaction undo-id)))
|
||||||
|
|
||||||
(let [new-shape-id (uuid/next)
|
;; Create Layout from selection
|
||||||
undo-id (js/Symbol)
|
|
||||||
flex-params (shapes->flex-params objects selected-shapes)]
|
|
||||||
(rx/of
|
(rx/of
|
||||||
(dwu/start-undo-transaction undo-id)
|
|
||||||
(dws/create-artboard-from-selection new-shape-id)
|
(dws/create-artboard-from-selection new-shape-id)
|
||||||
(cl/remove-all-fills [new-shape-id] {:color clr/black
|
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
|
||||||
:opacity 1})
|
(create-layout-from-id new-shape-id type false)
|
||||||
(create-layout-from-id [new-shape-id] type false)
|
(dwc/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
|
||||||
(dwc/update-shapes
|
(dwc/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))))
|
||||||
[new-shape-id]
|
|
||||||
(fn [shape]
|
|
||||||
(-> shape
|
|
||||||
(merge flex-params)
|
|
||||||
(assoc :layout-item-h-sizing :auto
|
|
||||||
:layout-item-v-sizing :auto))))
|
|
||||||
;; Set the children to fixed to remove strange interactions
|
|
||||||
(dwc/update-shapes
|
|
||||||
selected
|
|
||||||
(fn [shape]
|
|
||||||
(-> shape
|
|
||||||
(assoc :layout-item-h-sizing :fix
|
|
||||||
:layout-item-v-sizing :fix))))
|
|
||||||
|
|
||||||
(ptk/data-event :layout/update [new-shape-id])
|
(rx/of (ptk/data-event :layout/update [new-shape-id])
|
||||||
(dwu/commit-undo-transaction undo-id))))))))
|
(dwu/commit-undo-transaction undo-id)))))))
|
||||||
|
|
||||||
(defn remove-layout
|
(defn remove-layout
|
||||||
[ids]
|
[ids]
|
||||||
|
@ -342,7 +218,7 @@
|
||||||
(rx/of
|
(rx/of
|
||||||
(dwu/start-undo-transaction undo-id)
|
(dwu/start-undo-transaction undo-id)
|
||||||
(if (and single? is-frame?)
|
(if (and single? is-frame?)
|
||||||
(create-layout-from-id [(first selected)] type true)
|
(create-layout-from-id (first selected) type true)
|
||||||
(create-layout-from-selection type))
|
(create-layout-from-selection type))
|
||||||
(dwu/commit-undo-transaction undo-id))))))
|
(dwu/commit-undo-transaction undo-id))))))
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
|
[app.common.geom.modifiers :as gm]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.rect :as grc]
|
[app.common.geom.rect :as grc]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
@ -254,7 +255,7 @@
|
||||||
|
|
||||||
modif-tree
|
modif-tree
|
||||||
(-> (dwm/build-modif-tree ids objects get-modifier)
|
(-> (dwm/build-modif-tree ids objects get-modifier)
|
||||||
(gsh/set-objects-modifiers objects))]
|
(gm/set-objects-modifiers objects))]
|
||||||
|
|
||||||
(assoc state :workspace-modifiers modif-tree)))
|
(assoc state :workspace-modifiers modif-tree)))
|
||||||
|
|
||||||
|
@ -283,7 +284,7 @@
|
||||||
|
|
||||||
modif-tree
|
modif-tree
|
||||||
(-> (dwm/build-modif-tree ids objects get-modifier)
|
(-> (dwm/build-modif-tree ids objects get-modifier)
|
||||||
(gsh/set-objects-modifiers objects))]
|
(gm/set-objects-modifiers objects))]
|
||||||
|
|
||||||
(assoc state :workspace-modifiers modif-tree)))
|
(assoc state :workspace-modifiers modif-tree)))
|
||||||
|
|
||||||
|
|
|
@ -410,14 +410,18 @@
|
||||||
[{:keys [shapes]}]
|
[{:keys [shapes]}]
|
||||||
(let [single? (= (count shapes) 1)
|
(let [single? (= (count shapes) 1)
|
||||||
has-frame? (->> shapes (d/seek cph/frame-shape?))
|
has-frame? (->> shapes (d/seek cph/frame-shape?))
|
||||||
is-frame? (and single? has-frame?)
|
is-flex-container? (and single? has-frame? (= :flex (:layout (first shapes))))
|
||||||
is-flex-container? (and is-frame? (= :flex (:layout (first shapes))))
|
|
||||||
ids (->> shapes (map :id))
|
ids (->> shapes (map :id))
|
||||||
add-layout (fn [type]
|
|
||||||
(st/emit! (if is-frame?
|
add-layout
|
||||||
(dwsl/create-layout-from-id ids type true)
|
(fn [type]
|
||||||
(dwsl/create-layout-from-selection type))))
|
(if (and single? has-frame?)
|
||||||
remove-flex #(st/emit! (dwsl/remove-layout ids))]
|
(st/emit! (dwsl/create-layout-from-id (first ids) type true))
|
||||||
|
(st/emit! (dwsl/create-layout-from-selection type))))
|
||||||
|
|
||||||
|
remove-flex
|
||||||
|
(fn []
|
||||||
|
(st/emit! (dwsl/remove-layout ids)))]
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
(when (not is-flex-container?)
|
(when (not is-flex-container?)
|
||||||
|
|
|
@ -1316,20 +1316,25 @@
|
||||||
[:div.element-set-title
|
[:div.element-set-title
|
||||||
[:*
|
[:*
|
||||||
[:span "Layout"]
|
[:span "Layout"]
|
||||||
(if (and (not multiple) (:layout values))
|
|
||||||
|
(if (features/active-feature? :grid-layout)
|
||||||
[:div.title-actions
|
[:div.title-actions
|
||||||
(when (features/active-feature? :grid-layout)
|
|
||||||
[:div.layout-btns
|
[:div.layout-btns
|
||||||
[:button {:on-click set-flex
|
[:button {:on-click set-flex
|
||||||
:class (dom/classnames
|
:class (dom/classnames
|
||||||
:active (= :flex layout-type))} "Flex"]
|
:active (= :flex layout-type))} "Flex"]
|
||||||
[:button {:on-click set-grid
|
[:button {:on-click set-grid
|
||||||
:class (dom/classnames
|
:class (dom/classnames
|
||||||
:active (= :grid layout-type))} "Grid"]])
|
:active (= :grid layout-type))} "Grid"]]
|
||||||
[:button.remove-layout {:on-click on-remove-layout} i/minus]]
|
|
||||||
|
|
||||||
|
(when (and (not multiple) (:layout values))
|
||||||
|
[:button.remove-layout {:on-click on-remove-layout} i/minus])]
|
||||||
|
|
||||||
|
[:div.title-actions
|
||||||
|
(if (and (not multiple) (:layout values))
|
||||||
|
[:button.remove-layout {:on-click on-remove-layout} i/minus]
|
||||||
[:button.add-page {:data-value :flex
|
[:button.add-page {:data-value :flex
|
||||||
:on-click on-set-layout} i/close])]]
|
:on-click on-set-layout} i/close])])]]
|
||||||
|
|
||||||
(when (:layout values)
|
(when (:layout values)
|
||||||
(when (not= :multiple layout-type)
|
(when (not= :multiple layout-type)
|
||||||
|
|
|
@ -283,7 +283,6 @@
|
||||||
hover? (unchecked-get props "hover?")
|
hover? (unchecked-get props "hover?")
|
||||||
selected? (unchecked-get props "selected?")
|
selected? (unchecked-get props "selected?")
|
||||||
|
|
||||||
|
|
||||||
cell-bounds (gsg/cell-bounds layout-data cell)
|
cell-bounds (gsg/cell-bounds layout-data cell)
|
||||||
cell-origin (gpo/origin cell-bounds)
|
cell-origin (gpo/origin cell-bounds)
|
||||||
cell-width (gpo/width-points cell-bounds)
|
cell-width (gpo/width-points cell-bounds)
|
||||||
|
|
|
@ -101,6 +101,7 @@ body {
|
||||||
:grid-template-rows
|
:grid-template-rows
|
||||||
:grid-template-columns
|
:grid-template-columns
|
||||||
:grid-template-areas
|
:grid-template-areas
|
||||||
|
:grid-auto-flow
|
||||||
|
|
||||||
;; Flex/grid self properties
|
;; Flex/grid self properties
|
||||||
:flex-shrink
|
:flex-shrink
|
||||||
|
|
|
@ -420,6 +420,11 @@
|
||||||
justify-self (:justify-self cell)]
|
justify-self (:justify-self cell)]
|
||||||
(when (not= justify-self :auto) justify-self))))
|
(when (not= justify-self :auto) justify-self))))
|
||||||
|
|
||||||
|
(defmethod get-value :grid-auto-flow
|
||||||
|
[_ shape _]
|
||||||
|
(when (and (ctl/grid-layout? shape) (= (:layout-grid-dir shape) :column))
|
||||||
|
"column"))
|
||||||
|
|
||||||
(defmethod get-value :default
|
(defmethod get-value :default
|
||||||
[property shape _]
|
[property shape _]
|
||||||
(get shape property))
|
(get shape property))
|
||||||
|
|
Loading…
Add table
Reference in a new issue