From 94e1cb311466328c6fe49c98044a84f815e8f154 Mon Sep 17 00:00:00 2001 From: GMIKE Date: Mon, 17 Aug 2020 18:53:27 +0300 Subject: [PATCH 1/4] add --- .../Helpers/Commands/Observables.cs | 37 ++++ .../Commands/ReactiveCommandWithUndoRedo.cs | 186 ++++++++++++++++++ .../SimpleStateMachineNodeEditor.csproj | 1 + 3 files changed, 224 insertions(+) create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/Observables.cs create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/Observables.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/Observables.cs new file mode 100644 index 0000000..bbe3e5c --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/Observables.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2020 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace System.Reactive.Linq +{ + /// + /// Provides commonly required, statically-allocated, pre-canned observables. + /// + internal static class Observables + { + /// + /// An observable that ticks a single, Boolean value of true. + /// + public static readonly IObservable True = Observable.Return(true); + + /// + /// An observable that ticks a single, Boolean value of false. + /// + /// + /// + /// This observable is equivalent to Observable<bool>.Default, but is provided for convenience. + /// + /// + public static readonly IObservable False = Observable.Return(false); + + /// + /// An observable that ticks Unit.Default as a single value. + /// + /// + /// This observable is equivalent to Observable<Unit>.Default, but is provided for convenience. + /// + /// + public static readonly IObservable Unit = Observable.Default; + } +} diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs new file mode 100644 index 0000000..0125118 --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs @@ -0,0 +1,186 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Text; + +namespace SimpleStateMachineNodeEditor.Helpers.Commands +{ + //public class ReactiveCommandWithUndoRedo : ReactiveCommandBase + //{ + // private readonly Func> _execute; + // private readonly IScheduler _outputScheduler; + // private readonly Subject _executionInfo; + // private readonly ISubject _synchronizedExecutionInfo; + // private readonly IObservable _isExecuting; + // private readonly IObservable _canExecute; + // private readonly IObservable _results; + // private readonly ScheduledSubject _exceptions; + // private readonly IDisposable _canExecuteSubscription; + + // /// + // /// Initializes a new instance of the class. + // /// + // /// The Func to perform when the command is executed. + // /// A observable which has a value if the command can execute. + // /// The scheduler where to send output after the main execution. + // /// Thrown if any dependent parameters are null. + // protected internal ReactiveCommand( + // Func> execute, + // IObservable? canExecute, + // IScheduler? outputScheduler) + // { + // if (execute == null) + // { + // throw new ArgumentNullException(nameof(execute)); + // } + + // if (canExecute == null) + // { + // throw new ArgumentNullException(nameof(canExecute)); + // } + + // if (outputScheduler == null) + // { + // throw new ArgumentNullException(nameof(outputScheduler)); + // } + + // _execute = execute; + // _outputScheduler = outputScheduler; + // _executionInfo = new Subject(); + // _synchronizedExecutionInfo = Subject.Synchronize(_executionInfo, outputScheduler); + // _isExecuting = _synchronizedExecutionInfo + // .Scan( + // 0, + // (acc, next) => + // { + // if (next.Demarcation == ExecutionDemarcation.Begin) + // { + // return acc + 1; + // } + + // if (next.Demarcation == ExecutionDemarcation.End) + // { + // return acc - 1; + // } + + // return acc; + // }) + // .Select(inFlightCount => inFlightCount > 0) + // .StartWith(false) + // .DistinctUntilChanged() + // .Replay(1) + // .RefCount(); + // _canExecute = canExecute + // .Catch(ex => + // { + // _exceptions.OnNext(ex); + // return Observables.False; + // }) + // .StartWith(false) + // .CombineLatest(_isExecuting, (canEx, isEx) => canEx && !isEx) + // .DistinctUntilChanged() + // .Replay(1) + // .RefCount(); + // _results = _synchronizedExecutionInfo + // .Where(x => x.Demarcation == ExecutionDemarcation.Result) + // .Select(x => x.Result); + + // _exceptions = new ScheduledSubject(outputScheduler, RxApp.DefaultExceptionHandler); + + // _canExecuteSubscription = _canExecute.Subscribe(OnCanExecuteChanged); + // } + + // private enum ExecutionDemarcation + // { + // Begin, + // Result, + // End + // } + + // /// + // public override IObservable CanExecute => _canExecute; + + // /// + // public override IObservable IsExecuting => _isExecuting; + + // /// + // public override IObservable ThrownExceptions => _exceptions.AsObservable(); + + // /// + // public override IDisposable Subscribe(IObserver observer) + // { + // return _results.Subscribe(observer); + // } + + // /// + // public override IObservable Execute(TParam parameter = default(TParam)) + // { + // try + // { + // return Observable + // .Defer( + // () => + // { + // _synchronizedExecutionInfo.OnNext(ExecutionInfo.CreateBegin()); + // return Observable.Empty; + // }) + // .Concat(_execute(parameter)) + // .Do(result => _synchronizedExecutionInfo.OnNext(ExecutionInfo.CreateResult(result))) + // .Catch( + // ex => + // { + // _exceptions.OnNext(ex); + // return Observable.Throw(ex); + // }) + // .Finally(() => _synchronizedExecutionInfo.OnNext(ExecutionInfo.CreateEnd())) + // .PublishLast() + // .RefCount() + // .ObserveOn(_outputScheduler); + // } + // catch (Exception ex) + // { + // _exceptions.OnNext(ex); + // return Observable.Throw(ex); + // } + // } + + // /// + // protected override void Dispose(bool disposing) + // { + // if (disposing) + // { + // _executionInfo?.Dispose(); + // _exceptions?.Dispose(); + // _canExecuteSubscription?.Dispose(); + // } + // } + + // private struct ExecutionInfo + // { + // private readonly ExecutionDemarcation _demarcation; + // private readonly TResult _result; + + // private ExecutionInfo(ExecutionDemarcation demarcation, TResult result) + // { + // _demarcation = demarcation; + // _result = result; + // } + + // public ExecutionDemarcation Demarcation => _demarcation; + + // public TResult Result => _result; + + // public static ExecutionInfo CreateBegin() => + // new ExecutionInfo(ExecutionDemarcation.Begin, default!); + + // public static ExecutionInfo CreateResult(TResult result) => + // new ExecutionInfo(ExecutionDemarcation.Result, result); + + // public static ExecutionInfo CreateEnd() => + // new ExecutionInfo(ExecutionDemarcation.End, default!); + // } + //} +} diff --git a/SimpleStateMachineNodeEditor/SimpleStateMachineNodeEditor.csproj b/SimpleStateMachineNodeEditor/SimpleStateMachineNodeEditor.csproj index fa16753..bc7c6ba 100644 --- a/SimpleStateMachineNodeEditor/SimpleStateMachineNodeEditor.csproj +++ b/SimpleStateMachineNodeEditor/SimpleStateMachineNodeEditor.csproj @@ -72,6 +72,7 @@ New format for xml file with schemes + From 767cc86f58860ccba2d61c1d0a2be9e9d3a8a3e8 Mon Sep 17 00:00:00 2001 From: GMIKE Date: Sat, 22 Aug 2020 00:25:22 +0300 Subject: [PATCH 2/4] add ExecutionInfo for unExecution --- .../Helpers/Commands/ExecutionInfo.cs | 38 ++ .../Helpers/Commands/Observable.cs | 31 ++ .../Commands/ReactiveCommandWithUndoRedo.cs | 324 ++++++++---------- .../ReactiveCommandWithUndoRedoBase.cs | 35 ++ 4 files changed, 253 insertions(+), 175 deletions(-) create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/ExecutionInfo.cs create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/Observable.cs create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/ExecutionInfo.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/ExecutionInfo.cs new file mode 100644 index 0000000..dcb2e1d --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ExecutionInfo.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SimpleStateMachineNodeEditor.Helpers.Commands +{ + public enum ExecutionDemarcation + { + Begin, + Result, + End + } + + public struct ExecutionInfo + { + private readonly ExecutionDemarcation _demarcation; + private readonly TResult _result; + + private ExecutionInfo(ExecutionDemarcation demarcation, TResult result) + { + _demarcation = demarcation; + _result = result; + } + + public ExecutionDemarcation Demarcation => _demarcation; + + public TResult Result => _result; + + public static ExecutionInfo CreateBegin() => + new ExecutionInfo(ExecutionDemarcation.Begin, default!); + + public static ExecutionInfo CreateResult(TResult result) => + new ExecutionInfo(ExecutionDemarcation.Result, result); + + public static ExecutionInfo CreateEnd() => + new ExecutionInfo(ExecutionDemarcation.End, default!); + } +} diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/Observable.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/Observable.cs new file mode 100644 index 0000000..ad6678f --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/Observable.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2020 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace System.Reactive.Linq +{ + /// + /// Provides commonly required, statically-allocated, pre-canned observables. + /// + /// + /// The observable type. + /// + internal static class Observable + { + /// + /// An empty observable of type . + /// + public static readonly IObservable Empty = Observable.Empty(); + + /// + /// An observable of type that never ticks a value. + /// + public static readonly IObservable Never = Observable.Never(); + + /// + /// An observable of type that ticks a single, default value. + /// + public static readonly IObservable Default = Observable.Return(default(T) !); + } +} diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs index 0125118..5293aca 100644 --- a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs @@ -8,179 +8,153 @@ namespace SimpleStateMachineNodeEditor.Helpers.Commands { - //public class ReactiveCommandWithUndoRedo : ReactiveCommandBase - //{ - // private readonly Func> _execute; - // private readonly IScheduler _outputScheduler; - // private readonly Subject _executionInfo; - // private readonly ISubject _synchronizedExecutionInfo; - // private readonly IObservable _isExecuting; - // private readonly IObservable _canExecute; - // private readonly IObservable _results; - // private readonly ScheduledSubject _exceptions; - // private readonly IDisposable _canExecuteSubscription; - - // /// - // /// Initializes a new instance of the class. - // /// - // /// The Func to perform when the command is executed. - // /// A observable which has a value if the command can execute. - // /// The scheduler where to send output after the main execution. - // /// Thrown if any dependent parameters are null. - // protected internal ReactiveCommand( - // Func> execute, - // IObservable? canExecute, - // IScheduler? outputScheduler) - // { - // if (execute == null) - // { - // throw new ArgumentNullException(nameof(execute)); - // } - - // if (canExecute == null) - // { - // throw new ArgumentNullException(nameof(canExecute)); - // } - - // if (outputScheduler == null) - // { - // throw new ArgumentNullException(nameof(outputScheduler)); - // } - - // _execute = execute; - // _outputScheduler = outputScheduler; - // _executionInfo = new Subject(); - // _synchronizedExecutionInfo = Subject.Synchronize(_executionInfo, outputScheduler); - // _isExecuting = _synchronizedExecutionInfo - // .Scan( - // 0, - // (acc, next) => - // { - // if (next.Demarcation == ExecutionDemarcation.Begin) - // { - // return acc + 1; - // } - - // if (next.Demarcation == ExecutionDemarcation.End) - // { - // return acc - 1; - // } - - // return acc; - // }) - // .Select(inFlightCount => inFlightCount > 0) - // .StartWith(false) - // .DistinctUntilChanged() - // .Replay(1) - // .RefCount(); - // _canExecute = canExecute - // .Catch(ex => - // { - // _exceptions.OnNext(ex); - // return Observables.False; - // }) - // .StartWith(false) - // .CombineLatest(_isExecuting, (canEx, isEx) => canEx && !isEx) - // .DistinctUntilChanged() - // .Replay(1) - // .RefCount(); - // _results = _synchronizedExecutionInfo - // .Where(x => x.Demarcation == ExecutionDemarcation.Result) - // .Select(x => x.Result); - - // _exceptions = new ScheduledSubject(outputScheduler, RxApp.DefaultExceptionHandler); - - // _canExecuteSubscription = _canExecute.Subscribe(OnCanExecuteChanged); - // } - - // private enum ExecutionDemarcation - // { - // Begin, - // Result, - // End - // } - - // /// - // public override IObservable CanExecute => _canExecute; - - // /// - // public override IObservable IsExecuting => _isExecuting; - - // /// - // public override IObservable ThrownExceptions => _exceptions.AsObservable(); - - // /// - // public override IDisposable Subscribe(IObserver observer) - // { - // return _results.Subscribe(observer); - // } - - // /// - // public override IObservable Execute(TParam parameter = default(TParam)) - // { - // try - // { - // return Observable - // .Defer( - // () => - // { - // _synchronizedExecutionInfo.OnNext(ExecutionInfo.CreateBegin()); - // return Observable.Empty; - // }) - // .Concat(_execute(parameter)) - // .Do(result => _synchronizedExecutionInfo.OnNext(ExecutionInfo.CreateResult(result))) - // .Catch( - // ex => - // { - // _exceptions.OnNext(ex); - // return Observable.Throw(ex); - // }) - // .Finally(() => _synchronizedExecutionInfo.OnNext(ExecutionInfo.CreateEnd())) - // .PublishLast() - // .RefCount() - // .ObserveOn(_outputScheduler); - // } - // catch (Exception ex) - // { - // _exceptions.OnNext(ex); - // return Observable.Throw(ex); - // } - // } - - // /// - // protected override void Dispose(bool disposing) - // { - // if (disposing) - // { - // _executionInfo?.Dispose(); - // _exceptions?.Dispose(); - // _canExecuteSubscription?.Dispose(); - // } - // } - - // private struct ExecutionInfo - // { - // private readonly ExecutionDemarcation _demarcation; - // private readonly TResult _result; - - // private ExecutionInfo(ExecutionDemarcation demarcation, TResult result) - // { - // _demarcation = demarcation; - // _result = result; - // } - - // public ExecutionDemarcation Demarcation => _demarcation; - - // public TResult Result => _result; - - // public static ExecutionInfo CreateBegin() => - // new ExecutionInfo(ExecutionDemarcation.Begin, default!); - - // public static ExecutionInfo CreateResult(TResult result) => - // new ExecutionInfo(ExecutionDemarcation.Result, result); - - // public static ExecutionInfo CreateEnd() => - // new ExecutionInfo(ExecutionDemarcation.End, default!); - // } - //} + + public class ReactiveCommandWithUndoRedo : ReactiveCommandWithUndoRedoBase + { + private readonly ReactiveCommand _command; + private readonly IDisposable _canUnExecuteSubscription; + private readonly ScheduledSubject _exceptions; + + private readonly Subject> _unExecutionInfo; + private readonly ISubject, ExecutionInfo> _synchronizedUnExecutionInfo; + private readonly IScheduler _outputScheduler; + + private readonly Func, IObservable> _unExecute; + private readonly IObservable _canUnExecute; + private readonly IObservable _isUnExecuting; + private TParam _parameter; + private IObservable _result; + + protected internal ReactiveCommandWithUndoRedo( + Func> execute, + Func, IObservable> unExecute, + IObservable? canExecute, + IObservable? canUnExecute, + IScheduler? outputScheduler) + { + _outputScheduler = outputScheduler; + if (unExecute == null) + { + throw new ArgumentNullException(nameof(unExecute)); + } + + if (canUnExecute == null) + { + throw new ArgumentNullException(nameof(canUnExecute)); + } + + //_command = ReactiveCommand.CreateFromObservable>(execute, canExecute, outputScheduler); + + _unExecute = unExecute; + + _unExecutionInfo = new Subject>(); + _synchronizedUnExecutionInfo = Subject.Synchronize(_unExecutionInfo, outputScheduler); + _isUnExecuting = _synchronizedUnExecutionInfo + .Scan( + 0, + (acc, next) => + { + if (next.Demarcation == ExecutionDemarcation.Begin) + { + return acc + 1; + } + + if (next.Demarcation == ExecutionDemarcation.End) + { + return acc - 1; + } + + return acc; + }) + .Select(inFlightCount => inFlightCount > 0) + .StartWith(false) + .DistinctUntilChanged() + .Replay(1) + .RefCount(); + + _canUnExecute = canUnExecute + .Catch(ex => + { + _exceptions.OnNext(ex); + return Observables.False; + }) + .StartWith(false) + .CombineLatest(_isUnExecuting, (canEx, isEx) => canEx && !isEx) + .DistinctUntilChanged() + .Replay(1) + .RefCount(); + + _command + .ThrownExceptions + .Subscribe(); + _exceptions = new ScheduledSubject(outputScheduler, RxApp.DefaultExceptionHandler); + + _canUnExecuteSubscription = _canUnExecute.Subscribe(OnCanUnExecuteChanged); + } + + + public override IObservable CanExecute => _command.CanExecute; + + public override IObservable IsExecuting => _command.IsExecuting; + public override IObservable ThrownExceptions => _command.ThrownExceptions; + + public override IObservable CanUnExecute => _command.CanExecute.Where(x => !x); + + public override IObservable IsUnExecuting => _isUnExecuting; + + public override IObservable Execute(TParam parameter = default(TParam)) + { + _parameter = parameter; + return _command.Execute(parameter); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _command.Dispose(); + _unExecutionInfo?.Dispose(); + _exceptions?.Dispose(); + _canUnExecuteSubscription?.Dispose(); + } + } + /// + public IObservable UnExecute() + { + + try + { + return Observable + .Defer( + () => + { + _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateBegin()); + return Observable.Empty; + }) + .Concat(_unExecute(_parameter, _result)) + .Do(result => _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateResult(result))) + .Catch( + ex => + { + _exceptions.OnNext(ex); + return Observable.Throw(ex); + }) + .Finally(() => _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateEnd())) + .PublishLast() + .RefCount() + .ObserveOn(_outputScheduler); + } + catch (Exception ex) + { + _exceptions.OnNext(ex); + return Observable.Throw(ex); + } + + } + public override IDisposable Subscribe(IObserver observer) + { + return _command.Subscribe(observer); + } + + } } diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs new file mode 100644 index 0000000..3d7d49c --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs @@ -0,0 +1,35 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SimpleStateMachineNodeEditor.Helpers.Commands +{ + public abstract class ReactiveCommandWithUndoRedoBase : ReactiveCommandBase + { + private EventHandler? _canUnExecuteChanged; + private bool _canUnExecuteValue; + + event EventHandler CanUnExecuteChanged + { + add => _canUnExecuteChanged += value; + remove => _canUnExecuteChanged -= value; + } + + public abstract IObservable CanUnExecute + { + get; + } + + public abstract IObservable IsUnExecuting + { + get; + } + + protected void OnCanUnExecuteChanged(bool newValue) + { + _canUnExecuteValue = newValue; + _canUnExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } +} From 632c7b842fb4756b42fded1777fcf692ed32d2bf Mon Sep 17 00:00:00 2001 From: GMIKE Date: Sat, 22 Aug 2020 00:27:02 +0300 Subject: [PATCH 3/4] get result after execute command --- .../Helpers/Commands/ReactiveCommandWithUndoRedo.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs index 5293aca..0757bd2 100644 --- a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs @@ -43,6 +43,7 @@ protected internal ReactiveCommandWithUndoRedo( throw new ArgumentNullException(nameof(canUnExecute)); } + //How create? //_command = ReactiveCommand.CreateFromObservable>(execute, canExecute, outputScheduler); _unExecute = unExecute; @@ -105,7 +106,8 @@ protected internal ReactiveCommandWithUndoRedo( public override IObservable Execute(TParam parameter = default(TParam)) { _parameter = parameter; - return _command.Execute(parameter); + _result = _command.Execute(parameter); + return _result; } protected override void Dispose(bool disposing) From d105e4f6814085233729a8bf290e3e39dbae3557 Mon Sep 17 00:00:00 2001 From: GMIKE Date: Sat, 22 Aug 2020 23:36:18 +0300 Subject: [PATCH 4/4] History class --- SimpleStateMachineNodeEditor/App.xaml.cs | 3 +- .../Helpers/Commands/Command.cs | 2 + .../Commands/IReactiveCommandHistory.cs | 25 ++ .../Commands/IReactiveCommandWithUndoRedo.cs | 19 ++ .../Commands/IReactiveHistoryElement.cs | 13 + .../Commands/ReactiveCommandHistory.cs | 138 ++++++++ .../Commands/ReactiveCommandWithUndoRedo.cs | 299 ++++++++++-------- .../ReactiveCommandWithUndoRedoBase.cs | 74 ++++- 8 files changed, 424 insertions(+), 149 deletions(-) create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveCommandHistory.cs create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveCommandWithUndoRedo.cs create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveHistoryElement.cs create mode 100644 SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandHistory.cs diff --git a/SimpleStateMachineNodeEditor/App.xaml.cs b/SimpleStateMachineNodeEditor/App.xaml.cs index 07c417c..0fccb29 100644 --- a/SimpleStateMachineNodeEditor/App.xaml.cs +++ b/SimpleStateMachineNodeEditor/App.xaml.cs @@ -5,6 +5,7 @@ using SimpleStateMachineNodeEditor.Helpers.Converters; using WritableJsonConfiguration; using Application = System.Windows.Application; +using SimpleStateMachineNodeEditor.Helpers.Commands; namespace SimpleStateMachineNodeEditor { @@ -14,7 +15,7 @@ namespace SimpleStateMachineNodeEditor public partial class App : Application { public App() - { + { Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly()); Locator.CurrentMutable.RegisterConstant(new ConverterBoolAndVisibility(), typeof(IBindingTypeConverter)); diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/Command.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/Command.cs index 4169445..3708a11 100644 --- a/SimpleStateMachineNodeEditor/Helpers/Commands/Command.cs +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/Command.cs @@ -11,6 +11,8 @@ public class Command : ICommandWithUndoRedo, ICommand, IClo public Action OnExecute { get; set; } public TParameter Parameters { get; set; } public TResult Result { get; set; } + + public object Clone() { return new Command(_execute, _unExecute, OnExecute) diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveCommandHistory.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveCommandHistory.cs new file mode 100644 index 0000000..979551b --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveCommandHistory.cs @@ -0,0 +1,25 @@ +using ReactiveUI; +using System; +using System.Reactive; + +namespace SimpleStateMachineNodeEditor.Helpers.Commands +{ + public interface IReactiveCommandHistory + { + IObservable CanUndo { get; } + IObservable IsUndoing { get; } + + IObservable CanRedo { get; } + IObservable IsRedoing { get; } + + IObservable CanClear { get; } + IObservable IsClearing { get; } + + ReactiveCommand Undo { get; } + ReactiveCommand Redo { get; } + ReactiveCommand Clear { get; } + + IReactiveCommandWithUndoRedo AddInRedo(IReactiveCommandWithUndoRedo command); + IReactiveCommandWithUndoRedo AddInUndo(IReactiveCommandWithUndoRedo command); + } +} diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveCommandWithUndoRedo.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveCommandWithUndoRedo.cs new file mode 100644 index 0000000..6a73bbc --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveCommandWithUndoRedo.cs @@ -0,0 +1,19 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SimpleStateMachineNodeEditor.Helpers.Commands +{ + public interface IReactiveCommandWithUndoRedo:IReactiveCommand + { + IObservable IsUndoing { get; } + IObservable IsRedoing { get; } + + IObservable CanUndo { get; } + IObservable CanRedo { get; } + + void Undo(); + void Redo(); + } +} diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveHistoryElement.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveHistoryElement.cs new file mode 100644 index 0000000..2ea9f58 --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/IReactiveHistoryElement.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SimpleStateMachineNodeEditor.Helpers.Commands +{ + public interface IReactiveHistoryElement: IReactiveCommandWithUndoRedo + { + TParameter Parameter { get; set; } + TResult Result { get; set; } + } +} + diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandHistory.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandHistory.cs new file mode 100644 index 0000000..2ad44d9 --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandHistory.cs @@ -0,0 +1,138 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Text; +using System.Windows.Controls; + +namespace SimpleStateMachineNodeEditor.Helpers.Commands +{ + public class ReactiveCommandHistory: IReactiveCommandHistory, IDisposable + { + public ReactiveCommandHistory(IObservable? canUndo, IObservable? canRedo, IObservable? canClear, IScheduler? scheduler) + { + Undo = ReactiveCommand.Create(UndoCommand, CanUndo, scheduler); + Redo = ReactiveCommand.Create(RedoCommand, CanRedo, scheduler); + Clear = ReactiveCommand.Create(ClearHistory, CanClear, scheduler); + + + + var commandsForUndoCanExecute = Observable + .CombineLatest(StackUndo.Select(x => x.CanExecute)) + .Select(x => x.All(y => y)); + + var commandsForRedoCanExecute = Observable + .CombineLatest(StackRedo.Select(x => x.CanExecute)) + .Select(x => x.All(y => y)); + + var commandsForUndoCanUndo = Observable + .CombineLatest(StackUndo.Select(x => x.CanUndo)) + .Select(x => x.All(y => y)); + + var commandsForRedoCanUndo = Observable + .CombineLatest(StackRedo.Select(x => x.CanUndo)) + .Select(x => x.All(y => y)); + + var commandsForUndoCanRedo = Observable + .CombineLatest(StackUndo.Select(x => x.CanRedo)) + .Select(x => x.All(y => y)); + + var commandsForRedoCanRedo = Observable + .CombineLatest(StackRedo.Select(x => x.CanRedo)) + .Select(x => x.All(y => y)); + + + + var commandsForUndoIsExecuting = Observable + .CombineLatest(StackUndo.Select(x => x.IsExecuting)) + .Select(x => x.All(y => y)); + + var commandsForRedoIsExecuting = Observable + .CombineLatest(StackRedo.Select(x => x.IsExecuting)) + .Select(x => x.All(y => y)); + + var commandsForUndoIsUndoing = Observable + .CombineLatest(StackUndo.Select(x => x.IsUndoing)) + .Select(x => x.All(y => y)); + + var commandsForRedoIsUndoing = Observable + .CombineLatest(StackRedo.Select(x => x.IsUndoing)) + .Select(x => x.All(y => y)); + + var commandsForUndoIsRedoing = Observable + .CombineLatest(StackUndo.Select(x => x.IsRedoing)) + .Select(x => x.All(y => y)); + + var commandsForRedoIsRedoing = Observable + .CombineLatest(StackRedo.Select(x => x.IsRedoing)) + .Select(x => x.All(y => y)); + + + + } + + public Stack StackRedo { get; set; } = new Stack(); + + public Stack StackUndo { get; set; } = new Stack(); + + public ReactiveCommand Undo { get; } + + public ReactiveCommand Redo { get; } + + public ReactiveCommand Clear { get; } + + public IObservable CanUndo { get; } + public IObservable CanRedo { get; } + public IObservable CanClear { get; } + + public IObservable IsUndoing { get; } + public IObservable IsRedoing { get; } + public IObservable IsClearing { get; } + + public IReactiveCommandWithUndoRedo AddInRedo(IReactiveCommandWithUndoRedo command) + { + StackRedo.Push(command); + return command; + } + + public IReactiveCommandWithUndoRedo AddInUndo(IReactiveCommandWithUndoRedo command) + { + StackUndo.Push(command); + return command; + } + + protected void RedoCommand() + { + if (StackRedo.Count > 0) + { + IReactiveCommandWithUndoRedo last = StackRedo.Pop(); + last.Redo(); + } + } + protected void UndoCommand() + { + if (StackUndo.Count > 0) + { + IReactiveCommandWithUndoRedo last = StackUndo.Pop(); + last.Undo(); + } + } + protected void ClearHistory() + { + StackRedo.Clear(); + StackUndo.Clear(); + } + + public void Dispose() + { + StackRedo.Clear(); + StackUndo.Clear(); + Undo.Dispose(); + Redo.Dispose(); + Clear.Dispose(); + } + } +} diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs index 0757bd2..e3a32e4 100644 --- a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs @@ -9,154 +9,183 @@ namespace SimpleStateMachineNodeEditor.Helpers.Commands { + //public class ReactiveCommandWithUndoRedo : ReactiveCommandWithUndoRedoBase + //{ + // private readonly ReactiveCommand _command; + // private readonly IDisposable _canUnExecuteSubscription; + // private readonly ScheduledSubject _exceptions; + + // private readonly Subject> _unExecutionInfo; + // private readonly ISubject, ExecutionInfo> _synchronizedUnExecutionInfo; + // private readonly IScheduler _outputScheduler; + + // private readonly Func, IObservable> _unExecute; + // private readonly IObservable _canUnExecute; + // private readonly IObservable _isUnExecuting; + // private TParam _parameter; + // private IObservable _result; + + // protected internal ReactiveCommandWithUndoRedo( + // Func> execute, + // Func, IObservable> unExecute, + // IObservable? canExecute, + // IObservable? canUnExecute, + // IScheduler? outputScheduler) + // { + // _outputScheduler = outputScheduler; + // if (unExecute == null) + // { + // throw new ArgumentNullException(nameof(unExecute)); + // } + + // if (canUnExecute == null) + // { + // throw new ArgumentNullException(nameof(canUnExecute)); + // } + + + // _command = ReactiveCommand.CreateFromObservable(execute, canExecute, outputScheduler); + + // _unExecute = unExecute; + + // _unExecutionInfo = new Subject>(); + // _synchronizedUnExecutionInfo = Subject.Synchronize(_unExecutionInfo, outputScheduler); + // _isUnExecuting = _synchronizedUnExecutionInfo + // .Scan( + // 0, + // (acc, next) => + // { + // if (next.Demarcation == ExecutionDemarcation.Begin) + // { + // return acc + 1; + // } + + // if (next.Demarcation == ExecutionDemarcation.End) + // { + // return acc - 1; + // } + + // return acc; + // }) + // .Select(inFlightCount => inFlightCount > 0) + // .StartWith(false) + // .DistinctUntilChanged() + // .Replay(1) + // .RefCount(); + + // _canUnExecute = canUnExecute + // .Catch(ex => + // { + // _exceptions.OnNext(ex); + // return Observables.False; + // }) + // .StartWith(false) + // .CombineLatest(_isUnExecuting, (canEx, isEx) => canEx && !isEx) + // .DistinctUntilChanged() + // .Replay(1) + // .RefCount(); + + // _command + // .ThrownExceptions + // .Subscribe(); + // _exceptions = new ScheduledSubject(outputScheduler, RxApp.DefaultExceptionHandler); + + // _canUnExecuteSubscription = _canUnExecute.Subscribe(OnCanUnExecuteChanged); + // } + + + // public override IObservable CanExecute => _command.CanExecute; + + // public override IObservable IsExecuting => _command.IsExecuting; + // public override IObservable ThrownExceptions => _command.ThrownExceptions; + + // public override IObservable CanUnExecute => _command.CanExecute.Where(x => !x); + + // public override IObservable IsUnExecuting => _isUnExecuting; + + // public override IObservable Execute(TParam parameter = default(TParam)) + // { + // _parameter = parameter; + // _result = _command.Execute(parameter); + // return _result; + // } + + // protected override void Dispose(bool disposing) + // { + // if (disposing) + // { + // _command.Dispose(); + // _unExecutionInfo?.Dispose(); + // _exceptions?.Dispose(); + // _canUnExecuteSubscription?.Dispose(); + // } + // } + // /// + // public IObservable UnExecute() + // { + + // try + // { + // return Observable + // .Defer( + // () => + // { + // _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateBegin()); + // return Observable.Empty; + // }) + // .Concat(_unExecute(_parameter, _result)) + // .Do(result => _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateResult(result))) + // .Catch( + // ex => + // { + // _exceptions.OnNext(ex); + // return Observable.Throw(ex); + // }) + // .Finally(() => _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateEnd())) + // .PublishLast() + // .RefCount() + // .ObserveOn(_outputScheduler); + // } + // catch (Exception ex) + // { + // _exceptions.OnNext(ex); + // return Observable.Throw(ex); + // } + + // } + // public override IDisposable Subscribe(IObserver observer) + // { + // return _command.Subscribe(observer); + // } + + //} + public class ReactiveCommandWithUndoRedo : ReactiveCommandWithUndoRedoBase { - private readonly ReactiveCommand _command; - private readonly IDisposable _canUnExecuteSubscription; - private readonly ScheduledSubject _exceptions; - - private readonly Subject> _unExecutionInfo; - private readonly ISubject, ExecutionInfo> _synchronizedUnExecutionInfo; - private readonly IScheduler _outputScheduler; - - private readonly Func, IObservable> _unExecute; - private readonly IObservable _canUnExecute; - private readonly IObservable _isUnExecuting; - private TParam _parameter; - private IObservable _result; - - protected internal ReactiveCommandWithUndoRedo( - Func> execute, - Func, IObservable> unExecute, - IObservable? canExecute, - IObservable? canUnExecute, - IScheduler? outputScheduler) - { - _outputScheduler = outputScheduler; - if (unExecute == null) - { - throw new ArgumentNullException(nameof(unExecute)); - } - - if (canUnExecute == null) - { - throw new ArgumentNullException(nameof(canUnExecute)); - } - - //How create? - //_command = ReactiveCommand.CreateFromObservable>(execute, canExecute, outputScheduler); - - _unExecute = unExecute; - - _unExecutionInfo = new Subject>(); - _synchronizedUnExecutionInfo = Subject.Synchronize(_unExecutionInfo, outputScheduler); - _isUnExecuting = _synchronizedUnExecutionInfo - .Scan( - 0, - (acc, next) => - { - if (next.Demarcation == ExecutionDemarcation.Begin) - { - return acc + 1; - } - - if (next.Demarcation == ExecutionDemarcation.End) - { - return acc - 1; - } - - return acc; - }) - .Select(inFlightCount => inFlightCount > 0) - .StartWith(false) - .DistinctUntilChanged() - .Replay(1) - .RefCount(); - - _canUnExecute = canUnExecute - .Catch(ex => - { - _exceptions.OnNext(ex); - return Observables.False; - }) - .StartWith(false) - .CombineLatest(_isUnExecuting, (canEx, isEx) => canEx && !isEx) - .DistinctUntilChanged() - .Replay(1) - .RefCount(); - - _command - .ThrownExceptions - .Subscribe(); - _exceptions = new ScheduledSubject(outputScheduler, RxApp.DefaultExceptionHandler); - - _canUnExecuteSubscription = _canUnExecute.Subscribe(OnCanUnExecuteChanged); - } - + public override IObservable CanExecute => throw new NotImplementedException(); - public override IObservable CanExecute => _command.CanExecute; + public override IObservable IsExecuting => throw new NotImplementedException(); - public override IObservable IsExecuting => _command.IsExecuting; - public override IObservable ThrownExceptions => _command.ThrownExceptions; + public override IObservable ThrownExceptions => throw new NotImplementedException(); - public override IObservable CanUnExecute => _command.CanExecute.Where(x => !x); - - public override IObservable IsUnExecuting => _isUnExecuting; - - public override IObservable Execute(TParam parameter = default(TParam)) + public override IObservable Redo() { - _parameter = parameter; - _result = _command.Execute(parameter); - return _result; + throw new NotImplementedException(); } - protected override void Dispose(bool disposing) + public override IDisposable Subscribe(IObserver observer) { - if (disposing) - { - _command.Dispose(); - _unExecutionInfo?.Dispose(); - _exceptions?.Dispose(); - _canUnExecuteSubscription?.Dispose(); - } + throw new NotImplementedException(); } - /// - public IObservable UnExecute() - { - - try - { - return Observable - .Defer( - () => - { - _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateBegin()); - return Observable.Empty; - }) - .Concat(_unExecute(_parameter, _result)) - .Do(result => _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateResult(result))) - .Catch( - ex => - { - _exceptions.OnNext(ex); - return Observable.Throw(ex); - }) - .Finally(() => _synchronizedUnExecutionInfo.OnNext(ExecutionInfo.CreateEnd())) - .PublishLast() - .RefCount() - .ObserveOn(_outputScheduler); - } - catch (Exception ex) - { - _exceptions.OnNext(ex); - return Observable.Throw(ex); - } - } - public override IDisposable Subscribe(IObserver observer) + public override IObservable Undo() { - return _command.Subscribe(observer); + throw new NotImplementedException(); } + protected override void Dispose(bool disposing) + { + throw new NotImplementedException(); + } } } diff --git a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs index 3d7d49c..90ce3d2 100644 --- a/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs @@ -1,35 +1,83 @@ using ReactiveUI; using System; using System.Collections.Generic; +using System.Reactive.Linq; using System.Text; +using System.Windows.Media.TextFormatting; namespace SimpleStateMachineNodeEditor.Helpers.Commands { - public abstract class ReactiveCommandWithUndoRedoBase : ReactiveCommandBase + //public abstract class ReactiveCommandWithUndoRedoBase : ReactiveCommandBase, IReactiveCommandWithUndoRedo + //{ + // private EventHandler? _canUnExecuteChanged; + // private bool _canUnExecuteValue; + + // event EventHandler CanUnExecuteChanged + // { + // add => _canUnExecuteChanged += value; + // remove => _canUnExecuteChanged -= value; + // } + + // public abstract IObservable CanUnExecute + // { + // get; + // } + + // public abstract IObservable IsUnExecuting + // { + // get; + // } + + // protected void OnCanUnExecuteChanged(bool newValue) + // { + // _canUnExecuteValue = newValue; + // _canUnExecuteChanged?.Invoke(this, EventArgs.Empty); + // } + //} + + public abstract class ReactiveCommandWithUndoRedoBase : ReactiveCommandBase, IReactiveCommandWithUndoRedo { - private EventHandler? _canUnExecuteChanged; - private bool _canUnExecuteValue; + public ReactiveCommandWithUndoRedoBase(IReactiveCommandHistory history=null) + { + _history = history; + } - event EventHandler CanUnExecuteChanged + private IReactiveCommandHistory _history; + public IObservable IsUndoing => throw new NotImplementedException(); + + public IObservable IsRedoing => throw new NotImplementedException(); + + public IObservable CanUndo => throw new NotImplementedException(); + + public IObservable CanRedo => throw new NotImplementedException(); + + public override IObservable Execute(TParam parameter = default(TParam)) { - add => _canUnExecuteChanged += value; - remove => _canUnExecuteChanged -= value; + return null; } - public abstract IObservable CanUnExecute + void IReactiveCommandWithUndoRedo.Undo() { - get; + IUndoExecute(); } - public abstract IObservable IsUnExecuting + void IReactiveCommandWithUndoRedo.Redo() { - get; + IRedoExecute(); } - protected void OnCanUnExecuteChanged(bool newValue) + public abstract IObservable Undo(); + public abstract IObservable Redo(); + + protected virtual void IUndoExecute() + { + Undo().Catch(Observable.Empty) + .Subscribe(); + } + protected virtual void IRedoExecute() { - _canUnExecuteValue = newValue; - _canUnExecuteChanged?.Invoke(this, EventArgs.Empty); + Redo().Catch(Observable.Empty) + .Subscribe(); } } }