From 128c495b2a43c569f6bf35475b5438b7d56f0af2 Mon Sep 17 00:00:00 2001 From: 0xTHAC0 Date: Thu, 24 Sep 2026 18:59:33 +0530 Subject: [PATCH] :sparkles: Don't show duplicate cursor when selection cannot be alt-duplicated (#11195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :bug: Don't show duplicate cursor when selection cannot be alt-duplicated (#11165) When pressing Alt and dragging a shape that is inside a component copy (but is not its root), Penpot showed a :duplicate cursor, suggesting the operation would clone the shape. However duplicate-shapes filters those shapes out via ctk/allow-duplicate?, so the move proceeds but no duplicate is ever created — the cursor lied. Fix: compute can-alt-duplicate? in the viewport, which is truthy only when at least one selected shape passes ctk/allow-duplicate?. Pass it to setup-cursor and gate the :duplicate cursor branch on it. When none of the selected shapes can be duplicated the cursor falls through to :pointer-inner, honestly indicating that only a move will happen. * :paperclip: Add alt-duplicate check to cursor setup Signed-off-by: Andrey Antukh --------- Signed-off-by: Andrey Antukh Co-authored-by: Andrey Antukh --- frontend/src/app/main/ui/workspace/viewport.cljs | 8 +++++++- frontend/src/app/main/ui/workspace/viewport/hooks.cljs | 7 ++++--- frontend/src/app/main/ui/workspace/viewport_wasm.cljs | 9 ++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 1e2070c965..ee858a6182 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -129,6 +129,12 @@ (into [] (keep (d/getf objects-modified))) (not-empty)) + ;; True when at least one selected shape can be alt-duplicated. + ;; Inner shapes of component copies are excluded by ctk/allow-duplicate? + can-alt-duplicate? (mf/with-memo [selected base-objects] + (some #(ctk/allow-duplicate? base-objects %) + (map (d/getf base-objects) selected))) + ;; STATE alt? (mf/use-state false) shift? (mf/use-state false) @@ -317,7 +323,7 @@ (hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?) (hooks/setup-viewport-size vport viewport-ref) - (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? (get path-bar-state :drag-cursor) z? read-only?) + (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? (get path-bar-state :drag-cursor) z? read-only? can-alt-duplicate?) (hooks/setup-keyboard alt? mod? space? z? shift?) (hooks/setup-hover-shapes page-id move-stream base-objects selected mod? hover measure-hover hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures? read-only? transform) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index b7b3ff9292..6b91ba1984 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -92,9 +92,9 @@ (when (not= size vport) (st/emit! (dw/initialize-viewport (dom/get-client-size prnt))))))) -(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing? path-drag-cursor z? workspace-read-only?] +(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing? path-drag-cursor z? workspace-read-only? can-alt-duplicate?] (mf/use-effect - (mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing? path-drag-cursor z? workspace-read-only?) + (mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing? path-drag-cursor z? workspace-read-only? can-alt-duplicate?) (fn [] (let [show-pen? (or (= drawing-tool :path) (and drawing-path? @@ -123,7 +123,8 @@ path-editing? (utils/get-cursor :edit-path) (and @alt? - (not workspace-read-only?)) (utils/get-cursor :duplicate) + (not workspace-read-only?) + can-alt-duplicate?) (utils/get-cursor :duplicate) :else (utils/get-cursor :pointer-inner))] (when (not= @cursor new-cursor) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 6758ce4933..d696a91bed 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -204,6 +204,13 @@ selected-shapes (->> selected (into [] (keep (d/getf objects-modified))) (not-empty)) + + ;; True when at least one selected shape can be alt-duplicated. + ;; Inner shapes of component copies are excluded by ctk/allow-duplicate? + can-alt-duplicate? (mf/with-memo [selected base-objects] + (some #(ctk/allow-duplicate? base-objects %) + (map (d/getf base-objects) selected))) + ;; STATE alt? (mf/use-state false) shift? (mf/use-state false) @@ -630,7 +637,7 @@ (hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?) (hooks/setup-viewport-size vport viewport-ref) - (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? (get path-bar-state :drag-cursor) z? read-only?) + (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? (get path-bar-state :drag-cursor) z? read-only? can-alt-duplicate?) (hooks/setup-keyboard alt? mod? space? z? shift?) (hooks/setup-hover-shapes page-id move-stream base-objects selected mod? hover measure-hover hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures? read-only? transform)