Skip to main content

fyroxed_base/command/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::fyrox::{
22    core::{
23        err,
24        reflect::{
25            is_path_to_array_element, Reflect, ResolvePath, SetFieldByPathError, SetFieldError,
26        },
27        some_or_return, ComponentProvider,
28    },
29    gui::inspector::{PropertyAction, PropertyChanged},
30};
31use std::{
32    any::{type_name, TypeId},
33    fmt::{Debug, Formatter},
34    ops::{Deref, DerefMut, RangeBounds},
35};
36
37pub mod panel;
38
39pub trait CommandContext: ComponentProvider {}
40
41impl dyn CommandContext + '_ {
42    pub fn component_ref<T>(&self) -> Option<&T>
43    where
44        T: 'static,
45    {
46        self.query_component_ref(TypeId::of::<T>())
47            .and_then(|c| c.downcast_ref())
48    }
49
50    pub fn component_mut<T>(&mut self) -> Option<&mut T>
51    where
52        T: 'static,
53    {
54        self.query_component_mut(TypeId::of::<T>())
55            .and_then(|c| c.downcast_mut())
56    }
57
58    pub fn get<T>(&self) -> &T
59    where
60        T: 'static,
61    {
62        self.component_ref().unwrap_or_else(|| {
63            panic!(
64                "Unable to downcast command context to {} type",
65                type_name::<T>()
66            )
67        })
68    }
69
70    pub fn get_mut<T>(&mut self) -> &mut T
71    where
72        T: 'static,
73    {
74        self.component_mut().unwrap_or_else(|| {
75            panic!(
76                "Unable to downcast command context to {} type",
77                type_name::<T>()
78            )
79        })
80    }
81}
82
83/// An object that can be added to the editors [`CommandStack`] so the user
84/// can execute it and revert it.
85pub trait CommandTrait: Debug + 'static {
86    /// Returns `true` if the command does significant actions, that should be saved into a file
87    /// (pretty much any command that changes some scene data is significant). Otherwise, returns
88    /// `false` (for example, selection change is insignificant, because this command does not
89    /// really modify anything in the scene, only editor's runtime data).
90    fn is_significant(&self) -> bool {
91        true
92    }
93    /// The name that the user should see in the command stack.
94    fn name(&mut self, context: &dyn CommandContext) -> String;
95    /// Perform the operation that this object represents.
96    /// This happens when the object is first added to the command stack,
97    /// and when the object is redone after being undone.
98    fn execute(&mut self, context: &mut dyn CommandContext);
99    /// Undo the consequences of calling [`CommandTrait::execute`].
100    fn revert(&mut self, context: &mut dyn CommandContext);
101    /// This object is leaving the command stack, so it will never
102    /// be executed or reverted again.
103    fn finalize(&mut self, _: &mut dyn CommandContext) {}
104}
105
106/// An untyped command for the editor to execute or revert.
107#[derive(Debug)]
108pub struct Command(pub Box<dyn CommandTrait>);
109
110impl Command {
111    /// Create a command from the given `CommandTrait` object.
112    pub fn new<C: CommandTrait>(cmd: C) -> Self {
113        Self(Box::new(cmd))
114    }
115}
116
117impl Deref for Command {
118    type Target = dyn CommandTrait;
119
120    fn deref(&self) -> &Self::Target {
121        &*self.0
122    }
123}
124
125impl DerefMut for Command {
126    fn deref_mut(&mut self) -> &mut Self::Target {
127        &mut *self.0
128    }
129}
130
131/// A list of commands to execute in order as a single command.
132/// The commands are reverted in reverse order.
133/// Use [`CommandGroup::with_custom_name`] to give the command a
134/// name. Otherwise, a name is automatically constructed by listing
135/// the names of the commands in the group.
136#[derive(Debug, Default)]
137pub struct CommandGroup {
138    commands: Vec<Command>,
139    custom_name: String,
140}
141
142impl From<Vec<Command>> for CommandGroup {
143    fn from(commands: Vec<Command>) -> Self {
144        Self {
145            commands,
146            custom_name: Default::default(),
147        }
148    }
149}
150
151impl CommandGroup {
152    /// Add an object of the `CommandTriat` to the group.
153    pub fn push<C: CommandTrait>(&mut self, command: C) {
154        self.commands.push(Command::new(command))
155    }
156
157    /// Add a `Command` to the group.
158    pub fn push_command(&mut self, command: Command) {
159        self.commands.push(command)
160    }
161
162    /// Replace the automatically constructed name.
163    pub fn with_custom_name<S: AsRef<str>>(mut self, name: S) -> Self {
164        self.custom_name = name.as_ref().to_string();
165        self
166    }
167
168    /// True if this group contains no commands.
169    pub fn is_empty(&self) -> bool {
170        self.commands.is_empty()
171    }
172
173    /// The number of commands in the group.
174    pub fn len(&self) -> usize {
175        self.commands.len()
176    }
177}
178
179impl CommandTrait for CommandGroup {
180    fn is_significant(&self) -> bool {
181        self.commands.iter().any(|c| c.is_significant())
182    }
183
184    fn name(&mut self, context: &dyn CommandContext) -> String {
185        if self.custom_name.is_empty() {
186            let mut name = String::from("Command group: ");
187            for cmd in self.commands.iter_mut() {
188                name.push_str(&cmd.name(context));
189                name.push_str(", ");
190            }
191            name
192        } else {
193            self.custom_name.clone()
194        }
195    }
196
197    fn execute(&mut self, context: &mut dyn CommandContext) {
198        for cmd in self.commands.iter_mut() {
199            cmd.execute(context);
200        }
201    }
202
203    fn revert(&mut self, context: &mut dyn CommandContext) {
204        // revert must be done in reverse order.
205        for cmd in self.commands.iter_mut().rev() {
206            cmd.revert(context);
207        }
208    }
209
210    fn finalize(&mut self, context: &mut dyn CommandContext) {
211        for mut cmd in self.commands.drain(..) {
212            cmd.finalize(context);
213        }
214    }
215}
216
217pub struct CommandStack {
218    pub commands: Vec<Command>,
219    pub top: Option<usize>,
220    max_capacity: usize,
221    debug: bool,
222}
223
224impl CommandStack {
225    pub fn new(debug: bool, max_capacity: usize) -> Self {
226        Self {
227            commands: Default::default(),
228            top: None,
229            max_capacity,
230            debug,
231        }
232    }
233
234    pub fn do_command(&mut self, mut command: Command, context: &mut dyn CommandContext) {
235        if self.commands.is_empty() {
236            self.top = Some(0);
237        } else {
238            // Advance top
239            match self.top.as_mut() {
240                None => self.top = Some(0),
241                Some(top) => *top += 1,
242            }
243
244            fn drain<R: RangeBounds<usize>>(
245                commands: &mut Vec<Command>,
246                range: R,
247                context: &mut dyn CommandContext,
248                debug: bool,
249            ) {
250                for mut dropped_command in commands.drain(range) {
251                    if debug {
252                        println!("Finalizing command {dropped_command:?}");
253                    }
254                    dropped_command.finalize(context);
255                }
256            }
257
258            // Drop everything after top.
259            let top = self.top.unwrap_or(0);
260            if top < self.commands.len() {
261                drain(&mut self.commands, top.., context, self.debug);
262            }
263
264            // Drop everything after limit.
265            if self.commands.len() >= self.max_capacity {
266                let range = 0..(self.commands.len() - self.max_capacity);
267                drain(&mut self.commands, range, context, self.debug);
268                if let Some(top) = self.top.as_mut() {
269                    if *top > self.commands.len() {
270                        *top = self.commands.len();
271                    }
272                }
273            }
274        }
275
276        if self.debug {
277            println!("Executing command {command:?}");
278        }
279
280        command.execute(context);
281
282        self.commands.push(command);
283    }
284
285    pub fn top_command(&self) -> Option<&dyn CommandTrait> {
286        self.top
287            .and_then(|top| self.commands.get(top))
288            .map(|v| &**v)
289    }
290
291    pub fn undo(&mut self, context: &mut dyn CommandContext) {
292        if !self.commands.is_empty() {
293            if let Some(top) = self.top.as_mut() {
294                if let Some(command) = self.commands.get_mut(*top) {
295                    if self.debug {
296                        println!("Undo command {command:?}");
297                    }
298                    command.revert(context)
299                }
300                if *top == 0 {
301                    self.top = None;
302                } else {
303                    *top -= 1;
304                }
305            }
306        }
307    }
308
309    pub fn redo(&mut self, context: &mut dyn CommandContext) {
310        if !self.commands.is_empty() {
311            let command = match self.top.as_mut() {
312                None => {
313                    self.top = Some(0);
314                    self.commands.first_mut()
315                }
316                Some(top) => {
317                    let last = self.commands.len() - 1;
318                    if *top < last {
319                        *top += 1;
320                        self.commands.get_mut(*top)
321                    } else {
322                        None
323                    }
324                }
325            };
326
327            if let Some(command) = command {
328                if self.debug {
329                    println!("Redo command {command:?}");
330                }
331                command.execute(context)
332            }
333        }
334    }
335
336    pub fn clear(&mut self, context: &mut dyn CommandContext) {
337        for mut dropped_command in self.commands.drain(..) {
338            if self.debug {
339                println!("Finalizing command {dropped_command:?}");
340            }
341            dropped_command.finalize(context);
342        }
343    }
344}
345
346pub trait EntityGetter:
347    FnMut(&mut dyn CommandContext) -> Option<&mut dyn Reflect> + 'static
348{
349}
350impl<F> EntityGetter for F where
351    F: 'static + FnMut(&mut dyn CommandContext) -> Option<&mut dyn Reflect>
352{
353}
354
355pub fn make_command(
356    property_changed: &PropertyChanged,
357    entity_getter: impl EntityGetter,
358) -> Option<Command> {
359    match PropertyAction::from_field_action(&property_changed.action) {
360        PropertyAction::Modify { value } => Some(Command::new(SetPropertyCommand::new(
361            property_changed.path(),
362            value,
363            entity_getter,
364        ))),
365        PropertyAction::AddItem { value } => Some(Command::new(AddCollectionItemCommand::new(
366            property_changed.path(),
367            value,
368            entity_getter,
369        ))),
370        PropertyAction::RemoveItem { index } => Some(Command::new(
371            RemoveCollectionItemCommand::new(property_changed.path(), index, entity_getter),
372        )),
373        // Must be handled outside, there is not enough context and it near to impossible to create universal reversion
374        // for InheritableVariable<T>.
375        PropertyAction::Revert => None,
376    }
377}
378
379fn try_modify_property<F>(entity: &mut dyn Reflect, path: &str, func: F)
380where
381    F: FnOnce(&mut dyn Reflect),
382{
383    let mut func = Some(func);
384    entity.resolve_path_mut(path, &mut |result| match result {
385        Ok(field) => func.take().unwrap()(field),
386        Err(e) => {
387            err!("There is no such property {path}! Reason: {e:?}")
388        }
389    })
390}
391
392pub struct SetPropertyCommand<F: EntityGetter> {
393    value: Option<Box<dyn Reflect>>,
394    path: String,
395    entity_getter: F,
396}
397
398impl<F: EntityGetter> Debug for SetPropertyCommand<F> {
399    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
400        write!(f, "SetPropertyCommand")
401    }
402}
403
404impl<F: EntityGetter> SetPropertyCommand<F> {
405    pub fn new(path: String, value: Box<dyn Reflect>, entity_getter: F) -> Self {
406        Self {
407            value: Some(value),
408            path,
409            entity_getter,
410        }
411    }
412
413    fn swap(&mut self, ctx: &mut dyn CommandContext) {
414        if is_path_to_array_element(&self.path) {
415            let entity = some_or_return!((self.entity_getter)(ctx));
416            entity.resolve_path_mut(&self.path, &mut |result| match result {
417                Err(reason) => {
418                    err!(
419                        "Failed to set property {}! Invalid path {:?}!",
420                        self.path,
421                        reason
422                    );
423                }
424                Ok(property) => match property.set(self.value.take().unwrap()) {
425                    Ok(old_value) => {
426                        self.value = Some(old_value);
427                    }
428                    Err(current_value) => {
429                        err!(
430                            "Failed to set property {}! Incompatible types. \
431                            Target property: {}. Value: {}!",
432                            self.path,
433                            property.type_name(),
434                            current_value.type_name()
435                        );
436                        self.value = Some(current_value);
437                    }
438                },
439            });
440        } else {
441            let entity = some_or_return!((self.entity_getter)(ctx));
442            entity.set_field_by_path(&self.path, self.value.take().unwrap(), &mut |result| {
443                match result {
444                    Ok(old_value) => {
445                        self.value = Some(old_value);
446                    }
447                    Err(result) => {
448                        let value = match result {
449                            SetFieldByPathError::InvalidPath { value, reason } => {
450                                err!(
451                                    "Failed to set property {}! Invalid path {:?}!",
452                                    self.path,
453                                    reason
454                                );
455
456                                value
457                            }
458                            SetFieldByPathError::InvalidValue {
459                                field_type_name,
460                                value,
461                            } => {
462                                err!(
463                                    "Failed to set property {}! Incompatible types. \
464                                    Target property: {}. Value: {}!",
465                                    self.path,
466                                    field_type_name,
467                                    value.type_name()
468                                );
469
470                                value
471                            }
472                            SetFieldByPathError::SetFieldError(err) => match err {
473                                SetFieldError::NoSuchField { value, .. } => {
474                                    err!(
475                                        "Failed to set property {}, because it does not exist!",
476                                        self.path,
477                                    );
478
479                                    value
480                                }
481                                SetFieldError::InvalidValue {
482                                    field_type_name,
483                                    value,
484                                } => {
485                                    err!(
486                                        "Failed to set property {}! Incompatible types. \
487                                    Target property: {}. Value: {}!",
488                                        self.path,
489                                        field_type_name,
490                                        value.type_name()
491                                    );
492
493                                    value
494                                }
495                            },
496                        };
497                        self.value = Some(value);
498                    }
499                }
500            });
501        }
502    }
503}
504
505impl<F: EntityGetter> CommandTrait for SetPropertyCommand<F> {
506    fn name(&mut self, _: &dyn CommandContext) -> String {
507        format!("Set {} property", self.path)
508    }
509
510    fn execute(&mut self, ctx: &mut dyn CommandContext) {
511        self.swap(ctx);
512    }
513
514    fn revert(&mut self, ctx: &mut dyn CommandContext) {
515        self.swap(ctx);
516    }
517}
518
519pub struct AddCollectionItemCommand<F: EntityGetter> {
520    path: String,
521    item: Option<Box<dyn Reflect>>,
522    entity_getter: F,
523}
524
525impl<F: EntityGetter> Debug for AddCollectionItemCommand<F> {
526    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
527        write!(f, "AddCollectionItemCommand")
528    }
529}
530
531impl<F: EntityGetter> AddCollectionItemCommand<F> {
532    pub fn new(path: String, item: Box<dyn Reflect>, entity_getter: F) -> Self {
533        Self {
534            path,
535            item: Some(item),
536            entity_getter,
537        }
538    }
539}
540
541impl<F: EntityGetter> CommandTrait for AddCollectionItemCommand<F> {
542    fn name(&mut self, _: &dyn CommandContext) -> String {
543        format!("Add item to {} collection", self.path)
544    }
545
546    fn execute(&mut self, ctx: &mut dyn CommandContext) {
547        let entity = some_or_return!((self.entity_getter)(ctx));
548        try_modify_property(entity, &self.path, |field| {
549            field.as_list_mut(&mut |result| {
550                if let Some(list) = result {
551                    if let Err(item) = list.reflect_push(self.item.take().unwrap()) {
552                        err!(
553                            "Failed to push item to {} collection. Type mismatch {} and {}!",
554                            self.path,
555                            item.type_name(),
556                            list.type_name()
557                        );
558                        self.item = Some(item);
559                    }
560                } else {
561                    err!("Property {} is not a collection!", self.path)
562                }
563            });
564        })
565    }
566
567    fn revert(&mut self, ctx: &mut dyn CommandContext) {
568        let entity = some_or_return!((self.entity_getter)(ctx));
569        try_modify_property(entity, &self.path, |field| {
570            field.as_list_mut(&mut |result| {
571                if let Some(list) = result {
572                    if let Some(item) = list.reflect_pop() {
573                        self.item = Some(item);
574                    } else {
575                        err!("Failed to pop item from {} collection!", self.path)
576                    }
577                } else {
578                    err!("Property {} is not a collection!", self.path)
579                }
580            });
581        })
582    }
583}
584
585pub struct RemoveCollectionItemCommand<F: EntityGetter> {
586    path: String,
587    index: usize,
588    value: Option<Box<dyn Reflect>>,
589    entity_getter: F,
590}
591
592impl<F: EntityGetter> Debug for RemoveCollectionItemCommand<F> {
593    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
594        write!(f, "RemoveCollectionItemCommand")
595    }
596}
597
598impl<F: EntityGetter> RemoveCollectionItemCommand<F> {
599    pub fn new(path: String, index: usize, entity_getter: F) -> Self {
600        Self {
601            path,
602            index,
603            value: None,
604            entity_getter,
605        }
606    }
607}
608
609impl<F: EntityGetter> CommandTrait for RemoveCollectionItemCommand<F> {
610    fn name(&mut self, _: &dyn CommandContext) -> String {
611        format!("Remove collection {} item {}", self.path, self.index)
612    }
613
614    fn execute(&mut self, ctx: &mut dyn CommandContext) {
615        let entity = some_or_return!((self.entity_getter)(ctx));
616        try_modify_property(entity, &self.path, |field| {
617            field.as_list_mut(&mut |result| {
618                if let Some(list) = result {
619                    self.value = list.reflect_remove(self.index);
620                } else {
621                    err!("Property {} is not a collection!", self.path)
622                }
623            })
624        })
625    }
626
627    fn revert(&mut self, ctx: &mut dyn CommandContext) {
628        let entity = some_or_return!((self.entity_getter)(ctx));
629        try_modify_property(entity, &self.path, |field| {
630            field.as_list_mut(&mut |result| {
631                if let Some(list) = result {
632                    if let Err(item) = list.reflect_insert(self.index, self.value.take().unwrap()) {
633                        self.value = Some(item);
634                        err!(
635                            "Failed to insert item to {} collection. Type mismatch!",
636                            self.path
637                        )
638                    }
639                } else {
640                    err!("Property {} is not a collection!", self.path)
641                }
642            });
643        })
644    }
645}