diff --git a/common/src/app/common/geom/shapes/grid_layout/areas.cljc b/common/src/app/common/geom/shapes/grid_layout/areas.cljc new file mode 100644 index 000000000..569c52470 --- /dev/null +++ b/common/src/app/common/geom/shapes/grid_layout/areas.cljc @@ -0,0 +1,95 @@ +;; 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 + +;; Based on the code in: +;; https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Rectangle_difference +(ns app.common.geom.shapes.grid-layout.areas + (:refer-clojure :exclude [contains?])) + +(defn area->cell-props [[column row column-span row-span]] + {:row row + :column column + :row-span row-span + :column-span column-span}) + +(defn make-area + ([{:keys [column row column-span row-span]}] + (make-area column row column-span row-span)) + ([x y width height] + [x y width height])) + +(defn contains? + [[a-x a-y a-width a-height :as a] + [b-x b-y b-width b-height :as b]] + (and (>= b-x a-x) + (>= b-y a-y) + (<= (+ b-x b-width) (+ a-x a-width)) + (<= (+ b-y b-height) (+ a-y a-height)))) + +(defn intersects? + [[a-x a-y a-width a-height ] + [b-x b-y b-width b-height]] + (not (or (<= (+ b-x b-width) a-x) + (<= (+ b-y b-height) a-y) + (>= b-x (+ a-x a-width)) + (>= b-y (+ a-y a-height))))) + +(defn top-rect + [[a-x a-y a-width _] + [_ b-y _ _]] + (let [height (- b-y a-y)] + (when (> height 0) + (make-area a-x a-y a-width height)))) + +(defn bottom-rect + [[a-x a-y a-width a-height] + [_ b-y _ b-height]] + + (let [y (+ b-y b-height) + height (- a-height (- y a-y))] + (when (and (> height 0) (< y (+ a-y a-height))) + (make-area a-x y a-width height)))) + +(defn left-rect + [[a-x a-y _ a-height] + [b-x b-y _ b-height]] + + (let [rb-y (+ b-y b-height) + ra-y (+ a-y a-height) + y1 (max a-y b-y) + y2 (min ra-y rb-y) + height (- y2 y1) + width (- b-x a-x)] + (when (and (> width 0) (> height 0)) + (make-area a-x y1 width height)))) + +(defn right-rect + [[a-x a-y a-width a-height] + [b-x b-y b-width b-height]] + + (let [rb-y (+ b-y b-height) + ra-y (+ a-y a-height) + y1 (max a-y b-y) + y2 (min ra-y rb-y) + height (- y2 y1) + rb-x (+ b-x b-width) + width (- a-width (- rb-x a-x)) + ] + (when (and (> width 0) (> height 0)) + (make-area rb-x y1 width height))) + ) + +(defn difference + [area-a area-b] + (if (or (nil? area-b) + (not (intersects? area-a area-b)) + (contains? area-b area-a)) + [] + + (into [] + (keep #(% area-a area-b)) + [top-rect left-rect right-rect bottom-rect]))) + diff --git a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc index ebc1b5b76..79de30499 100644 --- a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc @@ -49,8 +49,6 @@ ;; ;; - Stretch auto tracks - - (ns app.common.geom.shapes.grid-layout.layout-data (:require [app.common.geom.point :as gpt] @@ -62,47 +60,6 @@ (let [[pad-top pad-right pad-bottom pad-left] (ctl/paddings parent)] (gpo/pad-points shape-bounds pad-top pad-right pad-bottom pad-left))) -#_(defn set-sample-data - [parent children] - - (let [parent (assoc parent - :layout-grid-columns - [{:type :percent :value 25} - {:type :percent :value 25} - {:type :fixed :value 100} - ;;{:type :auto} - ;;{:type :flex :value 1} - ] - - :layout-grid-rows - [{:type :percent :value 50} - {:type :percent :value 50} - ;;{:type :fixed :value 100} - ;;{:type :auto} - ;;{:type :flex :value 1} - ]) - - num-rows (count (:layout-grid-rows parent)) - num-columns (count (:layout-grid-columns parent)) - - layout-grid-cells - (into - {} - (for [[row-idx _row] (d/enumerate (:layout-grid-rows parent)) - [col-idx _col] (d/enumerate (:layout-grid-columns parent))] - (let [[_bounds shape] (nth children (+ (* row-idx num-columns) col-idx) nil) - cell-data {:id (uuid/next) - :row (inc row-idx) - :column (inc col-idx) - :row-span 1 - :col-span 1 - :shapes (when shape [(:id shape)])}] - [(:id cell-data) cell-data]))) - - parent (assoc parent :layout-grid-cells layout-grid-cells)] - - [parent children])) - (defn calculate-initial-track-size [total-value {:keys [type value] :as track}] @@ -119,7 +76,6 @@ [0.01 ##Inf])] (assoc track :size size :max-size max-size))) - (defn set-auto-base-size [track-list children shape-cells type] @@ -280,7 +236,6 @@ (gpt/add start-p (vv (+ size row-gap)))]) [[] start-p]) (first)) - ] {:origin start-p @@ -288,6 +243,8 @@ :row-tracks row-tracks :column-tracks column-tracks :shape-cells shape-cells + :column-gap column-gap + :row-gap row-gap ;; Convenient informaton for visualization :column-total-size column-total-size @@ -300,7 +257,6 @@ [{:keys [origin row-tracks column-tracks shape-cells]} _transformed-parent-bounds [_ child]] (let [grid-cell (get shape-cells (:id child))] - (when (some? grid-cell) (let [column (nth column-tracks (dec (:column grid-cell)) nil) row (nth row-tracks (dec (:row grid-cell)) nil) @@ -311,7 +267,7 @@ start-p (gpt/add origin (gpt/add (gpt/to-vec origin column-start-p) - (gpt/to-vec origin row-start-p))) - ] + (gpt/to-vec origin row-start-p)))] (assoc grid-cell :start-p start-p))))) + diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 386a384c8..165f55563 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.shapes.grid-layout.areas :as sga] [app.common.math :as mth] [app.common.schema :as sm] [app.common.uuid :as uuid])) @@ -673,12 +674,6 @@ (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 -(defn merge-cells - [parent _cells] - parent) - (defn get-cells ([parent] (get-cells parent nil)) @@ -815,31 +810,128 @@ [(assoc-in parent [:layout-grid-cells (get-in cells [index :id]) :shapes] []) (assoc-in result-cells [index :shapes] [])])))) + +(defn in-cell? + "Given a cell check if the row+column is inside this cell" + [{cell-row :row cell-column :column :keys [row-span column-span]} row column] + (and (>= row cell-row) + (>= column cell-column) + (<= row (+ cell-row row-span -1)) + (<= column (+ cell-column column-span -1)))) + +(defn cell-by-row-column + [parent row column] + (->> (:layout-grid-cells parent) + (vals) + (d/seek #(in-cell? % row column)))) + (defn seek-indexed-cell [cells row column] (let [cells+index (d/enumerate cells)] - (d/seek (fn [[_ {cell-row :row cell-column :column}]] - (and (= cell-row row) - (= cell-column column))) cells+index))) + (d/seek #(in-cell? (second %) row column) cells+index))) (defn push-into-cell "Push the shapes into the row/column cell and moves the rest" [parent shape-ids row column] (let [cells (vec (get-cells parent {:sort? true})) - cells+index (d/enumerate cells) + [start-index start-cell] (seek-indexed-cell cells row column)] - [start-index _] (seek-indexed-cell cells row column) + (if (some? start-cell) + (let [ ;; start-index => to-index is the range where the shapes inserted will be added + to-index (min (+ start-index (count shape-ids)) (dec (count cells)))] - ;; start-index => to-index is the range where the shapes inserted will be added - to-index (min (+ start-index (count shape-ids)) (dec (count cells)))] + ;; Move shift the `shapes` attribute between cells + (->> (range start-index (inc to-index)) + (map vector shape-ids) + (reduce (fn [[parent cells] [shape-id idx]] + (let [[parent cells] (free-cell-push parent cells idx)] + [(assoc-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes] [shape-id]) + cells])) + [parent cells]) + (first))) + parent))) - ;; Move shift the `shapes` attribute between cells - (->> (range start-index (inc to-index)) - (map vector shape-ids) - (reduce (fn [[parent cells] [shape-id idx]] - (let [[parent cells] (free-cell-push parent cells idx)] - [(assoc-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes] [shape-id]) - cells])) - [parent cells]) - (first)))) +(defn resize-cell-area + "Increases/decreases the cell size" + [parent row column new-row new-column new-row-span new-column-span] + + (let [cells (vec (get-cells parent {:sort? true})) + + prev-cell (cell-by-row-column parent row column) + prev-area (sga/make-area prev-cell) + + target-cell + (-> prev-cell + (assoc + :row new-row + :column new-column + :row-span new-row-span + :column-span new-column-span)) + + target-area (sga/make-area target-cell)] + + (if (sga/contains? prev-area target-area) + ;; The new area is smaller than the previous. We need to create cells in the empty space + (let [parent + (-> parent + (assoc-in [:layout-grid-cells (:id target-cell)] target-cell)) + + new-cells + (->> (sga/difference prev-area target-area) + (mapcat (fn [[column row column-span row-span]] + (for [new-col (range column (+ column column-span)) + new-row (range row (+ row row-span))] + (merge grid-cell-defaults + {:id (uuid/next) + :row new-row + :column new-col + :row-span 1 + :column-span 1}))))) + + parent + (->> new-cells + (reduce #(assoc-in %1 [:layout-grid-cells (:id %2)] %2) parent))] + + parent) + + ;; The new area is bigger we need to remove the cells filled and split the intersections + (let [remove-cells (->> cells + (filter #(and (not= (:id target-cell) (:id %)) + (sga/contains? target-area (sga/make-area %)))) + (into #{})) + + split-cells (->> cells (filter #(and (not= (:id target-cell) (:id %)) + (not (contains? remove-cells %)) + (sga/intersects? target-area (sga/make-area %))))) + + [parent _] + (->> (d/enumerate cells) + (reduce (fn [[parent cells] [index cur-cell]] + (if (contains? remove-cells cur-cell) + (let [[parent cells] (free-cell-push parent cells index)] + [parent (conj cells cur-cell)]) + [parent cells])) + [parent cells])) + + parent + (-> parent + (assoc-in [:layout-grid-cells (:id target-cell)] target-cell)) + + parent + (->> remove-cells + (reduce (fn [parent cell] + (update parent :layout-grid-cells dissoc (:id cell))) + parent)) + + parent + (->> split-cells + (reduce (fn [parent cell] + (let [new-areas (sga/difference (sga/make-area cell) target-area)] + (as-> parent $ + (update-in $ [:layout-grid-cells (:id cell)] merge (sga/area->cell-props (first new-areas))) + (reduce (fn [parent area] + (let [cell (merge (assoc grid-cell-defaults :id (uuid/next)) (sga/area->cell-props area))] + (assoc-in parent [:layout-grid-cells (:id cell)] cell))) $ new-areas)))) + parent))] + parent)))) diff --git a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs index 6f9bf9a8e..2a7710366 100644 --- a/frontend/src/app/main/data/workspace/grid_layout/editor.cljs +++ b/frontend/src/app/main/data/workspace/grid_layout/editor.cljs @@ -9,7 +9,7 @@ [potok.core :as ptk])) (defn hover-grid-cell - [grid-id row column add-to-set] + [grid-id cell-id add-to-set] (ptk/reify ::hover-grid-cell ptk/UpdateEvent (update [_ state] @@ -19,15 +19,15 @@ (fn [hover-set] (let [hover-set (or hover-set #{})] (if add-to-set - (conj hover-set [row column]) - (disj hover-set [row column])))))))) + (conj hover-set cell-id) + (disj hover-set cell-id)))))))) (defn select-grid-cell - [grid-id row column] + [grid-id cell-id] (ptk/reify ::select-grid-cell ptk/UpdateEvent (update [_ state] - (assoc-in state [:workspace-grid-edition grid-id :selected] [row column])))) + (assoc-in state [:workspace-grid-edition grid-id :selected] cell-id)))) (defn remove-selection [grid-id] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index efe244bcd..16ce75b7b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -73,10 +73,10 @@ first-selected-shape (first selected-shapes) shape-parent-frame (cph/get-frame objects (:frame-id first-selected-shape)) - [grid-id {[row-selected col-selected] :selected}] + [grid-id {cell-id :selected}] (d/seek (fn [[_ {:keys [selected]}]] (some? selected)) grid-edition) - grid-cell-selected? (and (some? grid-id) (some? row-selected) (some? col-selected)) + grid-cell-selected? (and (some? grid-id) (some? cell-id)) on-change-tab (fn [options-mode] @@ -96,8 +96,7 @@ [:& bool-options] (cond grid-cell-selected? [:& grid-cell/options {:shape (get objects grid-id) - :row row-selected - :column col-selected}] + :cell (get-in objects [grid-id :layout-grid-cells cell-id])}] (d/not-empty? drawing) [:& shape-options {:shape (:object drawing) :page-id page-id diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index 8e6f02eb6..a4e9979e4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -447,7 +447,7 @@ (when expanded? [:div.columns-info-wrapper (for [[index column] (d/enumerate column-values)] - [:div.column-info + [:div.column-info {:key (dm/str index "-" (name type) "-" column)} [:div.direction-grid-icon (if is-col? i/layout-rows diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs index 2d02bd604..115300595 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/grid_cell.cljs @@ -31,7 +31,7 @@ (mf/defc options {::mf/wrap [mf/memo]} - [{:keys [_shape row column] :as props}] + [{:keys [_shape cell] :as props}] (let [position-mode (mf/use-state :auto) ;; TODO this should come from shape @@ -51,10 +51,10 @@ #_(if (= align-self value) (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil})) (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value})))) - column-start column - column-end (inc column) - row-start row - row-end (inc row) + column-start (:column cell) + column-end (+ (:column cell) (:column-span cell)) + row-start (:row cell) + row-end (+ (:row cell) (:row-span cell)) on-change (fn [_side _orientation _value] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index d84b525df..48ebc40b7 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -203,6 +203,9 @@ show-pixel-grid? (and (contains? layout :show-pixel-grid) (>= zoom 8)) show-text-editor? (and editing-shape (= :text (:type editing-shape))) + + hover-grid? (and (some? @hover-top-frame-id) (ctl/grid-layout? objects @hover-top-frame-id)) + show-grid-editor? (and editing-shape (ctl/grid-layout? editing-shape)) show-presence? page-id show-prototypes? (= options-mode :prototype) @@ -586,8 +589,15 @@ {:id (first selected) :zoom zoom}]) - (when show-grid-editor? + ;; TODO: Temporary showing on hover. Remove eventualy + (when (or show-grid-editor? hover-grid?) [:& grid-layout/editor {:zoom zoom - :objects base-objects - :shape (get base-objects edition)}])]]])) + :objects objects-modified + :shape (or (get objects-modified edition) + (gsh/transform-shape + (get base-objects @hover-top-frame-id) + (dm/get-in modifiers [@hover-top-frame-id :modifiers]))) + :view-only (not show-grid-editor?) + }]) + ]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs index 995b67645..dee2f761c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/grid_layout_editor.cljs @@ -11,9 +11,10 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.grid-layout :as gsg] [app.common.geom.shapes.points :as gpo] - [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.grid-layout.editor :as dwge] + [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] [app.main.store :as st] @@ -145,8 +146,7 @@ (defn use-drag [{:keys [on-drag-start on-drag-end on-drag-delta on-drag-position]}] - (let [ - dragging-ref (mf/use-ref false) + (let [dragging-ref (mf/use-ref false) start-pos-ref (mf/use-ref nil) current-pos-ref (mf/use-ref nil) @@ -175,6 +175,7 @@ handle-pointer-move (mf/use-callback + (mf/deps on-drag-delta on-drag-position) (fn [event] (when (mf/ref-val dragging-ref) (let [start (mf/ref-val start-pos-ref) @@ -191,22 +192,67 @@ (mf/defc resize-cell-handler {::mf/wrap-props false} [props] - (let [x (unchecked-get props "x") + (let [shape (unchecked-get props "shape") + + x (unchecked-get props "x") y (unchecked-get props "y") width (unchecked-get props "width") height (unchecked-get props "height") + handler (unchecked-get props "handler") + + {cell-id :id} (unchecked-get props "cell") + {:keys [row column row-span column-span]} (get-in shape [:layout-grid-cells cell-id]) + direction (unchecked-get props "direction") layout-data (unchecked-get props "layout-data") cursor (if (= direction :row) (cur/scale-ns 0) (cur/scale-ew 0)) handle-drag-position (mf/use-callback + (mf/deps shape row column row-span column-span) (fn [position] - (prn ">>>" (gsg/get-position-grid-coord layout-data position)))) + (let [[drag-row drag-column] (gsg/get-position-grid-coord layout-data position) + + [new-row new-column new-row-span new-column-span] + (case handler + :top + (let [new-row (min (+ row (dec row-span)) drag-row) + new-row-span (+ (- row new-row) row-span)] + [new-row column new-row-span column-span]) + + :left + (let [new-column (min (+ column (dec column-span)) drag-column) + new-column-span (+ (- column new-column) column-span)] + [row new-column row-span new-column-span]) + + :bottom + (let [new-row-span (max 1 (inc (- drag-row row)))] + [row column new-row-span column-span]) + + :right + (let [new-column-span (max 1 (inc (- drag-column column)))] + [row column row-span new-column-span])) + + shape + (-> (ctl/resize-cell-area shape row column new-row new-column new-row-span new-column-span) + (ctl/assign-cells)) + + modifiers + (-> (ctm/empty) + (ctm/change-property :layout-grid-rows (:layout-grid-rows shape)) + (ctm/change-property :layout-grid-columns (:layout-grid-columns shape)) + (ctm/change-property :layout-grid-cells (:layout-grid-cells shape)))] + (st/emit! (dwm/set-modifiers (dwm/create-modif-tree [(:id shape)] modifiers)))))) + + handle-drag-end + (mf/use-callback + (fn [] + (st/emit! (dwm/apply-modifiers)))) {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} - (use-drag {:on-drag-position handle-drag-position})] - + (use-drag {:on-drag-position handle-drag-position + ;;:on-drag-start handle-drag-start + :on-drag-end handle-drag-end})] [:rect {:x x :y y @@ -223,7 +269,8 @@ [props] (let [shape (unchecked-get props "shape") - {:keys [origin row-tracks column-tracks layout-bounds] :as layout-data} + {:keys [row column row-span column-span] :as cell} (unchecked-get props "cell") + {:keys [origin row-tracks column-tracks layout-bounds column-gap row-gap] :as layout-data} (unchecked-get props "layout-data") zoom (unchecked-get props "zoom") @@ -231,23 +278,23 @@ hover? (unchecked-get props "hover?") selected? (unchecked-get props "selected?") - row (unchecked-get props "row") - column (unchecked-get props "column") - - column-track (nth column-tracks (dec column) nil) - row-track (nth row-tracks (dec row) nil) + span-column-tracks (subvec column-tracks (dec column) (+ (dec column) column-span)) + span-row-tracks (subvec row-tracks (dec row) (+ (dec row) row-span)) hv #(gpo/start-hv layout-bounds %) vv #(gpo/start-vv layout-bounds %) start-p (gpt/add origin (gpt/add - (gpt/to-vec origin (:start-p column-track)) - (gpt/to-vec origin (:start-p row-track)))) + (gpt/to-vec origin (dm/get-in span-column-tracks [0 :start-p])) + (gpt/to-vec origin (dm/get-in span-row-tracks [0 :start-p])))) - end-p (-> start-p - (gpt/add (hv (:size column-track))) - (gpt/add (vv (:size row-track)))) + end-p + (as-> start-p $ + (reduce (fn [p track] (gpt/add p (hv (:size track)))) $ span-column-tracks) + (reduce (fn [p track] (gpt/add p (vv (:size track)))) $ span-row-tracks) + (gpt/add $ (hv (* column-gap (dec (count span-column-tracks))))) + (gpt/add $ (vv (* row-gap (dec (count span-row-tracks)))))) cell-width (- (:x end-p) (:x start-p)) cell-height (- (:y end-p) (:y start-p))] @@ -259,10 +306,9 @@ :width cell-width :height cell-height - :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)) - - :on-click #(st/emit! (dwge/select-grid-cell (:id shape) row column)) + :on-pointer-enter #(st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) true)) + :on-pointer-leave #(st/emit! (dwge/hover-grid-cell (:id shape) (:id cell) false)) + :on-click #(st/emit! (dwge/select-grid-cell (:id shape) (:id cell))) :style {:fill "transparent" :stroke "var(--color-distance)" @@ -274,14 +320,18 @@ (when selected? (let [handlers ;; Handlers positions, size and cursor - [[(:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) :row] - [(+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column] - [(:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) :row] - [(+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column]]] + [[:top (:x start-p) (+ (:y start-p) (/ -10 zoom)) cell-width (/ 20 zoom) :row] + [:right (+ (:x start-p) cell-width (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column] + [:bottom (:x start-p) (+ (:y start-p) cell-height (/ -10 zoom)) cell-width (/ 20 zoom) :row] + [:left (+ (:x start-p) (/ -10 zoom)) (:y start-p) (/ 20 zoom) cell-height :column]]] [:* - (for [[x y width height dir] handlers] - [:& resize-cell-handler {:x x + (for [[handler x y width height dir] handlers] + [:& resize-cell-handler {:key (dm/str "resize-" (d/name handler) "-" (:id cell)) + :shape shape + :handler handler + :x x :y y + :cell cell :width width :height height :direction dir @@ -355,21 +405,28 @@ :style {:fill "transparent"}}])) (mf/defc editor - {::mf/wrap-props false} + {::mf/wrap [mf/memo] + ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - zoom (unchecked-get props "zoom") + (let [shape (unchecked-get props "shape") + objects (unchecked-get props "objects") + zoom (unchecked-get props "zoom") + view-only (unchecked-get props "view-only") bounds (:points shape) + ;; We need to know the state unmodified so we can create the modifiers + shape-ref (mf/use-memo (mf/deps (:id shape)) #(refs/object-by-id (:id shape))) + base-shape (mf/deref shape-ref) + grid-edition-id-ref (mf/use-memo #(refs/workspace-grid-edition-id (:id shape))) grid-edition (mf/deref grid-edition-id-ref) hover-cells (:hover grid-edition) selected-cells (:selected grid-edition) - children (->> (cph/get-immediate-children objects (:id shape)) + children (->> (:shapes shape) + (map (d/getf objects)) (remove :hidden) (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) @@ -400,22 +457,22 @@ (fn [] #(st/emit! (dwge/stop-grid-layout-editing (:id shape))))) - [:g.grid-editor - [:& grid-editor-frame {:zoom zoom - :bounds bounds}] - (let [start-p (-> origin (gpt/add (hv width)))] - [:& plus-btn {:start-p start-p - :zoom zoom - :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 - :on-click handle-add-row}]) - + [:g.grid-editor {:pointer-events (when view-only "none")} + (when-not view-only + [:* + [:& grid-editor-frame {:zoom zoom + :bounds bounds}] + (let [start-p (-> origin (gpt/add (hv width)))] + [:& plus-btn {:start-p start-p + :zoom zoom + :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 + :on-click handle-add-row}])]) (for [[idx column-data] (d/enumerate column-tracks)] (let [start-p (:start-p column-data) @@ -427,7 +484,7 @@ text-p (-> start-p (gpt/subtract (vv (/ 20 zoom))) (gpt/add (hv (/ (:size column-data) 2))))] - [:* + [:* {:key (dm/str "column-" idx)} [:& track-marker {:center marker-p :value (dm/str (inc idx)) :zoom zoom}] @@ -454,12 +511,12 @@ marker-p (-> start-p (gpt/subtract (hv (/ 20 zoom))) (cond-> (not= idx 0) - (gpt/subtract (vv (/ layout-gap-row 2))))) + (gpt/subtract (vv (/ layout-gap-row 2))))) text-p (-> start-p (gpt/subtract (hv (/ 20 zoom))) (gpt/add (vv (/ (:size row-data) 2))))] - [:* + [:* {:key (dm/str "row-" idx)} [:g {:transform (dm/fmt "rotate(-90 % %)" (:x marker-p) (:y marker-p))} [:& track-marker {:center marker-p :value (dm/str (inc idx)) @@ -483,11 +540,11 @@ :type :column :zoom zoom}])])) - (for [[_ {:keys [column row]}] (:layout-grid-cells shape)] - [:& grid-cell {:shape shape + (for [[_ cell] (:layout-grid-cells shape)] + [:& grid-cell {:key (dm/str "cell-" (:id cell)) + :shape base-shape :layout-data layout-data - :row row - :column column + :cell cell :zoom zoom - :hover? (contains? hover-cells [row column]) - :selected? (= selected-cells [row column])}])])) + :hover? (contains? hover-cells (:id cell)) + :selected? (= selected-cells (:id cell))}])]))