Multiple cells selection and area

This commit is contained in:
alonso.torres 2023-09-14 10:57:42 +02:00
parent 322767701c
commit 17f35cda15
12 changed files with 288 additions and 109 deletions

View file

@ -41,7 +41,7 @@
(> dy dx)
(assoc :x (- (:x point) (* sx (- dy dx)))))))
(defn resize-shape [{:keys [x y width height] :as shape} initial point lock?]
(defn resize-shape [{:keys [x y width height] :as shape} initial point lock? mod?]
(if (and (some? x) (some? y) (some? width) (some? height))
(let [draw-rect (grc/make-rect initial (cond-> point lock? (adjust-ratio initial)))
shape-rect (grc/make-rect x y width height)
@ -56,13 +56,14 @@
(-> shape
(assoc :click-draw? false)
(vary-meta merge {:mod? mod?})
(gsh/transform-shape (-> (ctm/empty)
(ctm/resize scalev (gpt/point x y))
(ctm/move movev)))))
shape))
(defn update-drawing [state initial point lock?]
(update-in state [:workspace-drawing :object] resize-shape initial point lock?))
(defn update-drawing [state initial point lock? mod?]
(update-in state [:workspace-drawing :object] resize-shape initial point lock? mod?))
(defn move-drawing
[{:keys [x y]}]
@ -105,9 +106,7 @@
(cond-> (some? drop-index)
(with-meta {:index drop-index}))
(cond-> (some? drop-cell)
(with-meta {:cell drop-cell})))
]
(with-meta {:cell drop-cell})))]
(rx/concat
;; Add shape to drawing state
@ -120,14 +119,14 @@
(->> ms/mouse-position
(rx/filter #(> (gpt/distance % initial) (/ 2 zoom)))
(rx/with-latest vector ms/mouse-position-shift)
(rx/with-latest conj ms/mouse-position-mod)
(rx/switch-map
(fn [[point :as current]]
(->> (snap/closest-snap-point page-id [shape] objects layout zoom focus point)
(rx/map #(conj current %)))))
(rx/map
(fn [[_ shift? point]]
#(update-drawing % initial (cond-> point snap-pixel? (gpt/round-step snap-prec)) shift?)))))
(fn [[_ shift? mod? point]]
#(update-drawing % initial (cond-> point snap-pixel? (gpt/round-step snap-prec)) shift? mod?)))))
(rx/take-until stoper))
(->> (rx/of (common/handle-finish-drawing))

View file

@ -25,18 +25,20 @@
(disj hover-set cell-id))))))))
(defn select-grid-cell
[grid-id cell-id]
[grid-id cell-id add?]
(ptk/reify ::select-grid-cell
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-grid-edition grid-id :selected] cell-id))))
(if add?
(update-in state [:workspace-grid-edition grid-id :selected] (fnil conj #{}) cell-id)
(assoc-in state [:workspace-grid-edition grid-id :selected] #{cell-id})))))
(defn remove-selection
[grid-id]
[grid-id cell-id]
(ptk/reify ::remove-selection
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-grid-edition grid-id] dissoc :selected))))
(update-in state [:workspace-grid-edition grid-id :selected] disj cell-id))))
(defn clear-selection
[grid-id]
@ -45,6 +47,19 @@
(update [_ state]
(update-in state [:workspace-grid-edition grid-id] dissoc :selected))))
(defn clean-selection
[grid-id]
(ptk/reify ::clean-selection
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
shape (get objects grid-id)]
(update-in state [:workspace-grid-edition grid-id :selected]
(fn [selected]
(into #{}
(filter #(contains? (:layout-grid-cells shape) %))
selected)))))))
(defn stop-grid-layout-editing
[grid-id]
(ptk/reify ::stop-grid-layout-editing

View file

@ -20,6 +20,7 @@
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dwc]
[app.main.data.workspace.colors :as cl]
[app.main.data.workspace.grid-layout.editor :as dwge]
[app.main.data.workspace.modifiers :as dwm]
[app.main.data.workspace.selection :as dwse]
[app.main.data.workspace.shapes :as dws]
@ -563,20 +564,72 @@
(ptk/data-event :layout/update ids)
(dwu/commit-undo-transaction undo-id))))))
(defn update-grid-cell
[layout-id cell-id props]
(ptk/reify ::update-grid-cell
(defn update-grid-cells
[layout-id ids props]
(ptk/reify ::update-grid-cells
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dwc/update-shapes
[layout-id]
(fn [shape]
(-> shape
(d/update-in-when [:layout-grid-cells cell-id]
#(d/without-nils (merge % props))))))
(->> ids
(reduce (fn [shape cell-id]
(-> shape
(d/update-in-when [:layout-grid-cells cell-id]
#(d/without-nils (merge % props)))))
shape))))
(ptk/data-event :layout/update [layout-id])
(dwu/commit-undo-transaction undo-id))))))
(defn change-cells-mode
[layout-id ids mode]
(ptk/reify ::change-cells-mode
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dwc/update-shapes
[layout-id]
(fn [shape]
(cond
(= mode :area)
;; Create area with the selected cells
(let [{:keys [first-row first-column last-row last-column]}
(ctl/cells-coordinates (->> ids (map #(get-in shape [:layout-grid-cells %]))))
target-cell
(ctl/get-cell-by-position shape first-row first-column)
shape
(-> shape
(ctl/resize-cell-area
(:row target-cell) (:column target-cell)
first-row
first-column
(inc (- last-row first-row))
(inc (- last-column first-column)))
(ctl/assign-cells))]
(-> shape
(d/update-in-when [:layout-grid-cells (:id target-cell)] assoc :position :area)))
(= mode :auto)
;; change the manual cells and move to auto
(->> ids
(reduce (fn [shape cell-id]
(cond-> shape
(= :manual (get-in shape [:layout-grid-cells cell-id :position]))
(-> (d/update-in-when [:layout-grid-cells cell-id] assoc :shapes [] :position :auto)
(ctl/assign-cells))))
shape)))))
(dwge/clean-selection layout-id)
(ptk/data-event :layout/update [layout-id])
(dwu/commit-undo-transaction undo-id))))))

View file

@ -36,10 +36,10 @@
(defn prepare-add-shape
[changes shape objects _selected]
(let [index (:index (meta shape))
;; FIXME: revisit
id (:id shape)
[row column :as cell] (:cell (meta shape))
mod? (:mod? (meta shape))
[row column :as cell] (when-not mod? (:cell (meta shape)))
changes (-> changes
(pcb/with-objects objects)

View file

@ -490,7 +490,7 @@
flex-layout? (ctl/flex-layout? objects target-frame)
grid-layout? (ctl/grid-layout? objects target-frame)
drop-index (when flex-layout? (gslf/get-drop-index target-frame objects position))
cell-data (when grid-layout? (gslg/get-drop-cell target-frame objects position))]
cell-data (when (and grid-layout? (not mod?)) (gslg/get-drop-cell target-frame objects position))]
[move-vector target-frame drop-index cell-data])))
(rx/take-until stopper))]

