evenorog / undo

An undo-redo library.
https://docs.rs/undo
Apache License 2.0
49 stars 6 forks source link

Any examples? #6

Closed jkelleyrtp closed 5 years ago

jkelleyrtp commented 5 years ago

Having a hard time integrating this into my project. How would a struct own a Record?

evenorog commented 5 years ago

Not any other than the ones in the documentation, unfortunately. If the record is meant to modify the struct, the record needs to own the struct, not the other way.

For example:

struct Foo {
    string: undo::Record<String>,
}
Boscop commented 4 years ago

@evenorog Thanks for this crate. What's the recommended way to store the pervious state of an applied action (to allow redo of destructive actions)?

evenorog commented 4 years ago

@Boscop it's not completely clear to me what you mean, do you want to take a copy of the "target" before applying the command and then copy it back when undoing?

For that to work as expected you might want to implement the redo function in addition to apply and undo (apply is only called once). Something like this i guess:

use undo::{Command, Record};

struct Add {
    c: char,
    old_string: String,
}

impl Command<String> for Add {
    fn apply(&mut self, s: &mut String) -> undo::Result {
        self.old_string = s.clone();
        self.redo()
    }

    fn undo(&mut self, s: &mut String) -> undo::Result {
        *s = self.old_string.clone();
        Ok(())
    }

    fn redo(&mut self, s: &mut String) -> undo::Result {
        s.push(self.c);
        Ok(())
    }
}
Boscop commented 4 years ago

@evenorog sorry, I meant, what's the recommended way to implement undo of destructive actions. It feels weird to mutate the action. IMO the action should not be mutated when it's applied (it will be dropped afterwards anyway, so any change to it will get lost). But I also don't want to infect the evolving object with its own history of states.

evenorog commented 4 years ago

@Boscop mutating the command/action is the intended way to do this. The command is not dropped afterwards, it is stored in the Record or History and lives as long as necessary. If you are familiar with Qt's undo framework then this works basically the same way.

Boscop commented 4 years ago

Ah right! So each (destructive) command should store the state necessary for undoing it.

Boscop commented 4 years ago

Btw, with the History, can emacs-like undoing be implemented easily? Such that when you do these actions: A, B, undo, C, undo, undo you end up at state A, B? It would mean: A, B, undo(B), C, undo(C), undo(undo(B)) = A, B

So everything you ever did becomes part of the history. Branching off in the past doesn't overwrite the following history.

evenorog commented 4 years ago

Hmm, History should store the necessary state for this to work, but the branching back is not done automatically when undoing so by default you would end up in the empty state.

Right now I can't say how easy it would be to implement this, but I can try to add an example that does this.

Boscop commented 4 years ago

Thanks, I'm looking forward to it!

Boscop commented 4 years ago

@evenorog In my project, I'd like to support undo/redo but also across sessions. How can I (de)serialize a Record to/from my own representation (sqlite)?

And when should I use the undo crate vs the redo crate? I have a huge application state, every command only changes a small part of it.

evenorog commented 4 years ago

@Boscop For (de)serialization you need to use the redo crate with the serde feature.

The commands in undo is stored as Box<dyn Command> and the commands in redo is stored as a single generic type C. So in undo you can store many different commands and certain features is easier to implement, like merging. When using redo you probably want to use an enum containing all the different commands, and certain features needs to be implemented manually.

Otherwise they are mostly the same and I try to keep the API similar so it is easy to switch from one to the other.