1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//! Chokepoint for pushing undo actions through the engine.
//!
//! Every engine-side push of an [`UndoAction`] funnels through here so that
//! actions evicted from the stack (by `max_steps` overflow or by a fresh
//! push clearing the redo history) have their [`UndoAction::on_evict`] hook
//! called with the compositor — releasing any tombstoned GPU textures they
//! own.
use super::DarklyEngine;
use crate::layer::LayerId;
use crate::undo::{CompoundAction, PropertyAction, UndoAction};
impl DarklyEngine {
/// Push a completed undo action. Runs `on_evict` on every action that
/// leaves the stack as a result (the cleared redo history plus anything
/// overflowed past `max_steps`).
///
/// Flushes any pending diff-based undo commit from a just-finished brush
/// stroke first, so the on-stack action ordering matches the user's
/// temporal order — a "paint, then duplicate, then undo" sequence first
/// undoes the duplicate.
pub(crate) fn push_undo(&mut self, action: Box<dyn UndoAction>) {
self.flush_pending_undo_commit();
let evicted = self.undo_stack.push(&mut self.doc, action);
for mut e in evicted {
e.on_evict(&mut self.compositor);
}
}
/// Coalesce variant. Mirrors [`Self::push_undo`] for property-change
/// actions that may merge into the existing top step.
pub(crate) fn coalesce_property_undo(&mut self, action: PropertyAction) {
self.flush_pending_undo_commit();
let evicted = self.undo_stack.coalesce_property(&mut self.doc, action);
for mut e in evicted {
e.on_evict(&mut self.compositor);
}
}
/// Run `op` for each id, collecting any returned undo actions into one
/// [`CompoundAction`] and pushing the bundle as a single undo step. The
/// closure does the per-id mutation directly (so per-id state can be
/// snapshotted between iterations — e.g. `position_in_parent` after a
/// prior id was detached) and returns `Some(action)` if the op produced
/// any reversible work, `None` if it skipped that id.
///
/// No undo entry is pushed when every id was skipped — keeps the stack
/// clean when, say, every requested removal hit a locked layer.
pub(crate) fn batched_undo<F>(&mut self, ids: &[LayerId], mut op: F)
where
F: FnMut(&mut Self, LayerId) -> Option<Box<dyn UndoAction>>,
{
let mut actions = Vec::with_capacity(ids.len());
for &id in ids {
if let Some(a) = op(self, id) {
actions.push(a);
}
}
if !actions.is_empty() {
self.push_undo(Box::new(CompoundAction::new(actions)));
}
}
/// Drain both stacks and run `on_evict` on everything. For document
/// close / engine teardown so tombstoned textures don't outlive their
/// owning engine.
pub(crate) fn drain_undo_for_teardown(&mut self) {
let actions = self.undo_stack.drain_all();
for mut a in actions {
a.on_evict(&mut self.compositor);
}
}
}