using System; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; public static void Example() { // Subjects and functions for performing undos and redos Subject<Unit> undoSubject = new Subject<Unit>(); Subject<Unit> redoSubject = new Subject<Unit>(); Action undo = () => undoSubject.OnNext(Unit.Default); Action redo = () => redoSubject.OnNext(Unit.Default); // The undo recorder ReactiveUndoRedoRecorder undoRedo = new ReactiveUndoRedoRecorder(undoSubject, redoSubject); // Data sources Subject<string> strSubject0 = new Subject<string>(); Subject<string> strSubject1 = new Subject<string>(); Subject<string> strSubject2 = new Subject<string>(); Action<string> pushSrc0 = strSubject0.OnNext; Action<string> pushSrc1 = strSubject1.OnNext; Action<string> pushSrc2 = strSubject2.OnNext; /* The ReactiveUndoRedoRecorder.Record() method simply records the history of the given observable. When undone/redone, that history is played back through the downstream sequence to the observer. */ // Embed Undo/Redo in streams, and print output to console IDisposable subscriptions = new CompositeDisposable( new[] {strSubject0, strSubject1} .Select( // For each source (source, i) => // Record it and print values source.Record("init", undoRedo) .Select(s => string.Format("{0}: {1}", i, s)) .Subscribe(Console.WriteLine))); // Simulate user interaction pushSrc0("a"); pushSrc1("b"); undo(); //Undo "b" on 1 redo(); //Redo "b" on 1 pushSrc0("d"); redo(); //Does nothing, redo stack has been wiped-out undo(); //Undo "d" on 0 undo(); //Undo "b" on 1 undo(); //Undo "a" on 0 undo(); //Does nothing, undo stack has been wiped-out // Console output: /* 0: a 1: b 1: init 1: b 0: d 0: a 1: init 0: init */ /* The ReactiveUndoRedoRecorder.RecordScan() method acts similarly to the built-in Observable.Scan() method for observable sequences. The recorder will record all intermediate states as they are passed through the output sequence so that they can be undone/redone. Performing an undo or redo will not only play back those intermediate states, but will also reset the internal state of the scan operation to be the replayed value. */ IDisposable recordScanSub = strSubject2.Select(int.Parse) .RecordScan( (state, i) => state + i), 0, undoRedo) .Subscribe(Console.WriteLine); pushSrc2("1"); //0+1 = 1 pushSrc2("1"); //1+1 = 2 pushSrc2("2"); //2+2 = 4 undo(); //Reset to 2 pushSrc2("1"); //2+1 = 3 // Console output: /* 1 2 4 2 3 */ }