git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

src/edit_history.rs (23.8K)

  1 //! Undo for edits to the node tree: parameters, and the graph itself.
  2 //!
  3 //! The app has no project-wide history: a text box undoes its own typing
  4 //! and a viewer state its own handles. This is the third. It holds two
  5 //! kinds of step, on ONE stack so that undo takes them back in the order
  6 //! they were made:
  7 //!
  8 //! - **Parameters** — parameters of one node as they stood before
  9 //!   something changed them: a row of the params pane, the row menu,
 10 //!   `set_param`, or a command that rewrites the lot (Reset Parameters).
 11 //!   Recorded by the writers.
 12 //! - **Structure** — nodes added, nodes removed, nodes moved, nodes
 13 //!   renamed, wires made and broken, the display and bypass flags. Recorded by NOTICING: the
 14 //!   tree is compared with how it stood at the last look
 15 //!   (`State::structure_base`) once an event or an action has been
 16 //!   applied, and what differs is the step. There are a dozen writers of
 17 //!   the graph — the widget's own drag read back a frame later, the
 18 //!   keyboard families, paste, the palette, MCP, the image commands,
 19 //!   Arrange — and a recording call in each is one that the thirteenth
 20 //!   would not make.
 21 //!
 22 //! **A step holds what changed and nothing else.** A parameter step names
 23 //! its parameters; a structure step names its nodes, and of a node that
 24 //! stayed only its position, its two flags and its wires. Restoring a
 25 //! node's whole parameter list would take back what is written to it
 26 //! without passing here — a camera node's Rotation under an orbit, a
 27 //! curve's Points under its handles.
 28 //!
 29 //! **One step per gesture.** The pane writes back on every motion of a
 30 //! drag, so records that share a group are one step until the group is
 31 //! broken: by a press or a release, by Enter, Tab or Escape, or by
 32 //! [`GROUP_IDLE`] without a record, which is what ends a run of wheel
 33 //! notches or of alt+hjkl. The graph is not looked at while a drag is
 34 //! held, so a dragged node is one step, from where it was picked up.
 35 //!
 36 //! It is not `cce_ui::history::History` because a step has to be looked at
 37 //! before it is taken: what is filed for redo is the CURRENT state of what
 38 //! the step names, and what that is is in the step.
 39 
 40 use crate::app::{FsNode, State};
 41 use crate::param::{ParamDef, ParamKind};
 42 use std::time::{Duration, Instant};
 43 
 44 /// Parameters of a node as they stood, and what changed them.
 45 #[derive(Clone)]
 46 pub struct ParamSnapshot {
 47     pub node_id: String,
 48     pub params: Vec<ParamDef>,
 49     /// What the step was, for the status line: "Reset Parameters",
 50     /// "Radius".
 51     pub what: String,
 52 }
 53 
 54 impl ParamSnapshot {
 55     /// The group an edit of these parameters belongs to.
 56     pub fn group(&self) -> String {
 57         let names: Vec<&str> = self.params.iter().map(|p| p.name.as_str()).collect();
 58         format!("{}\u{0}{}", self.node_id, names.join("\u{0}"))
 59     }
 60 }
 61 
 62 /// Whether two states of a parameter are one: what a step would restore.
 63 pub fn same(a: &ParamDef, b: &ParamDef) -> bool {
 64     a.text() == b.text() && a.is_expr() == b.is_expr() && a.view == b.view
 65 }
 66 
 67 pub const LIMIT: usize = 256;
 68 /// How long a group stands with nothing recorded into it.
 69 pub const GROUP_IDLE: Duration = Duration::from_millis(1000);
 70 
 71 /// What a node of a level was, before a structure step.
 72 #[derive(Clone)]
 73 pub enum NodeBefore {
 74     /// Not there: the step added it.
 75     Absent { id: String },
 76     /// There, whole, at this place among its siblings: the step removed it.
 77     Whole { index: usize, node: FsNode },
 78     /// There, and these of its fields were otherwise. `wires` holds the
 79     /// wire parameters that differed, as they were.
 80     Fields { id: String, position: (f32, f32), geometry_visible: bool, bypassed: bool, wires: Vec<ParamDef> },
 81 }
 82 
 83 /// The nodes of one level a structure step changed. `dir_id` is the id of
 84 /// the node whose children they are.
 85 #[derive(Clone)]
 86 pub struct LevelBefore {
 87     pub dir_id: String,
 88     pub nodes: Vec<NodeBefore>,
 89 }
 90 
 91 #[derive(Clone)]
 92 pub struct StructureStep {
 93     pub levels: Vec<LevelBefore>,
 94     /// Nodes that had another name: (id, the name it was). Put back by
 95     /// renaming, not by writing the name, so that what names the node —
 96     /// wires, expression paths anywhere in the tree — follows it back.
 97     pub renames: Vec<(String, String)>,
 98     pub what: String,
 99 }
