0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-03 10:31:38 -05:00

Enable grid editor

This commit is contained in:
alonso.torres 2023-04-05 11:55:08 +02:00
parent 0268964f36
commit 4bfe81f771
7 changed files with 225 additions and 60 deletions

View file

@ -121,7 +121,7 @@
:shape-cells shape-cells}))
(defn get-cell-data
[{:keys [row-tracks column-tracks shape-cells]} transformed-parent-bounds [_child-bounds child]]
[{:keys [row-tracks column-tracks shape-cells]} transformed-parent-bounds [_ child]]
(let [origin (gpo/origin transformed-parent-bounds)
hv #(gpo/start-hv transformed-parent-bounds %)

View file

@ -204,6 +204,7 @@
[(-> (get-group-bounds objects bounds modif-tree child)
(gpo/parent-coords-bounds @transformed-parent-bounds))
child])
(set-child-modifiers [modif-tree cell-data [child-bounds child]]
(let [modifiers (gcgl/child-modifiers parent transformed-parent-bounds child child-bounds cell-data)
modif-tree
@ -217,13 +218,14 @@
(map apply-modifiers))
grid-data (gcgl/calc-layout-data parent children @transformed-parent-bounds)]
(loop [modif-tree modif-tree
child (first children)
bound+child (first children)
pending (rest children)]
(if (some? child)
(let [cell-data (gcgl/get-cell-data grid-data @transformed-parent-bounds child)
(if (some? bound+child)
(let [[_ child] bound+child
cell-data (gcgl/get-cell-data grid-data @transformed-parent-bounds bound+child)
modif-tree (cond-> modif-tree
(some? cell-data)
(set-child-modifiers cell-data child))]
(set-child-modifiers cell-data bound+child))]
(recur modif-tree (first pending) (rest pending)))
modif-tree)))))

View file

@ -555,6 +555,10 @@
(declare assign-cells)
(def default-track-value
{:type :fixed
:value 100})
(def grid-cell-defaults
{:row-span 1
:column-span 1
@ -573,7 +577,7 @@
(grid-definition? value))
(let [rows (:layout-grid-rows parent)
new-col-num (count (:layout-grid-columns parent))
new-col-num (inc (count (:layout-grid-columns parent)))
layout-grid-cells
(->> (d/enumerate rows)
@ -614,14 +618,54 @@
(update :layout-grid-rows (fnil conj []) value)
(assoc :layout-grid-cells layout-grid-cells))))
;; TODO: Remove a track and its corresponding cells. We need to reassign the orphaned shapes into not-tracked cells
;; TODO: SPAN NOT CHECK!!
(defn make-remove-track
[attr track-num]
(comp #(= % track-num) attr second))
(defn make-decrease-track-num
[attr track-num]
(fn [[key value]]
(let [new-val
(cond-> value
(> (get value attr) track-num)
(update attr dec))]
[key new-val])))
(defn remove-grid-column
[parent _index]
parent)
[parent index]
(let [track-num (inc index)
decrease-track-num (make-decrease-track-num :column track-num)
remove-track? (make-remove-track :column track-num)
remove-cells
(fn [cells]
(into {} (comp
(remove remove-track?)
(map decrease-track-num)) cells))]
(-> parent
(update :layout-grid-columns d/remove-at-index index)
(update :layout-grid-cells remove-cells)
(assign-cells))))
(defn remove-grid-row
[parent _index]
parent)
[parent index]
(let [track-num (inc index)
decrease-track-num (make-decrease-track-num :row track-num)
remove-track? (make-remove-track :row track-num)
remove-cells
(fn [cells]
(into {} (comp
(remove remove-track?)
(map decrease-track-num)) cells))]
(-> parent
(update :layout-grid-rows d/remove-at-index index)
(update :layout-grid-cells remove-cells)
(assign-cells))))
;; TODO: Mix the cells given as arguments leaving only one. It should move all the shapes in those cells in the direction for the grid
;; and lastly use assign-cells to reassing the orphaned shapes
@ -629,10 +673,38 @@
[parent _cells]
parent)
(defn get-free-cells
([parent]
(get-free-cells parent nil))
([{:keys [layout-grid-cells layout-grid-dir]} {:keys [sort?] :or {sort? false}}]
(let [comp-fn (if (= layout-grid-dir :row)
(juxt :row :column)
(juxt :column :row))
maybe-sort?
(if sort? (partial sort-by (comp comp-fn second)) identity)]
(->> layout-grid-cells
(filter (comp empty? :shapes second))
(maybe-sort?)
(map first)))))
(defn check-deassigned-cells
"Clean the cells whith shapes that are no longer in the layout"
[parent]
(let [child? (into #{} (:shapes parent))
cells (update-vals
(:layout-grid-cells parent)
(fn [cell] (update cell :shapes #(filterv child? %))))]
(assoc parent :layout-grid-cells cells)))
;; TODO
;; Assign cells takes the children and move them into the allotted cells. If there are not enough cells it creates
;; not-tracked rows/columns and put the shapes there
;; Non-tracked tracks need to be deleted when they are empty and there are no more shapes unallocated
;; Should be caled each time a child can be added like:
;; - On shape creation
;; - When moving a child from layers
@ -641,9 +713,51 @@
;; - (maybe) create group/frames. This case will assigna a cell that had one of its children
(defn assign-cells
[parent]
#_(let [allocated-shapes
(into #{} (mapcat :shapes) (:layout-grid-cells parent))
(let [parent (-> parent check-deassigned-cells)
shape-has-cell?
(into #{} (mapcat (comp :shapes second)) (:layout-grid-cells parent))
no-cell-shapes
(->> (:shapes parent) (remove allocated-shapes))])
parent)
(->> (:shapes parent) (remove shape-has-cell?))]
(if (empty? no-cell-shapes)
;; All shapes are within a cell. No need to assign
parent
(let [;; We need to have at least 1 col and 1 row otherwise we can't assign
parent
(cond-> parent
(empty? (:layout-grid-columns parent))
(add-grid-column default-track-value)
(empty? (:layout-grid-rows parent))
(add-grid-row default-track-value))
;; Free cells should be ordered columns/rows depending on the parameter
;; in the parent
free-cells (get-free-cells parent)
to-add-tracks
(if (= (:layout-grid-dir parent) :row)
(mth/ceil (/ (- (count no-cell-shapes) (count free-cells)) (count (:layout-grid-columns parent))))
(mth/ceil (/ (- (count no-cell-shapes) (count free-cells)) (count (:layout-grid-rows parent)))))
add-track (if (= (:layout-grid-dir parent) :row) add-grid-row add-grid-column)
parent
(->> (range to-add-tracks)
(reduce #(add-track %1 default-track-value) parent))
[pending-shapes cells]
(loop [cells (:layout-grid-cells parent)
free-cells (get-free-cells parent {:sort? true})
pending no-cell-shapes]
(if (or (empty? free-cells) (empty? pending))
[pending cells]
(let [next-free (first free-cells)
current (first pending)
cells (update-in cells [next-free :shapes] conj current)]
(recur cells (rest free-cells) (rest pending)))))]
(assoc parent :layout-grid-cells cells)))))

View file

@ -63,6 +63,7 @@
:layout-justify-content :start
:layout-padding-type :simple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}
:layout-grid-cells {}
:layout-grid-rows []
:layout-grid-columns []})
@ -443,12 +444,13 @@
(defn change-layout-track
[ids type index props]
(assert (#{:row :column} type))
(ptk/reify ::change-layout-column
(ptk/reify ::change-layout-track
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)
property (case :row :layout-grid-rows
:column :layout-grid-columns)]
property (case type
:row :layout-grid-rows
:column :layout-grid-columns)]
(rx/of (dwu/start-undo-transaction undo-id)
(dwc/update-shapes
ids

View file

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.math :as mth]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace :as udw]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.refs :as refs]
@ -441,7 +442,7 @@
:on-click toggle} generated-name]
[:button.add-column {:on-click #(do
(when-not expanded? (toggle))
(add-new-element type {:type :fixed :value 100}))} i/plus]]
(add-new-element type ctl/default-track-value))} i/plus]]
(when expanded?
[:div.columns-info-wrapper
@ -489,12 +490,12 @@
(st/emit! (dwsl/remove-layout ids))
(reset! open? false))
_set-flex
set-flex
(fn []
(st/emit! (dwsl/remove-layout ids))
(on-add-layout :flex))
_set-grid
set-grid
(fn []
(st/emit! (dwsl/remove-layout ids))
(on-add-layout :grid))
@ -637,7 +638,7 @@
[:span "Layout"]
(if (and (not multiple) (:layout values))
[:div.title-actions
#_[:div.layout-btns
[:div.layout-btns
[:button {:on-click set-flex
:class (dom/classnames
:active (= :flex layout-type))} "Flex"]

View file

@ -98,7 +98,7 @@
(when (= :auto @position-mode)
[:div.grid-auto
[:div.grid-columns-auto
[:spam.icon i/layout-rows]
[:span.icon i/layout-rows]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
@ -106,7 +106,7 @@
:on-change (partial on-change :all :column) ;; TODO cambiar este on-change y el value
:value column-start}]]]
[:div.grid-rows-auto
[:spam.icon i/layout-columns]
[:span.icon i/layout-columns]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
@ -129,7 +129,7 @@
(when (or (= :manual @position-mode) (= :area @position-mode))
[:div.grid-manual
[:div.grid-columns-auto
[:spam.icon i/layout-rows]
[:span.icon i/layout-rows]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"
@ -142,7 +142,7 @@
:on-change (partial on-change :end :column)
:value column-end}]]]
[:div.grid-rows-auto
[:spam.icon i/layout-columns]
[:span.icon i/layout-columns]
[:div.input-wrapper
[:> numeric-input
{:placeholder "--"

View file

@ -12,7 +12,9 @@
[app.common.geom.shapes.grid-layout :as gsg]
[app.common.geom.shapes.points :as gpo]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.grid-layout.editor :as dwge]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.refs :as refs]
[app.main.store :as st]
[app.util.dom :as dom]
@ -92,9 +94,10 @@
{::mf/wrap-props false}
[props]
(let [start-p (unchecked-get props "start-p")
zoom (unchecked-get props "zoom")
type (unchecked-get props "type")
(let [start-p (unchecked-get props "start-p")
zoom (unchecked-get props "zoom")
type (unchecked-get props "type")
on-click (unchecked-get props "on-click")
[rect-x rect-y icon-x icon-y]
(if (= type :column)
@ -106,9 +109,17 @@
[(- (:x start-p) (/ 40 zoom))
(:y start-p)
(- (:x start-p) (/ 28 zoom))
(+ (:y start-p) (/ 12 zoom))])]
(+ (:y start-p) (/ 12 zoom))])
[:g.plus-button
handle-click
(mf/use-callback
(mf/deps on-click)
(fn [event]
(when on-click
(on-click))))]
[:g.plus-button {:cursor "pointer"
:on-click handle-click}
[:rect {:x rect-x
:y rect-y
:width (/ 40 zoom)
@ -152,25 +163,45 @@
end-p (-> start-p
(gpt/add (hv (:value column-track)))
(gpt/add (vv (:value row-track))))]
(gpt/add (vv (:value row-track))))
[:rect.cell-editor
{:x (:x start-p)
:y (:y start-p)
:width (- (:x end-p) (:x start-p))
:height (- (:y end-p) (:y start-p))
cell-width (- (:x end-p) (:x start-p))
cell-height (- (:y end-p) (:y start-p))]
:on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) row column true))
:on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) row column false))
[:g.cell-editor
[:rect
{:x (:x start-p)
:y (:y start-p)
:width cell-width
:height cell-height
:on-click #(st/emit! (dwge/select-grid-cell (:id shape) row column))
:on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) row column true))
:on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) row column false))
:style {:fill "transparent"
:stroke "var(--color-distance)"
:stroke-dasharray (when-not (or hover? selected?)
(str/join " " (map #(/ % zoom) [0 8]) ))
:stroke-linecap "round"
:stroke-width (/ 2 zoom)}}]))
:on-click #(st/emit! (dwge/select-grid-cell (:id shape) row column))
:style {:fill "transparent"
:stroke "var(--color-distance)"
:stroke-dasharray (when-not (or hover? selected?)
(str/join " " (map #(/ % zoom) [0 8]) ))
:stroke-linecap "round"
:stroke-width (/ 2 zoom)}}]
(when selected?
(let [handlers
;; Handlers positions, size and cursor
[[(:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) (cur/scale-ns 0)]
[(+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height (cur/scale-ew 0)]
[(:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) (cur/scale-ns 0)]
[(+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height (cur/scale-ew 0)]]]
[:*
(for [[x y width height cursor] handlers]
[:rect
{:x x
:y y
:height height
:width width
:style {:fill "transparent" :stroke-width 0 :cursor cursor}}])]))]))
(mf/defc resize-handler
{::mf/wrap-props false}
@ -265,7 +296,19 @@
origin (gpo/origin bounds)
{:keys [row-tracks column-tracks] :as layout-data}
(gsg/calc-layout-data shape children bounds)]
(gsg/calc-layout-data shape children bounds)
handle-add-column
(mf/use-callback
(mf/deps (:id shape))
(fn []
(st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :column ctl/default-track-value)))))
handle-add-row
(mf/use-callback
(mf/deps (:id shape))
(fn []
(st/emit! (st/emit! (dwsl/add-layout-track [(:id shape)] :row ctl/default-track-value)))))]
(mf/use-effect
(fn []
@ -277,23 +320,16 @@
(let [start-p (-> origin (gpt/add (hv width)))]
[:& plus-btn {:start-p start-p
:zoom zoom
:type :column}])
:type :column
:on-click handle-add-column}])
(let [start-p (-> origin (gpt/add (vv height)))]
[:& plus-btn {:start-p start-p
:zoom zoom
:type :row}])
:type :row
:on-click handle-add-row}])
(for [[_ {:keys [column row]}] (:layout-grid-cells shape)]
[:& grid-cell {:shape shape
:layout-data layout-data
:row row
:column column
:bounds bounds
:zoom zoom
:hover? (contains? hover-cells [row column])
:selected? (= selected-cells [row column])
}])
(for [[idx column-data] (d/enumerate column-tracks)]
(let [start-p (-> origin (gpt/add (hv (:distance column-data))))
@ -320,4 +356,14 @@
[:& resize-handler {:type :row
:start-p start-p
:zoom zoom
:bounds bounds}]]))]))
:bounds bounds}]]))
(for [[_ {:keys [column row]}] (:layout-grid-cells shape)]
[:& grid-cell {:shape shape
:layout-data layout-data
:row row
:column column
:bounds bounds
:zoom zoom
:hover? (contains? hover-cells [row column])
:selected? (= selected-cells [row column])}])]))