diff --git a/common/src/app/common/geom/shapes/grid_layout.cljc b/common/src/app/common/geom/shapes/grid_layout.cljc index eb45960f8..cb28f3ab7 100644 --- a/common/src/app/common/geom/shapes/grid_layout.cljc +++ b/common/src/app/common/geom/shapes/grid_layout.cljc @@ -13,7 +13,5 @@ (dm/export glld/calc-layout-data) (dm/export glld/get-cell-data) (dm/export glp/child-modifiers) - -(defn get-drop-index - [frame objects _position] - (dec (count (get-in objects [frame :shapes])))) +(dm/export glp/get-position-grid-coord) +(dm/export glp/get-drop-cell) 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 f45fb7326..ebc1b5b76 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 @@ -131,7 +131,7 @@ (reduce (fn [tracks [child-bounds child-shape]] (let [cell (get shape-cells (:id child-shape)) idx (dec (get cell prop)) - track (nth tracks idx)] + track (get tracks idx)] (cond-> tracks (and (= (get cell prop-span) 1) (= :auto (:type track))) (update-in [idx :size] max (size-fn child-bounds))))) @@ -190,9 +190,6 @@ bound-width (gpo/width-points layout-bounds) bound-corner (gpo/origin layout-bounds) - grid-columns (:layout-grid-columns parent) - grid-rows (:layout-grid-rows parent) - [row-gap column-gap] (ctl/gaps parent) ;; Map shape->cell @@ -205,11 +202,11 @@ ;; Initialize tracks column-tracks - (->> grid-columns + (->> (:layout-grid-columns parent) (mapv (partial calculate-initial-track-size bound-width))) row-tracks - (->> grid-rows + (->> (:layout-grid-rows parent) (mapv (partial calculate-initial-track-size bound-height))) ;; Go through cells to adjust auto sizes for span=1. Base is the max of its children diff --git a/common/src/app/common/geom/shapes/grid_layout/positions.cljc b/common/src/app/common/geom/shapes/grid_layout/positions.cljc index 3f81928b4..3dcdeba33 100644 --- a/common/src/app/common/geom/shapes/grid_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/positions.cljc @@ -6,11 +6,91 @@ (ns app.common.geom.shapes.grid-layout.positions (:require + [app.common.data :as d] + [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] + [app.common.geom.shapes.grid-layout.layout-data :as ld] [app.common.geom.shapes.points :as gpo] + [app.common.math :as mth] + [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm])) (defn child-modifiers [_parent _transformed-parent-bounds _child child-bounds cell-data] (ctm/move-modifiers (gpt/subtract (:start-p cell-data) (gpo/origin child-bounds)))) + + +(defn line-value + [[{px :x py :y} {vx :x vy :y}] {:keys [x y]}] + (let [a vy + b (- vx) + c (+ (* (- vy) px) (* vx py))] + (+ (* a x) (* b y) c))) + +(defn is-inside-lines? + [line-1 line-2 pos] + (< (* (line-value line-1 pos) (line-value line-2 pos)) 0)) + +(defn get-position-grid-coord + [{:keys [layout-bounds row-tracks column-tracks]} position] + + (let [hv #(gpo/start-hv layout-bounds %) + vv #(gpo/start-vv layout-bounds %) + + ;;make-is-inside-track + ;;(fn [type] + ;; (let [[vfn ofn] (if (= type :column) [vv hv] [hv vv])] + ;; (fn is-inside-track? [{:keys [start-p size] :as track}] + ;; (let [unit-v (vfn 1) + ;; end-p (gpt/add start-p (ofn size))] + ;; (is-inside-lines? [start-p unit-v] [end-p unit-v] position))))) + + make-min-distance-track + (fn [type] + (let [[vfn ofn] (if (= type :column) [vv hv] [hv vv])] + (fn [[selected selected-dist] [cur-idx {:keys [start-p size] :as track}]] + (let [unit-v (vfn 1) + end-p (gpt/add start-p (ofn size)) + dist-1 (mth/abs (line-value [start-p unit-v] position)) + dist-2 (mth/abs (line-value [end-p unit-v] position))] + + (if (or (< dist-1 selected-dist) (< dist-2 selected-dist)) + [[cur-idx track] (min dist-1 dist-2)] + [selected selected-dist]))))) + + ;;[col-idx column] + ;;(->> (d/enumerate column-tracks) + ;; (d/seek (comp (make-is-inside-track :column) second))) + ;; + ;;[row-idx row] + ;;(->> (d/enumerate row-tracks) + ;; (d/seek (comp (make-is-inside-track :row) second))) + + + [col-idx column] + (->> (d/enumerate column-tracks) + (reduce (make-min-distance-track :column) [[nil nil] ##Inf]) + (first)) + + [row-idx row] + (->> (d/enumerate row-tracks) + (reduce (make-min-distance-track :row) [[nil nil] ##Inf]) + (first)) + ] + + (when (and (some? column) (some? row)) + [(inc row-idx) (inc col-idx)]))) + +(defn get-drop-cell + [frame-id objects position] + + (let [frame (get objects frame-id) + children (->> (cph/get-immediate-children objects (:id frame)) + (remove :hidden) + (map #(vector (gpo/parent-coords-bounds (:points %) (:points frame)) %))) + layout-data (ld/calc-layout-data frame children (:points frame)) + position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame))] + + (get-position-grid-coord layout-data position))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 8fc029b43..386a384c8 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -679,6 +679,22 @@ [parent _cells] parent) +(defn get-cells + ([parent] + (get-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 + (maybe-sort?) + (map (fn [[id cell]] (assoc cell :id id))))))) + (defn get-free-cells ([parent] (get-free-cells parent nil)) @@ -700,7 +716,7 @@ "Clean the cells whith shapes that are no longer in the layout" [parent] - (let [child? (into #{} (:shapes parent)) + (let [child? (set (:shapes parent)) cells (update-vals (:layout-grid-cells parent) (fn [cell] (update cell :shapes #(filterv child? %))))] @@ -767,3 +783,63 @@ (recur cells (rest free-cells) (rest pending)))))] (assoc parent :layout-grid-cells cells))))) + +(defn free-cell-push + "Frees the cell at index and push the shapes in the order given by the `cells` attribute" + [parent cells index] + + (let [start-cell (get cells index)] + (if (empty? (:shapes start-cell)) + [parent cells] + (let [[parent result-cells] + (loop [parent parent + result-cells cells + idx index] + + (if (> idx (- (count cells) 2)) + [parent result-cells] + + (let [cell-from (get cells idx) + cell-to (get cells (inc idx)) + cell (assoc cell-to :shapes (:shapes cell-from)) + parent (assoc-in parent [:layout-grid-cells (:id cell)] cell) + result-cells (assoc result-cells (inc idx) cell)] + + (if (empty? (:shapes cell-to)) + ;; to-cell was empty, so we've finished and every cell allocated + [parent result-cells] + + ;; otherwise keep pushing cells + (recur parent result-cells (inc idx))))))] + + [(assoc-in parent [:layout-grid-cells (get-in cells [index :id]) :shapes] []) + (assoc-in result-cells [index :shapes] [])])))) + +(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))) + +(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 _] (seek-indexed-cell cells row column) + + ;; 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)))) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index a27f9c4d1..bf9d8c728 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -175,8 +175,28 @@ (update-in modif-tree [parent-id :modifiers] ctm/remove-children [child-id]))) modif-tree))) +(defn add-grid-children-modifiers + [modifiers frame-id selected objects [row column :as cell]] + (let [frame (get objects frame-id) + + ;; Temporary remove the children when moving them + frame (-> frame + (update :shapes #(d/removev selected %)) + (ctl/assign-cells)) + + frame (-> frame + (update :shapes d/concat-vec selected) + (cond-> (some? cell) + (ctl/push-into-cell selected row column)) + (ctl/assign-cells))] + + (-> modifiers + (ctm/change-property :layout-grid-rows (:layout-grid-rows frame)) + (ctm/change-property :layout-grid-columns (:layout-grid-columns frame)) + (ctm/change-property :layout-grid-cells (:layout-grid-cells frame))))) + (defn build-change-frame-modifiers - [modif-tree objects selected target-frame-id drop-index] + [modif-tree objects selected target-frame-id drop-index cell-data] (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) child-set (set (get-in objects [target-frame-id :shapes])) @@ -209,8 +229,10 @@ children-ids (->> (dm/get-in objects [original-frame :shapes]) (remove (set selected))) - h-sizing? (ctl/change-h-sizing? original-frame objects children-ids) - v-sizing? (ctl/change-v-sizing? original-frame objects children-ids)] + h-sizing? (and (ctl/flex-layout? objects original-frame) + (ctl/change-h-sizing? original-frame objects children-ids)) + v-sizing? (and (ctl/flex-layout? objects original-frame) + (ctl/change-v-sizing? original-frame objects children-ids))] (cond-> modif-tree (not= original-frame target-frame-id) (-> (modifier-remove-from-parent objects shapes) @@ -227,11 +249,19 @@ (as-> modif-tree $ (reduce update-frame-modifiers $ origin-frame-ids) (cond-> $ - (ctl/change-h-sizing? target-frame-id objects children-ids) - (update-in [target-frame-id :modifiers] ctm/change-property :layout-item-h-sizing :fix)) - (cond-> $ - (ctl/change-v-sizing? target-frame-id objects children-ids) - (update-in [target-frame-id :modifiers] ctm/change-property :layout-item-v-sizing :fix))))) + ;; Set fix position to target frame (horizontal) + (and (ctl/flex-layout? objects target-frame-id) + (ctl/change-h-sizing? target-frame-id objects children-ids)) + (update-in [target-frame-id :modifiers] ctm/change-property :layout-item-h-sizing :fix) + + ;; Set fix position to target frame (vertical) + (and (ctl/flex-layout? objects target-frame-id) + (ctl/change-v-sizing? target-frame-id objects children-ids)) + (update-in [target-frame-id :modifiers] ctm/change-property :layout-item-v-sizing :fix) + + ;; Add the object to the cell + (ctl/grid-layout? objects target-frame-id) + (update-in [target-frame-id :modifiers] add-grid-children-modifiers target-frame-id selected objects cell-data))))) (defn modif->js [modif-tree objects] @@ -454,6 +484,9 @@ :layout-gap :layout-item-margin :layout-item-margin-type + :layout-grid-cells + :layout-grid-columns + :layout-grid-rows ]}) ;; We've applied the text-modifier so we can dissoc the temporary data (fn [state] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 862cb4599..125f12cea 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -490,10 +490,9 @@ target-frame (ctst/top-nested-frame objects position exclude-frames) flex-layout? (ctl/flex-layout? objects target-frame) grid-layout? (ctl/grid-layout? objects target-frame) - drop-index (cond - flex-layout? (gslf/get-drop-index target-frame objects position) - grid-layout? (gslg/get-drop-index target-frame objects position))] - [move-vector target-frame drop-index]))) + 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))] + [move-vector target-frame drop-index cell-data]))) (rx/take-until stopper))] @@ -502,7 +501,7 @@ (->> move-stream (rx/with-latest-from ms/mouse-position-shift) (rx/map - (fn [[[move-vector target-frame drop-index] shift?]] + (fn [[[move-vector target-frame drop-index cell-data] shift?]] (let [x-disp? (> (mth/abs (:x move-vector)) (mth/abs (:y move-vector))) [move-vector snap-ignore-axis] (cond @@ -516,7 +515,7 @@ [move-vector nil])] (-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector)) - (dwm/build-change-frame-modifiers objects selected target-frame drop-index) + (dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data) (dwm/set-modifiers false false {:snap-ignore-axis snap-ignore-axis})))))) (->> move-stream 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 af7897717..995b67645 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 @@ -17,6 +17,7 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.workspace.viewport.viewport-ref :as uwvv] [app.util.dom :as dom] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -153,18 +154,20 @@ (mf/use-callback (mf/deps on-drag-start) (fn [event] - (let [position (dom/get-client-position event)] + (let [raw-pt (dom/get-client-position event) + position (uwvv/point->viewport raw-pt)] (dom/capture-pointer event) (mf/set-ref-val! dragging-ref true) - (mf/set-ref-val! start-pos-ref position) - (mf/set-ref-val! current-pos-ref position) + (mf/set-ref-val! start-pos-ref raw-pt) + (mf/set-ref-val! current-pos-ref raw-pt) (when on-drag-start (on-drag-start position))))) handle-lost-pointer-capture (mf/use-callback (mf/deps on-drag-end) (fn [event] - (let [position (mf/ref-val current-pos-ref)] + (let [raw-pt (mf/ref-val current-pos-ref) + position (uwvv/point->viewport raw-pt)] (dom/release-pointer event) (mf/set-ref-val! dragging-ref false) (mf/set-ref-val! start-pos-ref nil) @@ -175,10 +178,11 @@ (fn [event] (when (mf/ref-val dragging-ref) (let [start (mf/ref-val start-pos-ref) - pos (dom/get-client-position event)] + pos (dom/get-client-position event) + pt (uwvv/point->viewport pos)] (mf/set-ref-val! current-pos-ref pos) (when on-drag-delta (on-drag-delta (gpt/to-vec start pos))) - (when on-drag-position (on-drag-position pos))))))] + (when on-drag-position (on-drag-position pt))))))] {:handle-pointer-down handle-pointer-down :handle-lost-pointer-capture handle-lost-pointer-capture @@ -192,15 +196,16 @@ width (unchecked-get props "width") height (unchecked-get props "height") 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-delta + handle-drag-position (mf/use-callback - (fn [delta] - (prn ">>>" delta))) + (fn [position] + (prn ">>>" (gsg/get-position-grid-coord layout-data position)))) {:keys [handle-pointer-down handle-lost-pointer-capture handle-pointer-move]} - (use-drag {:on-drag-delta handle-drag-delta})] + (use-drag {:on-drag-position handle-drag-position})] [:rect {:x x @@ -218,7 +223,8 @@ [props] (let [shape (unchecked-get props "shape") - {:keys [origin row-tracks column-tracks layout-bounds]} (unchecked-get props "layout-data") + {:keys [origin row-tracks column-tracks layout-bounds] :as layout-data} + (unchecked-get props "layout-data") zoom (unchecked-get props "zoom") @@ -278,7 +284,8 @@ :y y :width width :height height - :direction dir}])]))])) + :direction dir + :layout-data layout-data}])]))])) (mf/defc resize-handler {::mf/wrap-props false}