mirror of
https://github.com/penpot/penpot.git
synced 2025-05-22 21:56:11 +02:00
✨ Create grid from selection
This commit is contained in:
parent
02399add7a
commit
351f7fd1bb
17 changed files with 395 additions and 234 deletions
|
@ -9,6 +9,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.modifiers :as gm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
|
@ -328,11 +329,11 @@
|
|||
(as-> objects $
|
||||
(apply-text-modifiers $ (get state :workspace-text-modifier))
|
||||
;;(apply-path-modifiers $ (get-in state [:workspace-local :edit-path]))
|
||||
(gsh/set-objects-modifiers modif-tree $ (merge
|
||||
params
|
||||
{:ignore-constraints ignore-constraints
|
||||
:snap-pixel? snap-pixel?
|
||||
:snap-precision snap-precision}))))))
|
||||
(gm/set-objects-modifiers modif-tree $ (merge
|
||||
params
|
||||
{:ignore-constraints ignore-constraints
|
||||
:snap-pixel? snap-pixel?
|
||||
:snap-precision snap-precision}))))))
|
||||
|
||||
(defn- calculate-update-modifiers
|
||||
[old-modif-tree state ignore-constraints ignore-snap-pixel modif-tree]
|
||||
|
@ -348,7 +349,14 @@
|
|||
objects
|
||||
(-> objects
|
||||
(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
|
||||
([modif-tree]
|
||||
|
@ -405,7 +413,7 @@
|
|||
|
||||
modif-tree
|
||||
(-> (build-modif-tree ids objects get-modifier)
|
||||
(gsh/set-objects-modifiers objects))]
|
||||
(gm/set-objects-modifiers objects))]
|
||||
|
||||
(assoc state :workspace-modifiers modif-tree))))))
|
||||
|
||||
|
@ -432,7 +440,7 @@
|
|||
|
||||
modif-tree
|
||||
(-> (build-modif-tree ids objects get-modifier)
|
||||
(gsh/set-objects-modifiers objects))]
|
||||
(gm/set-objects-modifiers objects))]
|
||||
|
||||
(assoc state :workspace-modifiers modif-tree))))))
|
||||
|
||||
|
|
|
@ -9,13 +9,11 @@
|
|||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.geom.shapes.flex-layout :as flex]
|
||||
[app.common.geom.shapes.grid-layout :as grid]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dwc]
|
||||
|
@ -70,140 +68,27 @@
|
|||
:layout-grid-columns []})
|
||||
|
||||
(defn get-layout-initializer
|
||||
[type from-frame?]
|
||||
(let [initial-layout-data
|
||||
[type from-frame? objects]
|
||||
(let [[initial-layout-data calculate-params]
|
||||
(case type
|
||||
:flex initial-flex-layout
|
||||
:grid initial-grid-layout)]
|
||||
:flex [initial-flex-layout flex/calculate-params]
|
||||
:grid [initial-grid-layout grid/calculate-params])]
|
||||
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(merge initial-layout-data)
|
||||
(cond-> (= type :grid) ctl/assign-cells)
|
||||
;; If the original shape is not a frame we set clip content and show-viewer to false
|
||||
(cond-> (not from-frame?)
|
||||
(assoc :show-content true :hide-in-viewer true))))))
|
||||
|
||||
|
||||
(defn shapes->flex-params
|
||||
"Given the shapes calculate its flex parameters (horizontal vs vertical, gaps, etc)"
|
||||
([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))))))
|
||||
(let [shape
|
||||
(-> shape
|
||||
(merge initial-layout-data)
|
||||
|
||||
;; (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
|
||||
(cond-> (not from-frame?)
|
||||
(assoc :show-content true :hide-in-viewer true)))
|
||||
|
||||
params (calculate-params objects (cph/get-immediate-children objects (:id shape)) shape)]
|
||||
|
||||
(cond-> (merge shape params)
|
||||
(= type :grid) (-> ctl/assign-cells ctl/reorder-grid-children)))
|
||||
)))
|
||||
|
||||
;; Never call this directly but through the data-event `:layout/update`
|
||||
;; Otherwise a lot of cycle dependencies could be generated
|
||||
|
@ -236,6 +121,22 @@
|
|||
[]
|
||||
(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
|
||||
[type]
|
||||
|
@ -253,66 +154,41 @@
|
|||
has-mask? (->> selected-shapes (d/seek cph/mask-shape?))
|
||||
is-mask? (and single? has-mask?)
|
||||
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?))
|
||||
(let [new-shape-id (uuid/next)
|
||||
parent-id (:parent-id (first selected-shapes))
|
||||
shapes-ids (:shapes (first selected-shapes))
|
||||
ordered-ids (into (d/ordered-set) shapes-ids)
|
||||
undo-id (js/Symbol)
|
||||
group-index (cph/get-index-replacement selected objects)]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dwse/select-shapes ordered-ids)
|
||||
(dws/create-artboard-from-selection new-shape-id parent-id group-index)
|
||||
(cl/remove-all-fills [new-shape-id] {:color clr/black
|
||||
:opacity 1})
|
||||
(create-layout-from-id [new-shape-id] type false)
|
||||
(dwc/update-shapes
|
||||
[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))))
|
||||
new-shape-id (uuid/next)
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
(ptk/data-event :layout/update [new-shape-id])
|
||||
(dws/delete-shapes page-id selected)
|
||||
(dwu/commit-undo-transaction undo-id)))
|
||||
(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))
|
||||
ordered-ids (into (d/ordered-set) shapes-ids)
|
||||
group-index (cph/get-index-replacement selected objects)]
|
||||
(rx/of
|
||||
(dwse/select-shapes ordered-ids)
|
||||
(dws/create-artboard-from-selection new-shape-id parent-id group-index)
|
||||
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
|
||||
(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 selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))
|
||||
(dws/delete-shapes page-id selected)
|
||||
(ptk/data-event :layout/update [new-shape-id])
|
||||
(dwu/commit-undo-transaction undo-id)))
|
||||
|
||||
(let [new-shape-id (uuid/next)
|
||||
undo-id (js/Symbol)
|
||||
flex-params (shapes->flex-params objects selected-shapes)]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dws/create-artboard-from-selection new-shape-id)
|
||||
(cl/remove-all-fills [new-shape-id] {:color clr/black
|
||||
:opacity 1})
|
||||
(create-layout-from-id [new-shape-id] type false)
|
||||
(dwc/update-shapes
|
||||
[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])
|
||||
(dwu/commit-undo-transaction undo-id))))))))
|
||||
;; Create Layout from selection
|
||||
(rx/of
|
||||
(dws/create-artboard-from-selection new-shape-id)
|
||||
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
|
||||
(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 selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))))
|
||||
|
||||
(rx/of (ptk/data-event :layout/update [new-shape-id])
|
||||
(dwu/commit-undo-transaction undo-id)))))))
|
||||
|
||||
(defn remove-layout
|
||||
[ids]
|
||||
|
@ -342,7 +218,7 @@
|
|||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(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))
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.modifiers :as gm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
|
@ -254,7 +255,7 @@
|
|||
|
||||
modif-tree
|
||||
(-> (dwm/build-modif-tree ids objects get-modifier)
|
||||
(gsh/set-objects-modifiers objects))]
|
||||
(gm/set-objects-modifiers objects))]
|
||||
|
||||
(assoc state :workspace-modifiers modif-tree)))
|
||||
|
||||
|
@ -283,7 +284,7 @@
|
|||
|
||||
modif-tree
|
||||
(-> (dwm/build-modif-tree ids objects get-modifier)
|
||||
(gsh/set-objects-modifiers objects))]
|
||||
(gm/set-objects-modifiers objects))]
|
||||
|
||||
(assoc state :workspace-modifiers modif-tree)))
|
||||
|
||||
|
|
|
@ -410,14 +410,18 @@
|
|||
[{:keys [shapes]}]
|
||||
(let [single? (= (count shapes) 1)
|
||||
has-frame? (->> shapes (d/seek cph/frame-shape?))
|
||||
is-frame? (and single? has-frame?)
|
||||
is-flex-container? (and is-frame? (= :flex (:layout (first shapes))))
|
||||
is-flex-container? (and single? has-frame? (= :flex (:layout (first shapes))))
|
||||
ids (->> shapes (map :id))
|
||||
add-layout (fn [type]
|
||||
(st/emit! (if is-frame?
|
||||
(dwsl/create-layout-from-id ids type true)
|
||||
(dwsl/create-layout-from-selection type))))
|
||||
remove-flex #(st/emit! (dwsl/remove-layout ids))]
|
||||
|
||||
add-layout
|
||||
(fn [type]
|
||||
(if (and single? has-frame?)
|
||||
(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?)
|
||||
|
|
|
@ -1316,20 +1316,25 @@
|
|||
[:div.element-set-title
|
||||
[:*
|
||||
[:span "Layout"]
|
||||
(if (and (not multiple) (:layout values))
|
||||
[:div.title-actions
|
||||
(when (features/active-feature? :grid-layout)
|
||||
[:div.layout-btns
|
||||
[:button {:on-click set-flex
|
||||
:class (dom/classnames
|
||||
:active (= :flex layout-type))} "Flex"]
|
||||
[:button {:on-click set-grid
|
||||
:class (dom/classnames
|
||||
:active (= :grid layout-type))} "Grid"]])
|
||||
[:button.remove-layout {:on-click on-remove-layout} i/minus]]
|
||||
|
||||
[:button.add-page {:data-value :flex
|
||||
:on-click on-set-layout} i/close])]]
|
||||
(if (features/active-feature? :grid-layout)
|
||||
[:div.title-actions
|
||||
[:div.layout-btns
|
||||
[:button {:on-click set-flex
|
||||
:class (dom/classnames
|
||||
:active (= :flex layout-type))} "Flex"]
|
||||
[:button {:on-click set-grid
|
||||
:class (dom/classnames
|
||||
:active (= :grid layout-type))} "Grid"]]
|
||||
|
||||
(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
|
||||
:on-click on-set-layout} i/close])])]]
|
||||
|
||||
(when (:layout values)
|
||||
(when (not= :multiple layout-type)
|
||||
|
|
|
@ -283,7 +283,6 @@
|
|||
hover? (unchecked-get props "hover?")
|
||||
selected? (unchecked-get props "selected?")
|
||||
|
||||
|
||||
cell-bounds (gsg/cell-bounds layout-data cell)
|
||||
cell-origin (gpo/origin cell-bounds)
|
||||
cell-width (gpo/width-points cell-bounds)
|
||||
|
|
|
@ -101,6 +101,7 @@ body {
|
|||
:grid-template-rows
|
||||
:grid-template-columns
|
||||
:grid-template-areas
|
||||
:grid-auto-flow
|
||||
|
||||
;; Flex/grid self properties
|
||||
:flex-shrink
|
||||
|
|
|
@ -420,6 +420,11 @@
|
|||
justify-self (:justify-self cell)]
|
||||
(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
|
||||
[property shape _]
|
||||
(get shape property))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue