From 4bfe81f771fa1df60a1d0b3f26fc7e169de0ba4f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 5 Apr 2023 11:55:08 +0200 Subject: [PATCH] :sparkles: Enable grid editor --- .../geom/shapes/grid_layout/layout_data.cljc | 2 +- .../src/app/common/geom/shapes/modifiers.cljc | 10 +- common/src/app/common/types/shape/layout.cljc | 134 ++++++++++++++++-- .../app/main/data/workspace/shape_layout.cljs | 8 +- .../options/menus/layout_container.cljs | 9 +- .../sidebar/options/shapes/grid_cell.cljs | 8 +- .../viewport/grid_layout_editor.cljs | 114 ++++++++++----- 7 files changed, 225 insertions(+), 60 deletions(-) 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 e797b1c64..73d500b57 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 @@ -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 %) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 14ffa6ddf..9b0fd7722 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -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))))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 7f7814ef6..3ce36466f 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -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))))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index e235137a8..fc1ec6395 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -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 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 1e987b488..8e6f02eb6 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 @@ -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"] 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 5b4f96dd8..2d02bd604 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 @@ -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 "--" 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 c9f351e90..897e03eaa 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 @@ -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])}])]))