View file

@ -82,7 +82,8 @@
shape-parent-frame (cph/get-frame objects (:frame-id first-selected-shape))
edit-grid? (ctl/grid-layout? objects edition)
selected-cell (dm/get-in grid-edition [edition :selected])
selected-cells (->> (dm/get-in grid-edition [edition :selected])
(map #(dm/get-in objects [edition :layout-grid-cells %])))
on-change-tab
(fn [options-mode]
@ -105,10 +106,10 @@
[:& bool-options]
(cond
(some? selected-cell)
(d/not-empty? selected-cells)
[:& grid-cell/options
{:shape (get objects edition)
:cell (dm/get-in objects [edition :layout-grid-cells selected-cell])}]
:cells selected-cells}]
edit-grid?
[:& layout-container/grid-layout-edition
@ -166,10 +167,10 @@
[:& align-options]
[:& bool-options]
(cond
(some? selected-cell)
(d/not-empty? selected-cells)
[:& grid-cell/options
{:shape (get objects edition)
:cell (dm/get-in objects [edition :layout-grid-cells selected-cell])}]
:cells selected-cells}]
edit-grid?
[:& layout-container/grid-layout-edition

View file

@ -6,17 +6,31 @@
(ns app.main.ui.workspace.sidebar.options.menus.grid-cell
(:require
[app.common.attrs :as attrs]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace :as dw]
[app.main.data.workspace.grid-layout.editor :as dwge]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.menus.layout-container :as lyc]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
(def cell-props [:id
:position
:row
:row-span
:column
:column-span
:align-self
:justify-self
:area-name])
(mf/defc set-self-alignment
[{:keys [is-col? alignment set-alignment] :as props}]
(let [dir-v [:auto :start :center :end :stretch #_:baseline]
@ -35,78 +49,91 @@
(mf/defc options
{::mf/wrap [mf/memo]}
[{:keys [shape cell] :as props}]
[{:keys [shape cell cells] :as props}]
(let [{:keys [mode area-name align-self justify-self column column-span row row-span]} cell
column-end (+ column column-span)
row-end (+ row row-span)
(let [cells (hooks/use-equal-memo cells)
cell (or cell (attrs/get-attrs-multi cells cell-props))
cell-mode (or mode :auto)
multiple? (= :multiple (:id cell))
cell-ids (if (some? cell) [(:id cell)] (->> cells (map :id)))
cell-ids (hooks/use-equal-memo cell-ids)
{:keys [position area-name align-self justify-self column column-span row row-span]} cell
column-end (when (and (d/num? column) (d/num? column-span))
(+ column column-span))
row-end (when (and (d/num? row) (d/num? row-span))
(+ row row-span))
cell-mode (or position :auto)
cell-mode (if (and (= :auto cell-mode)
(or (> (:column-span cell) 1)
(> (:row-span cell) 1)))
:manual
cell-mode)
valid-area-cells? (mf/use-memo
(mf/deps cells)
#(ctl/valid-area-cells? cells))
set-alignment
(mf/use-callback
(mf/deps align-self (:id shape) (:id cell))
(mf/deps align-self (:id shape) cell-ids)
(fn [value]
(if (= align-self value)
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:align-self nil}))
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:align-self value})))))
(st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:align-self nil}))
(st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:align-self value})))))
set-justify-self
(mf/use-callback
(mf/deps justify-self (:id shape) (:id cell))
(mf/deps justify-self (:id shape) cell-ids)
(fn [value]
(if (= justify-self value)
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:justify-self nil}))
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:justify-self value})))))
(st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:justify-self nil}))
(st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:justify-self value})))))
on-change
on-grid-coordinates
(mf/use-callback
(mf/deps column row (:id shape) (:id cell))
(fn [field type value]
(let [[property value]
(cond
(and (= type :column) (or (= field :all) (= field :start)))
[:column value]
(when-not multiple?
(let [[property value]
(cond
(and (= type :column) (or (= field :all) (= field :start)))
[:column value]
(and (= type :column) (= field :end))
[:column-span (max 1 (- value column))]
(and (= type :column) (= field :end))
[:column-span (max 1 (- value column))]
(and (= type :row) (or (= field :all) (= field :start)))
[:row value]
(and (= type :row) (or (= field :all) (= field :start)))
[:row value]
(and (= type :row) (= field :end))
[:row-span (max 1 (- value row))])]
(and (= type :row) (= field :end))
[:row-span (max 1 (- value row))])]
(st/emit! (dwsl/update-grid-cell-position (:id shape) (:id cell) {property value})))))
(st/emit! (dwsl/update-grid-cell-position (:id shape) (:id cell) {property value}))))))
on-area-name-change
(mf/use-callback
(mf/deps (:id shape) (:id cell))
(mf/deps (:id shape) cell-ids)
(fn [event]
(let [value (dom/get-value (dom/get-target event))]
(if (= value "")
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:area-name nil}))
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) {:area-name value}))))))
(st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:area-name nil}))
(st/emit! (dwsl/update-grid-cells (:id shape) cell-ids {:area-name value}))))))
set-cell-mode
(mf/use-callback
(mf/deps (:id shape) (:id cell))
(mf/deps (:id shape) cell-ids)
(fn [mode]
(let [props (cond-> {:mode mode}
(not= mode :area)
(assoc :area-name nil))]
(st/emit! (dwsl/update-grid-cell (:id shape) (:id cell) props)))))
(st/emit! (dwsl/change-cells-mode (:id shape) cell-ids mode))))
toggle-edit-mode
(mf/use-fn
(mf/deps (:id shape))
(fn []
(st/emit! (dwge/remove-selection (:id shape)))))]
(st/emit! (dw/start-edition-mode (:id shape))
(dwge/clear-selection (:id shape)))))]
[:div.element-set
[:div.element-set-title
@ -119,15 +146,17 @@
[:button.position-btn
{:on-click #(set-cell-mode :auto)
:class (dom/classnames :active (= :auto cell-mode))} "Auto"]
[:button.position-btn
{:on-click #(set-cell-mode :manual)
:class (dom/classnames :active (= :manual cell-mode))} "Manual"]
(when-not multiple?
[:button.position-btn
{:on-click #(set-cell-mode :manual)
:class (dom/classnames :active (= :manual cell-mode))} "Manual"])
[:button.position-btn
{:on-click #(set-cell-mode :area)
:disabled (not valid-area-cells?)
:class (dom/classnames :active (= :area cell-mode))} "Area"]]]
[:div.manage-grid-columns
(when (= :auto cell-mode)
(when (and (not multiple?) (= :auto cell-mode))
[:div.grid-auto
[:div.grid-columns-auto
[:span.icon i/layout-rows]
@ -135,7 +164,7 @@
[:> numeric-input*
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :all :column)
:on-change (partial on-grid-coordinates :all :column)
:value column}]]]
[:div.grid-rows-auto
[:span.icon i/layout-columns]
@ -143,7 +172,7 @@
[:> numeric-input*
{:placeholder "--"
:on-click #(dom/select-target %)
:on-change (partial on-change :all :row)
:on-change (partial on-grid-coordinates :all :row)
:value row}]]]])
(when (= :area cell-mode)
@ -158,7 +187,7 @@
:auto-complete "off"
:on-change on-area-name-change}]])
(when (or (= :manual cell-mode) (= :area cell-mode))
(when (and (not multiple?) (or (= :manual cell-mode) (= :area cell-mode)))
[:div.grid-manual
[:div.grid-columns-auto
[:span.icon i/layout-rows]
@ -166,12 +195,12 @@
[:> numeric-input*
{:placeholder "--"
:on-pointer-down #(dom/select-target %)
:on-change (partial on-change :start :column)
:on-change (partial on-grid-coordinates :start :column)
:value column}]
[:> numeric-input*
{:placeholder "--"
:on-pointer-down #(dom/select-target %)
:on-change (partial on-change :end :column)
:on-change (partial on-grid-coordinates :end :column)
:value column-end}]]]
[:div.grid-rows-auto
[:span.icon i/layout-columns]
@ -179,12 +208,12 @@
[:> numeric-input*
{:placeholder "--"
:on-pointer-down #(dom/select-target %)
:on-change (partial on-change :start :row)
:on-change (partial on-grid-coordinates :start :row)
:value row}]
[:> numeric-input*
{:placeholder "--"
:on-pointer-down #(dom/select-target %)
:on-change (partial on-change :end :row)
:on-change (partial on-grid-coordinates :end :row)
:value row-end}]]]])]
[:div.layout-row

View file

@ -894,7 +894,7 @@
[dprops dref]
(h/use-sortable
:data-type "penpot/layer"
:data-type "penpot/grid-track"
:on-drop drop-track
:data {:is-col? is-col?
:index index
@ -1449,8 +1449,8 @@
(mf/deps ids)
(fn [value type]
(if (= type :row)
(st/emit! (dwsl/update-layout ids {:layout-align-content value}))
(st/emit! (dwsl/update-layout ids {:layout-justify-content value})))))
(st/emit! (dwsl/update-layout ids {:layout-justify-content value}))
(st/emit! (dwsl/update-layout ids {:layout-align-content value})))))
;;Grid columns
column-grid-values (:layout-grid-columns values)
@ -1544,10 +1544,10 @@
[:div.jusfiy-content-grid.row-title "Content"]
[:div.btn-wrapper.align-grid-content
[:& justify-grid-row {:is-col? true
:justify-items grid-justify-content-column
:justify-items grid-justify-content-row
:set-justify set-content-grid}]
[:& justify-grid-row {:is-col? false
:justify-items grid-justify-content-row
:justify-items grid-justify-content-column
:set-justify set-content-grid}]]]
[:& grid-columns-row {:is-col? true
:expanded? @grid-columns-open?

View file

@ -302,10 +302,10 @@
handle-pointer-down
(mf/use-callback
(mf/deps (:id shape) (:id cell) selected?)
(fn []
(if selected?
(st/emit! (dwge/remove-selection (:id shape)))
(st/emit! (dwge/select-grid-cell (:id shape) (:id cell))))))]
(fn [event]
(if (and (kbd/shift? event) selected?)
(st/emit! (dwge/remove-selection (:id shape) (:id cell)))
(st/emit! (dwge/select-grid-cell (:id shape) (:id cell) (kbd/shift? event)) ))))]
[:g.cell-editor
[:rect
@ -435,11 +435,11 @@
[width height]
(if (= type :column)
[(max layout-gap-col (/ 16 zoom))
[(max 0 (- layout-gap-col (/ 10 zoom)) (/ 16 zoom))
(+ row-total-size row-total-gap)]
[(+ column-total-size column-total-gap)
(max layout-gap-row (/ 16 zoom))])
(max 0 (- layout-gap-row (/ 10 zoom)) (/ 16 zoom))])
start-p
(cond-> start-p
@ -789,7 +789,7 @@
:cell cell
:zoom zoom
:hover? (contains? hover-cells (:id cell))
:selected? (= selected-cells (:id cell))}])]
:selected? (contains? selected-cells (:id cell))}])]
(when-not view-only
[:*
[:& grid-editor-frame {:zoom zoom