100 
101 #[derive(Clone)]
102 pub enum Step {
103     Params(ParamSnapshot),
104     Structure(StructureStep),
105 }
106 
107 impl Step {
108     pub fn what(&self) -> &str {
109         match self {
110             Step::Params(p) => &p.what,
111             Step::Structure(s) => &s.what,
112         }
113     }
114 }
115 
116 #[derive(Default)]
117 pub struct EditHistory {
118     undo: Vec<Step>,
119     redo: Vec<Step>,
120     /// The group of the last record and when it was made.
121     group: Option<(String, Instant)>,
122 }
123 
124 impl EditHistory {
125     /// File `before` as what the next undo returns to. A new edit forks:
126     /// what had been undone cannot be redone over it.
127     pub fn record(&mut self, before: Step) {
128         self.group = None;
129         self.push(before);
130     }
131 
132     /// [`Self::record`], unless the last record was of the same group and
133     /// the group still stands: the earlier step already holds what this
134     /// gesture began from.
135     pub fn record_grouped(&mut self, before: Step, group: String) {
136         let now = Instant::now();
137         let standing = matches!(&self.group, Some((g, at)) if *g == group && now.duration_since(*at) < GROUP_IDLE);
138         if !(standing && !self.undo.is_empty()) {
139             self.push(before);
140         }
141         self.group = Some((group, now));
142     }
143 
144     /// The gesture is over: the next grouped record is a step of its own.
145     pub fn break_group(&mut self) {
146         self.group = None;
147     }
148 
149     fn push(&mut self, before: Step) {
150         self.redo.clear();
151         self.undo.push(before);
152         if self.undo.len() > LIMIT {
153             self.undo.remove(0);
154         }
155     }
156 
157     /// Take a step off one stack. The caller restores it and files what
158     /// the restore replaced with [`Self::file`].
159     pub fn take(&mut self, undo: bool) -> Option<Step> {
160         self.group = None;
161         if undo { self.undo.pop() } else { self.redo.pop() }
162     }
163 
164     /// File the state a taken step replaced, on the OTHER stack.
165     pub fn file(&mut self, undo: bool, replaced: Step) {
166         if undo { self.redo.push(replaced) } else { self.undo.push(replaced) }
167     }
168 
169     pub fn can_undo(&self) -> bool {
170         !self.undo.is_empty()
171     }
172 
173     pub fn can_redo(&self) -> bool {
174         !self.redo.is_empty()
175     }
176 
177     pub fn undo_len(&self) -> usize {
178         self.undo.len()
179     }
180 
181     /// Another document: its nodes are not these.
182     pub fn clear(&mut self) {
183         self.undo.clear();
184         self.redo.clear();
185         self.group = None;
186     }
187 }
188 
189 /// A node's wires: its parameters of the `node` kind.
190 fn wires(node: &FsNode) -> impl Iterator<Item = &ParamDef> {
191     node.params.iter().filter(|p| p.kind() == ParamKind::Node)
192 }
193 
194 /// Whether a level's children can be told apart by id. A hand-built tree
195 /// may carry empty ids or one id twice; such a level is not followed.
196 fn ids_tell_apart(nodes: &[FsNode]) -> bool {
197     let mut seen = std::collections::HashSet::new();
198     nodes.iter().all(|n| !n.id.is_empty() && seen.insert(n.id.as_str()))
199 }
200 
201 /// What differs between how a tree stood and how it stands.
202 #[derive(Default)]
203 pub struct Difference {
204     /// The structure that changed, as it WAS: a step's content.
205     pub levels: Vec<LevelBefore>,
206     /// The nodes renamed: (id, the name it was).
207     pub renames: Vec<(String, String)>,
208     /// Whether anything differs at all, a parameter's value included —
209     /// which is no step, and is when the base has to be taken again.
210     pub any: bool,
211     added: Vec<String>,
212     removed: Vec<String>,
213     moved: Vec<String>,
214     wired: usize,
215     flagged: usize,
216 }
217 
218 impl Difference {
219     /// The step's name, for the status line.
220     pub fn what(&self) -> String {
221         let list = |names: &[String]| match names {
222             [one] => one.clone(),
223             many => format!("{} nodes", many.len()),
224         };
225         if !self.renames.is_empty() && self.added.is_empty() && self.removed.is_empty() {
226             // The wires that named the node changed with it, and are part
227             // of the rename.
228             let names: Vec<String> = self.renames.iter().map(|(_, was)| was.clone()).collect();
229             format!("Rename {}", list(&names))
230         } else if !self.added.is_empty() && self.removed.is_empty() {
231             format!("Add {}", list(&self.added))
232         } else if !self.removed.is_empty() && self.added.is_empty() {
233             format!("Delete {}", list(&self.removed))
234         } else if !self.added.is_empty() {
235             "Edit Nodes".to_string()
236         } else if self.wired > 0 {
237             "Wire".to_string()
238         } else if !self.moved.is_empty() {
239             format!("Move {}", list(&self.moved))
240         } else {
241             "Node Flag".to_string()
242         }
243     }
244 
245     /// The group a run of these belongs to, when it is a move and nothing
246     /// else: a run of alt+hjkl is one step.
247     pub fn move_group(&self) -> Option<String> {
248         let only_moves = self.added.is_empty() && self.removed.is_empty() && self.wired == 0 && self.flagged == 0;
249         (only_moves && self.renames.is_empty() && !self.moved.is_empty()).then(|| format!("move\u{0}{}", self.moved.join("\u{0}")))
250     }
251 }
252 
253 /// Compare a level, and the levels under the nodes that stayed.
254 pub fn difference(base: &FsNode, now: &FsNode, out: &mut Difference) {
255     if !ids_tell_apart(&base.children) || !ids_tell_apart(&now.children) {
256         out.any |= base.children.len() != now.children.len();
257         return;
258     }
259     let mut nodes = Vec::new();
260     for (index, was) in base.children.iter().enumerate() {
261         let Some(is) = now.children.iter().find(|n| n.id == was.id) else {
262             out.removed.push(was.name.clone());
263             nodes.push(NodeBefore::Whole { index, node: was.clone() });
264             continue;
265         };
266         let changed_wires: Vec<ParamDef> = wires(was)
267             .filter(|w| is.params.iter().find(|p| p.name == w.name).is_some_and(|p| !same(p, w)))
268             .cloned()
269             .collect();
270         let moved = was.position != is.position;
271         let flagged = was.geometry_visible != is.geometry_visible || was.bypassed != is.bypassed;
272         if moved || flagged || !changed_wires.is_empty() {
273             if moved {
274                 out.moved.push(is.name.clone());
275             }
276             out.flagged += flagged as usize;
277             out.wired += changed_wires.len();
278             nodes.push(NodeBefore::Fields {
279                 id: was.id.clone(),
280                 position: was.position,
281                 geometry_visible: was.geometry_visible,
282                 bypassed: was.bypassed,
283                 wires: changed_wires,
284             });
285         }
286         if was.name != is.name {
287             out.renames.push((was.id.clone(), was.name.clone()));
288         }
289         out.any |= was.name != is.name
290             || was.params.len() != is.params.len()
291             || was.params.iter().zip(is.params.iter()).any(|(a, b)| a.name != b.name || !same(a, b));
292         difference(was, is, out);
293     }
294     for is in &now.children {
295         if !base.children.iter().any(|n| n.id == is.id) {
296             out.added.push(is.name.clone());
297             nodes.push(NodeBefore::Absent { id: is.id.clone() });
298         }
299     }
300     if !nodes.is_empty() {
301         out.any = true;
302         out.levels.push(LevelBefore { dir_id: base.id.clone(), nodes });
303     }
304 }
305 
306 /// Put a tree back as a step says it was, and return the step that would
307 /// put it back as it is: undo's is redo's and redo's undo's.
308 pub fn restore(root: &mut FsNode, step: StructureStep) -> StructureStep {
309     let mut levels = Vec::new();
310     for level in step.levels {
311         let Some(dir) = crate::viewer_state::find_node_by_id_mut(root, &level.dir_id) else { continue };
312         let mut nodes: Vec<NodeBefore> = Vec::new();
313         let mut going: Vec<String> = Vec::new();
314         let mut back: Vec<(usize, FsNode)> = Vec::new();
315         for node in level.nodes {
316             match node {
317                 NodeBefore::Absent { id } => going.push(id),
318                 NodeBefore::Whole { index, node } => back.push((index, node)),
319                 NodeBefore::Fields { id, position, geometry_visible, bypassed, wires } => {
320                     let Some(n) = dir.children.iter_mut().find(|n| n.id == id) else { continue };
321                     let mut were = Vec::new();
322                     for w in wires {
323                         if let Some(p) = n.params.iter_mut().find(|p| p.name == w.name) {
324                             were.push(std::mem::replace(p, w));
325                         }
326                     }
327                     nodes.push(NodeBefore::Fields {
328                         id,
329                         position: std::mem::replace(&mut n.position, position),
330                         geometry_visible: std::mem::replace(&mut n.geometry_visible, geometry_visible),
331                         bypassed: std::mem::replace(&mut n.bypassed, bypassed),
332                         wires: were,
333                     });
334                 }
335             }
336         }
337         // What the step added goes, as it stands NOW and from where it
338         // stands — the places read before any of them is taken out.
339         let mut gone: Vec<(usize, FsNode)> = dir
340             .children
341             .iter()
342             .enumerate()
343             .filter(|(_, n)| going.contains(&n.id))
344             .map(|(i, n)| (i, n.clone()))
345             .collect();
346         dir.children.retain(|n| !going.contains(&n.id));
347         nodes.extend(gone.drain(..).map(|(index, node)| NodeBefore::Whole { index, node }));
348         // What it removed comes back where it was, lowest first, so each
349         // lands among the siblings it had.
350         back.sort_by_key(|(index, _)| *index);
351         for (index, node) in back {
352             nodes.push(NodeBefore::Absent { id: node.id.clone() });
353             let at = index.min(dir.children.len());
354             dir.children.insert(at, node);
355         }
356         levels.push(LevelBefore { dir_id: level.dir_id, nodes });
357     }
358     // The names last, and by renaming: every wire and every expression
359     // path that names the node is written back with it. After the wires
360     // the step holds, or what is filed for redo would be those wires as
361     // the rename had just left them. Last renamed, first put back.
362     let mut renames = Vec::new();
363     for (id, was) in step.renames.into_iter().rev() {
364         let Some(is) = crate::viewer_state::find_node_by_id(root, &id).map(|n| n.name.clone()) else { continue };
365         // A sibling has taken the name since: two nodes of one name would
366         // leave every wire to either naming both. The node keeps the name
367         // it has.
368         let taken = crate::geometry::find_parent_node(root, &id)
369             .is_some_and(|p| p.children.iter().any(|c| c.id != id && c.name == was));
370         if !taken && crate::geometry::rename_node_in_tree(root, &id, &was) {
371             renames.push((id, is));
372         }
373     }
374     renames.reverse();
375     StructureStep { levels, renames, what: step.what }
376 }
377 
378 impl State {
379     /// Take the tree as it stands for what the next look compares with.
380     /// After anything that replaces the tree or that has recorded its own
381     /// step, so that it is not noticed as an edit.
382     pub fn rebase_structure(&mut self) {
383         self.structure_base = Some(self.fs_root.clone());
384     }
385 
386     /// Whether a drag is held. The graph is not looked at until it is let
387     /// go: a dragged node is one step, from where it was picked up.
388     fn gesture_held(&self) -> bool {
389         self.drag_widget.is_some()
390             || self.app_drag.is_some()
391             || self.node_drag_group.is_some()
392             // A camera orbit or a pan is not an edit, but an orbit with a
393             // camera node active writes its rotation every motion — and a
394             // changed parameter is a full-tree clone here (`rebase_structure`).
395             || self.orbit_drag.is_some()
396             || self.pan_drag.is_some()
397             || self.is_panning
398     }
399 
400     /// Look at the tree, and record what of its structure has changed
401     /// since the last look. Run once an event or an action has been
402     /// applied.
403     pub fn record_structure_changes(&mut self) {
404         if !self.gesture_held() {
405             self.note_structure_changes();
406         }
407     }
408 
409     fn note_structure_changes(&mut self) {
410         let Some(base) = &self.structure_base else {
411             self.rebase_structure();
412             return;
413         };
414         let mut diff = Difference::default();
415         difference(base, &self.fs_root, &mut diff);
416         if !diff.any {
417             return;
418         }
419         if !diff.levels.is_empty() || !diff.renames.is_empty() {
420             let group = diff.move_group();
421             let step =
422                 Step::Structure(StructureStep { what: diff.what(), levels: diff.levels, renames: diff.renames });
423             match group {
424                 Some(group) => self.edit_history.record_grouped(step, group),
425                 None => self.edit_history.record(step),
426             }
427         }
428         self.rebase_structure();
429     }
430 
431     /// Record parameters of a node as they were. For the writers of
432     /// parameters, which know what they changed, and call this once they
433     /// have changed it.
434     pub fn record_params(&mut self, before: ParamSnapshot, grouped: bool) {
435         // A wire is structure too. The base is told of these parameters
436         // as they are now, so that the look below does not take this edit
437         // for one of its own and record it a second time.
438         let now: Vec<ParamDef> = crate::viewer_state::find_node_by_id(&self.fs_root, &before.node_id)
439             .map(|n| {
440                 n.params.iter().filter(|p| before.params.iter().any(|b| b.name == p.name)).cloned().collect()
441             })
442             .unwrap_or_default();
443         if let Some(node) = self
444             .structure_base
445             .as_mut()
446             .and_then(|base| crate::viewer_state::find_node_by_id_mut(base, &before.node_id))
447         {
448             for p in now {
449                 if let Some(was) = node.params.iter_mut().find(|w| w.name == p.name) {
450                     *was = p;
451                 }
452             }
453         }
454         // What the graph did before this edit is its own step, under it.
455         self.note_structure_changes();
456         if grouped {
457             let group = before.group();
458             self.edit_history.record_grouped(Step::Params(before), group);
459         } else {
460             self.edit_history.record(Step::Params(before));
461         }
462     }
463 
464     /// Record that `pname` of a node was `before` until just now, if it is
465     /// not still. For the writers that change one parameter in one go: the
466     /// row menu and `set_param`.
467     pub fn record_param_edit(&mut self, node_id: &str, before: ParamDef) {
468         let now = crate::viewer_state::find_node_by_id(&self.fs_root, node_id)
469             .and_then(|n| n.params.iter().find(|p| p.name == before.name));
470         if now.is_some_and(|p| !same(p, &before)) {
471             self.record_params(
472                 ParamSnapshot { node_id: node_id.to_string(), what: before.name.clone(), params: vec![before] },
473                 false,
474             );
475         }
476     }
477 
478     /// Undo (or redo) the last edit to the tree. False when there is none,
479     /// so the caller can say nothing was taken.
480     pub fn history_step(&mut self, undo: bool) -> bool {
481         // What has been done and not yet looked at is the step to take.
482         self.note_structure_changes();
483         let Some(step) = self.edit_history.take(undo) else { return false };
484         let verb = if undo { "Undo" } else { "Redo" };
485         let what = step.what().to_string();
486         match step {
487             Step::Params(step) => {
488                 let Some(node) = crate::viewer_state::find_node_by_id_mut(&mut self.fs_root, &step.node_id) else {
489                     // Deleted since. The step names nothing, and is dropped.
490                     self.update_status_text(&format!("{verb} {what}: the node is gone"));
491                     return false;
492                 };
493                 // By name: the step holds the parameters that changed, and
494                 // the rest of the node is as whatever wrote it last left it.
495                 let mut replaced = Vec::new();
496                 for was in step.params {
497                     if let Some(p) = node.params.iter_mut().find(|p| p.name == was.name) {
498                         replaced.push(std::mem::replace(p, was));
499                     }
500                 }
501                 let name = node.name.clone();
502                 self.edit_history.file(
503                     undo,
504                     Step::Params(ParamSnapshot { node_id: step.node_id, params: replaced, what: step.what }),
505                 );
506                 self.sync_grid_settings();
507                 self.sync_nodes();
508                 self.rebuild_scene_geometry();
509                 self.sync_parameters_pane();
510                 self.update_status_text(&format!("{verb} {what}: {name}"));
511             }
512             Step::Structure(step) => {
513                 // Where the editor is and what is selected are slots, which
514                 // a node coming or going moves: held by id across it.
515                 let path = self.ids_along(&self.current_path.clone());
516                 let selected = self
517                     .graph()
518                     .selected_node()
519                     .and_then(|i| self.current_dir().children.get(i))
520                     .map(|n| n.id.clone());
521                 // The active camera is a name, which a rename changes.
522                 let camera = self
523                     .camera_level()
524                     .children
525                     .iter()
526                     .find(|c| c.node_type == "camera" && c.name == self.active_camera)
527                     .map(|c| c.id.clone());
528                 let inverse = restore(&mut self.fs_root, step);
529                 self.edit_history.file(undo, Step::Structure(inverse));
530                 self.current_path = self.slots_along(&path);
531                 let camera = camera
532                     .and_then(|id| crate::viewer_state::find_node_by_id(&self.fs_root, &id))
533                     .map(|n| n.name.clone());
534                 if let Some(name) = camera {
535                     self.set_active_camera(name);
536                 }
537                 let slot = selected.and_then(|id| self.current_dir().children.iter().position(|n| n.id == id));
538                 self.graph_mut().set_selected_node(slot);
539                 self.grid_cursor_expanse = None;
540                 let at = slot.and_then(|i| self.current_dir().children.get(i)).map(|n| n.position);
541                 if let Some((col, row)) = at {
542                     self.grid_cursor_col = col as i32;
543                     self.grid_cursor_row = row as i32;
544                 }
545                 self.sync_nodes();
546                 self.rebuild_positions();
547                 self.apply_layout();
548                 self.update_panel_bounds();
549                 self.rebuild_scene_geometry();
550                 self.sync_parameters_pane();
551                 self.viewport_dirty = true;
552                 self.update_status_text(&format!("{verb} {what}"));
553             }
554         }
555         self.rebase_structure();
556         if self.syncing_windows() {
557             self.needs_autosave = true;
558         }
559         true
560     }
561 
562     /// The ids of the nodes a path of slots goes down through.
563     fn ids_along(&self, path: &[usize]) -> Vec<String> {
564         let mut node = &self.fs_root;
565         let mut ids = Vec::new();
566         for &slot in path {
567             let Some(child) = node.children.get(slot) else { break };
568             ids.push(child.id.clone());
569             node = child;
570         }
571         ids
572     }
573 
574     /// The path of slots that goes down through those nodes now, as far as
575     /// they are still there.
576     fn slots_along(&self, ids: &[String]) -> Vec<usize> {
577         let mut node = &self.fs_root;
578         let mut path = Vec::new();
579         for id in ids {
580             let Some(slot) = node.children.iter().position(|n| n.id == *id) else { break };
581             path.push(slot);
582             node = &node.children[slot];
583         }
584         path
585     }
586 }