mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 18:56:55 -06:00
Undo groups: library styles and field scrubs close only the group they open
Using a library's character or paragraph style, and scrubbing a numeric field, closed their undo step with Session::end_undo_group, which closes the open undo group of every document. An undo group open in another document, such as one begun with edit.undoGroup.begin for an MCP command_batch with atomic, was closed too, and that document's next edits became undo steps of their own. When the style failed to apply, or Escape ended the scrub, the other document's group was canceled as well and the edits made in it were undone. Inside an undo group already open in the same document, a scrub closed that group, Escape undid the edits made in it before the drag, and a style that failed to apply left the style it had added. Session::begin_undo_scope and end_undo_scope make the edits in between one undo step of the active document: the scope opens an undo group there, or joins the one already open. Ending a scope closes only the group it opened, in its own document, even when another document became active in the meantime, and a canceled scope undoes only its own edits, also inside a group that was already open. library.use and the scrub use the scope, so Escape inside an open group also takes back a scrub of the Transform panel's Shear field. edit.undoGroup.end still closes the open group of every document.
This commit is contained in:
@@ -305,6 +305,19 @@ pub struct UndoGroup {
|
||||
label: Option<String>,
|
||||
}
|
||||
|
||||
/// One undo step begun with [`Session::begin_undo_scope`], which [`Session::end_undo_scope`] ends.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UndoScope {
|
||||
/// The document ([`DocState::uid`]).
|
||||
uid: u64,
|
||||
/// The journal's length when the scope began.
|
||||
journal: usize,
|
||||
/// When the document's undo group was open already: the document, its selection and the document
|
||||
/// that the group's undo step keeps, as they were when the scope began; `None` when the scope
|
||||
/// opened the group.
|
||||
joined: Option<(Arc<Document>, Selection, Option<Arc<Document>>)>,
|
||||
}
|
||||
|
||||
/// Per-document editing state.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DocState {
|
||||
@@ -2054,6 +2067,72 @@ impl Session {
|
||||
self.journal.remove(&gone);
|
||||
}
|
||||
|
||||
/// [`Session::end_undo_group`] for the open document with [`DocState::uid`] `uid` only (it may
|
||||
/// have closed since): its open undo group closes, and undo groups open in other documents stay
|
||||
/// open.
|
||||
fn end_document_undo_group(&mut self, uid: u64, cancel: bool) {
|
||||
// The other documents' groups are set aside while `end_undo_group` closes this one's.
|
||||
let others: Vec<(u64, UndoGroup)> =
|
||||
self.docs.iter_mut().filter(|st| st.uid != uid).filter_map(|st| Some((st.uid, st.undo_group.take()?))).collect();
|
||||
self.end_undo_group(cancel);
|
||||
for (other, g) in others {
|
||||
if let Some(st) = self.document_mut(other) {
|
||||
st.undo_group = Some(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the edits that follow in the active document one undo step, until
|
||||
/// [`Session::end_undo_scope`]: open an undo group there or, when one is open already, join it,
|
||||
/// so that the edits are part of its step. `None` without a document.
|
||||
pub fn begin_undo_scope(&mut self) -> Option<UndoScope> {
|
||||
let journal = self.journal.len();
|
||||
let st = self.active()?;
|
||||
let uid = st.uid;
|
||||
let joined = st.undo_group.as_ref().map(|g| (st.doc.clone(), st.selection.clone(), g.first.clone()));
|
||||
if joined.is_none() {
|
||||
self.begin_undo_group();
|
||||
}
|
||||
Some(UndoScope { uid, journal, joined })
|
||||
}
|
||||
|
||||
/// End `scope` in its own document, whichever document is active now; undo groups open in other
|
||||
/// documents stay open. The undo group the scope opened closes as [`Session::end_undo_group`]
|
||||
/// closes it. A scope that joined an open group leaves the group open; with `cancel`, it puts the
|
||||
/// document and its selection back as they were when the scope began and cuts the journal back to
|
||||
/// where the scope began (only while the group's step is still the newest one), and an undo step
|
||||
/// that the scope's first edit recorded goes too.
|
||||
pub fn end_undo_scope(&mut self, scope: UndoScope, cancel: bool) {
|
||||
let Some((doc, selection, first)) = scope.joined else {
|
||||
self.end_document_undo_group(scope.uid, cancel);
|
||||
return;
|
||||
};
|
||||
let guides_locked = self.menu.guides_locked;
|
||||
let Some(st) = self.document_mut(scope.uid) else { return };
|
||||
let Some(g) = &mut st.undo_group else { return };
|
||||
let newest = g.first.as_ref().is_some_and(|f| st.history.undo.last().is_some_and(|e| Arc::ptr_eq(&e.doc, f)));
|
||||
if !cancel || !newest {
|
||||
return;
|
||||
}
|
||||
match &first {
|
||||
// The scope's first edit recorded the group's step.
|
||||
None => {
|
||||
st.history.undo.pop();
|
||||
g.first = None;
|
||||
}
|
||||
Some(f) if g.first.as_ref().is_some_and(|now| Arc::ptr_eq(now, f)) => {}
|
||||
Some(_) => return,
|
||||
}
|
||||
st.doc = doc;
|
||||
st.selection = selection;
|
||||
if guides_locked {
|
||||
deselect_guides(&st.doc, &mut st.selection);
|
||||
}
|
||||
st.history.recost_last(&st.doc);
|
||||
st.revision += 1;
|
||||
self.journal.truncate(scope.journal);
|
||||
}
|
||||
|
||||
/// Commands with enablement (for menus, palette, MCP `list_commands`).
|
||||
pub fn commands(&self) -> Vec<CommandInfo> {
|
||||
command_specs().iter().map(|c| c.info(self)).collect()
|
||||
|
||||
Reference in New Issue
Block a user