From c3ed46d3ab2f58465dda71d8d4fbc3032d3d68ef Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 14 Oct 2022 14:44:10 +0200 Subject: [PATCH] :sparkles: Move auto-layout children --- common/src/app/common/geom/point.cljc | 9 +- common/src/app/common/geom/shapes.cljc | 1 + .../app/common/geom/shapes/constraints.cljc | 4 +- common/src/app/common/geom/shapes/layout.cljc | 114 +++++++++++------- .../src/app/common/geom/shapes/modifiers.cljc | 17 ++- common/src/app/common/geom/shapes/rect.cljc | 23 ++-- .../app/common/geom/shapes/transforms.cljc | 43 ++++++- common/src/app/common/math.cljc | 5 + common/src/app/common/pages/helpers.cljc | 6 + common/src/app/common/types/modifiers.cljc | 5 +- common/src/app/common/types/shape_tree.cljc | 4 +- .../app/main/data/workspace/drawing/box.cljs | 49 +++++--- .../main/data/workspace/drawing/common.cljs | 3 +- .../src/app/main/data/workspace/shapes.cljs | 8 +- .../app/main/data/workspace/transforms.cljs | 89 +++++++++++--- .../src/app/main/ui/workspace/shapes.cljs | 78 ++++++------ .../app/main/ui/workspace/shapes/frame.cljs | 91 +------------- .../shapes/frame/dynamic_modifiers.cljs | 69 ++++++++++- .../src/app/main/ui/workspace/viewport.cljs | 94 ++------------- .../app/main/ui/workspace/viewport/debug.cljs | 54 +++++++++ .../app/main/ui/workspace/viewport/hooks.cljs | 8 +- frontend/src/debug.cljs | 12 +- 22 files changed, 457 insertions(+), 329 deletions(-) create mode 100644 frontend/src/app/main/ui/workspace/viewport/debug.cljs diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 9ce97f8af..5b17160d9 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -5,7 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.geom.point - (:refer-clojure :exclude [divide min max]) + (:refer-clojure :exclude [divide min max abs]) (:require #?(:cljs [cljs.pprint :as pp] :clj [clojure.pprint :as pp]) @@ -328,6 +328,13 @@ (update :x #(if (mth/almost-zero? %) 0.001 %)) (update :y #(if (mth/almost-zero? %) 0.001 %)))) + +(defn abs + [point] + (-> point + (update :x mth/abs) + (update :y mth/abs))) + ;; --- Debug (defmethod pp/simple-dispatch Point [obj] (pr obj)) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 5364b9daa..25da561f3 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -160,6 +160,7 @@ (dm/export gpr/join-rects) (dm/export gpr/join-selrects) (dm/export gpr/contains-selrect?) +(dm/export gpr/contains-point?) (dm/export gtr/move) (dm/export gtr/absolute-move) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 0c898348f..2e9aa8a9b 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -288,7 +288,9 @@ (defn calc-child-modifiers [parent child modifiers ignore-constraints transformed-parent] - (let [constraints-h + (let [modifiers (select-keys modifiers [:v2]) + + constraints-h (if-not ignore-constraints (:constraints-h child (default-constraints-h child)) :scale) diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index 8d1fbed87..a5f0d79c5 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -7,9 +7,12 @@ (ns app.common.geom.shapes.layout (: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.rect :as gsr] - [app.common.geom.shapes.transforms :as gst])) + [app.common.geom.shapes.transforms :as gst] + [app.common.pages.helpers :as cph])) ;; :layout ;; true if active, false if not ;; :layout-dir ;; :right, :left, :top, :bottom @@ -213,6 +216,8 @@ layout-height (height-points layout-bounds) row? (row? parent) col? (col? parent) + space-between? (= :space-between (:layout-type parent)) + space-around? (= :space-around (:layout-type parent)) h-center? (h-center? parent) h-end? (h-end? parent) v-center? (v-center? parent) @@ -258,11 +263,11 @@ start-p (cond-> base-p ;; X AXIS - (and col? h-center?) + (and col? h-center? (not space-around?) (not space-between?)) (-> (gpt/add (xv (/ layout-width 2))) (gpt/subtract (xv (/ (+ line-width children-gap) 2)))) - (and col? h-end?) + (and col? h-end? (not space-around?) (not space-between?)) (-> (gpt/add (xv layout-width)) (gpt/subtract (xv (+ line-width children-gap)))) @@ -273,11 +278,11 @@ (gpt/add (xv line-width)) ;; Y AXIS - (and row? v-center?) + (and row? v-center? (not space-around?) (not space-between?)) (-> (gpt/add (yv (/ layout-height 2))) (gpt/subtract (yv (/ (+ line-height children-gap) 2)))) - (and row? v-end?) + (and row? v-end? (not space-around?) (not space-between?)) (-> (gpt/add (yv layout-height)) (gpt/subtract (yv (+ line-height children-gap)))) @@ -385,17 +390,10 @@ (let [row? (row? parent) col? (col? parent) - layout-type (:layout-type parent) - space-around? (= :space-around layout-type) - space-between? (= :space-between layout-type) - - stretch-h? (and row? (or space-around? space-between?)) - stretch-v? (and col? (or space-around? space-between?)) - - h-center? (and (h-center? parent) (not stretch-h?)) - h-end? (and (h-end? parent) (not stretch-h?)) - v-center? (and (v-center? parent) (not stretch-v?)) - v-end? (and (v-end? parent) (not stretch-v?)) + h-center? (h-center? parent) + h-end? (h-end? parent) + v-center? (v-center? parent) + v-end? (v-end? parent) points (:points parent) xv (partial start-hv points) @@ -409,10 +407,10 @@ (and row? h-end?) (gpt/add (xv (- child-width))) - (and col? v-center? (not space-around?)) + (and col? v-center?) (gpt/add (yv (- (/ child-height 2)))) - (and col? v-end? (not space-around?)) + (and col? v-end?) (gpt/add (yv (- child-height))) (some? margin-x) @@ -584,29 +582,35 @@ v-center? (and col? (v-center? frame)) v-end? (and row? (v-end? frame)) layout-gap (:layout-gap frame 0) + reverse? (:reverse? layout-data) - children (vec (cond->> children - (:reverse? layout-data) reverse)) + children (vec (cond->> (d/enumerate children) + reverse? reverse)) redfn-child - (fn [[result parent-rect prev-x prev-y] [child next]] + (fn [[result parent-rect prev-x prev-y] [[index child] next]] (let [prev-x (or prev-x (:x parent-rect)) prev-y (or prev-y (:y parent-rect)) + last? (nil? next) - box-x (-> child :selrect :x) - box-y (-> child :selrect :y) - box-width (-> child :selrect :width) - box-height(-> child :selrect :height) + start-p (gpt/point (:selrect child)) + start-p (-> start-p + (gmt/transform-point-center (gco/center-shape child) (:transform frame)) + (gmt/transform-point-center (gco/center-shape frame) (:transform-inverse frame))) + + box-x (:x start-p) + box-y (:y start-p) + box-width (-> child :selrect :width) + box-height (-> child :selrect :height) - x (if row? (:x parent-rect) prev-x) y (if col? (:y parent-rect) prev-y) width (cond (and col? last?) (- (+ (:x parent-rect) (:width parent-rect)) x) - + row? (:width parent-rect) @@ -616,21 +620,42 @@ height (cond (and row? last?) (- (+ (:y parent-rect) (:height parent-rect)) y) - + col? (:height parent-rect) :else (+ box-height (- box-y prev-y) (/ layout-gap 2))) - - line-area (gsr/make-rect x y width height) - result (conj result line-area)] + + [line-area-1 line-area-2] + (if col? + (let [half-point-width (+ (- box-x x) (/ box-width 2))] + [(-> (gsr/make-rect x y half-point-width height) + (assoc :index (if reverse? (inc index) index))) + (-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height) + (assoc :index (if reverse? index (inc index))))]) + (let [half-point-height (+ (- box-y y) (/ box-height 2))] + [(-> (gsr/make-rect x y width half-point-height) + (assoc :index (if reverse? (inc index) index))) + (-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height)) + (assoc :index (if reverse? index (inc index))))])) + + result (conj result line-area-1 line-area-2) + + ;;line-area + ;;(-> (gsr/make-rect x y width height) + ;; (assoc :index (if reverse? (inc index) index))) + ;;result (conj result line-area) + ;;result (conj result (gsr/make-rect box-x box-y box-width box-height)) + ] [result parent-rect (+ x width) (+ y height)])) - + redfn-lines (fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap num-children line-width line-height]} next]] - (let [prev-x (or prev-x (:x frame)) + (let [start-p (gmt/transform-point-center start-p (gco/center-shape frame) (:transform-inverse frame)) + + prev-x (or prev-x (:x frame)) prev-y (or prev-y (:y frame)) last? (nil? next) @@ -668,7 +693,7 @@ width (cond (and row? last?) (- (+ (:x frame) (:width frame)) x) - + col? (:width frame) @@ -678,7 +703,7 @@ height (cond (and col? last?) (- (+ (:y frame) (:height frame)) y) - + row? (:height frame) @@ -695,13 +720,16 @@ result (first (reduce redfn-child [result line-area] (d/with-next children)))] - [result (+ from-idx num-children) (+ x width) (+ y height)])) + [result (+ from-idx num-children) (+ x width) (+ y height)]))] - ret (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))) - ] + (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))))) - - ;;(.log js/console "RET" (clj->js ret)) - ret - - )) +(defn get-drop-index + [frame-id objects position] + (let [frame (get objects frame-id) + position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame)) + children (cph/get-immediate-children objects frame-id) + layout-data (calc-layout-data frame children) + drop-areas (drop-areas frame layout-data children) + area (d/seek #(gsr/contains-point? % position) drop-areas)] + (:index area))) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 7eaf9fb5a..6cb5f0d6f 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -166,9 +166,9 @@ [layout-line modif-tree]))] - (let [children (map (d/getf objects) (:shapes parent)) - modifiers (get-in modif-tree [(:id parent) :modifiers]) + (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) transformed-parent (gtr/transform-shape parent modifiers) + children (map (d/getf objects) (:shapes transformed-parent)) modif-tree (reduce (partial normalize-child transformed-parent _snap-pixel?) modif-tree children) @@ -343,27 +343,26 @@ (assoc id {:modifiers modifiers})))) modif-tree (reduce set-modifiers {} ids) - shapes-tree (resolve-tree-sequence ids objects) modif-tree (->> shapes-tree (reduce (fn [modif-tree shape] - (let [has-modifiers? (some? (get-in modif-tree [(:id shape) :modifiers])) + (let [modifiers (get-in modif-tree [(:id shape) :modifiers]) + has-modifiers? (some? modifiers) is-layout? (layout? shape) is-parent? (or (group? shape) (and (frame? shape) (not (layout? shape)))) - + root? (= uuid/zero (:id shape)) ;; If the current child is inside the layout we ignore the constraints is-inside-layout? (inside-layout? objects shape)] (cond-> modif-tree - (and has-modifiers? is-parent?) + (and has-modifiers? is-parent? (not root?)) (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) - + is-layout? - (set-layout-modifiers objects shape snap-pixel?) - ))) + (set-layout-modifiers objects shape snap-pixel?)))) modif-tree))] diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 6637b7533..895388371 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -11,14 +11,21 @@ [app.common.math :as mth])) (defn make-rect - [x y width height] - (when (d/num? x y width height) - (let [width (max width 0.01) - height (max height 0.01)] - {:x x - :y y - :width width - :height height}))) + ([p1 p2] + (let [x1 (min (:x p1) (:x p2)) + y1 (min (:y p1) (:y p2)) + x2 (max (:x p1) (:x p2)) + y2 (max (:y p1) (:y p2))] + (make-rect x1 y1 (- x2 x1) (- y2 y1)))) + + ([x y width height] + (when (d/num? x y width height) + (let [width (max width 0.01) + height (max height 0.01)] + {:x x + :y y + :width width + :height height})))) (defn make-selrect [x y width height] diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 6c1957519..08bf7383e 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -14,8 +14,10 @@ [app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth] + [app.common.pages.helpers :as cph] [app.common.text :as txt] - [app.common.types.modifiers :as ctm])) + [app.common.types.modifiers :as ctm] + [app.common.uuid :as uuid])) (def ^:dynamic *skip-adjust* false) @@ -436,13 +438,46 @@ %))) shape)) +(defn- apply-structure-modifiers + [shape modifiers] + + (let [remove-children + (fn [shapes children-to-remove] + (let [remove? (set children-to-remove)] + (d/removev remove? shapes))) + + apply-modifier + (fn [shape {:keys [type value index]}] + (cond-> shape + (and (= type :add-children) (some? index)) + (update :shapes + (fn [shapes] + (if (vector? shapes) + (cph/insert-at-index shapes index value) + (d/concat-vec shapes value)))) + + (and (= type :add-children) (nil? index)) + (update :shapes d/concat-vec value) + + (= type :remove-children) + (update :shapes remove-children value)))] + + (reduce apply-modifier shape (:v3 modifiers)))) + (defn apply-modifiers [shape modifiers] (let [center (gco/center-shape shape) transform (ctm/modifiers->transform center modifiers)] - (-> shape - #_(set-flip-2 transform) - (apply-transform transform)))) + + (cond-> shape + #_(set-flip-2 transform) + (and (some? transform) + ;; Never transform the root frame + (not= uuid/zero (:id shape))) + (apply-transform transform) + + :always + (apply-structure-modifiers modifiers)))) (defn apply-objects-modifiers [objects modifiers] diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index d32531a26..fc35d15f3 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -174,3 +174,8 @@ (defn max-abs [a b] (max (abs a) (abs b))) + +(defn sign + "Get the sign (+1 / -1) for the number" + [n] + (if (neg? n) -1 1)) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 5d243c363..f984574f5 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -33,6 +33,12 @@ ([{:keys [type]}] (= type :frame))) +(defn layout-shape? + ([objects id] + (layout-shape? (get objects id))) + ([{:keys [type layout]}] + (and (= type :frame) layout))) + (defn group-shape? [{:keys [type]}] (= type :group)) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 7c8658cfb..5c905aeff 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -162,9 +162,6 @@ :move (gmt/multiply (gmt/translate-matrix vector) matrix) - ;;:transform - ;;(gmt/multiply transform matrix) - :resize (gmt/multiply (-> (gmt/matrix) @@ -178,7 +175,7 @@ matrix) :rotation - ;; TODO LAYOUT: Comprobar que pasa si no hay centro + ;; TODO LAYOUT: Maybe an issue when no center data (gmt/multiply (-> (gmt/matrix) (gmt/translate center) diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 72388c216..905854d03 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -234,6 +234,7 @@ (if (nil? child-frame-id) (or current-id uuid/zero) (recur child-frame-id)))))) + (defn top-nested-frame-ids "Search the top nested frame in a list of ids" [objects ids] @@ -246,8 +247,7 @@ (-> (:shapes current-shape) reverse))] (if (nil? child-frame-id) (or current-id uuid/zero) - (recur child-frame-id))))) - ) + (recur child-frame-id)))))) (defn get-viewer-frames ([objects] diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index dc9e86762..65843fc6b 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -21,27 +21,37 @@ [beicon.core :as rx] [potok.core :as ptk])) -(defn truncate-zero [num default] - (if (mth/almost-zero? num) default num)) +(defn adjust-ratio + [point initial] + (let [v (gpt/to-vec point initial) + dx (mth/abs (:x v)) + dy (mth/abs (:y v)) + sx (mth/sign (:x v)) + sy (mth/sign (:y v))] + + (cond-> point + (> dx dy) + (assoc :y (- (:y point) (* sy (- dx dy)))) + + (> dy dx) + (assoc :x (- (:x point) (* sx (- dy dx))))))) + +(defn resize-shape [{:keys [x y width height] :as shape} initial point lock?] + (let [draw-rect (gsh/make-rect initial (cond-> point lock? (adjust-ratio initial))) + shape-rect (gsh/make-rect x y width height) + + scalev (gpt/point (/ (:width draw-rect) (:width shape-rect)) + (/ (:height draw-rect) (:height shape-rect))) + + movev (gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))] -(defn resize-shape [{:keys [x y width height] :as shape} point lock?] - (let [;; The new shape behaves like a resize on the bottom-right corner - initial (gpt/point (+ x width) (+ y height)) - shapev (gpt/point width height) - deltav (gpt/to-vec initial point) - scalev (-> (gpt/divide (gpt/add shapev deltav) shapev) - (update :x truncate-zero 0.01) - (update :y truncate-zero 0.01)) - scalev (if lock? - (let [v (max (:x scalev) (:y scalev))] - (gpt/point v v)) - scalev)] (-> shape (assoc :click-draw? false) - (gsh/transform-shape (ctm/resize scalev (gpt/point x y)))))) + (gsh/transform-shape (ctm/resize scalev (gpt/point x y))) + (gsh/transform-shape (ctm/move movev))))) -(defn update-drawing [state point lock?] - (update-in state [:workspace-drawing :object] resize-shape point lock?)) +(defn update-drawing [state initial point lock?] + (update-in state [:workspace-drawing :object] resize-shape initial point lock?)) (defn move-drawing [{:keys [x y]}] @@ -57,8 +67,7 @@ layout (get state :workspace-layout) snap-pixel? (contains? layout :snap-pixel-grid) - initial (cond-> @ms/mouse-position - snap-pixel? gpt/round) + initial (cond-> @ms/mouse-position snap-pixel? gpt/round) page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -96,7 +105,7 @@ (rx/map #(conj current %))))) (rx/map (fn [[_ shift? point]] - #(update-drawing % (cond-> point snap-pixel? gpt/round) shift?))) + #(update-drawing % initial (cond-> point snap-pixel? gpt/round) shift?))) (rx/take-until stoper)) (rx/of (common/handle-finish-drawing))))))) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index c13f76e70..2016c3c7d 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -51,8 +51,7 @@ (and click-draw? (not text?)) (-> (assoc :width min-side :height min-side) - (gsh/transform-shape (ctm/move (- (/ min-side 2)) (- (/ min-side 2)))) - #_(ctm/add-move (- (/ min-side 2)) (- (/ min-side 2)))) + (gsh/transform-shape (ctm/move (- (/ min-side 2)) (- (/ min-side 2))))) (and click-draw? text?) (assoc :height 17 :width 4 :grow-type :auto-width) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index e7c8e6705..daa033365 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -22,6 +22,7 @@ [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.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] [app.main.features :as features] [app.main.streams :as ms] @@ -146,6 +147,10 @@ ids (cph/clean-loops objects ids) lookup (d/getf objects) + layout-ids (->> ids + (mapcat (partial cph/get-parent-ids objects)) + (filter (partial cph/layout-shape? objects))) + components-v2 (features/active-feature? state :components-v2) groups-to-unmask @@ -266,7 +271,8 @@ (rx/of (dc/detach-comment-thread ids) (dwsl/update-layout-positions all-parents) - (dch/commit-changes changes))))))) + (dch/commit-changes changes) + (dwsl/update-layout-positions layout-ids))))))) (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 9d8c186d5..7873b7eba 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -11,6 +11,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.layout :as gsl] [app.common.math :as mth] [app.common.pages.changes-builder :as pcb] [app.common.pages.common :as cpc] @@ -137,8 +138,20 @@ snap-pixel? (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) + workspace-modifiers (:workspace-modifiers state) + modif-tree - (gsh/set-objects-modifiers ids objects (constantly modifiers) ignore-constraints snap-pixel?)] + (gsh/set-objects-modifiers + ;; TODO LAYOUT: I don't like this + (concat (keys workspace-modifiers) ids) + objects + (fn [shape] + (let [modifiers (if (contains? ids (:id shape)) modifiers {}) + old-modifiers-v3 (get-in state [:workspace-modifiers (:id shape) :modifiers :v3])] + (cond-> modifiers + (some? old-modifiers-v3) + (assoc :v3 old-modifiers-v3)))) + ignore-constraints snap-pixel?)] (update state :workspace-modifiers merge modif-tree)))))) @@ -619,6 +632,40 @@ (rx/take 1) (rx/map #(start-move from-position)))))) +(defn set-change-frame-modifiers + [selected target-frame position] + + (ptk/reify ::set-change-frame-modifiers + ptk/UpdateEvent + (update [_ state] + (let [objects (wsh/lookup-page-objects state) + + origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) + + layout? (get-in objects [target-frame :layout]) + + drop-index + (when layout? (gsl/get-drop-index target-frame objects position)) + + modif-tree + (into {} + (mapcat + (fn [original-frame] + (let [shapes (->> (get origin-frame-ids original-frame) + (d/removev #(= target-frame %)))] + (cond + (not= original-frame target-frame) + [[original-frame {:modifiers {:v3 [{:type :remove-children :value shapes}]}}] + [target-frame {:modifiers {:v3 [{:type :add-children + :value shapes + :index drop-index}]}}]] + layout? + [[target-frame {:modifiers {:v3 [{:type :add-children + :value shapes + :index drop-index}]}}]])))) + (keys origin-frame-ids))] + (assoc state :workspace-modifiers modif-tree))))) + (defn- start-move ([from-position] (start-move from-position nil)) ([from-position ids] @@ -664,17 +711,25 @@ (if (empty? shapes) (rx/of (finish-transform)) (rx/concat - (->> position - ;; We ask for the snap position but we continue even if the result is not available - (rx/with-latest vector snap-delta) - ;; We try to use the previous snap so we don't have to wait for the result of the new - (rx/map snap/correct-snap-point) + (rx/merge + (->> position + (rx/map (fn [delta] + (let [position (gpt/add from-position delta) + target-frame (ctst/top-nested-frame objects position)] + (set-change-frame-modifiers selected target-frame position)))) + (rx/take-until stopper)) - #_(rx/map #(hash-map :displacement (gmt/translate-matrix %))) - (rx/map #(array-map :v2 [{:type :move :vector %}])) + (->> position + ;; We ask for the snap position but we continue even if the result is not available + (rx/with-latest vector snap-delta) - (rx/map (partial set-modifiers ids)) - (rx/take-until stopper)) + ;; We try to use the previous snap so we don't have to wait for the result of the new + (rx/map snap/correct-snap-point) + + (rx/map (fn [move-vec] {:v2 [{:type :move :vector move-vec}]})) + + (rx/map (partial set-modifiers ids)) + (rx/take-until stopper))) (rx/of (dwu/start-undo-transaction) (calculate-frame-for-move ids) @@ -767,18 +822,22 @@ page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) frame-id (ctst/top-nested-frame objects position) + layout? (get-in objects [frame-id :layout]) lookup (d/getf objects) + shapes (->> ids (cph/clean-loops objects) (keep lookup)) + moving-shapes - (->> ids - (cph/clean-loops objects) - (keep lookup) - (remove #(= (:frame-id %) frame-id))) + (cond->> shapes + (not layout?) + (remove #(= (:frame-id %) frame-id))) + + drop-index (when layout? (gsl/get-drop-index frame-id objects position)) changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) - (pcb/change-parent frame-id moving-shapes))] + (pcb/change-parent frame-id moving-shapes drop-index))] (when-not (empty? changes) (rx/of (dch/commit-changes changes) diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 2e71107b3..836114c51 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -12,7 +12,9 @@ others are defined using a generic wrapper implemented in common." (:require + [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] + [app.common.uuid :as uuid] [app.main.ui.context :as ctx] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.image :as image] @@ -55,33 +57,36 @@ (mf/deps objects) #(cph/objects-by-frame objects))] - [:& (mf/provider ctx/active-frames) {:value active-frames} - ;; Render font faces only for shapes that are part of the root - ;; frame but don't belongs to any other frame. - (let [xform (comp - (remove cph/frame-shape?) - (mapcat #(cph/get-children-with-self objects (:id %))))] - [:& ff/fontfaces-style {:shapes (into [] xform shapes)}]) + [:g {:id (dm/str "shape-" uuid/zero)} + [:& (mf/provider ctx/active-frames) {:value active-frames} + ;; Render font faces only for shapes that are part of the root + ;; frame but don't belongs to any other frame. + (let [xform (comp + (remove cph/frame-shape?) + (mapcat #(cph/get-children-with-self objects (:id %))))] + [:& ff/fontfaces-style {:shapes (into [] xform shapes)}]) - (for [shape shapes] - (cond - (not (cph/frame-shape? shape)) - [:& shape-wrapper - {:shape shape - :key (:id shape)}] + [:g.frame-children + (for [shape shapes] + [:g.ws-shape-wrapper + (cond + (not (cph/frame-shape? shape)) + [:& shape-wrapper + {:shape shape + :key (:id shape)}] - (cph/root-frame? shape) - [:& root-frame-wrapper - {:shape shape - :key (:id shape) - :objects (get frame-objects (:id shape)) - :thumbnail? (not (contains? active-frames (:id shape)))}] + (cph/root-frame? shape) + [:& root-frame-wrapper + {:shape shape + :key (:id shape) + :objects (get frame-objects (:id shape)) + :thumbnail? (not (contains? active-frames (:id shape)))}] - :else - [:& nested-frame-wrapper - {:shape shape - :key (:id shape) - :objects (get frame-objects (:id shape))}]))])) + :else + [:& nested-frame-wrapper + {:shape shape + :key (:id shape) + :objects (get frame-objects (:id shape))}])])]]])) (mf/defc shape-wrapper {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))] @@ -98,20 +103,21 @@ opts #js {:shape shape :thumbnail? thumbnail?}] (when (and (some? shape) (not (:hidden shape))) - (case (:type shape) - :path [:> path/path-wrapper opts] - :text [:> text/text-wrapper opts] - :group [:> group-wrapper opts] - :rect [:> rect-wrapper opts] - :image [:> image-wrapper opts] - :circle [:> circle-wrapper opts] - :svg-raw [:> svg-raw-wrapper opts] - :bool [:> bool-wrapper opts] + [:g.ws-shape-wrapper + (case (:type shape) + :path [:> path/path-wrapper opts] + :text [:> text/text-wrapper opts] + :group [:> group-wrapper opts] + :rect [:> rect-wrapper opts] + :image [:> image-wrapper opts] + :circle [:> circle-wrapper opts] + :svg-raw [:> svg-raw-wrapper opts] + :bool [:> bool-wrapper opts] - ;; Only used when drawing a new frame. - :frame [:> nested-frame-wrapper opts] + ;; Only used when drawing a new frame. + :frame [:> nested-frame-wrapper opts] - nil)))) + nil)]))) (def group-wrapper (group/group-wrapper-factory shape-wrapper)) (def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper)) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index a3745f16d..1de768035 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -24,87 +24,7 @@ [app.main.ui.workspace.shapes.frame.node-store :as fns] [app.main.ui.workspace.shapes.frame.thumbnail-render :as ftr] [beicon.core :as rx] - [rumext.v2 :as mf] - - [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.layout :as gsl] - [app.main.data.workspace.state-helpers :as wsh] - [app.main.store :as st])) - -(mf/defc debug-layout - {::mf/wrap-props false} - [props] - - (let [shape (unchecked-get props "shape") - children (-> (wsh/lookup-page-objects @st/state) - (cph/get-immediate-children (:id shape))) - - layout-data (gsl/calc-layout-data shape children) - - drop-areas - (gsl/drop-areas shape layout-data children) - - ] - - [:g.debug-layout {:pointer-events "none"} - (for [[idx drop-area] (d/enumerate drop-areas)] - [:rect {:x (:x drop-area) - :y (:y drop-area) - :width (:width drop-area) - :height (:height drop-area) - :style {:fill "blue" - :fill-opacity 0.3 - :stroke "red" - :stroke-width 1 - :stroke-dasharray "3 6"}}]) - - - #_(for [[idx layout-line] (d/enumerate (:layout-lines layout-data))] - (let [col? (gsl/col? shape) - row? (gsl/row? shape) - h-center? (and row? (gsl/h-center? shape)) - h-end? (and row? (gsl/h-end? shape)) - v-center? (and col? (gsl/v-center? shape)) - v-end? (and row? (gsl/v-end? shape)) - - line-width - (+ (-> layout-line :line-width) - (:margin-x shape) - (if col? - (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) - 0)) - - line-height - (+ (-> layout-line :line-height) - (:margin-y shape) - (if row? - (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) - 0)) - ] - [:g {:key (dm/str "line-" idx)} - [:rect {:x (- (-> layout-line :start-p :x) - (cond - h-center? (/ line-width 2) - h-end? line-width - :else 0)) - :y (- (-> layout-line :start-p :y) - (cond - v-center? (/ line-height 2) - v-end? line-height - :else 0)) - :width line-width - :height line-height - :style {:fill "blue" - :fill-opacity 0.3} - }] - #_[:line {:x1 (-> layout-line :start-p :x) - :y1 (-> layout-line :start-p :y) - :x2 (+ (-> layout-line :start-p :x) (if col? line-width 0)) - :y2 (+ (-> layout-line :start-p :y) (if row? line-height 0)) - :transform (gsh/transform-str shape) - :style {:fill "none" - :stroke "red" - :stroke-width 2}}]]))])) + [rumext.v2 :as mf])) (defn frame-shape-factory [shape-wrapper] @@ -119,12 +39,9 @@ childs-ref (mf/use-memo (mf/deps (:id shape)) #(refs/children-objects (:id shape))) childs (mf/deref childs-ref)] - [:* - [:& (mf/provider embed/context) {:value true} - [:& shape-container {:shape shape :ref ref :disable-shadows? (cph/root-frame? shape)} - [:& frame-shape {:shape shape :childs childs} ]]] - - #_[:& debug-layout {:shape shape}]])))) + [:& (mf/provider embed/context) {:value true} + [:& shape-container {:shape shape :ref ref :disable-shadows? (cph/root-frame? shape)} + [:& frame-shape {:shape shape :childs childs} ]]])))) (defn check-props [new-props old-props] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 0ea026601..6d95966d4 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -13,8 +13,10 @@ [app.common.geom.shapes :as gsh] [app.common.types.modifiers :as ctm] [app.main.store :as st] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.viewport.utils :as vwu] [app.util.dom :as dom] + [app.util.globals :as globals] [rumext.v2 :as mf])) (defn- transform-no-resize @@ -78,13 +80,20 @@ [result width height])) +(defn get-shape-node + ([id] + (get-shape-node js/document id)) + + ([base-node id] + (if (= (.-id base-node) (dm/str "shape-" id)) + base-node + (dom/query base-node (dm/str "#shape-" id))))) + (defn get-nodes "Retrieve the DOM nodes to apply the matrix transformation" [base-node {:keys [id type masked-group?] :as shape}] (when (some? base-node) - (let [shape-node (if (= (.-id base-node) (dm/str "shape-" id)) - base-node - (dom/query base-node (dm/str "#shape-" id))) + (let [shape-node (get-shape-node base-node id) frame? (= :frame type) group? (= :group type) @@ -164,7 +173,7 @@ (defn set-transform-att! [node att value] - + (let [old-att (dom/get-attribute node (dm/str "data-old-" att)) new-value (if (some? old-att) (dm/str value " " old-att) @@ -269,6 +278,33 @@ (ctm/modifiers->transform center modifiers))) modifiers)))) + structure-changes + (mf/use-memo + (mf/deps modifiers) + (fn [] + (into {} + (comp (filter (fn [[_ val]] (-> val :modifiers :v3 some?))) + (map (fn [[key val]] + [key (-> val :modifiers :v3)]))) + + modifiers))) + + structure-changes (hooks/use-equal-memo structure-changes) + + add-children + (mf/use-memo + (mf/deps structure-changes) + (fn [] + (into [] + (mapcat (fn [[frame-id changes]] + (->> changes + (filter (fn [{:keys [type]}] (= type :add-children))) + (mapcat (fn [{:keys [value]}] + (->> value (map (fn [id] {:frame frame-id :shape id})))))))) + structure-changes))) + + add-children-prev (hooks/use-previous add-children) + shapes (mf/use-memo (mf/deps transforms) @@ -280,6 +316,31 @@ prev-modifiers (mf/use-var nil) prev-transforms (mf/use-var nil)] + (mf/use-effect + (mf/deps add-children) + (fn [] + (doseq [{:keys [frame shape]} add-children-prev] + (let [frame-node (get-shape-node node frame) + shape-node (get-shape-node shape) + mirror-node (dom/query frame-node (dm/fmt ".mirror-shape[href='#shape-%'" shape))] + (when mirror-node (.remove mirror-node)) + (dom/remove-attribute! (dom/get-parent shape-node) "display"))) + + (doseq [{:keys [frame shape]} add-children] + (let [frame-node (get-shape-node node frame) + shape-node (get-shape-node shape) + + use-node + (.createElementNS globals/document "http://www.w3.org/2000/svg" "use") + + contents-node + (or (dom/query frame-node ".frame-children") frame-node)] + + (dom/set-attribute! use-node "href" (dm/fmt "#shape-%" shape)) + (dom/add-class! use-node "mirror-shape") + (dom/append-child! contents-node use-node) + (dom/set-attribute! (dom/get-parent shape-node) "display" "none"))))) + (mf/use-layout-effect (mf/deps transforms) (fn [] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 4c219d3ac..2896f6d4b 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -23,6 +23,7 @@ [app.main.ui.workspace.shapes.text.viewport-texts-html :as stvh] [app.main.ui.workspace.viewport.actions :as actions] [app.main.ui.workspace.viewport.comments :as comments] + [app.main.ui.workspace.viewport.debug :as wvd] [app.main.ui.workspace.viewport.drawarea :as drawarea] [app.main.ui.workspace.viewport.frame-grid :as frame-grid] [app.main.ui.workspace.viewport.gradients :as gradients] @@ -41,85 +42,7 @@ [app.main.ui.workspace.viewport.widgets :as widgets] [beicon.core :as rx] [debug :refer [debug?]] - [rumext.v2 :as mf] - - [app.common.uuid :as uuid] - [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.layout :as gsl] - [app.main.data.workspace.state-helpers :as wsh] - [app.main.store :as st] - - )) - -(mf/defc debug-layout - {::mf/wrap-props false} - [props] - - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - children (cph/get-immediate-children objects (:id shape)) - layout-data (gsl/calc-layout-data shape children) - drop-areas (gsl/drop-areas shape layout-data children)] - - [:g.debug-layout {:pointer-events "none"} - (for [[idx drop-area] (d/enumerate drop-areas)] - [:rect {:x (:x drop-area) - :y (:y drop-area) - :width (:width drop-area) - :height (:height drop-area) - :style {:fill "blue" - :fill-opacity 0.3 - :stroke "red" - :stroke-width 1 - :stroke-dasharray "3 6"}}]) - - - #_(for [[idx layout-line] (d/enumerate (:layout-lines layout-data))] - (let [col? (gsl/col? shape) - row? (gsl/row? shape) - h-center? (and row? (gsl/h-center? shape)) - h-end? (and row? (gsl/h-end? shape)) - v-center? (and col? (gsl/v-center? shape)) - v-end? (and row? (gsl/v-end? shape)) - - line-width - (+ (-> layout-line :line-width) - (:margin-x shape) - (if col? - (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) - 0)) - - line-height - (+ (-> layout-line :line-height) - (:margin-y shape) - (if row? - (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) - 0)) - ] - [:g {:key (dm/str "line-" idx)} - [:rect {:x (- (-> layout-line :start-p :x) - (cond - h-center? (/ line-width 2) - h-end? line-width - :else 0)) - :y (- (-> layout-line :start-p :y) - (cond - v-center? (/ line-height 2) - v-end? line-height - :else 0)) - :width line-width - :height line-height - :style {:fill "blue" - :fill-opacity 0.3} - }] - #_[:line {:x1 (-> layout-line :start-p :x) - :y1 (-> layout-line :start-p :y) - :x2 (+ (-> layout-line :start-p :x) (if col? line-width 0)) - :y2 (+ (-> layout-line :start-p :y) (if row? line-height 0)) - :transform (gsh/transform-str shape) - :style {:fill "none" - :stroke "red" - :stroke-width 2}}]]))])) + [rumext.v2 :as mf])) ;; --- Viewport @@ -493,15 +416,12 @@ :hover-frame frame-parent :disabled-guides? disabled-guides?}]) - (let [selected-frame (when (= 1 (count selected-shapes)) - (let [selected-shape (get objects-modified (first selected))] - (when (= :frame (:type selected-shape)) - selected-shape))) + ;; DEBUG LAYOUT DROP-ZONES + (when (debug? :layout-drop-zones) + [:& wvd/debug-layout {:selected-shapes selected-shapes + :objects objects-modified + :hover-top-frame-id @hover-top-frame-id}]) - top-frame (or selected-frame (get objects-modified @hover-top-frame-id))] - (when (and top-frame (not= uuid/zero top-frame) (:layout top-frame)) - [:& debug-layout {:shape top-frame - :objects objects-modified}])) (when show-selection-handlers? [:g.selection-handlers {:clipPath "url(#clip-handlers)"} diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs new file mode 100644 index 000000000..0f23f9eee --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -0,0 +1,54 @@ +;; 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.main.ui.workspace.viewport.debug + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.layout :as gsl] + [app.common.pages.helpers :as cph] + [rumext.v2 :as mf])) + +(mf/defc debug-layout + "Debug component to show the auto-layout drop areas" + {::mf/wrap-props false} + [props] + + (let [objects (unchecked-get props "objects") + selected-shapes (unchecked-get props "selected-shapes") + hover-top-frame-id (unchecked-get props "hover-top-frame-id") + + selected-frame + (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type))) + (first selected-shapes)) + + shape (or selected-frame (get objects hover-top-frame-id))] + + (when (and shape (:layout shape)) + (let [children (cph/get-immediate-children objects (:id shape)) + layout-data (gsl/calc-layout-data shape children) + drop-areas (gsl/drop-areas shape layout-data children)] + [:g.debug-layout {:pointer-events "none" + :transform (gsh/transform-str shape)} + (for [[idx drop-area] (d/enumerate drop-areas)] + [:g.drop-area {:key (dm/str "drop-area-" idx)} + [:rect {:x (:x drop-area) + :y (:y drop-area) + :width (:width drop-area) + :height (:height drop-area) + :style {:fill "blue" + :fill-opacity 0.3 + :stroke "red" + :stroke-width 1 + :stroke-dasharray "3 6"}}] + [:text {:x (:x drop-area) + :y (:y drop-area) + :width (:width drop-area) + :height (:height drop-area) + :alignment-baseline "hanging" + :fill "black"} + (:index drop-area)]])])))) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index cc6c7bf8d..2ea2310da 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -11,6 +11,7 @@ [app.common.pages :as cp] [app.common.pages.helpers :as cph] [app.common.types.shape-tree :as ctt] + [app.common.uuid :as uuid] [app.main.data.shortcuts :as dsc] [app.main.data.workspace :as dw] [app.main.data.workspace.path.shortcuts :as psc] @@ -28,8 +29,7 @@ [beicon.core :as rx] [debug :refer [debug?]] [goog.events :as events] - [rumext.v2 :as mf] - [app.common.types.shape-tree :as ctst]) + [rumext.v2 :as mf]) (:import goog.events.EventType)) (defn setup-dom-events [viewport-ref zoom disable-paste in-viewport?] @@ -217,7 +217,7 @@ (get objects))] (reset! hover hover-shape) (reset! hover-ids ids) - (reset! hover-top-frame-id (ctst/top-nested-frame objects (deref last-point-ref)))))))) + (reset! hover-top-frame-id (ctt/top-nested-frame objects (deref last-point-ref)))))))) (defn setup-viewport-modifiers [modifiers objects] @@ -225,7 +225,7 @@ (mf/use-memo (mf/deps objects) #(ctt/get-root-shapes-ids objects)) - modifiers (select-keys modifiers root-frame-ids)] + modifiers (select-keys modifiers (conj root-frame-ids uuid/zero))] (sfd/use-dynamic-modifiers objects globals/document modifiers))) (defn inside-vbox [vbox objects frame-id] diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 96c986604..3a3b9f366 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -67,6 +67,9 @@ ;; Disable frame thumbnails :disable-frame-thumbnails + + ;; Enable a widget to show the auto-layout drop-zones + :layout-drop-zones }) ;; These events are excluded when we activate the :events flag @@ -294,9 +297,16 @@ num-nodes (->> (dom/seq-nodes root-node) count)] #js {:number num-nodes})) -#_(defn modif->js +(defn modif->js [modif-tree objects] (clj->js (into {} (map (fn [[k v]] [(get-in objects [k :name]) v])) modif-tree))) + +(defn ^:export dump-modifiers + [] + (let [page-id (get @st/state :current-page-id) + objects (get-in @st/state [:workspace-data :pages-index page-id :objects])] + (.log js/console (modif->js (:workspace-modifiers @st/state) objects))) + nil)