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/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/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/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/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/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 new file mode 100644 index 0000000..e3a32e4 --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedo.cs @@ -0,0 +1,191 @@ +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 : 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 + { + public override IObservable CanExecute => throw new NotImplementedException(); + + public override IObservable IsExecuting => throw new NotImplementedException(); + + public override IObservable ThrownExceptions => throw new NotImplementedException(); + + public override IObservable Redo() + { + throw new NotImplementedException(); + } + + public override IDisposable Subscribe(IObserver observer) + { + throw new NotImplementedException(); + } + + public override IObservable Undo() + { + 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 new file mode 100644 index 0000000..90ce3d2 --- /dev/null +++ b/SimpleStateMachineNodeEditor/Helpers/Commands/ReactiveCommandWithUndoRedoBase.cs @@ -0,0 +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, 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 + { + public ReactiveCommandWithUndoRedoBase(IReactiveCommandHistory history=null) + { + _history = history; + } + + 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)) + { + return null; + } + + void IReactiveCommandWithUndoRedo.Undo() + { + IUndoExecute(); + } + + void IReactiveCommandWithUndoRedo.Redo() + { + IRedoExecute(); + } + + public abstract IObservable Undo(); + public abstract IObservable Redo(); + + protected virtual void IUndoExecute() + { + Undo().Catch(Observable.Empty) + .Subscribe(); + } + protected virtual void IRedoExecute() + { + Redo().Catch(Observable.Empty) + .Subscribe(); + } + } +} 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 +