From 351f7fd1bb893075de7a3c901b5ad5e6944b8bc8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" <alonso.torres@kaleidos.net> Date: Tue, 24 Oct 2023 16:58:28 +0200 Subject: [PATCH] :sparkles: Create grid from selection --- .../common/geom/{shapes => }/modifiers.cljc | 2 +- common/src/app/common/geom/shapes.cljc | 4 - .../app/common/geom/shapes/flex_layout.cljc | 4 +- .../geom/shapes/flex_layout/params.cljc | 97 +++++++ .../app/common/geom/shapes/grid_layout.cljc | 2 + .../geom/shapes/grid_layout/layout_data.cljc | 2 +- .../geom/shapes/grid_layout/params.cljc | 166 +++++++++++ common/src/app/common/pages/changes.cljc | 2 +- common/src/app/common/types/shape_tree.cljc | 3 +- .../app/main/data/workspace/modifiers.cljs | 24 +- .../app/main/data/workspace/shape_layout.cljs | 262 +++++------------- .../app/main/data/workspace/transforms.cljs | 5 +- .../app/main/ui/workspace/context_menu.cljs | 18 +- .../options/menus/layout_container.cljs | 31 ++- .../viewport/grid_layout_editor.cljs | 1 - frontend/src/app/util/code_gen/style_css.cljs | 1 + .../app/util/code_gen/style_css_values.cljs | 5 + 17 files changed, 395 insertions(+), 234 deletions(-) rename common/src/app/common/geom/{shapes => }/modifiers.cljc (99%) create mode 100644 common/src/app/common/geom/shapes/flex_layout/params.cljc create mode 100644 common/src/app/common/geom/shapes/grid_layout/params.cljc diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/modifiers.cljc similarity index 99% rename from common/src/app/common/geom/shapes/modifiers.cljc rename to common/src/app/common/geom/modifiers.cljc index 40727cf44..6a93c6af9 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/modifiers.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.common.geom.shapes.modifiers +(ns app.common.geom.modifiers (:require [app.common.data :as d] [app.common.data.macros :as dm] diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 1bf496b99..5555f9386 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -15,7 +15,6 @@ [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.corners :as gsc] [app.common.geom.shapes.intersect :as gsi] - [app.common.geom.shapes.modifiers :as gsm] [app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth])) @@ -203,8 +202,5 @@ (dm/export gsc/shape-corners-1) (dm/export gsc/shape-corners-4) -;; Modifiers -(dm/export gsm/set-objects-modifiers) - ;; Rect (dm/export grc/rect->points) diff --git a/common/src/app/common/geom/shapes/flex_layout.cljc b/common/src/app/common/geom/shapes/flex_layout.cljc index 3be7382da..52eb913ab 100644 --- a/common/src/app/common/geom/shapes/flex_layout.cljc +++ b/common/src/app/common/geom/shapes/flex_layout.cljc @@ -10,7 +10,8 @@ [app.common.geom.shapes.flex-layout.bounds :as fbo] [app.common.geom.shapes.flex-layout.drop-area :as fdr] [app.common.geom.shapes.flex-layout.layout-data :as fld] - [app.common.geom.shapes.flex-layout.modifiers :as fmo])) + [app.common.geom.shapes.flex-layout.modifiers :as fmo] + [app.common.geom.shapes.flex-layout.params :as fp])) (dm/export fbo/layout-content-bounds) (dm/export fbo/layout-content-points) @@ -19,3 +20,4 @@ (dm/export fdr/get-drop-areas) (dm/export fld/calc-layout-data) (dm/export fmo/layout-child-modifiers) +(dm/export fp/calculate-params) diff --git a/common/src/app/common/geom/shapes/flex_layout/params.cljc b/common/src/app/common/geom/shapes/flex_layout/params.cljc new file mode 100644 index 000000000..4293dd53d --- /dev/null +++ b/common/src/app/common/geom/shapes/flex_layout/params.cljc @@ -0,0 +1,97 @@ +;; 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 + +(ns app.common.geom.shapes.flex-layout.params + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] + [app.common.math :as mth] + [app.common.types.shape-tree :as ctt])) + +(defn calculate-params + "Given the shapes calculate its flex parameters (horizontal vs vertical, gaps, etc)" + ([objects shapes] + (calculate-params objects shapes nil)) + + ([objects shapes parent] + (when (d/not-empty? shapes) + (let [points + (->> shapes + (map :id) + (ctt/sort-z-index objects) + (map (comp gco/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)})))))) diff --git a/common/src/app/common/geom/shapes/grid_layout.cljc b/common/src/app/common/geom/shapes/grid_layout.cljc index 5de5a5ce3..29eef9699 100644 --- a/common/src/app/common/geom/shapes/grid_layout.cljc +++ b/common/src/app/common/geom/shapes/grid_layout.cljc @@ -9,6 +9,7 @@ [app.common.data.macros :as dm] [app.common.geom.shapes.grid-layout.bounds :as glpb] [app.common.geom.shapes.grid-layout.layout-data :as glld] + [app.common.geom.shapes.grid-layout.params :as glpr] [app.common.geom.shapes.grid-layout.positions :as glp])) (dm/export glld/calc-layout-data) @@ -19,3 +20,4 @@ (dm/export glp/cell-bounds) (dm/export glpb/layout-content-points) (dm/export glpb/layout-content-bounds) +(dm/export glpr/calculate-params) 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 881bf1c8e..51ce068aa 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 @@ -590,7 +590,7 @@ [{:keys [origin row-tracks column-tracks shape-cells]} _transformed-parent-bounds [_ child]] (let [grid-cell (get shape-cells (:id child))] - (when (some? grid-cell) + (when (and (some? grid-cell) (d/not-empty? grid-cell)) (let [column (nth column-tracks (dec (:column grid-cell)) nil) row (nth row-tracks (dec (:row grid-cell)) nil) diff --git a/common/src/app/common/geom/shapes/grid_layout/params.cljc b/common/src/app/common/geom/shapes/grid_layout/params.cljc new file mode 100644 index 000000000..b50fbf448 --- /dev/null +++ b/common/src/app/common/geom/shapes/grid_layout/params.cljc @@ -0,0 +1,166 @@ +;; 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 + +(ns app.common.geom.shapes.grid-layout.params + (:require + [app.common.geom.point :as gpt] + [app.common.geom.rect :as grc] + [app.common.geom.shapes.common :as gco] + [app.common.geom.shapes.points :as gpo] + [app.common.math :as mth] + [app.common.types.shape.layout :as ctl] + [clojure.set :as set])) + +;; Small functions to help with ranges +(defn rect->range + "Creates ranges" + [axis rect] + (let [start (get (gpt/point rect) axis)] + (if (= axis :x) + [start (+ start (:width rect))] + [start (+ start (:height rect))]))) + +(defn overlaps-range? + "Return true if the ranges overlaps in the given axis" + [axis [start-a end-a] rect] + (let [[start-b end-b] (rect->range axis rect)] + (or (< start-a start-b end-a) + (< start-b start-a end-b) + (mth/close? start-a start-b) + (mth/close? end-a end-b)))) + +(defn join-range + "Creates a new range given the rect" + [axis [start-a end-a :as range] rect] + (if (not range) + (rect->range axis rect) + (let [[start-b end-b] (rect->range axis rect)] + [(min start-a start-b) (max end-a end-b)]))) + +(defn size-range + [[start end]] + (- end start)) + +(defn calculate-tracks + "Given the geometry and the axis calculates the tracks for the given shapes" + [axis shapes-by-axis] + (loop [pending (seq shapes-by-axis) + result [] + index 1 + current-track #{} + current-range nil] + (if pending + (let [[next-shape rect :as next-shape+rects] (first pending)] + + (if (or (not current-range) (overlaps-range? axis current-range rect)) + ;; Add shape to current row + (let [current-track (conj current-track (:id next-shape)) + current-range (join-range axis current-range rect)] + (recur (next pending) result index current-track current-range)) + + ;; New row + (recur (next pending) + (conj result {:index index + :shapes current-track + :size (size-range current-range)}) + (inc index) + #{(:id next-shape)} + (rect->range axis rect)))) + + ;; Add the still ongoing row + (conj result {:index index + :shapes current-track + :size (size-range current-range)})))) + +(defn assign-shape-cells + "Create cells for the defined tracks and assign the shapes to these cells" + [params rows cols] + + (let [assign-cell + (fn [[params auto?] row column] + (let [row-num (:index row) + column-num (:index column) + cell (ctl/cell-by-row-column params row-num column-num) + shape (first (set/intersection (:shapes row) (:shapes column))) + auto? (and auto? (some? shape))] + + [(cond-> params + (some? shape) + (assoc-in [:layout-grid-cells (:id cell) :shapes] [shape]) + + (not auto?) + (assoc-in [:layout-grid-cells (:id cell) :position] :manual)) + auto?])) + + [params _] + (->> rows + (reduce + (fn [result row] + (->> cols + (reduce + #(assign-cell %1 row %2) + result))) + [params true]))] + params)) + +(defn calculate-params + "Given the shapes calculate its grid parameters (horizontal vs vertical, gaps, etc)" + ([objects shapes] + (calculate-params objects shapes nil)) + + ([_objects shapes parent] + (if (empty? shapes) + (-> {:layout-grid-columns [{:type :auto} {:type :auto}] + :layout-grid-rows [{:type :auto} {:type :auto}]} + (ctl/create-cells [1 1 2 2])) + + (let [all-shapes-rect (gco/shapes->rect shapes) + shapes+bounds + (->> shapes + (map #(vector % (grc/points->rect (get % :points))))) + + shapes-by-x + (->> shapes+bounds + (sort-by (comp :x second))) + + shapes-by-y + (->> shapes+bounds + (sort-by (comp :y second))) + + cols (calculate-tracks :x shapes-by-x) + rows (calculate-tracks :y shapes-by-y) + + num-cols (count cols) + num-rows (count rows) + + total-cols-width (->> cols (reduce #(+ %1 (:size %2)) 0)) + total-rows-height (->> rows (reduce #(+ %1 (:size %2)) 0)) + + column-gap + (if (= num-cols 1) + 0 + (/ (- (:width all-shapes-rect) total-cols-width) (dec num-cols))) + + row-gap + (if (= num-rows 1) + 0 + (/ (- (:height all-shapes-rect) total-rows-height) (dec num-rows))) + + layout-grid-rows (mapv (constantly (array-map :type :auto)) rows) + layout-grid-columns (mapv (constantly (array-map :type :auto)) cols) + + parent-childs-vector (gpt/to-vec (gpo/origin (:points parent)) (gpt/point all-shapes-rect)) + p-left (:x parent-childs-vector) + p-top (:y parent-childs-vector)] + + (-> {:layout-grid-columns layout-grid-columns + :layout-grid-rows layout-grid-rows + :layout-gap {:row-gap row-gap + :column-gap column-gap} + :layout-padding {:p1 p-top :p2 p-left :p3 p-top :p4 p-left} + :layout-grid-dir (if (> num-cols num-rows) :row :column)} + (ctl/create-cells [1 1 num-cols num-rows]) + (assign-shape-cells rows cols)))))) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 8a32ef120..f8e6ad811 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -373,7 +373,7 @@ (cond-> objects @changed? - (assoc-in [parent-id :shapes] new-shapes)))) + (d/assoc-in-when [parent-id :shapes] new-shapes)))) check-modify-component (fn [data] diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 2798c9ff8..adefe1d07 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -227,8 +227,7 @@ ([objects ids {:keys [bottom-frames?] :as options :or {bottom-frames? false}}] - (letfn [ - (comp [id-a id-b] + (letfn [(comp [id-a id-b] (cond (= id-a id-b) 0 diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 2c11d0a8a..dfb031e5d 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -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)))))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index e69d6c914..3f55718a1 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -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)))))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 6c004325d..f4b2f87b3 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -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))) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index f9fc514eb..ada7c3369 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -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?) 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 a91fbfbce..95f766ab2 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 @@ -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) 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 8d7f64886..4076b8131 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 @@ -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) diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs index eb51ddc04..0175faae7 100644 --- a/frontend/src/app/util/code_gen/style_css.cljs +++ b/frontend/src/app/util/code_gen/style_css.cljs @@ -101,6 +101,7 @@ body { :grid-template-rows :grid-template-columns :grid-template-areas + :grid-auto-flow ;; Flex/grid self properties :flex-shrink diff --git a/frontend/src/app/util/code_gen/style_css_values.cljs b/frontend/src/app/util/code_gen/style_css_values.cljs index 175d185e1..cdca2cbbc 100644 --- a/frontend/src/app/util/code_gen/style_css_values.cljs +++ b/frontend/src/app/util/code_gen/style_css_values.cljs @@ -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))