forked from 1b-agent/opencadstudio-upstream
fix(scene): isolate drawing-space state
Cancel stale interaction state across space changes and keep live previews and zoom extents in their active paper/model context.\n\nRefs #539\nRefs #540\nRefs #541
This commit is contained in:
1 parent
8050fc79fd
commit
00cc17ea55
10 files changed
+424
-96
No files matched your search
@@ -524,46 +524,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.command_line.input.clear();
|
||||
return Task::none();
|
||||
}
|
||||
// A hot grip (click-move-click placement in progress) ends on
|
||||
// Escape, leaving the entity at its last previewed position.
|
||||
if self.tabs[self.active_tab].active_grip.take().is_some() {
|
||||
// An Add-Leader arrow being placed: Esc removes it again.
|
||||
if let Some((h, gid)) = self.grip_add_provisional.take() {
|
||||
let i = self.active_tab;
|
||||
use crate::entities::traits::EntityTypeOps;
|
||||
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(h) {
|
||||
e.apply_grip_menu(
|
||||
gid,
|
||||
crate::scene::model::object::GripMenuAction::RemoveLeader,
|
||||
);
|
||||
}
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
self.refresh_selected_grips();
|
||||
}
|
||||
// Cancel an in-progress grip drag: restore the edited
|
||||
// entity from its pre-drag backup, un-hide it, re-tessellate
|
||||
// once, and drop the preview.
|
||||
if let Some(h) = self.grip_preview_handle.take() {
|
||||
let i = self.active_tab;
|
||||
self.grip_text_verts = Vec::new();
|
||||
self.grip_text_slide = false;
|
||||
if let Some(orig) = self.grip_original.take() {
|
||||
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(h) {
|
||||
*e = orig;
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.preview_hidden.remove(&h);
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
// Geometry restored to the backup — re-tessellate just it.
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
self.refresh_selected_grips();
|
||||
}
|
||||
self.tabs[self.active_tab].snap_result = None;
|
||||
self.refresh_properties();
|
||||
// A hot grip (click-move-click placement in progress) rolls
|
||||
// back to its pre-drag image on Escape.
|
||||
if self.cancel_active_grip_edit() {
|
||||
return Task::none();
|
||||
}
|
||||
// Cancel layout rename first, then fall through.
|
||||
|
||||
+57
-13
@@ -3393,14 +3393,25 @@ impl OpenCADStudio {
|
||||
|
||||
Message::LayoutDelete(name) => {
|
||||
let i = self.active_tab;
|
||||
let deleting_current = self.tabs[i].scene.current_layout == name;
|
||||
let cancel_task = if deleting_current {
|
||||
self.cancel_active_command_for_space_change()
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
self.push_undo_snapshot(i, "LAYOUT DEL");
|
||||
let switch_task = if deleting_current {
|
||||
self.on_layout_switch("Model".to_string())
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
if self.tabs[i].scene.delete_layout(&name) {
|
||||
self.layout_rename_state = None;
|
||||
self.command_line
|
||||
.push_output(&format!("Layout \"{name}\" silindi"));
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
Task::none()
|
||||
Task::batch([cancel_task, switch_task])
|
||||
}
|
||||
|
||||
Message::LayoutRenameStart(name) => {
|
||||
@@ -3520,20 +3531,28 @@ impl OpenCADStudio {
|
||||
if name == "Model" {
|
||||
self.command_line
|
||||
.push_error("Cannot delete the Model layout.");
|
||||
Task::none()
|
||||
} else {
|
||||
let deleting_current = self.tabs[i].scene.current_layout == name;
|
||||
let cancel_task = if deleting_current {
|
||||
self.cancel_active_command_for_space_change()
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
self.push_undo_snapshot(i, "LAYOUT DELETE");
|
||||
let switch_task = if deleting_current {
|
||||
self.on_layout_switch("Model".to_string())
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
self.tabs[i].scene.delete_layout(&name);
|
||||
self.tabs[i].dirty = true;
|
||||
// Switch to Model if active layout was deleted.
|
||||
if self.tabs[i].scene.current_layout == name {
|
||||
self.tabs[i].scene.set_current_layout("Model".to_string());
|
||||
}
|
||||
self.layout_manager_selected = "Model".to_string();
|
||||
self.layout_manager_rename_buf = String::new();
|
||||
self.command_line
|
||||
.push_output(&format!("Layout '{name}' deleted."));
|
||||
Task::batch([cancel_task, switch_task])
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::LayoutManagerMoveLeft => {
|
||||
let i = self.active_tab;
|
||||
@@ -3571,12 +3590,13 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
Message::LayoutManagerSetCurrent => {
|
||||
let i = self.active_tab;
|
||||
let name = self.layout_manager_selected.clone();
|
||||
self.tabs[i].scene.set_current_layout(name.clone());
|
||||
self.command_line
|
||||
.push_output(&format!("Switched to layout '{name}'."));
|
||||
Task::none()
|
||||
let task = self.on_layout_switch(name.clone());
|
||||
if self.tabs[self.active_tab].scene.current_layout == name {
|
||||
self.command_line
|
||||
.push_output(&format!("Switched to layout '{name}'."));
|
||||
}
|
||||
task
|
||||
}
|
||||
|
||||
Message::SetTheme(theme) => {
|
||||
@@ -4084,8 +4104,17 @@ impl OpenCADStudio {
|
||||
|
||||
Message::EnterViewport(handle) => {
|
||||
let i = self.active_tab;
|
||||
let context_changed = self.tabs[i].scene.active_viewport != Some(handle);
|
||||
let cancel_task = if context_changed {
|
||||
self.cancel_active_command_for_space_change()
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
let perf = crate::perf::enabled();
|
||||
let total = Instant::now();
|
||||
if context_changed {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
}
|
||||
// Clear paper-space selection before entering model space.
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].scene.active_viewport = Some(handle);
|
||||
@@ -4119,11 +4148,23 @@ impl OpenCADStudio {
|
||||
handle.value(),
|
||||
);
|
||||
}
|
||||
Task::none()
|
||||
if context_changed {
|
||||
self.sync_dyn_fields();
|
||||
}
|
||||
cancel_task
|
||||
}
|
||||
|
||||
Message::ExitViewport => {
|
||||
let i = self.active_tab;
|
||||
let context_changed = self.tabs[i].scene.active_viewport.is_some();
|
||||
let cancel_task = if context_changed {
|
||||
self.cancel_active_command_for_space_change()
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
if context_changed {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
}
|
||||
// Clear model-space selection before returning to paper space.
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].scene.active_viewport = None;
|
||||
@@ -4133,7 +4174,10 @@ impl OpenCADStudio {
|
||||
self.tabs[i].refresh_active_ucs();
|
||||
self.refresh_properties();
|
||||
self.command_line.push_output("PSPACE");
|
||||
Task::none()
|
||||
if context_changed {
|
||||
self.sync_dyn_fields();
|
||||
}
|
||||
cancel_task
|
||||
}
|
||||
|
||||
Message::MspaceCommand => {
|
||||
|
||||
@@ -1488,7 +1488,7 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub(super) fn on_viewport_exit(&mut self) -> Task<Message> {
|
||||
pub(crate) fn on_viewport_exit(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = false;
|
||||
@@ -3676,6 +3676,24 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
pub(crate) fn on_layout_switch(&mut self, name: String) -> Task<Message> {
|
||||
self.on_layout_switch_inner(name, false)
|
||||
}
|
||||
|
||||
/// MVIEW's "Insert view > New" flow deliberately visits Model space to
|
||||
/// define a view and then returns to its paper layout. This is the only
|
||||
/// layout transition allowed to preserve an active command.
|
||||
pub(crate) fn on_layout_switch_preserving_command(
|
||||
&mut self,
|
||||
name: String,
|
||||
) -> Task<Message> {
|
||||
self.on_layout_switch_inner(name, true)
|
||||
}
|
||||
|
||||
fn on_layout_switch_inner(
|
||||
&mut self,
|
||||
name: String,
|
||||
preserve_active_command: bool,
|
||||
) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].is_start {
|
||||
self.command_line
|
||||
@@ -3693,6 +3711,25 @@ impl OpenCADStudio {
|
||||
let perf = crate::perf::enabled();
|
||||
let perf_total = Instant::now();
|
||||
let perf_from = self.tabs[i].scene.current_layout.clone();
|
||||
let context_changed =
|
||||
self.tabs[i].scene.current_layout != name || self.tabs[i].scene.active_viewport.is_some();
|
||||
let preserve_active_command = preserve_active_command
|
||||
&& self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.is_some_and(|command| command.name() == "MVIEW");
|
||||
if context_changed {
|
||||
if preserve_active_command {
|
||||
// MVIEW keeps its command-owned step data, but all host-owned
|
||||
// cursor/snap/dynamic-input state belongs to the old space.
|
||||
self.reset_space_interaction_state();
|
||||
}
|
||||
}
|
||||
let cancel_task = if context_changed && !preserve_active_command {
|
||||
self.cancel_active_command_for_space_change()
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
let going_to_paper = name != "Model";
|
||||
// Persist the camera of the layout we're leaving BEFORE switching
|
||||
// so returning to it restores where the user left off (the
|
||||
@@ -3715,6 +3752,9 @@ impl OpenCADStudio {
|
||||
self.tabs[i].refresh_active_ucs();
|
||||
self.tabs[i].scene.restore_saved_camera();
|
||||
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
|
||||
// `deselect_all` invalidates the scene highlight, but grips and the
|
||||
// Properties panel are separate caches owned by the app.
|
||||
self.refresh_properties();
|
||||
// Grid/snap are per-view: load the layout we just entered (its
|
||||
// sheet viewport in paper space, the model tile in model space)
|
||||
// so model and each layout keep independent grid state.
|
||||
@@ -3744,7 +3784,10 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.geometry_epoch,
|
||||
);
|
||||
}
|
||||
Task::none()
|
||||
if context_changed {
|
||||
self.sync_dyn_fields();
|
||||
}
|
||||
cancel_task
|
||||
}
|
||||
|
||||
pub(super) fn on_layout_create(&mut self) -> Task<Message> {
|
||||
@@ -3754,6 +3797,7 @@ impl OpenCADStudio {
|
||||
.push_info("Open or create a drawing to add a layout.");
|
||||
return Task::none();
|
||||
}
|
||||
let cancel_task = self.cancel_active_command_for_space_change();
|
||||
// Find a unique name (e.g. Layout2, Layout3, ...).
|
||||
let existing = self.tabs[i].scene.layout_names();
|
||||
let mut idx = existing.len();
|
||||
@@ -3779,22 +3823,22 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.set_current_layout(new_name.clone());
|
||||
// Safety net — `add_layout` already creates the overall
|
||||
// sheet viewport; this covers any path that doesn't.
|
||||
self.tabs[i].scene.ensure_sheet_viewport(&new_name);
|
||||
self.tabs[i].scene.deselect_all();
|
||||
let switch_task = self.on_layout_switch(new_name.clone());
|
||||
self.tabs[i].scene.fit_all();
|
||||
self.command_line.push_output(&format!(
|
||||
"Layout \"{new_name}\" created — use MVIEW to add a viewport"
|
||||
));
|
||||
self.tabs[i].dirty = true;
|
||||
return Task::batch([cancel_task, switch_task]);
|
||||
}
|
||||
Err(e) => self
|
||||
.command_line
|
||||
.push_error(&format!("Failed to create layout: {e}")),
|
||||
}
|
||||
Task::none()
|
||||
cancel_task
|
||||
}
|
||||
|
||||
pub(super) fn on_layout_rename_commit(&mut self) -> Task<Message> {
|
||||
|
||||
Reference in new issue
Block a user