From 29cd8530a3fc8eb9c647a73888de534dcf97c61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 30 Jul 2021 11:12:41 +0200 Subject: [PATCH] :tada: Remember displacements when duplicating several shapes in a row --- CHANGES.md | 1 + .../app/main/data/workspace/selection.cljs | 60 +++++++++++++++++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a0d0b458e..19879c16d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ - Use space + mouse drag to pan, instead of only space [Taiga #1800](https://tree.taiga.io/project/penpot/us/1800). - Allow to zoom with ctrl + middle button [Taiga #1428](https://tree.taiga.io/project/penpot/us/1428). +- Auto placement of duplicated objects [Taiga #1386](https://tree.taiga.io/project/penpot/us/1386). ### :bug: Bugs fixed ### :arrow_up: Deps updates diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index c43c27dfb..0847cc5b5 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -383,6 +383,53 @@ (into [fch] sch))) +(defn clear-memorize-duplicated + [] + (ptk/reify ::clear-memorize-duplicated + ptk/UpdateEvent + (update [_ state] + (d/dissoc-in state [:workspace-local :duplicated])))) + +(defn memorize-duplicated + "When duplicate an object, remember the operation during the following seconds. + If the user moves the duplicated object, and then duplicates it again, check + the displacement and apply it to the third copy. This is useful for doing + grids or cascades of cloned objects." + [id-original id-duplicated] + (ptk/reify ::memorize-duplicated + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :duplicated] {:id-original id-original + :id-duplicated id-duplicated})) + + ptk/WatchEvent + (watch [_ _ stream] + (let [stoper (rx/filter (ptk/type? ::memorize-duplicated) stream)] + (->> (rx/timer 10000) ;; This time may be adjusted after some user testing. + (rx/take-until stoper) + (rx/map clear-memorize-duplicated)))))) + +(defn calc-duplicate-delta + [obj state objects] + (let [{:keys [id-original id-duplicated]} + (get-in state [:workspace-local :duplicated])] + (if (and (not= id-original (:id obj)) + (not= id-duplicated (:id obj))) + + ;; The default is leave normal shapes in place, but put + ;; new frames to the right of the original. + (if (= (:type obj) :frame) + (gpt/point (+ (:width obj) 50) 0) + (gpt/point 0 0)) + + (let [obj-original (get objects id-original) + obj-duplicated (get objects id-duplicated) + distance (gpt/subtract (gpt/point obj-duplicated) + (gpt/point obj-original)) + new-pos (gpt/add (gpt/point obj-duplicated) distance) + delta (gpt/subtract new-pos (gpt/point obj))] + delta)))) + (def duplicate-selected (ptk/reify ::duplicate-selected ptk/WatchEvent @@ -392,9 +439,7 @@ selected (wsh/lookup-selected state) delta (if (= (count selected) 1) (let [obj (get objects (first selected))] - (if (= (:type obj) :frame) - (gpt/point (+ (:width obj) 50) 0) - (gpt/point 0 0))) + (calc-duplicate-delta obj state objects)) (gpt/point 0 0)) unames (dwc/retrieve-used-names objects) @@ -405,15 +450,20 @@ uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %)) (reverse rchanges)) + id-original (when (= (count selected) 1) (first selected)) + selected (->> rchanges (filter #(selected (:old-id %))) (map #(get-in % [:obj :id])) - (into (d/ordered-set)))] + (into (d/ordered-set))) + + id-duplicated (when (= (count selected) 1) (first selected))] (rx/of (dch/commit-changes {:redo-changes rchanges :undo-changes uchanges :origin it}) - (select-shapes selected)))))) + (select-shapes selected) + (memorize-duplicated id-original id-duplicated)))))) (defn change-hover-state [id value]