diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index bf88e4286f..8c283455e5 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -11,31 +11,169 @@ [app.common.geom.shapes.rect :as gre] [app.common.geom.shapes.transforms :as gtr])) +;; :layout ;; true if active, false if not +;; :layout-dir ;; :right, :left, :top, :bottom +;; :layout-gap ;; number could be negative +;; :layout-type ;; :packed, :space-between, :space-around +;; :layout-wrap-type ;; :wrap, :no-wrap +;; :layout-padding-type ;; :simple, :multiple +;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative +;; :layout-h-orientation ;; :top, :center, :bottom +;; :layout-v-orientation ;; :left, :center, :right + +(defn col? + [{:keys [layout-dir]}] + (or (= :right layout-dir) (= :left layout-dir))) + +(defn row? + [{:keys [layout-dir]}] + (or (= :top layout-dir) (= :bottom layout-dir))) + +(defn h-start? + [{:keys [layout-v-orientation]}] + (= layout-v-orientation :left)) + +(defn h-center? + [{:keys [layout-v-orientation]}] + (= layout-v-orientation :center)) + +(defn h-end? + [{:keys [layout-v-orientation]}] + (= layout-v-orientation :right)) + +(defn v-start? + [{:keys [layout-h-orientation]}] + (= layout-h-orientation :top)) + +(defn v-center? + [{:keys [layout-h-orientation]}] + (= layout-h-orientation :center)) + +(defn v-end? + [{:keys [layout-h-orientation]}] + (= layout-h-orientation :bottom)) + +(defn add-padding [transformed-rect {:keys [layout-padding-type layout-padding]}] + (let [{:keys [p1 p2 p3 p4]} layout-padding + [p1 p2 p3 p4] + (if (= layout-padding-type :multiple) + [p1 p2 p3 p4] + [p1 p1 p1 p1])] + + (-> transformed-rect + (update :y + p1) + (update :width - p2 p3) + (update :x + p3) + (update :height - p1 p4)))) + (defn calc-layout-data "Digest the layout data to pass it to the constrains" - [_parent children transformed-rect] + [{:keys [layout-gap] :as shape} children modif-tree transformed-rect] - (let [[children-width children-height] - (->> children (reduce (fn [[acc-width acc-height] shape] - [(+ acc-width (-> shape :points gre/points->rect :width)) - (+ acc-height (-> shape :points gre/points->rect :height))]) [0 0]))] - {:start-x (:x transformed-rect) - :start-y (:y transformed-rect) - :children-width children-width - :children-height children-height}) + (let [transformed-rect (-> transformed-rect (add-padding shape)) + num-children (count children) + children-gap (* layout-gap (dec num-children) ) + [children-width children-height] + (->> children + (map #(-> (merge % (get modif-tree (:id %))) gtr/transform-shape)) + (reduce (fn [[acc-width acc-height] shape] + [(+ acc-width (-> shape :points gre/points->rect :width)) + (+ acc-height (-> shape :points gre/points->rect :height))]) [0 0])) + + {:keys [x y width height]} transformed-rect + + start-x + (cond + (and (row? shape) (h-center? shape)) + (+ x (/ width 2)) + + (and (row? shape) (h-end? shape)) + (+ x width) + + (and (col? shape) (h-center? shape)) + (- (+ x (/ width 2)) (/ (+ children-width children-gap) 2)) + + (and (col? shape) (h-end? shape)) + (- (+ x width) (+ children-width children-gap)) + + :else + (:x transformed-rect)) + + start-y + (cond + (and (col? shape) (v-center? shape)) + (+ y (/ height 2)) + + (and (col? shape) (v-end? shape)) + (+ y height) + + (and (row? shape) (v-center? shape)) + (- (+ y (/ height 2)) (/ (+ children-height children-gap) 2)) + + (and (row? shape) (v-end? shape)) + (- (+ y height) (+ children-height children-gap)) + + :else + (:y transformed-rect) )] + + {:start-x start-x + :start-y start-y + :reverse? (or (= :left (:layout-dir shape)) (= :bottom (:layout-dir shape)))})) + +(defn next-p + "Calculates the position for the current shape given the layout-data context" + [{:keys [layout-gap] :as shape} {:keys [width height]} {:keys [start-x start-y] :as layout-data}] + + (let [pos-x + (cond + (and (row? shape) (h-center? shape)) + (- start-x (/ width 2)) + + (and (row? shape) (h-end? shape)) + (- start-x width) + + :else + start-x) + + pos-y + (cond + (and (col? shape) (v-center? shape)) + (- start-y (/ height 2)) + + (and (col? shape) (v-end? shape)) + (- start-y height) + + :else + start-y) + + corner-p (gpt/point pos-x pos-y) + + next-x + (if (col? shape) + (+ start-x width layout-gap) + start-x) + + next-y + (if (row? shape) + (+ start-y height layout-gap) + start-y) + + layout-data + (assoc layout-data :start-x next-x :start-y next-y)] + [corner-p layout-data]) ) (defn calc-layout-modifiers - [_parent child current-modifier _modifiers _transformed-rect {:keys [start-x start-y] :as layout-data}] + "Calculates the modifiers for the layout" + [parent child current-modifier _modifiers _transformed-rect layout-data] - (let [current-modifier (dissoc current-modifier :displacement-after) - child' (-> child (assoc :modifiers current-modifier) gtr/transform-shape) - bounds' (-> child' :points gre/points->selrect) - corner-p (gpt/point start-x start-y) - displacement (gmt/translate-matrix (gpt/subtract corner-p (gpt/point bounds'))) - modifiers (-> current-modifier - (assoc :displacement-after displacement)) + (let [current-modifier (-> current-modifier (dissoc :displacement-after)) + child (-> child (assoc :modifiers current-modifier) gtr/transform-shape) + bounds (-> child :points gre/points->selrect) - next-x (+ start-x (:width bounds'))] + [corner-p layout-data] (next-p parent bounds layout-data) - [modifiers (assoc layout-data :start-x next-x )])) + delta-p (-> corner-p (gpt/subtract (gpt/point bounds))) + modifiers (-> current-modifier (assoc :displacement-after (gmt/translate-matrix delta-p)))] + + [modifiers layout-data])) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 9694db988c..98a47522ca 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -19,6 +19,7 @@ [app.main.data.workspace.changes :as dch] [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] [app.util.names :as un] @@ -98,6 +99,7 @@ (rx/concat (rx/of (dch/commit-changes changes) + (dwsl/update-layout-positions [(:parent-id shape)]) (when-not no-select? (dws/select-shapes (d/ordered-set id)))) (when (= :text (:type attrs)) @@ -239,7 +241,8 @@ flows starting-flows)))))] - (rx/of (dch/commit-changes changes)))))) + (rx/of (dch/commit-changes changes) + (dwsl/update-layout-positions all-parents)))))) (defn- viewport-center [state] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 92dea577a3..4104e92b45 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -415,12 +415,12 @@ (cond (:layout shape) - (let [result - (->> children - (reduce (partial set-layout-child (and snap-pixel? resize-modif?)) - (merge {:modif-tree modif-tree} - (gsh/calc-layout-data shape children transformed-rect))))] - (:modif-tree result)) + (let [layout-data (gsh/calc-layout-data shape children modif-tree transformed-rect) + children (cond-> children (:reverse? layout-data) reverse)] + (->> children + (reduce (partial set-layout-child (and snap-pixel? resize-modif?)) + (merge {:modif-tree modif-tree} layout-data)) + :modif-tree)) :else modif-tree)))] 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 6bdaec134b..bbac7ed45f 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 @@ -88,9 +88,11 @@ type (:layout-type values) is-col? (or (= dir :top) (= dir :bottom)) - saved-pos [(:layout-h-orientation values) (:layout-v-orientation values)]] + saved-pos [(:layout-h-orientation values) + (:layout-v-orientation values)]] - (if (= type :packed) + (cond + (= type :packed) [:div.orientation-grid [:div.button-wrapper (for [[pv ph] grid-pos] @@ -110,47 +112,48 @@ :rotated is-col?)} (get-layout-icon dir type pv ph)]])]] - (if is-col? - [:div.orientation-grid.col - [:div.button-wrapper - (for [[idx col] (d/enumerate grid-cols)] - [:button.orientation - {:key (dm/str idx col) - :on-click (partial on-change-orientation :top col type) - :class (dom/classnames - :active (= col (second saved-pos)) - :top (= :left col) - :centered (= :center col) - :bottom (= :right col))} - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type nil col)] - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type nil col)] - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type nil col)]])]] + is-col? + [:div.orientation-grid.col + [:div.button-wrapper + (for [[idx col] (d/enumerate grid-cols)] + [:button.orientation + {:key (dm/str idx col) + :on-click (partial on-change-orientation :top col type) + :class (dom/classnames + :active (= col (second saved-pos)) + :top (= :left col) + :centered (= :center col) + :bottom (= :right col))} + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-layout-icon dir type nil col)] + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-layout-icon dir type nil col)] + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-layout-icon dir type nil col)]])]] - [:div.orientation-grid.row - [:div.button-wrapper - (for [row grid-rows] - [:button.orientation - {:on-click (partial on-change-orientation row :left type) - :class (dom/classnames - :active (= row (first saved-pos)) - :top (= :top row) - :centered (= :center row) - :bottom (= :bottom row))} - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type row nil)] - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type row nil)] - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type row nil)]])]])))) + :else + [:div.orientation-grid.row + [:div.button-wrapper + (for [row grid-rows] + [:button.orientation + {:on-click (partial on-change-orientation row :left type) + :class (dom/classnames + :active (= row (first saved-pos)) + :top (= :top row) + :centered (= :center row) + :bottom (= :bottom row))} + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-layout-icon dir type row nil)] + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-layout-icon dir type row nil)] + [:span.icon + {:class (dom/classnames :rotated is-col?)} + (get-layout-icon dir type row nil)]])]]))) (mf/defc padding-section [{:keys [values on-change-style on-change] :as props}]