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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use crate::scene::commands::SceneContext;
use std::fmt::Debug;

pub mod panel;
pub mod universal;

#[macro_export]
macro_rules! define_command_stack {
    ($command_trait:ident, $command_stack:ident, $context:ty) => {
        pub trait $command_trait: Debug + 'static {
            fn name(&mut self, context: &$context) -> String;
            fn execute(&mut self, context: &mut $context);
            fn revert(&mut self, context: &mut $context);
            fn finalize(&mut self, _: &mut $context) {}
        }

        pub struct $command_stack {
            commands: Vec<Box<dyn $command_trait>>,
            top: Option<usize>,
            debug: bool,
        }

        impl $command_stack {
            pub fn new(debug: bool) -> Self {
                Self {
                    commands: Default::default(),
                    top: None,
                    debug,
                }
            }

            pub fn do_command(
                &mut self,
                mut command: Box<dyn $command_trait>,
                mut context: $context,
            ) {
                if self.commands.is_empty() {
                    self.top = Some(0);
                } else {
                    // Advance top
                    match self.top.as_mut() {
                        None => self.top = Some(0),
                        Some(top) => *top += 1,
                    }
                    // Drop everything after top.
                    let top = self.top.unwrap_or(0);
                    if top < self.commands.len() {
                        for mut dropped_command in self.commands.drain(top..) {
                            if self.debug {
                                println!("Finalizing command {:?}", dropped_command);
                            }
                            dropped_command.finalize(&mut context);
                        }
                    }
                }

                if self.debug {
                    println!("Executing command {:?}", command);
                }

                command.execute(&mut context);

                self.commands.push(command);
            }

            pub fn undo(&mut self, mut context: $context) {
                if !self.commands.is_empty() {
                    if let Some(top) = self.top.as_mut() {
                        if let Some(command) = self.commands.get_mut(*top) {
                            if self.debug {
                                println!("Undo command {:?}", command);
                            }
                            command.revert(&mut context)
                        }
                        if *top == 0 {
                            self.top = None;
                        } else {
                            *top -= 1;
                        }
                    }
                }
            }

            pub fn redo(&mut self, mut context: $context) {
                if !self.commands.is_empty() {
                    let command = match self.top.as_mut() {
                        None => {
                            self.top = Some(0);
                            self.commands.first_mut()
                        }
                        Some(top) => {
                            let last = self.commands.len() - 1;
                            if *top < last {
                                *top += 1;
                                self.commands.get_mut(*top)
                            } else {
                                None
                            }
                        }
                    };

                    if let Some(command) = command {
                        if self.debug {
                            println!("Redo command {:?}", command);
                        }
                        command.execute(&mut context)
                    }
                }
            }

            pub fn clear(&mut self, mut context: $context) {
                for mut dropped_command in self.commands.drain(..) {
                    if self.debug {
                        println!("Finalizing command {:?}", dropped_command);
                    }
                    dropped_command.finalize(&mut context);
                }
            }
        }
    };
}

define_command_stack!(Command, CommandStack, SceneContext);