From 4ecc166055fb28736cfc7b3c4dec4b408fa0701a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 3 Nov 2022 15:11:29 +0100 Subject: [PATCH] :sparkles: Remove fill/auto when resizing --- .../src/app/common/geom/shapes/modifiers.cljc | 9 +- common/src/app/common/pages/helpers.cljc | 9 +- common/src/app/common/types/modifiers.cljc | 15 +- common/src/app/common/types/shape/layout.cljc | 12 +- .../app/main/data/workspace/modifiers.cljs | 284 ++++++++++++++ .../app/main/data/workspace/selection.cljs | 7 +- .../app/main/data/workspace/shape_layout.cljs | 10 +- .../app/main/data/workspace/transforms.cljs | 347 +++--------------- frontend/src/app/main/refs.cljs | 7 +- .../sidebar/options/menus/measures.cljs | 8 + .../src/app/main/ui/workspace/viewport.cljs | 2 +- .../app/main/ui/workspace/viewport/debug.cljs | 40 +- frontend/src/app/util/snap_data.cljs | 78 ++-- 13 files changed, 479 insertions(+), 349 deletions(-) create mode 100644 frontend/src/app/main/data/workspace/modifiers.cljs diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 9f7b9ae5c..878c97759 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -138,20 +138,21 @@ (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) transformed-parent (gtr/transform-shape parent modifiers) + children (->> transformed-parent :shapes (map (comp apply-modifiers (d/getf objects)))) {auto-width :width auto-height :height} - (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) - (gcl/layout-content-bounds parent children)) + (when (and (d/not-empty? children) (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent))) + (gcl/layout-content-bounds transformed-parent children)) modifiers (cond-> modifiers - (and (some? auto-width) (ctl/auto-width? parent)) + (and (some? auto-width) (ctl/auto-width? transformed-parent)) (set-parent-auto-width transformed-parent auto-width) - (and (some? auto-height) (ctl/auto-height? parent)) + (and (some? auto-height) (ctl/auto-height? transformed-parent)) (set-parent-auto-height transformed-parent auto-height))] (assoc-in modif-tree [(:id parent) :modifiers] modifiers)))) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index f984574f5..55b3c571e 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -71,9 +71,12 @@ (defn get-children-ids [objects id] - (if-let [shapes (-> (get objects id) :shapes (some-> vec))] - (into shapes (mapcat #(get-children-ids objects %)) shapes) - [])) + (letfn [(get-children-ids-rec + [id processed] + (when (not (contains? processed id)) + (when-let [shapes (-> (get objects id) :shapes (some-> vec))] + (into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))] + (get-children-ids-rec id #{}))) (defn get-children [objects id] diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index a99c5b893..25e17df9d 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -32,6 +32,7 @@ ;; - structure-child: Structure recursive ;; * scale-content ;; * rotation +;; * change-properties (def conjv (fnil conj [])) @@ -118,6 +119,13 @@ (-> modifiers (update :structure-child conjv {:type :scale-content :value value}))) +(defn set-change-property + [modifiers property value] + (-> modifiers + (update :structure-child conjv {:type :change-property + :property property + :value value}))) + (defn add-modifiers [modifiers new-modifiers] @@ -376,7 +384,7 @@ (d/removev remove? shapes))) apply-modifier - (fn [shape {:keys [type value index rotation]}] + (fn [shape {:keys [type property value index rotation]}] (cond-> shape (= type :rotation) (update :rotation #(mod (+ % rotation) 360)) @@ -395,7 +403,10 @@ (update :shapes remove-children value) (= type :scale-content) - (apply-scale-content value)))] + (apply-scale-content value) + + (= type :change-property) + (assoc property value)))] (as-> shape $ (reduce apply-modifier $ (:structure-parent modifiers)) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 8ab1f67c4..87a1ad3fe 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -97,8 +97,16 @@ ::layout-item-min-w ::layout-item-align-self])) -(defn layout? [shape] - (and (= :frame (:type shape)) (= :flex (:layout shape)))) +(defn layout? + ([objects id] + (layout? (get objects id))) + ([shape] + (and (= :frame (:type shape)) (= :flex (:layout shape))))) + +(defn layout-child? [objects shape] + (let [parent-id (:parent-id shape) + parent (get objects parent-id)] + (layout? parent))) (defn wrap? [{:keys [layout-wrap-type]}] (= layout-wrap-type :wrap)) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs new file mode 100644 index 000000000..4861414e3 --- /dev/null +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -0,0 +1,284 @@ +;; 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.data.workspace.modifiers + "Events related with shapes transformations" + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.flex-layout :as gsl] + [app.common.math :as mth] + [app.common.pages.common :as cpc] + [app.common.pages.helpers :as cph] + [app.common.spec :as us] + [app.common.types.modifiers :as ctm] + [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.comments :as-alias dwcm] + [app.main.data.workspace.guides :as-alias dwg] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.undo :as dwu] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [potok.core :as ptk])) + +;; -- temporary modifiers ------------------------------------------- + +;; During an interactive transformation of shapes (e.g. when resizing or rotating +;; a group with the mouse), there are a lot of objects that need to be modified +;; (in this case, the group and all its children). +;; +;; To avoid updating the shapes theirselves, and forcing redraw of all components +;; that depend on the "objects" global state, we set a "modifiers" structure, with +;; the changes that need to be applied, and store it in :workspace-modifiers global +;; variable. The viewport reads this and merges it into the objects list it uses to +;; paint the viewport content, redrawing only the objects that have new modifiers. +;; +;; When the interaction is finished (e.g. user releases mouse button), the +;; apply-modifiers event is done, that consolidates all modifiers into the base +;; geometric attributes of the shapes. + + +(defn- check-delta + "If the shape is a component instance, check its relative position respect the + root of the component, and see if it changes after applying a transformation." + [shape root transformed-shape transformed-root objects modif-tree] + (let [root + (cond + (:component-root? shape) + shape + + (nil? root) + (cph/get-root-shape objects shape) + + :else root) + + transformed-root + (cond + (:component-root? transformed-shape) + transformed-shape + + (nil? transformed-root) + (as-> (cph/get-root-shape objects transformed-shape) $ + (gsh/transform-shape (merge $ (get modif-tree (:id $))))) + + :else transformed-root) + + shape-delta + (when root + (gpt/point (- (gsh/left-bound shape) (gsh/left-bound root)) + (- (gsh/top-bound shape) (gsh/top-bound root)))) + + transformed-shape-delta + (when transformed-root + (gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root)) + (- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root)))) + + ;; There are cases in that the coordinates change slightly (e.g. when + ;; rounding to pixel, or when recalculating text positions in different + ;; zoom levels). To take this into account, we ignore movements smaller + ;; than 1 pixel. + distance (if (and shape-delta transformed-shape-delta) + (gpt/distance-vector shape-delta transformed-shape-delta) + (gpt/point 0 0)) + + ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))] + + [root transformed-root ignore-geometry?])) + +(defn- get-ignore-tree + "Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers" + ([modif-tree objects shape] + (get-ignore-tree modif-tree objects shape nil nil {})) + + ([modif-tree objects shape root transformed-root ignore-tree] + (let [children (map (d/getf objects) (:shapes shape)) + + shape-id (:id shape) + transformed-shape (gsh/transform-shape shape (get modif-tree shape-id)) + + [root transformed-root ignore-geometry?] + (check-delta shape root transformed-shape transformed-root objects modif-tree) + + ignore-tree (assoc ignore-tree shape-id ignore-geometry?) + + set-child + (fn [ignore-tree child] + (get-ignore-tree modif-tree objects child root transformed-root ignore-tree))] + + (reduce set-child ignore-tree children)))) + +(defn- update-grow-type + [shape old-shape] + (let [auto-width? (= :auto-width (:grow-type shape)) + auto-height? (= :auto-height (:grow-type shape)) + + changed-width? (not (mth/close? (:width shape) (:width old-shape))) + changed-height? (not (mth/close? (:height shape) (:height old-shape))) + + change-to-fixed? (or (and auto-width? (or changed-height? changed-width?)) + (and auto-height? changed-height?))] + (cond-> shape + change-to-fixed? + (assoc :grow-type :fixed)))) + +(defn- clear-local-transform [] + (ptk/reify ::clear-local-transform + ptk/UpdateEvent + (update [_ state] + (-> state + (dissoc :workspace-modifiers) + (dissoc ::current-move-selected))))) + +(defn create-modif-tree + [ids modifiers] + (us/verify (s/coll-of uuid?) ids) + (into {} (map #(vector % {:modifiers modifiers})) ids)) + +(defn build-modif-tree + [ids objects get-modifier] + (us/verify (s/coll-of uuid?) ids) + (into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids)) + +(defn build-change-frame-modifiers + [modif-tree objects selected target-frame position] + + (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) + layout? (get-in objects [target-frame :layout]) + child-set (set (get-in objects [target-frame :shapes])) + drop-index (when layout? (gsl/get-drop-index target-frame objects position)) + + update-frame-modifiers + (fn [modif-tree [original-frame shapes]] + (let [shapes (->> shapes (d/removev #(= target-frame %))) + shapes (cond->> shapes + (and layout? (= original-frame target-frame)) + ;; When movining inside a layout frame remove the shapes that are not immediate children + (filterv #(contains? child-set %)))] + (cond-> modif-tree + (not= original-frame target-frame) + (-> (update-in [original-frame :modifiers] ctm/set-remove-children shapes) + (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index)) + + (and layout? (= original-frame target-frame)) + (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index))))] + + (reduce update-frame-modifiers modif-tree origin-frame-ids))) + +(defn modif->js + [modif-tree objects] + (clj->js (into {} + (map (fn [[k v]] + [(get-in objects [k :name]) v])) + modif-tree))) + +(defn set-modifiers + ([modif-tree] + (set-modifiers modif-tree false)) + + ([modif-tree ignore-constraints] + (set-modifiers modif-tree ignore-constraints false)) + + ([modif-tree ignore-constraints ignore-snap-pixel] + (ptk/reify ::set-modifiers + ptk/UpdateEvent + (update [_ state] + (let [objects + (wsh/lookup-page-objects state) + + snap-pixel? + (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) + + modif-tree + (gsh/set-objects-modifiers modif-tree objects ignore-constraints snap-pixel?)] + + (assoc state :workspace-modifiers modif-tree)))))) + +;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints). +(defn set-rotation-modifiers + ([angle shapes] + (set-rotation-modifiers angle shapes (-> shapes gsh/selection-rect gsh/center-selrect))) + + ([angle shapes center] + (ptk/reify ::set-rotation-modifiers + ptk/UpdateEvent + (update [_ state] + (let [objects (wsh/lookup-page-objects state) + ids + (->> shapes + (remove #(get % :blocked false)) + (filter #((cpc/editable-attrs (:type %)) :rotation)) + (map :id)) + + get-modifier + (fn [shape] + (ctm/rotation shape center angle)) + + modif-tree + (-> (build-modif-tree ids objects get-modifier) + (gsh/set-objects-modifiers objects false false))] + + (assoc state :workspace-modifiers modif-tree)))))) + + +(defn apply-modifiers + ([] + (apply-modifiers nil)) + + ([{:keys [undo-transation?] :or {undo-transation? true}}] + (ptk/reify ::apply-modifiers + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + object-modifiers (get state :workspace-modifiers) + + ids (or (keys object-modifiers) []) + ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids) + + shapes (map (d/getf objects) ids) + ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes) + (reduce merge {}))] + + (rx/concat + (if undo-transation? + (rx/of (dwu/start-undo-transaction)) + (rx/empty)) + (rx/of (ptk/event ::dwg/move-frame-guides ids-with-children) + (ptk/event ::dwcm/move-frame-comment-threads ids-with-children) + (dch/update-shapes + ids + (fn [shape] + (let [modif (get-in object-modifiers [(:id shape) :modifiers]) + text-shape? (cph/text-shape? shape)] + (-> shape + (gsh/transform-shape modif) + (cond-> text-shape? + (update-grow-type shape))))) + {:reg-objects? true + :ignore-tree ignore-tree + ;; Attributes that can change in the transform. This way we don't have to check + ;; all the attributes + :attrs [:selrect + :points + :x + :y + :width + :height + :content + :transform + :transform-inverse + :rotation + :position-data + :flip-x + :flip-y + :grow-type + :layout-item-h-sizing + :layout-item-v-sizing + ]}) + (clear-local-transform)) + (if undo-transation? + (rx/of (dwu/commit-undo-transaction)) + (rx/empty)))))))) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index e662eb486..4d3dffaa7 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -21,6 +21,7 @@ [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] + [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.zoom :as dwz] @@ -552,8 +553,11 @@ (filter #(= :frame (get-in % [:obj :type]))) (map #(vector (:old-id %) (get-in % [:obj :id])))) - id-duplicated (first new-selected)] + id-duplicated (first new-selected) + frames (into #{} + (map #(get-in objects [% :frame-id])) + selected)] (rx/concat (->> (rx/from dup-frames) (rx/map (fn [[old-id new-id]] (dwt/duplicate-thumbnail old-id new-id)))) @@ -561,6 +565,7 @@ ;; Warning: This order is important for the focus mode. (rx/of (dch/commit-changes changes) (select-shapes new-selected) + (dwsl/update-layout-positions frames) (memorize-duplicated id-original id-duplicated)))))))))) (defn change-hover-state diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 956d24e1e..1e6864349 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -11,8 +11,8 @@ [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.changes :as dwc] + [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.state-helpers :as wsh] - [app.main.data.workspace.transforms :as dwt] [beicon.core :as rx] [potok.core :as ptk])) @@ -50,11 +50,11 @@ ptk/WatchEvent (watch [_ state _] (let [objects (wsh/lookup-page-objects state) - ids (->> ids (filter #(get-in objects [% :layout])))] + ids (->> ids (filter (partial ctl/layout? objects)))] (if (d/not-empty? ids) - (let [modif-tree (dwt/create-modif-tree ids (ctm/reflow))] - (rx/of (dwt/set-modifiers modif-tree) - (dwt/apply-modifiers))) + (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow))] + (rx/of (dwm/set-modifiers modif-tree) + (dwm/apply-modifiers))) (rx/empty)))))) ;; TODO LAYOUT: Remove constraints from children diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 1769d41ae..9ec4c4383 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -14,15 +14,14 @@ [app.common.geom.shapes.flex-layout :as gsl] [app.common.math :as mth] [app.common.pages.changes-builder :as pcb] - [app.common.pages.common :as cpc] [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.modifiers :as ctm] [app.common.types.shape-tree :as ctst] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] - [app.main.data.workspace.comments :as-alias dwcm] - [app.main.data.workspace.guides :as-alias dwg] + [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] @@ -97,269 +96,12 @@ (update state :workspace-local dissoc :transform)))) -;; -- Temporary modifiers ------------------------------------------- - -;; During an interactive transformation of shapes (e.g. when resizing or rotating -;; a group with the mouse), there are a lot of objects that need to be modified -;; (in this case, the group and all its children). -;; -;; To avoid updating the shapes theirselves, and forcing redraw of all components -;; that depend on the "objects" global state, we set a "modifiers" structure, with -;; the changes that need to be applied, and store it in :workspace-modifiers global -;; variable. The viewport reads this and merges it into the objects list it uses to -;; paint the viewport content, redrawing only the objects that have new modifiers. -;; -;; When the interaction is finished (e.g. user releases mouse button), the -;; apply-modifiers event is done, that consolidates all modifiers into the base -;; geometric attributes of the shapes. - -(declare clear-local-transform) - -(declare get-ignore-tree) - -(defn create-modif-tree - [ids modifiers] - (us/verify (s/coll-of uuid?) ids) - (into {} (map #(vector % {:modifiers modifiers})) ids)) - -(defn build-modif-tree - [ids objects get-modifier] - (us/verify (s/coll-of uuid?) ids) - (into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids)) - -(defn build-change-frame-modifiers - [modif-tree objects selected target-frame position] - - (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) - layout? (get-in objects [target-frame :layout]) - child-set (set (get-in objects [target-frame :shapes])) - drop-index (when layout? (gsl/get-drop-index target-frame objects position)) - - update-frame-modifiers - (fn [modif-tree [original-frame shapes]] - (let [shapes (->> shapes (d/removev #(= target-frame %))) - shapes (cond->> shapes - (and layout? (= original-frame target-frame)) - ;; When movining inside a layout frame remove the shapes that are not immediate children - (filterv #(contains? child-set %)))] - (cond-> modif-tree - (not= original-frame target-frame) - (-> (update-in [original-frame :modifiers] ctm/set-remove-children shapes) - (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index)) - - (and layout? (= original-frame target-frame)) - (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index))))] - - (reduce update-frame-modifiers modif-tree origin-frame-ids))) - -(defn modif->js - [modif-tree objects] - (clj->js (into {} - (map (fn [[k v]] - [(get-in objects [k :name]) v])) - modif-tree))) - -(defn set-modifiers - ([modif-tree] - (set-modifiers modif-tree false)) - - ([modif-tree ignore-constraints] - (set-modifiers modif-tree ignore-constraints false)) - - ([modif-tree ignore-constraints ignore-snap-pixel] - (ptk/reify ::set-modifiers - ptk/UpdateEvent - (update [_ state] - (let [objects - (wsh/lookup-page-objects state) - - snap-pixel? - (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) - - modif-tree - (gsh/set-objects-modifiers modif-tree objects ignore-constraints snap-pixel?)] - - (assoc state :workspace-modifiers modif-tree)))))) - -;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints). -(defn- set-rotation-modifiers - ([angle shapes] - (set-rotation-modifiers angle shapes (-> shapes gsh/selection-rect gsh/center-selrect))) - - ([angle shapes center] - (ptk/reify ::set-rotation-modifiers - ptk/UpdateEvent - (update [_ state] - (let [objects (wsh/lookup-page-objects state) - ids - (->> shapes - (remove #(get % :blocked false)) - (filter #((cpc/editable-attrs (:type %)) :rotation)) - (map :id)) - - get-modifier - (fn [shape] - (ctm/rotation shape center angle)) - - modif-tree - (-> (build-modif-tree ids objects get-modifier) - (gsh/set-objects-modifiers objects false false))] - - (assoc state :workspace-modifiers modif-tree)))))) - -(defn- update-grow-type - [shape old-shape] - (let [auto-width? (= :auto-width (:grow-type shape)) - auto-height? (= :auto-height (:grow-type shape)) - - changed-width? (not (mth/close? (:width shape) (:width old-shape))) - changed-height? (not (mth/close? (:height shape) (:height old-shape))) - - change-to-fixed? (or (and auto-width? (or changed-height? changed-width?)) - (and auto-height? changed-height?))] - (cond-> shape - change-to-fixed? - (assoc :grow-type :fixed)))) - -(defn apply-modifiers - ([] - (apply-modifiers nil)) - - ([{:keys [undo-transation?] :or {undo-transation? true}}] - (ptk/reify ::apply-modifiers - ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - object-modifiers (get state :workspace-modifiers) - - ids (or (keys object-modifiers) []) - ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids) - - shapes (map (d/getf objects) ids) - ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes) - (reduce merge {}))] - - (rx/concat - (if undo-transation? - (rx/of (dwu/start-undo-transaction)) - (rx/empty)) - (rx/of (ptk/event ::dwg/move-frame-guides ids-with-children) - (ptk/event ::dwcm/move-frame-comment-threads ids-with-children) - (dch/update-shapes - ids - (fn [shape] - (let [modif (get-in object-modifiers [(:id shape) :modifiers]) - text-shape? (cph/text-shape? shape)] - (-> shape - (gsh/transform-shape modif) - (cond-> text-shape? - (update-grow-type shape))))) - {:reg-objects? true - :ignore-tree ignore-tree - ;; Attributes that can change in the transform. This way we don't have to check - ;; all the attributes - :attrs [:selrect - :points - :x - :y - :width - :height - :content - :transform - :transform-inverse - :rotation - :position-data - :flip-x - :flip-y - :grow-type]}) - (clear-local-transform)) - (if undo-transation? - (rx/of (dwu/commit-undo-transaction)) - (rx/empty)))))))) - -(defn- check-delta - "If the shape is a component instance, check its relative position respect the - root of the component, and see if it changes after applying a transformation." - [shape root transformed-shape transformed-root objects modif-tree] - (let [root - (cond - (:component-root? shape) - shape - - (nil? root) - (cph/get-root-shape objects shape) - - :else root) - - transformed-root - (cond - (:component-root? transformed-shape) - transformed-shape - - (nil? transformed-root) - (as-> (cph/get-root-shape objects transformed-shape) $ - (gsh/transform-shape (merge $ (get modif-tree (:id $))))) - - :else transformed-root) - - shape-delta - (when root - (gpt/point (- (gsh/left-bound shape) (gsh/left-bound root)) - (- (gsh/top-bound shape) (gsh/top-bound root)))) - - transformed-shape-delta - (when transformed-root - (gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root)) - (- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root)))) - - ;; There are cases in that the coordinates change slightly (e.g. when - ;; rounding to pixel, or when recalculating text positions in different - ;; zoom levels). To take this into account, we ignore movements smaller - ;; than 1 pixel. - distance (if (and shape-delta transformed-shape-delta) - (gpt/distance-vector shape-delta transformed-shape-delta) - (gpt/point 0 0)) - - ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))] - - [root transformed-root ignore-geometry?])) - -(defn- get-ignore-tree - "Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers" - ([modif-tree objects shape] - (get-ignore-tree modif-tree objects shape nil nil {})) - - ([modif-tree objects shape root transformed-root ignore-tree] - (let [children (map (d/getf objects) (:shapes shape)) - - shape-id (:id shape) - transformed-shape (gsh/transform-shape shape (get modif-tree shape-id)) - - [root transformed-root ignore-geometry?] - (check-delta shape root transformed-shape transformed-root objects modif-tree) - - ignore-tree (assoc ignore-tree shape-id ignore-geometry?) - - set-child - (fn [ignore-tree child] - (get-ignore-tree modif-tree objects child root transformed-root ignore-tree))] - - (reduce set-child ignore-tree children)))) - -(defn- clear-local-transform [] - (ptk/reify ::clear-local-transform - ptk/UpdateEvent - (update [_ state] - (-> state - (dissoc :workspace-modifiers) - (dissoc ::current-move-selected))))) - ;; -- Resize -------------------------------------------------------- (defn start-resize "Enter mouse resize mode, until mouse button is released." [handler ids shape] - (letfn [(resize [shape initial layout [point lock? center? point-snap]] + (letfn [(resize [shape objects initial layout [point lock? center? point-snap]] (let [{:keys [width height]} (:selrect shape) {:keys [rotation]} shape @@ -423,18 +165,42 @@ (some? displacement) (gpt/add displacement)) + ;; When the horizontal/vertical scale a flex children with auto/fill + ;; we change it too fixed + layout? (ctl/layout? shape) + layout-child? (ctl/layout-child? objects shape) + auto-width? (ctl/auto-width? shape) + fill-width? (ctl/fill-width? shape) + auto-height? (ctl/auto-height? shape) + fill-height? (ctl/fill-height? shape) + + set-fix-width? + (and (not (mth/close? (:x scalev) 1)) + (or (and (or layout? layout-child?) auto-width?) + (and layout-child? fill-width?))) + + set-fix-height? + (and (not (mth/close? (:y scalev) 1)) + (or (and (or layout? layout-child?) auto-height?) + (and layout-child? fill-height?))) + modifiers (-> (ctm/empty-modifiers) (cond-> displacement (ctm/set-move displacement)) (ctm/set-resize scalev resize-origin shape-transform shape-transform-inverse) + (cond-> set-fix-width? + (ctm/set-change-property :layout-item-h-sizing :fix)) + + (cond-> set-fix-height? + (ctm/set-change-property :layout-item-v-sizing :fix)) + (cond-> scale-text (ctm/set-scale-content (:x scalev)))) - modif-tree (create-modif-tree ids modifiers)] - - (rx/of (set-modifiers modif-tree)))) + modif-tree (dwm/create-modif-tree ids modifiers)] + (rx/of (dwm/set-modifiers modif-tree)))) ;; Unifies the instantaneous proportion lock modifier ;; activated by Shift key and the shapes own proportion @@ -458,6 +224,7 @@ zoom (get-in state [:workspace-local :zoom] 1) objects (wsh/lookup-page-objects state page-id) resizing-shapes (map #(get objects %) ids)] + (rx/concat (->> ms/mouse-position (rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt) @@ -465,9 +232,9 @@ (rx/switch-map (fn [[point _ _ :as current]] (->> (snap/closest-snap-point page-id resizing-shapes objects layout zoom focus point) (rx/map #(conj current %))))) - (rx/mapcat (partial resize shape initial-position layout)) + (rx/mapcat (partial resize shape objects initial-position layout)) (rx/take-until stoper)) - (rx/of (apply-modifiers) + (rx/of (dwm/apply-modifiers) (finish-transform)))))))) (defn update-dimensions @@ -487,14 +254,14 @@ (fn [shape] (ctm/change-dimensions shape attr value)) modif-tree - (-> (build-modif-tree ids objects get-modifier) + (-> (dwm/build-modif-tree ids objects get-modifier) (gsh/set-objects-modifiers objects false snap-pixel?))] (assoc state :workspace-modifiers modif-tree))) ptk/WatchEvent (watch [_ _ _] - (rx/of (apply-modifiers))))) + (rx/of (dwm/apply-modifiers))))) (defn change-orientation "Change orientation of shapes, from the sidebar options form. @@ -512,14 +279,14 @@ (fn [shape] (ctm/change-orientation-modifiers shape orientation)) modif-tree - (-> (build-modif-tree ids objects get-modifier) + (-> (dwm/build-modif-tree ids objects get-modifier) (gsh/set-objects-modifiers objects false snap-pixel?))] (assoc state :workspace-modifiers modif-tree))) ptk/WatchEvent (watch [_ _ _] - (rx/of (apply-modifiers))))) + (rx/of (dwm/apply-modifiers))))) ;; -- Rotate -------------------------------------------------------- @@ -560,9 +327,9 @@ (rx/map (fn [[[pos mod?] shift?]] (let [delta-angle (calculate-angle pos mod? shift?)] - (set-rotation-modifiers delta-angle shapes group-center)))) + (dwm/set-rotation-modifiers delta-angle shapes group-center)))) (rx/take-until stoper)) - (rx/of (apply-modifiers) + (rx/of (dwm/apply-modifiers) (finish-transform))))))) (defn increase-rotation @@ -576,10 +343,10 @@ objects (wsh/lookup-page-objects state page-id) rotate-shape (fn [shape] (let [delta (- rotation (:rotation shape))] - (set-rotation-modifiers delta [shape])))] + (dwm/set-rotation-modifiers delta [shape])))] (rx/concat (rx/from (->> ids (map #(get objects %)) (map rotate-shape))) - (rx/of (apply-modifiers))))))) + (rx/of (dwm/apply-modifiers))))))) ;; -- Move ---------------------------------------------------------- @@ -705,15 +472,15 @@ (fn [move-vector] (let [position (gpt/add from-position move-vector) target-frame (ctst/top-nested-frame objects position)] - (-> (create-modif-tree ids (ctm/move move-vector)) - (build-change-frame-modifiers objects selected target-frame position) - (set-modifiers))))) + (-> (dwm/create-modif-tree ids (ctm/move move-vector)) + (dwm/build-change-frame-modifiers objects selected target-frame position) + (dwm/set-modifiers))))) (rx/take-until stopper))) (rx/of (dwu/start-undo-transaction) (calculate-frame-for-move ids) - (apply-modifiers {:undo-transation? false}) + (dwm/apply-modifiers {:undo-transation? false}) (finish-transform) (dwu/commit-undo-transaction))))))))) @@ -756,12 +523,12 @@ (rx/merge (->> move-events (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) - (rx/map #(create-modif-tree selected (ctm/move %))) - (rx/map (partial set-modifiers)) + (rx/map #(dwm/create-modif-tree selected (ctm/move %))) + (rx/map (partial dwm/set-modifiers)) (rx/take-until stopper)) (rx/of (move-selected direction shift?))) - (rx/of (apply-modifiers) + (rx/of (dwm/apply-modifiers) (finish-transform)))) (rx/empty)))))) @@ -789,10 +556,10 @@ (or (:y position) (:y bbox))) delta (gpt/subtract pos cpos) - modif-tree (create-modif-tree [id] (ctm/move delta))] + modif-tree (dwm/create-modif-tree [id] (ctm/move delta))] - (rx/of (set-modifiers modif-tree) - (apply-modifiers)))))) + (rx/of (dwm/set-modifiers modif-tree) + (dwm/apply-modifiers)))))) (defn- calculate-frame-for-move [ids] @@ -851,14 +618,14 @@ selrect (gsh/selection-rect shapes) origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2))) - modif-tree (create-modif-tree + modif-tree (dwm/create-modif-tree selected (-> (ctm/empty-modifiers) (ctm/set-resize (gpt/point -1.0 1.0) origin) (ctm/move (gpt/point (:width selrect) 0))))] - (rx/of (set-modifiers modif-tree true) - (apply-modifiers)))))) + (rx/of (dwm/set-modifiers modif-tree true) + (dwm/apply-modifiers)))))) (defn flip-vertical-selected [] (ptk/reify ::flip-vertical-selected @@ -870,11 +637,11 @@ selrect (gsh/selection-rect shapes) origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect)) - modif-tree (create-modif-tree + modif-tree (dwm/create-modif-tree selected (-> (ctm/empty-modifiers) (ctm/set-resize (gpt/point 1.0 -1.0) origin) (ctm/move (gpt/point 0 (:height selrect)))))] - (rx/of (set-modifiers modif-tree true) - (apply-modifiers)))))) + (rx/of (dwm/set-modifiers modif-tree true) + (dwm/apply-modifiers)))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index fe9b1ca48..72e4420d1 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -443,7 +443,8 @@ (l/derived (fn [objects] (->> ids - (some #(-> (cph/get-parent objects %) ctl/layout?)))) + (map (d/getf objects)) + (some (partial ctl/layout-child? objects)))) workspace-page-objects)) (defn get-flex-child-viewer? @@ -452,8 +453,8 @@ (fn [state] (let [objects (wsh/lookup-viewer-objects state page-id)] (into [] - (comp (filter #(= :flex (:layout (cph/get-parent objects %)))) - (map #(get objects %))) + (comp (filter (partial ctl/layout-child? objects)) + (map (d/getf objects))) ids))) st/state =)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index cba05bc1e..6488fafe0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -87,6 +87,12 @@ flex-child? (->> selection-parents (some ctl/layout?)) + flex-container? (ctl/layout? shape) + flex-auto-width? (ctl/auto-width? shape) + flex-fill-width? (ctl/fill-width? shape) + flex-auto-height? (ctl/auto-height? shape) + flex-fill-height? (ctl/fill-height? shape) + ;; To show interactively the measures while the user is manipulating ;; the shape with the mouse, generate a copy of the shapes applying ;; the transient transformations. @@ -306,6 +312,7 @@ :placeholder "--" :on-click select-all :on-change on-width-change + :disabled (and (or flex-child? flex-container?) (or flex-auto-width? flex-fill-width?)) :value (:width values)}]] [:div.input-element.height {:title (tr "workspace.options.height")} @@ -314,6 +321,7 @@ :placeholder "--" :on-click select-all :on-change on-height-change + :disabled (and (or flex-child? flex-container?) (or flex-auto-height? flex-fill-height?)) :value (:height values)}]] [:div.lock-size {:class (dom/classnames diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 2896f6d4b..586e1b389 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -286,7 +286,7 @@ (when show-frame-outline? [:& outline/shape-outlines - {:objects base-objects + {:objects objects-modified :hover #{(->> @hover-ids (filter #(cph/frame-shape? (get base-objects %))) (remove selected) diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index 869caa8ca..d338c0a71 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -13,6 +13,44 @@ [app.common.pages.helpers :as cph] [rumext.v2 :as mf])) +;; Helper to debug the bounds when set the "hug" content property +#_(mf/defc debug-bounds + "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) + + {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} (:layout-padding shape) + pad-top (or pad-top 0) + pad-right (or pad-right 0) + pad-bottom (or pad-bottom 0) + pad-left (or pad-left 0) + + layout-bounds (gsl/layout-content-bounds shape children)] + [:g.debug-layout {:pointer-events "none" + :transform (gsh/transform-str shape)} + + + [:rect {:x (:x layout-bounds) + :y (:y layout-bounds) + :width (:width layout-bounds) + :height (:height layout-bounds) + :style {:stroke "red" + :fill "none"}}]])))) + (mf/defc debug-layout "Debug component to show the auto-layout drop areas" {::mf/wrap-props false} @@ -27,7 +65,7 @@ (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) diff --git a/frontend/src/app/util/snap_data.cljs b/frontend/src/app/util/snap_data.cljs index 865162aa3..1de3b7a25 100644 --- a/frontend/src/app/util/snap_data.cljs +++ b/frontend/src/app/util/snap_data.cljs @@ -13,6 +13,7 @@ [app.common.pages.diff :as diff] [app.common.pages.helpers :as cph] [app.common.types.shape-tree :as ctst] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.util.geom.grid :as gg] [app.util.geom.snap-points :as snap] @@ -70,7 +71,7 @@ (mapv grid->snap))))) (defn- add-frame - [page-data frame] + [objects page-data frame] (let [frame-id (:id frame) parent-id (:parent-id frame) frame-data (->> (snap/shape-snap-points frame) @@ -79,21 +80,24 @@ :pt %))) grid-x-data (get-grids-snap-points frame :x) grid-y-data (get-grids-snap-points frame :y)] - (-> page-data - ;; Update root frame information - (assoc-in [uuid/zero :objects-data frame-id] frame-data) - (update-in [parent-id :x] (make-insert-tree-data frame-data :x)) - (update-in [parent-id :y] (make-insert-tree-data frame-data :y)) - ;; Update frame information - (assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data)) - (update-in [frame-id :x] #(or % (rt/make-tree))) - (update-in [frame-id :y] #(or % (rt/make-tree))) - (update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x)) - (update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y))))) + (cond-> page-data + (not (ctl/layout-child? objects frame)) + + (-> ;; Update root frame information + (assoc-in [uuid/zero :objects-data frame-id] frame-data) + (update-in [parent-id :x] (make-insert-tree-data frame-data :x)) + (update-in [parent-id :y] (make-insert-tree-data frame-data :y)) + + ;; Update frame information + (assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data)) + (update-in [frame-id :x] #(or % (rt/make-tree))) + (update-in [frame-id :y] #(or % (rt/make-tree))) + (update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x)) + (update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y)))))) (defn- add-shape - [page-data shape] + [objects page-data shape] (let [frame-id (:frame-id shape) snap-points (snap/shape-snap-points shape) shape-data (->> snap-points @@ -101,11 +105,11 @@ :type :shape :id (:id shape) :pt %)))] - (-> page-data - (assoc-in [frame-id :objects-data (:id shape)] shape-data) - (update-in [frame-id :x] (make-insert-tree-data shape-data :x)) - (update-in [frame-id :y] (make-insert-tree-data shape-data :y))))) - + (cond-> page-data + (not (ctl/layout-child? objects shape)) + (-> (assoc-in [frame-id :objects-data (:id shape)] shape-data) + (update-in [frame-id :x] (make-insert-tree-data shape-data :x)) + (update-in [frame-id :y] (make-insert-tree-data shape-data :y)))))) (defn- add-guide [objects page-data guide] @@ -164,22 +168,22 @@ (update-in [:guides (:axis guide)] (make-delete-tree-data guide-data (:axis guide))))))) (defn- update-frame - [page-data [_ new-frame]] + [objects page-data [_ new-frame]] (let [frame-id (:id new-frame) root-data (get-in page-data [uuid/zero :objects-data frame-id]) frame-data (get-in page-data [frame-id :objects-data frame-id])] - (-> page-data - (update-in [uuid/zero :x] (make-delete-tree-data root-data :x)) - (update-in [uuid/zero :y] (make-delete-tree-data root-data :y)) - (update-in [frame-id :x] (make-delete-tree-data frame-data :x)) - (update-in [frame-id :y] (make-delete-tree-data frame-data :y)) - (add-frame new-frame)))) + (as-> page-data $ + (update-in $ [uuid/zero :x] (make-delete-tree-data root-data :x)) + (update-in $ [uuid/zero :y] (make-delete-tree-data root-data :y)) + (update-in $ [frame-id :x] (make-delete-tree-data frame-data :x)) + (update-in $ [frame-id :y] (make-delete-tree-data frame-data :y)) + (add-frame objects $ new-frame)))) (defn- update-shape - [page-data [old-shape new-shape]] - (-> page-data - (remove-shape old-shape) - (add-shape new-shape))) + [objects page-data [old-shape new-shape]] + (as-> page-data $ + (remove-shape $ old-shape) + (add-shape objects $ new-shape))) (defn- update-guide [objects page-data [old-guide new-guide]] @@ -205,8 +209,8 @@ page-data (as-> {} $ (add-root-frame $) - (reduce add-frame $ frames) - (reduce add-shape $ shapes) + (reduce (partial add-frame objects) $ frames) + (reduce (partial add-shape objects) $ shapes) (reduce (partial add-guide objects) $ guides))] (assoc snap-data (:id page) page-data))) @@ -233,16 +237,16 @@ (diff/calculate-page-diff old-page page snap-attrs)] (as-> page-data $ - (reduce update-shape $ change-frame-shapes) + (reduce (partial update-shape objects) $ change-frame-shapes) (reduce remove-frame $ removed-frames) (reduce remove-shape $ removed-shapes) - (reduce update-frame $ updated-frames) - (reduce update-shape $ updated-shapes) - (reduce add-frame $ new-frames) - (reduce add-shape $ new-shapes) - (reduce remove-guide $ removed-guides) + (reduce (partial update-frame objects) $ updated-frames) + (reduce (partial update-shape objects) $ updated-shapes) + (reduce (partial add-frame objects) $ new-frames) + (reduce (partial add-shape objects) $ new-shapes) ;; Guides functions. Need objects to get its frame data + (reduce remove-guide $ removed-guides) (reduce (partial update-guide objects) $ change-frame-guides) (reduce (partial update-guide objects) $ updated-guides) (reduce (partial add-guide objects) $ new-guides)))))