Skip to main content

nightshade_api/
undo.rs

1//! A reusable undo and redo stack over the component snapshots in
2//! [`reflect`](crate::reflect). A tool that edits a scene captures the state it
3//! is about to change, commits a transaction, and can step back and forth. This
4//! is the stack the standalone editor builds on the same primitives.
5
6use crate::reflect::{ComponentSnapshot, SnapshotKind, restore_component, snapshot_component};
7use nightshade::prelude::{Entity, World};
8
9struct Change {
10    entity: Entity,
11    kind: SnapshotKind,
12    snapshot: ComponentSnapshot,
13}
14
15/// A dual stack of edit transactions. Each transaction batches the before-state
16/// of every component it touched, so one undo reverses a whole edit and one redo
17/// reapplies it.
18#[derive(Default)]
19pub struct UndoStack {
20    undo: Vec<Vec<Change>>,
21    redo: Vec<Vec<Change>>,
22    pending: Vec<Change>,
23}
24
25impl UndoStack {
26    /// A fresh empty stack.
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Captures an entity's current value for `kind` into the open transaction,
32    /// before applying the change. Call once per component about to change, then
33    /// make the change, then [`commit`](Self::commit).
34    pub fn capture(&mut self, world: &World, entity: Entity, kind: SnapshotKind) {
35        if let Some(snapshot) = snapshot_component(world, entity, kind) {
36            self.pending.push(Change {
37                entity,
38                kind,
39                snapshot,
40            });
41        }
42    }
43
44    /// Closes the open transaction and pushes it onto the undo stack, clearing
45    /// the redo stack since a new edit invalidates any redo history. A commit
46    /// with nothing captured is a no-op.
47    pub fn commit(&mut self) {
48        if self.pending.is_empty() {
49            return;
50        }
51        let transaction = std::mem::take(&mut self.pending);
52        self.undo.push(transaction);
53        self.redo.clear();
54    }
55
56    /// Reverses the most recent committed transaction, restoring every component
57    /// it captured and recording the current state for [`redo`](Self::redo).
58    /// Returns false when there is nothing to undo.
59    pub fn undo(&mut self, world: &mut World) -> bool {
60        let Some(transaction) = self.undo.pop() else {
61            return false;
62        };
63        let mut redo_transaction = Vec::with_capacity(transaction.len());
64        for change in &transaction {
65            if let Some(current) = snapshot_component(world, change.entity, change.kind) {
66                redo_transaction.push(Change {
67                    entity: change.entity,
68                    kind: change.kind,
69                    snapshot: current,
70                });
71            }
72            restore_component(&change.snapshot, world, change.entity);
73        }
74        self.redo.push(redo_transaction);
75        true
76    }
77
78    /// Reapplies the most recently undone transaction. Returns false when there
79    /// is nothing to redo.
80    pub fn redo(&mut self, world: &mut World) -> bool {
81        let Some(transaction) = self.redo.pop() else {
82            return false;
83        };
84        let mut undo_transaction = Vec::with_capacity(transaction.len());
85        for change in &transaction {
86            if let Some(current) = snapshot_component(world, change.entity, change.kind) {
87                undo_transaction.push(Change {
88                    entity: change.entity,
89                    kind: change.kind,
90                    snapshot: current,
91                });
92            }
93            restore_component(&change.snapshot, world, change.entity);
94        }
95        self.undo.push(undo_transaction);
96        true
97    }
98
99    /// Whether an undo is available.
100    pub fn can_undo(&self) -> bool {
101        !self.undo.is_empty()
102    }
103
104    /// Whether a redo is available.
105    pub fn can_redo(&self) -> bool {
106        !self.redo.is_empty()
107    }
108
109    /// Drops all history and any open transaction.
110    pub fn clear(&mut self) {
111        self.undo.clear();
112        self.redo.clear();
113        self.pending.clear();
114    }
115}