From 2723e4d47f33777f93bb00022f57a2256350a5bc Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 1 Jul 2026 13:14:33 +0300 Subject: [PATCH] fix(layers): close locked-layer gaps found in review - Double-clicking a locked object no longer opens its text / attribute / in-place block editor. - Clicking a locked object during a command's object-gather no longer counts as completing the selection (don't set selection_just_completed on the locked path). - copy_entities skips locked entities. - The attribute editor's Apply refuses to write when the block's layer was locked while the dialog was open. Co-Authored-By: Claude Opus 4.8 --- src/app/update/command.rs | 8 ++++++++ src/app/update/viewport.rs | 37 +++++++++++++++++++++++++------------ src/scene/modify.rs | 2 ++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/app/update/command.rs b/src/app/update/command.rs index 58ee4f07f..8b03d1882 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -1286,6 +1286,14 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { let Some(handle) = self.attr_editor_handle else { return Task::none(); }; + // The block's layer may have been locked while the editor was open — + // refuse to write attributes to a locked-layer block. + if let Some(layer) = self.tabs[i].scene.locked_layer_name(handle) { + self.command_line.push_info(&format!( + "Object is on locked layer \"{layer}\" — unlock the layer to edit its attributes." + )); + return Task::none(); + } // Snapshot the working copy so the document can be mutated while the // dialog's rows stay put (it remains open for further edits). let rows = self.attr_editor_rows.clone(); diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index db72341dd..2d446b2f8 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -2286,26 +2286,31 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { let hit = hit.filter(|&h| self.tabs[i].scene.passes_selection_filter(h)); if let Some(handle) = hit { - // Individual picks accumulate (issue #47): - // each plain click adds to the selection, - // Shift+click removes the picked entity. - // Esc / empty-space click clears. if let Some(layer) = self.tabs[i].scene.locked_layer_name(handle) { - // Locked layer: visible + snappable, but - // not selectable. Tell the user why. + // Locked layer: visible + snappable but + // not selectable. Report and do nothing + // else — in particular do NOT set + // `selection_just_completed`, or a + // gather command (MOVE's "select + // objects") would wrongly finish. self.command_line.push_info(&format!( "Object is on locked layer \"{layer}\" — unlock the layer to select or edit it." )); - } else if self.shift_down { - self.tabs[i].scene.deselect_entity(handle); } else { - self.tabs[i].scene.select_entity(handle, false); - self.tabs[i].scene.expand_selection_for_groups(&[handle]); + // Individual picks accumulate (issue #47): + // each plain click adds to the selection, + // Shift+click removes the picked entity. + if self.shift_down { + self.tabs[i].scene.deselect_entity(handle); + } else { + self.tabs[i].scene.select_entity(handle, false); + self.tabs[i].scene.expand_selection_for_groups(&[handle]); + } + self.refresh_properties(); + selection_just_completed = true; } - self.refresh_properties(); - selection_just_completed = true; } else { // Empty-space click only ARMS a box here; it // no longer clears the selection, so a box can @@ -2438,6 +2443,14 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { .and_then(|s| Scene::handle_from_wire_name(s)) .or_else(|| self.tabs[i].scene.solid_click_hit(p, view_rot, eye, bounds)); if let Some(handle) = hit { + // Locked layer: double-click must not open any editor + // (text / attribute / in-place block edit). + if let Some(layer) = self.tabs[i].scene.locked_layer_name(handle) { + self.command_line.push_info(&format!( + "Object is on locked layer \"{layer}\" — unlock the layer to edit it." + )); + return Task::none(); + } // Any text-bearing entity opens its in-place editor // (plain box or rich MText editor, per type). A // Leader resolves to the entity it annotates. diff --git a/src/scene/modify.rs b/src/scene/modify.rs index cecda157c..66a177ee2 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -209,8 +209,10 @@ impl Scene { } pub fn copy_entities(&mut self, handles: &[Handle], t: &EntityTransform) -> Vec { + // Objects on a locked layer can't be copied (they can't be selected). let clones: Vec = handles .iter() + .filter(|&&h| !self.is_layer_locked(h)) .filter_map(|&h| self.document.get_entity(h).cloned()) .collect(); let mut new_handles = Vec::with_capacity(clones.len());