fix(undo): grip-drag history entry + mid-command Ctrl+Z steps (#332)

Grip drags mutated the document without ever pushing history — dragging
a polyline vertex (or any grip) could not be undone. The release-commit
now swaps the pre-drag backup in, snapshots that PRE state as a "GRIP"
entry, swaps the dragged result back and clears the redo stack; the
Esc-cancel path is unchanged and a no-move engaging click records
nothing.

Ctrl+Z during a drawing command steps the command instead of the
document undo swallowing the in-progress object (which also killed the
active command): new CadCommand::on_undo_step hook, consulted before
undo_active_tab. PLINE pops its last vertex (shared with the U option),
LINE reverts the last committed segment and keeps drawing from the
previous point (rides its existing U/UndoDocument path), and ARC
Continue reverts the last arc and resumes from the prior anchor with
the correct tangent via a new anchor history. With nothing placed yet
each falls through to the document undo.

Also drops the two warnings the #336 work left behind (unused Entity
import, dead Snapper::override_active).

Closes #332

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Fable 5 committed 2026-07-22 15:48:22 +03:00
1 parent 9b40881ab1
commit 9b28170d4b
8 files changed
+109 -34

No files matched your search

+11
View File
@@ -3709,6 +3709,17 @@ impl OpenCADStudio {
Message::PspaceCommand => Task::done(Message::ExitViewport),
Message::Undo => {
// Mid-command Ctrl+Z: a drawing command steps itself back
// (PLINE pops the last vertex) instead of the document undo
// swallowing the whole in-progress object.
let i = self.active_tab;
let step = self.tabs[i]
.active_cmd
.as_mut()
.and_then(|c| c.on_undo_step());
if let Some(r) = step {
return self.apply_cmd_result(r);
}
self.undo_active_tab();
Task::none()
}
+22 -4
View File
@@ -1829,11 +1829,29 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
return Task::none();
}
self.tabs[i].active_grip = None;
// Commit the grip drag: keep the doc's dragged geometry
// (drop the cancel backup), un-hide the edited entity and
// re-tessellate the base once, dropping the overlay preview.
// Commit the grip drag: keep the doc's dragged geometry,
// un-hide the edited entity and re-tessellate the base
// once, dropping the overlay preview.
if let Some(h) = self.grip_preview_handle.take() {
self.grip_original = None;
// Undo entry for the drag (#332): the pre-drag backup
// swaps in, the PRE state is snapshotted, then the
// dragged result swaps back. Without this a grip edit
// (e.g. reshaping a polyline vertex) was un-undoable —
// the drag mutated the document without any history.
if let Some(orig) = self.grip_original.take() {
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(h) {
let dragged = std::mem::replace(e, orig);
let snap = self.capture_history_snapshot(i, "GRIP");
if let Some(e2) =
self.tabs[i].scene.document.get_entity_mut(h)
{
*e2 = dragged;
}
self.tabs[i].history.undo_stack.push(snap);
self.tabs[i].history.redo_stack.clear();
self.tabs[i].dirty = true;
}
}
self.grip_text_verts = Vec::new();
self.grip_text_slide = false;
self.tabs[i].scene.hidden.remove(&h);