File scoped namespaces
This commit is contained in:
Родитель
2dd487f1fc
Коммит
4af5ed488a
|
@ -3,27 +3,26 @@ using Avalonia.Controls.ApplicationLifetimes;
|
|||
using Avalonia.Markup.Xaml;
|
||||
using ReactiveHistorySample.Views;
|
||||
|
||||
namespace ReactiveHistorySample
|
||||
namespace ReactiveHistorySample;
|
||||
|
||||
public class App : Application
|
||||
{
|
||||
public class App : Application
|
||||
public override void Initialize()
|
||||
{
|
||||
public override void Initialize()
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
desktopLifetime.MainWindow = new MainWindow();
|
||||
}
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
|
||||
{
|
||||
singleViewLifetime.MainView = new MainView();
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
|
||||
{
|
||||
desktopLifetime.MainWindow = new MainWindow();
|
||||
}
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
|
||||
{
|
||||
singleViewLifetime.MainView = new MainView();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,29 +3,28 @@ using Avalonia.Controls;
|
|||
using Avalonia.Media;
|
||||
using ReactiveHistorySample.ViewModels;
|
||||
|
||||
namespace ReactiveHistorySample.Controls
|
||||
{
|
||||
public class LayerCanvas : Canvas
|
||||
{
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
base.Render(context);
|
||||
namespace ReactiveHistorySample.Controls;
|
||||
|
||||
var layer = DataContext as LayerViewModel;
|
||||
if (layer != null)
|
||||
public class LayerCanvas : Canvas
|
||||
{
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
base.Render(context);
|
||||
|
||||
var layer = DataContext as LayerViewModel;
|
||||
if (layer != null)
|
||||
{
|
||||
foreach (var shape in layer.Shapes)
|
||||
{
|
||||
foreach (var shape in layer.Shapes)
|
||||
if (shape is LineShapeViewModel)
|
||||
{
|
||||
if (shape is LineShapeViewModel)
|
||||
{
|
||||
var line = shape as LineShapeViewModel;
|
||||
context.DrawLine(
|
||||
new Pen(Brushes.Red, 2.0),
|
||||
new Point(line.Start.Value.X.Value, line.Start.Value.Y.Value),
|
||||
new Point(line.End.Value.X.Value, line.End.Value.Y.Value));
|
||||
}
|
||||
var line = shape as LineShapeViewModel;
|
||||
context.DrawLine(
|
||||
new Pen(Brushes.Red, 2.0),
|
||||
new Point(line.Start.Value.X.Value, line.Start.Value.Y.Value),
|
||||
new Point(line.End.Value.X.Value, line.End.Value.Y.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,26 @@
|
|||
|
||||
namespace ReactiveHistorySample.Models
|
||||
namespace ReactiveHistorySample.Models;
|
||||
|
||||
public abstract class BaseObject : ObservableObject
|
||||
{
|
||||
public abstract class BaseObject : ObservableObject
|
||||
private object _owner;
|
||||
private string _name;
|
||||
|
||||
public object Owner
|
||||
{
|
||||
private object _owner;
|
||||
private string _name;
|
||||
|
||||
public object Owner
|
||||
{
|
||||
get { return _owner; }
|
||||
set { Update(ref _owner, value); }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { Update(ref _name, value); }
|
||||
}
|
||||
|
||||
public BaseObject(object owner, string name)
|
||||
{
|
||||
_owner = owner;
|
||||
_name = name;
|
||||
}
|
||||
get { return _owner; }
|
||||
set { Update(ref _owner, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { Update(ref _name, value); }
|
||||
}
|
||||
|
||||
public BaseObject(object owner, string name)
|
||||
{
|
||||
_owner = owner;
|
||||
_name = name;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
namespace ReactiveHistorySample.Models
|
||||
namespace ReactiveHistorySample.Models;
|
||||
|
||||
public abstract class BaseShape : BaseObject
|
||||
{
|
||||
public abstract class BaseShape : BaseObject
|
||||
public BaseShape(object owner, string name) : base(owner, name)
|
||||
{
|
||||
public BaseShape(object owner, string name) : base(owner, name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,19 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace ReactiveHistorySample.Models
|
||||
namespace ReactiveHistorySample.Models;
|
||||
|
||||
public class Layer : BaseObject
|
||||
{
|
||||
public class Layer : BaseObject
|
||||
private ObservableCollection<LineShape> _shapes;
|
||||
|
||||
public ObservableCollection<LineShape> Shapes
|
||||
{
|
||||
private ObservableCollection<LineShape> _shapes;
|
||||
|
||||
public ObservableCollection<LineShape> Shapes
|
||||
{
|
||||
get { return _shapes; }
|
||||
set { Update(ref _shapes, value); }
|
||||
}
|
||||
|
||||
public Layer(object owner, string name) : base(owner, name)
|
||||
{
|
||||
_shapes = new ObservableCollection<LineShape>();
|
||||
}
|
||||
get { return _shapes; }
|
||||
set { Update(ref _shapes, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public Layer(object owner, string name) : base(owner, name)
|
||||
{
|
||||
_shapes = new ObservableCollection<LineShape>();
|
||||
}
|
||||
}
|
|
@ -1,27 +1,26 @@
|
|||
|
||||
namespace ReactiveHistorySample.Models
|
||||
namespace ReactiveHistorySample.Models;
|
||||
|
||||
public class LineShape : BaseShape
|
||||
{
|
||||
public class LineShape : BaseShape
|
||||
private PointShape _start;
|
||||
private PointShape _end;
|
||||
|
||||
public PointShape Start
|
||||
{
|
||||
private PointShape _start;
|
||||
private PointShape _end;
|
||||
|
||||
public PointShape Start
|
||||
{
|
||||
get { return _start; }
|
||||
set { Update(ref _start, value); }
|
||||
}
|
||||
|
||||
public PointShape End
|
||||
{
|
||||
get { return _end; }
|
||||
set { Update(ref _end, value); }
|
||||
}
|
||||
|
||||
public LineShape(object owner, string name, PointShape start, PointShape end) : base(owner, name)
|
||||
{
|
||||
_start = start;
|
||||
_end = end;
|
||||
}
|
||||
get { return _start; }
|
||||
set { Update(ref _start, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public PointShape End
|
||||
{
|
||||
get { return _end; }
|
||||
set { Update(ref _end, value); }
|
||||
}
|
||||
|
||||
public LineShape(object owner, string name, PointShape start, PointShape end) : base(owner, name)
|
||||
{
|
||||
_start = start;
|
||||
_end = end;
|
||||
}
|
||||
}
|
|
@ -1,28 +1,27 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ReactiveHistorySample.Models
|
||||
namespace ReactiveHistorySample.Models;
|
||||
|
||||
public abstract class ObservableObject : INotifyPropertyChanged
|
||||
{
|
||||
public abstract class ObservableObject : INotifyPropertyChanged
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public void Notify([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public bool Update<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (!Equals(field, value))
|
||||
{
|
||||
field = value;
|
||||
Notify(propertyName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public void Notify([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Update<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (!Equals(field, value))
|
||||
{
|
||||
field = value;
|
||||
Notify(propertyName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,28 +1,27 @@
|
|||
|
||||
namespace ReactiveHistorySample.Models
|
||||
namespace ReactiveHistorySample.Models;
|
||||
|
||||
public class PointShape : BaseShape
|
||||
{
|
||||
public class PointShape : BaseShape
|
||||
private double _x;
|
||||
private double _y;
|
||||
|
||||
public double X
|
||||
{
|
||||
private double _x;
|
||||
private double _y;
|
||||
|
||||
public double X
|
||||
{
|
||||
get { return _x; }
|
||||
set { Update(ref _x, value); }
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get { return _y; }
|
||||
set { Update(ref _y, value); }
|
||||
}
|
||||
|
||||
public PointShape(object owner, string name, double x, double y)
|
||||
: base(owner, name)
|
||||
{
|
||||
_x = x;
|
||||
_y = y;
|
||||
}
|
||||
get { return _x; }
|
||||
set { Update(ref _x, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get { return _y; }
|
||||
set { Update(ref _y, value); }
|
||||
}
|
||||
|
||||
public PointShape(object owner, string name, double x, double y)
|
||||
: base(owner, name)
|
||||
{
|
||||
_x = x;
|
||||
_y = y;
|
||||
}
|
||||
}
|
|
@ -5,46 +5,45 @@ using Reactive.Bindings.Extensions;
|
|||
using ReactiveHistory;
|
||||
using ReactiveHistorySample.Models;
|
||||
|
||||
namespace ReactiveHistorySample.ViewModels
|
||||
namespace ReactiveHistorySample.ViewModels;
|
||||
|
||||
public class LayerViewModel : IDisposable
|
||||
{
|
||||
public class LayerViewModel : IDisposable
|
||||
private CompositeDisposable Disposable { get; set; }
|
||||
|
||||
public ReactiveProperty<string> Name { get; set; }
|
||||
public ReadOnlyReactiveCollection<LineShapeViewModel> Shapes { get; set; }
|
||||
|
||||
public ReactiveCommand UndoCommand { get; set; }
|
||||
public ReactiveCommand RedoCommand { get; set; }
|
||||
public ReactiveCommand ClearCommand { get; set; }
|
||||
|
||||
public LayerViewModel(Layer layer, IHistory history)
|
||||
{
|
||||
private CompositeDisposable Disposable { get; set; }
|
||||
Disposable = new CompositeDisposable();
|
||||
|
||||
public ReactiveProperty<string> Name { get; set; }
|
||||
public ReadOnlyReactiveCollection<LineShapeViewModel> Shapes { get; set; }
|
||||
this.Name = layer.ToReactivePropertyAsSynchronized(l => l.Name)
|
||||
.SetValidateNotifyError(name => string.IsNullOrWhiteSpace(name) ? "Name can not be null or whitespace." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
public ReactiveCommand UndoCommand { get; set; }
|
||||
public ReactiveCommand RedoCommand { get; set; }
|
||||
public ReactiveCommand ClearCommand { get; set; }
|
||||
this.Shapes = layer.Shapes
|
||||
.ToReadOnlyReactiveCollection(x => new LineShapeViewModel(x, history))
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
public LayerViewModel(Layer layer, IHistory history)
|
||||
{
|
||||
Disposable = new CompositeDisposable();
|
||||
this.Name.ObserveWithHistory(name => layer.Name = name, layer.Name, history).AddTo(this.Disposable);
|
||||
|
||||
this.Name = layer.ToReactivePropertyAsSynchronized(l => l.Name)
|
||||
.SetValidateNotifyError(name => string.IsNullOrWhiteSpace(name) ? "Name can not be null or whitespace." : null)
|
||||
.AddTo(this.Disposable);
|
||||
UndoCommand = new ReactiveCommand(history.CanUndo, false);
|
||||
UndoCommand.Subscribe(_ => history.Undo()).AddTo(this.Disposable);
|
||||
|
||||
this.Shapes = layer.Shapes
|
||||
.ToReadOnlyReactiveCollection(x => new LineShapeViewModel(x, history))
|
||||
.AddTo(this.Disposable);
|
||||
RedoCommand = new ReactiveCommand(history.CanRedo, false);
|
||||
RedoCommand.Subscribe(_ => history.Redo()).AddTo(this.Disposable);
|
||||
|
||||
this.Name.ObserveWithHistory(name => layer.Name = name, layer.Name, history).AddTo(this.Disposable);
|
||||
|
||||
UndoCommand = new ReactiveCommand(history.CanUndo, false);
|
||||
UndoCommand.Subscribe(_ => history.Undo()).AddTo(this.Disposable);
|
||||
|
||||
RedoCommand = new ReactiveCommand(history.CanRedo, false);
|
||||
RedoCommand.Subscribe(_ => history.Redo()).AddTo(this.Disposable);
|
||||
|
||||
ClearCommand = new ReactiveCommand(history.CanClear, false);
|
||||
ClearCommand.Subscribe(_ => history.Clear()).AddTo(this.Disposable);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disposable.Dispose();
|
||||
}
|
||||
ClearCommand = new ReactiveCommand(history.CanClear, false);
|
||||
ClearCommand.Subscribe(_ => history.Clear()).AddTo(this.Disposable);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disposable.Dispose();
|
||||
}
|
||||
}
|
|
@ -5,68 +5,67 @@ using Reactive.Bindings.Extensions;
|
|||
using ReactiveHistory;
|
||||
using ReactiveHistorySample.Models;
|
||||
|
||||
namespace ReactiveHistorySample.ViewModels
|
||||
namespace ReactiveHistorySample.ViewModels;
|
||||
|
||||
public class LineShapeViewModel : IDisposable
|
||||
{
|
||||
public class LineShapeViewModel : IDisposable
|
||||
private CompositeDisposable Disposable { get; set; }
|
||||
|
||||
public ReactiveProperty<string> Name { get; set; }
|
||||
public ReactiveProperty<PointShapeViewModel> Start { get; set; }
|
||||
public ReactiveProperty<PointShapeViewModel> End { get; set; }
|
||||
|
||||
public ReactiveCommand DeleteCommand { get; set; }
|
||||
|
||||
public ReactiveCommand UndoCommand { get; set; }
|
||||
public ReactiveCommand RedoCommand { get; set; }
|
||||
public ReactiveCommand ClearCommand { get; set; }
|
||||
|
||||
public LineShapeViewModel(LineShape line, IHistory history)
|
||||
{
|
||||
private CompositeDisposable Disposable { get; set; }
|
||||
Disposable = new CompositeDisposable();
|
||||
|
||||
public ReactiveProperty<string> Name { get; set; }
|
||||
public ReactiveProperty<PointShapeViewModel> Start { get; set; }
|
||||
public ReactiveProperty<PointShapeViewModel> End { get; set; }
|
||||
var lineHistoryScope = new StackHistory().AddTo(this.Disposable);
|
||||
|
||||
public ReactiveCommand DeleteCommand { get; set; }
|
||||
this.Name = line.ToReactivePropertyAsSynchronized(l => l.Name)
|
||||
.SetValidateNotifyError(name => string.IsNullOrWhiteSpace(name) ? "Name can not be null or whitespace." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
public ReactiveCommand UndoCommand { get; set; }
|
||||
public ReactiveCommand RedoCommand { get; set; }
|
||||
public ReactiveCommand ClearCommand { get; set; }
|
||||
var startInitialValue = new PointShapeViewModel(line.Start, lineHistoryScope).AddTo(this.Disposable);
|
||||
this.Start = new ReactiveProperty<PointShapeViewModel>(startInitialValue)
|
||||
.SetValidateNotifyError(start => start == null ? "Point can not be null." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
public LineShapeViewModel(LineShape line, IHistory history)
|
||||
var endInitialValue = new PointShapeViewModel(line.End, lineHistoryScope).AddTo(this.Disposable);
|
||||
this.End = new ReactiveProperty<PointShapeViewModel>(endInitialValue)
|
||||
.SetValidateNotifyError(end => end == null ? "Point can not be null." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
this.Name.ObserveWithHistory(name => line.Name = name, line.Name, lineHistoryScope).AddTo(this.Disposable);
|
||||
|
||||
this.DeleteCommand = new ReactiveCommand();
|
||||
this.DeleteCommand.Subscribe((x) => Delete(line, history)).AddTo(this.Disposable);
|
||||
|
||||
UndoCommand = new ReactiveCommand(lineHistoryScope.CanUndo, false);
|
||||
UndoCommand.Subscribe(_ => lineHistoryScope.Undo()).AddTo(this.Disposable);
|
||||
|
||||
RedoCommand = new ReactiveCommand(lineHistoryScope.CanRedo, false);
|
||||
RedoCommand.Subscribe(_ => lineHistoryScope.Redo()).AddTo(this.Disposable);
|
||||
|
||||
ClearCommand = new ReactiveCommand(lineHistoryScope.CanClear, false);
|
||||
ClearCommand.Subscribe(_ => lineHistoryScope.Clear()).AddTo(this.Disposable);
|
||||
}
|
||||
|
||||
private void Delete(LineShape line, IHistory history)
|
||||
{
|
||||
if (line.Owner != null && line.Owner is Layer layer)
|
||||
{
|
||||
Disposable = new CompositeDisposable();
|
||||
|
||||
var lineHistoryScope = new StackHistory().AddTo(this.Disposable);
|
||||
|
||||
this.Name = line.ToReactivePropertyAsSynchronized(l => l.Name)
|
||||
.SetValidateNotifyError(name => string.IsNullOrWhiteSpace(name) ? "Name can not be null or whitespace." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
var startInitialValue = new PointShapeViewModel(line.Start, lineHistoryScope).AddTo(this.Disposable);
|
||||
this.Start = new ReactiveProperty<PointShapeViewModel>(startInitialValue)
|
||||
.SetValidateNotifyError(start => start == null ? "Point can not be null." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
var endInitialValue = new PointShapeViewModel(line.End, lineHistoryScope).AddTo(this.Disposable);
|
||||
this.End = new ReactiveProperty<PointShapeViewModel>(endInitialValue)
|
||||
.SetValidateNotifyError(end => end == null ? "Point can not be null." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
this.Name.ObserveWithHistory(name => line.Name = name, line.Name, lineHistoryScope).AddTo(this.Disposable);
|
||||
|
||||
this.DeleteCommand = new ReactiveCommand();
|
||||
this.DeleteCommand.Subscribe((x) => Delete(line, history)).AddTo(this.Disposable);
|
||||
|
||||
UndoCommand = new ReactiveCommand(lineHistoryScope.CanUndo, false);
|
||||
UndoCommand.Subscribe(_ => lineHistoryScope.Undo()).AddTo(this.Disposable);
|
||||
|
||||
RedoCommand = new ReactiveCommand(lineHistoryScope.CanRedo, false);
|
||||
RedoCommand.Subscribe(_ => lineHistoryScope.Redo()).AddTo(this.Disposable);
|
||||
|
||||
ClearCommand = new ReactiveCommand(lineHistoryScope.CanClear, false);
|
||||
ClearCommand.Subscribe(_ => lineHistoryScope.Clear()).AddTo(this.Disposable);
|
||||
}
|
||||
|
||||
private void Delete(LineShape line, IHistory history)
|
||||
{
|
||||
if (line.Owner != null && line.Owner is Layer layer)
|
||||
{
|
||||
layer.Shapes.RemoveWithHistory(line, history);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disposable.Dispose();
|
||||
layer.Shapes.RemoveWithHistory(line, history);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disposable.Dispose();
|
||||
}
|
||||
}
|
|
@ -5,40 +5,39 @@ using Reactive.Bindings.Extensions;
|
|||
using ReactiveHistory;
|
||||
using ReactiveHistorySample.Models;
|
||||
|
||||
namespace ReactiveHistorySample.ViewModels
|
||||
namespace ReactiveHistorySample.ViewModels;
|
||||
|
||||
public class PointShapeViewModel : IDisposable
|
||||
{
|
||||
public class PointShapeViewModel : IDisposable
|
||||
private CompositeDisposable Disposable { get; set; }
|
||||
|
||||
public ReactiveProperty<string> Name { get; set; }
|
||||
public ReactiveProperty<double> X { get; set; }
|
||||
public ReactiveProperty<double> Y { get; set; }
|
||||
|
||||
public PointShapeViewModel(PointShape point, IHistory history)
|
||||
{
|
||||
private CompositeDisposable Disposable { get; set; }
|
||||
Disposable = new CompositeDisposable();
|
||||
|
||||
public ReactiveProperty<string> Name { get; set; }
|
||||
public ReactiveProperty<double> X { get; set; }
|
||||
public ReactiveProperty<double> Y { get; set; }
|
||||
this.Name = point.ToReactivePropertyAsSynchronized(p => p.Name)
|
||||
.SetValidateNotifyError(name => string.IsNullOrWhiteSpace(name) ? "Name can not be null or whitespace." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
public PointShapeViewModel(PointShape point, IHistory history)
|
||||
{
|
||||
Disposable = new CompositeDisposable();
|
||||
this.X = point.ToReactivePropertyAsSynchronized(p => p.X)
|
||||
.SetValidateNotifyError(x => double.IsNaN(x) || double.IsInfinity(x) ? "X can not be NaN or Infinity." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
this.Name = point.ToReactivePropertyAsSynchronized(p => p.Name)
|
||||
.SetValidateNotifyError(name => string.IsNullOrWhiteSpace(name) ? "Name can not be null or whitespace." : null)
|
||||
.AddTo(this.Disposable);
|
||||
this.Y = point.ToReactivePropertyAsSynchronized(p => p.Y)
|
||||
.SetValidateNotifyError(y => double.IsNaN(y) || double.IsInfinity(y) ? "Y can not be NaN or Infinity." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
this.X = point.ToReactivePropertyAsSynchronized(p => p.X)
|
||||
.SetValidateNotifyError(x => double.IsNaN(x) || double.IsInfinity(x) ? "X can not be NaN or Infinity." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
this.Y = point.ToReactivePropertyAsSynchronized(p => p.Y)
|
||||
.SetValidateNotifyError(y => double.IsNaN(y) || double.IsInfinity(y) ? "Y can not be NaN or Infinity." : null)
|
||||
.AddTo(this.Disposable);
|
||||
|
||||
this.Name.ObserveWithHistory(name => point.Name = name, point.Name, history).AddTo(this.Disposable);
|
||||
this.X.ObserveWithHistory(x => point.X = x, point.X, history).AddTo(this.Disposable);
|
||||
this.Y.ObserveWithHistory(y => point.Y = y, point.Y, history).AddTo(this.Disposable);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disposable.Dispose();
|
||||
}
|
||||
this.Name.ObserveWithHistory(name => point.Name = name, point.Name, history).AddTo(this.Disposable);
|
||||
this.X.ObserveWithHistory(x => point.X = x, point.X, history).AddTo(this.Disposable);
|
||||
this.Y.ObserveWithHistory(y => point.Y = y, point.Y, history).AddTo(this.Disposable);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disposable.Dispose();
|
||||
}
|
||||
}
|
|
@ -1,18 +1,17 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReactiveHistorySample.Views
|
||||
{
|
||||
public class LineShapeView : UserControl
|
||||
{
|
||||
public LineShapeView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
namespace ReactiveHistorySample.Views;
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
public class LineShapeView : UserControl
|
||||
{
|
||||
public LineShapeView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReactiveHistorySample.Views
|
||||
{
|
||||
public class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
namespace ReactiveHistorySample.Views;
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
public class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
|
@ -1,18 +1,17 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReactiveHistorySample.Views
|
||||
{
|
||||
public class PointShapeView : UserControl
|
||||
{
|
||||
public PointShapeView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
namespace ReactiveHistorySample.Views;
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
public class PointShapeView : UserControl
|
||||
{
|
||||
public PointShapeView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
using Avalonia;
|
||||
|
||||
namespace ReactiveHistorySample.Avalonia
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
namespace ReactiveHistorySample.Avalonia;
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace();
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace();
|
||||
}
|
|
@ -1,54 +1,53 @@
|
|||
using System;
|
||||
|
||||
namespace ReactiveHistory
|
||||
namespace ReactiveHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Undo/redo action history contract.
|
||||
/// </summary>
|
||||
public interface IHistory
|
||||
{
|
||||
/// <summary>
|
||||
/// Undo/redo action history contract.
|
||||
/// Gets or sets flag indicating whether history is paused.
|
||||
/// </summary>
|
||||
public interface IHistory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating whether history is paused.
|
||||
/// </summary>
|
||||
bool IsPaused { get; set; }
|
||||
bool IsPaused { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating whether undo action can execute.
|
||||
/// </summary>
|
||||
IObservable<bool> CanUndo { get; }
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating whether undo action can execute.
|
||||
/// </summary>
|
||||
IObservable<bool> CanUndo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating whether redo action can execute.
|
||||
/// </summary>
|
||||
IObservable<bool> CanRedo { get; }
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating whether redo action can execute.
|
||||
/// </summary>
|
||||
IObservable<bool> CanRedo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating whether clear action can execute.
|
||||
/// </summary>
|
||||
IObservable<bool> CanClear { get; }
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating whether clear action can execute.
|
||||
/// </summary>
|
||||
IObservable<bool> CanClear { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Makes undo/redo history snapshot.
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo state action.</param>
|
||||
/// <param name="redo">The redo state action.</param>
|
||||
void Snapshot(Action undo, Action redo);
|
||||
/// <summary>
|
||||
/// Makes undo/redo history snapshot.
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo state action.</param>
|
||||
/// <param name="redo">The redo state action.</param>
|
||||
void Snapshot(Action undo, Action redo);
|
||||
|
||||
/// <summary>
|
||||
/// Executes undo action.
|
||||
/// </summary>
|
||||
/// <returns>True if undo action was executed.</returns>
|
||||
bool Undo();
|
||||
/// <summary>
|
||||
/// Executes undo action.
|
||||
/// </summary>
|
||||
/// <returns>True if undo action was executed.</returns>
|
||||
bool Undo();
|
||||
|
||||
/// <summary>
|
||||
/// Executes redo action.
|
||||
/// </summary>
|
||||
/// <returns>True if redo action was executed.</returns>
|
||||
bool Redo();
|
||||
/// <summary>
|
||||
/// Executes redo action.
|
||||
/// </summary>
|
||||
/// <returns>True if redo action was executed.</returns>
|
||||
bool Redo();
|
||||
|
||||
/// <summary>
|
||||
/// Clears undo/redo actions history.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears undo/redo actions history.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
|
@ -2,181 +2,180 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ReactiveHistory
|
||||
namespace ReactiveHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Stack history extension methods for the generic list implementations.
|
||||
/// </summary>
|
||||
public static class IListExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Stack history extension methods for the generic list implementations.
|
||||
/// Adds item to the source list with history.
|
||||
/// </summary>
|
||||
public static class IListExtensions
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void AddWithHistory<T>(this IList<T> source, T item, IHistory history)
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds item to the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void AddWithHistory<T>(this IList<T> source, T item, IHistory history)
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
int index = source.Count;
|
||||
void redo() => source.Insert(index, item);
|
||||
void undo() => source.RemoveAt(index);
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts item to the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="index">The item insertion index.</param>
|
||||
/// <param name="item">The item to insert.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void InsertWithHistory<T>(this IList<T> source, int index, T item, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (index < 0)
|
||||
throw new IndexOutOfRangeException("Index can not be negative.");
|
||||
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
void redo() => source.Insert(index, item);
|
||||
void undo() => source.RemoveAt(index);
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces item at specified index in the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="index">The item index to replace.</param>
|
||||
/// <param name="item">The replaced item.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void ReplaceWithHistory<T>(this IList<T> source, int index, T item, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (index < 0)
|
||||
throw new IndexOutOfRangeException("Index can not be negative.");
|
||||
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
var oldValue = source[index];
|
||||
var newValue = item;
|
||||
void redo() => source[index] = newValue;
|
||||
void undo() => source[index] = oldValue;
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes item at specified index from the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void RemoveWithHistory<T>(this IList<T> source, T item, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
int index = source.IndexOf(item);
|
||||
void redo() => source.RemoveAt(index);
|
||||
void undo() => source.Insert(index, item);
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes item from the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="index">The item index to remove.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void RemoveWithHistory<T>(this IList<T> source, int index, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (index < 0)
|
||||
throw new IndexOutOfRangeException("Index can not be negative.");
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
var item = source[index];
|
||||
void redo() => source.RemoveAt(index);
|
||||
void undo() => source.Insert(index, item);
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void ClearWithHistory<T>(this IList<T> source, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
if (source.Count > 0)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
int index = source.Count;
|
||||
void redo() => source.Insert(index, item);
|
||||
void undo() => source.RemoveAt(index);
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts item to the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="index">The item insertion index.</param>
|
||||
/// <param name="item">The item to insert.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void InsertWithHistory<T>(this IList<T> source, int index, T item, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (index < 0)
|
||||
throw new IndexOutOfRangeException("Index can not be negative.");
|
||||
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
void redo() => source.Insert(index, item);
|
||||
void undo() => source.RemoveAt(index);
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces item at specified index in the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="index">The item index to replace.</param>
|
||||
/// <param name="item">The replaced item.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void ReplaceWithHistory<T>(this IList<T> source, int index, T item, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (index < 0)
|
||||
throw new IndexOutOfRangeException("Index can not be negative.");
|
||||
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
var oldValue = source[index];
|
||||
var newValue = item;
|
||||
void redo() => source[index] = newValue;
|
||||
void undo() => source[index] = oldValue;
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes item at specified index from the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void RemoveWithHistory<T>(this IList<T> source, T item, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
int index = source.IndexOf(item);
|
||||
void redo() => source.RemoveAt(index);
|
||||
void undo() => source.Insert(index, item);
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes item from the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="index">The item index to remove.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void RemoveWithHistory<T>(this IList<T> source, int index, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (index < 0)
|
||||
throw new IndexOutOfRangeException("Index can not be negative.");
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
var item = source[index];
|
||||
void redo() => source.RemoveAt(index);
|
||||
void undo() => source.Insert(index, item);
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the source list with history.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
public static void ClearWithHistory<T>(this IList<T> source, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
if (source.Count > 0)
|
||||
var items = source.ToArray();
|
||||
void redo()
|
||||
{
|
||||
var items = source.ToArray();
|
||||
void redo()
|
||||
foreach (var item in items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
source.Remove(item);
|
||||
}
|
||||
source.Remove(item);
|
||||
}
|
||||
void undo()
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
void undo()
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
history.Snapshot(undo, redo);
|
||||
redo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +1,46 @@
|
|||
using System;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace ReactiveHistory
|
||||
namespace ReactiveHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Observable extension methods for the generic observable implementations.
|
||||
/// </summary>
|
||||
public static class IObservableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Observable extension methods for the generic observable implementations.
|
||||
/// Observe property changes with history.
|
||||
/// </summary>
|
||||
public static class IObservableExtensions
|
||||
/// <param name="source">The property value observable.</param>
|
||||
/// <param name="update">The property update action.</param>
|
||||
/// <param name="currentValue">The property current value.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
/// <returns>The property value changes subscription.</returns>
|
||||
public static IDisposable ObserveWithHistory<T>(this IObservable<T> source, Action<T> update, T currentValue, IHistory history)
|
||||
{
|
||||
/// <summary>
|
||||
/// Observe property changes with history.
|
||||
/// </summary>
|
||||
/// <param name="source">The property value observable.</param>
|
||||
/// <param name="update">The property update action.</param>
|
||||
/// <param name="currentValue">The property current value.</param>
|
||||
/// <param name="history">The history object.</param>
|
||||
/// <returns>The property value changes subscription.</returns>
|
||||
public static IDisposable ObserveWithHistory<T>(this IObservable<T> source, Action<T> update, T currentValue, IHistory history)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (update == null)
|
||||
throw new ArgumentNullException(nameof(update));
|
||||
if (update == null)
|
||||
throw new ArgumentNullException(nameof(update));
|
||||
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
if (history == null)
|
||||
throw new ArgumentNullException(nameof(history));
|
||||
|
||||
var previous = currentValue;
|
||||
var previous = currentValue;
|
||||
|
||||
return source.Skip(1).Subscribe(
|
||||
next =>
|
||||
return source.Skip(1).Subscribe(
|
||||
next =>
|
||||
{
|
||||
if (!history.IsPaused)
|
||||
{
|
||||
if (!history.IsPaused)
|
||||
{
|
||||
var undoValue = previous;
|
||||
var redoValue = next;
|
||||
void undo() => update(undoValue);
|
||||
void redo() => update(redoValue);
|
||||
history.Snapshot(undo, redo);
|
||||
}
|
||||
previous = next;
|
||||
});
|
||||
}
|
||||
var undoValue = previous;
|
||||
var redoValue = next;
|
||||
void undo() => update(undoValue);
|
||||
void redo() => update(redoValue);
|
||||
history.Snapshot(undo, redo);
|
||||
}
|
||||
previous = next;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,146 +3,145 @@ using System.Collections.Generic;
|
|||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace ReactiveHistory
|
||||
namespace ReactiveHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Undo/redo stack based action history.
|
||||
/// </summary>
|
||||
public class StackHistory : IHistory, IDisposable
|
||||
{
|
||||
private readonly Subject<bool> _canUndo;
|
||||
private readonly Subject<bool> _canRedo;
|
||||
private readonly Subject<bool> _canClear;
|
||||
private volatile bool _isPaused;
|
||||
|
||||
/// <summary>
|
||||
/// Undo/redo stack based action history.
|
||||
/// Gets or sets undo states stack.
|
||||
/// </summary>
|
||||
public class StackHistory : IHistory, IDisposable
|
||||
public Stack<State> Undos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets redo states stack.
|
||||
/// </summary>
|
||||
public Stack<State> Redos { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPaused
|
||||
{
|
||||
private readonly Subject<bool> _canUndo;
|
||||
private readonly Subject<bool> _canRedo;
|
||||
private readonly Subject<bool> _canClear;
|
||||
private volatile bool _isPaused;
|
||||
get { return _isPaused; }
|
||||
set { _isPaused = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets undo states stack.
|
||||
/// </summary>
|
||||
public Stack<State> Undos { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public IObservable<bool> CanUndo
|
||||
{
|
||||
get { return _canUndo.AsObservable(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets redo states stack.
|
||||
/// </summary>
|
||||
public Stack<State> Redos { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public IObservable<bool> CanRedo
|
||||
{
|
||||
get { return _canRedo.AsObservable(); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPaused
|
||||
/// <inheritdoc/>
|
||||
public IObservable<bool> CanClear
|
||||
{
|
||||
get { return _canClear.AsObservable(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="StackHistory"/> instance.
|
||||
/// </summary>
|
||||
public StackHistory()
|
||||
{
|
||||
Undos = new Stack<State>();
|
||||
Redos = new Stack<State>();
|
||||
|
||||
_isPaused = false;
|
||||
_canUndo = new Subject<bool>();
|
||||
_canRedo = new Subject<bool>();
|
||||
_canClear = new Subject<bool>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Snapshot(Action undo, Action redo)
|
||||
{
|
||||
if (undo == null)
|
||||
throw new ArgumentNullException(nameof(undo));
|
||||
|
||||
if (redo == null)
|
||||
throw new ArgumentNullException(nameof(redo));
|
||||
|
||||
if (Redos.Count > 0)
|
||||
{
|
||||
get { return _isPaused; }
|
||||
set { _isPaused = value; }
|
||||
Redos.Clear();
|
||||
_canRedo.OnNext(false);
|
||||
}
|
||||
Undos.Push(new State(undo, redo, string.Empty, string.Empty));
|
||||
_canUndo.OnNext(true);
|
||||
_canClear.OnNext(true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IObservable<bool> CanUndo
|
||||
/// <inheritdoc/>
|
||||
public bool Undo()
|
||||
{
|
||||
if (Undos.Count > 0)
|
||||
{
|
||||
get { return _canUndo.AsObservable(); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IObservable<bool> CanRedo
|
||||
{
|
||||
get { return _canRedo.AsObservable(); }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IObservable<bool> CanClear
|
||||
{
|
||||
get { return _canClear.AsObservable(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="StackHistory"/> instance.
|
||||
/// </summary>
|
||||
public StackHistory()
|
||||
{
|
||||
Undos = new Stack<State>();
|
||||
Redos = new Stack<State>();
|
||||
|
||||
_isPaused = false;
|
||||
_canUndo = new Subject<bool>();
|
||||
_canRedo = new Subject<bool>();
|
||||
_canClear = new Subject<bool>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Snapshot(Action undo, Action redo)
|
||||
{
|
||||
if (undo == null)
|
||||
throw new ArgumentNullException(nameof(undo));
|
||||
|
||||
if (redo == null)
|
||||
throw new ArgumentNullException(nameof(redo));
|
||||
|
||||
if (Redos.Count > 0)
|
||||
IsPaused = true;
|
||||
var state = Undos.Pop();
|
||||
if (Undos.Count == 0)
|
||||
{
|
||||
_canUndo.OnNext(false);
|
||||
}
|
||||
state.Undo.Invoke();
|
||||
Redos.Push(state);
|
||||
_canRedo.OnNext(true);
|
||||
_canClear.OnNext(true);
|
||||
IsPaused = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Redo()
|
||||
{
|
||||
if (Redos.Count > 0)
|
||||
{
|
||||
IsPaused = true;
|
||||
var state = Redos.Pop();
|
||||
if (Redos.Count == 0)
|
||||
{
|
||||
Redos.Clear();
|
||||
_canRedo.OnNext(false);
|
||||
}
|
||||
Undos.Push(new State(undo, redo, string.Empty, string.Empty));
|
||||
state.Redo.Invoke();
|
||||
Undos.Push(state);
|
||||
_canUndo.OnNext(true);
|
||||
_canClear.OnNext(true);
|
||||
IsPaused = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Undo()
|
||||
{
|
||||
if (Undos.Count > 0)
|
||||
{
|
||||
IsPaused = true;
|
||||
var state = Undos.Pop();
|
||||
if (Undos.Count == 0)
|
||||
{
|
||||
_canUndo.OnNext(false);
|
||||
}
|
||||
state.Undo.Invoke();
|
||||
Redos.Push(state);
|
||||
_canRedo.OnNext(true);
|
||||
_canClear.OnNext(true);
|
||||
IsPaused = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Redo()
|
||||
{
|
||||
if (Redos.Count > 0)
|
||||
{
|
||||
IsPaused = true;
|
||||
var state = Redos.Pop();
|
||||
if (Redos.Count == 0)
|
||||
{
|
||||
_canRedo.OnNext(false);
|
||||
}
|
||||
state.Redo.Invoke();
|
||||
Undos.Push(state);
|
||||
_canUndo.OnNext(true);
|
||||
_canClear.OnNext(true);
|
||||
IsPaused = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
{
|
||||
Undos.Clear();
|
||||
Redos.Clear();
|
||||
_canUndo.OnNext(false);
|
||||
_canRedo.OnNext(false);
|
||||
_canClear.OnNext(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Undos.Clear();
|
||||
Redos.Clear();
|
||||
_canUndo.Dispose();
|
||||
_canRedo.Dispose();
|
||||
_canClear.Dispose();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
{
|
||||
Undos.Clear();
|
||||
Redos.Clear();
|
||||
_canUndo.OnNext(false);
|
||||
_canRedo.OnNext(false);
|
||||
_canClear.OnNext(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Undos.Clear();
|
||||
Redos.Clear();
|
||||
_canUndo.Dispose();
|
||||
_canRedo.Dispose();
|
||||
_canClear.Dispose();
|
||||
}
|
||||
}
|
|
@ -1,45 +1,44 @@
|
|||
using System;
|
||||
|
||||
namespace ReactiveHistory
|
||||
namespace ReactiveHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Undo/redo action pair.
|
||||
/// </summary>
|
||||
public struct State
|
||||
{
|
||||
/// <summary>
|
||||
/// Undo/redo action pair.
|
||||
/// The undo state action.
|
||||
/// </summary>
|
||||
public struct State
|
||||
public readonly Action Undo;
|
||||
|
||||
/// <summary>
|
||||
/// The redo state action.
|
||||
/// </summary>
|
||||
public readonly Action Redo;
|
||||
|
||||
/// <summary>
|
||||
/// The undo state name.
|
||||
/// </summary>
|
||||
public readonly string UndoName;
|
||||
|
||||
/// <summary>
|
||||
/// The redo state name.
|
||||
/// </summary>
|
||||
public readonly string RedoName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="State"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo state action.</param>
|
||||
/// <param name="redo">The redo state action.</param>
|
||||
/// <param name="undoName">The undo state name.</param>
|
||||
/// <param name="redoName">The redo state name.</param>
|
||||
public State(Action undo, Action redo, string undoName, string redoName)
|
||||
{
|
||||
/// <summary>
|
||||
/// The undo state action.
|
||||
/// </summary>
|
||||
public readonly Action Undo;
|
||||
|
||||
/// <summary>
|
||||
/// The redo state action.
|
||||
/// </summary>
|
||||
public readonly Action Redo;
|
||||
|
||||
/// <summary>
|
||||
/// The undo state name.
|
||||
/// </summary>
|
||||
public readonly string UndoName;
|
||||
|
||||
/// <summary>
|
||||
/// The redo state name.
|
||||
/// </summary>
|
||||
public readonly string RedoName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="State"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo state action.</param>
|
||||
/// <param name="redo">The redo state action.</param>
|
||||
/// <param name="undoName">The undo state name.</param>
|
||||
/// <param name="redoName">The redo state name.</param>
|
||||
public State(Action undo, Action redo, string undoName, string redoName)
|
||||
{
|
||||
Undo = undo;
|
||||
Redo = redo;
|
||||
UndoName = undoName;
|
||||
RedoName = redoName;
|
||||
}
|
||||
Undo = undo;
|
||||
Redo = redo;
|
||||
UndoName = undoName;
|
||||
RedoName = redoName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,32 +2,31 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace ReactiveHistory.UnitTests
|
||||
namespace ReactiveHistory.UnitTests;
|
||||
|
||||
internal class HistoryHelper : IDisposable
|
||||
{
|
||||
internal class HistoryHelper : IDisposable
|
||||
CompositeDisposable _disposable;
|
||||
IList<bool> _canUndos;
|
||||
IList<bool> _canRedos;
|
||||
IList<bool> _canClears;
|
||||
public IList<bool> CanUndos { get { return _canUndos; } }
|
||||
public IList<bool> CanRedos { get { return _canRedos; } }
|
||||
public IList<bool> CanClears { get { return _canClears; } }
|
||||
|
||||
public HistoryHelper(IHistory target)
|
||||
{
|
||||
CompositeDisposable _disposable;
|
||||
IList<bool> _canUndos;
|
||||
IList<bool> _canRedos;
|
||||
IList<bool> _canClears;
|
||||
public IList<bool> CanUndos { get { return _canUndos; } }
|
||||
public IList<bool> CanRedos { get { return _canRedos; } }
|
||||
public IList<bool> CanClears { get { return _canClears; } }
|
||||
|
||||
public HistoryHelper(IHistory target)
|
||||
{
|
||||
_disposable = new CompositeDisposable();
|
||||
_canUndos = new List<bool>();
|
||||
_canRedos = new List<bool>();
|
||||
_canClears = new List<bool>();
|
||||
_disposable.Add(target.CanUndo.Subscribe(x => _canUndos.Add(x)));
|
||||
_disposable.Add(target.CanRedo.Subscribe(x => _canRedos.Add(x)));
|
||||
_disposable.Add(target.CanClear.Subscribe(x => _canClears.Add(x)));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposable.Dispose();
|
||||
}
|
||||
_disposable = new CompositeDisposable();
|
||||
_canUndos = new List<bool>();
|
||||
_canRedos = new List<bool>();
|
||||
_canClears = new List<bool>();
|
||||
_disposable.Add(target.CanUndo.Subscribe(x => _canUndos.Add(x)));
|
||||
_disposable.Add(target.CanRedo.Subscribe(x => _canRedos.Add(x)));
|
||||
_disposable.Add(target.CanClear.Subscribe(x => _canClears.Add(x)));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposable.Dispose();
|
||||
}
|
||||
}
|
|
@ -3,97 +3,96 @@ using System.Reactive.Linq;
|
|||
using System.Reactive.Subjects;
|
||||
using Xunit;
|
||||
|
||||
namespace ReactiveHistory.UnitTests
|
||||
namespace ReactiveHistory.UnitTests;
|
||||
|
||||
public class ObservableHistoryExtensionsTests
|
||||
{
|
||||
public class ObservableHistoryExtensionsTests
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
|
||||
public void ObserveWithHistory_Skips_First_Value()
|
||||
{
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
|
||||
public void ObserveWithHistory_Skips_First_Value()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var subject = new Subject<int>())
|
||||
using (subject.AsObservable().ObserveWithHistory(x => { }, 0, target))
|
||||
{
|
||||
subject.OnNext(1);
|
||||
Assert.Empty(target.Undos);
|
||||
}
|
||||
using (var subject = new Subject<int>())
|
||||
using (subject.AsObservable().ObserveWithHistory(x => { }, 0, target))
|
||||
{
|
||||
subject.OnNext(1);
|
||||
Assert.Empty(target.Undos);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
|
||||
public void ObserveWithHistory_Creates_History_Snapshot()
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
|
||||
public void ObserveWithHistory_Creates_History_Snapshot()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var subject = new Subject<int>())
|
||||
using (subject.AsObservable().ObserveWithHistory(x => { }, 0, target))
|
||||
{
|
||||
var target = new StackHistory();
|
||||
subject.OnNext(1);
|
||||
subject.OnNext(2);
|
||||
subject.OnNext(3);
|
||||
|
||||
using (var subject = new Subject<int>())
|
||||
using (subject.AsObservable().ObserveWithHistory(x => { }, 0, target))
|
||||
{
|
||||
subject.OnNext(1);
|
||||
subject.OnNext(2);
|
||||
subject.OnNext(3);
|
||||
|
||||
Assert.Equal(2, target.Undos.Count);
|
||||
}
|
||||
Assert.Equal(2, target.Undos.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
|
||||
public void ObserveWithHistory_Does_Not_Create_History_Snapshot_When_IsPaused_True()
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
|
||||
public void ObserveWithHistory_Does_Not_Create_History_Snapshot_When_IsPaused_True()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var subject = new Subject<int>())
|
||||
using (subject.AsObservable().ObserveWithHistory(x => { }, 0, target))
|
||||
{
|
||||
var target = new StackHistory();
|
||||
subject.OnNext(1);
|
||||
subject.OnNext(2);
|
||||
|
||||
using (var subject = new Subject<int>())
|
||||
using (subject.AsObservable().ObserveWithHistory(x => { }, 0, target))
|
||||
{
|
||||
subject.OnNext(1);
|
||||
subject.OnNext(2);
|
||||
target.IsPaused = true;
|
||||
subject.OnNext(3);
|
||||
subject.OnNext(4);
|
||||
target.IsPaused = false;
|
||||
|
||||
target.IsPaused = true;
|
||||
subject.OnNext(3);
|
||||
subject.OnNext(4);
|
||||
target.IsPaused = false;
|
||||
subject.OnNext(5);
|
||||
subject.OnNext(6);
|
||||
|
||||
subject.OnNext(5);
|
||||
subject.OnNext(6);
|
||||
|
||||
Assert.Equal(3, target.Undos.Count);
|
||||
}
|
||||
Assert.Equal(3, target.Undos.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
|
||||
public void ObserveWithHistory_Sets_CurrentValue()
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "ObservableHistoryExtensions")]
|
||||
public void ObserveWithHistory_Sets_CurrentValue()
|
||||
{
|
||||
var history = new StackHistory();
|
||||
|
||||
using (var subject = new Subject<int>())
|
||||
{
|
||||
var history = new StackHistory();
|
||||
var target = new List<int>();
|
||||
var initialValue = 10;
|
||||
|
||||
using (var subject = new Subject<int>())
|
||||
using (subject.AsObservable().ObserveWithHistory(x => target.Add(x), currentValue: initialValue, history: history))
|
||||
{
|
||||
var target = new List<int>();
|
||||
var initialValue = 10;
|
||||
subject.OnNext(initialValue); // empty -> 10 (the initial state of variable)
|
||||
subject.OnNext(2); // empty -> 10 -> 2
|
||||
subject.OnNext(3); // empty -> 10 -> 2 -> 3
|
||||
|
||||
using (subject.AsObservable().ObserveWithHistory(x => target.Add(x), currentValue: initialValue, history: history))
|
||||
{
|
||||
subject.OnNext(initialValue); // empty -> 10 (the initial state of variable)
|
||||
subject.OnNext(2); // empty -> 10 -> 2
|
||||
subject.OnNext(3); // empty -> 10 -> 2 -> 3
|
||||
history.Undo(); // 3 -> 2
|
||||
history.Undo(); // 2 -> 10 (finally restores initial state)
|
||||
|
||||
history.Undo(); // 3 -> 2
|
||||
history.Undo(); // 2 -> 10 (finally restores initial state)
|
||||
Assert.Empty(history.Undos);
|
||||
Assert.Equal(2, history.Redos.Count);
|
||||
Assert.Equal(new int[] { 2, 10 }, target);
|
||||
|
||||
Assert.Empty(history.Undos);
|
||||
Assert.Equal(2, history.Redos.Count);
|
||||
Assert.Equal(new int[] { 2, 10 }, target);
|
||||
history.Redo(); // 10 -> 2
|
||||
history.Redo(); // 2 -> 3
|
||||
|
||||
history.Redo(); // 10 -> 2
|
||||
history.Redo(); // 2 -> 3
|
||||
|
||||
Assert.Equal(2, history.Undos.Count);
|
||||
Assert.Empty(history.Redos);
|
||||
Assert.Equal(new int[] { 2, 10, 2, 3 }, target);
|
||||
}
|
||||
Assert.Equal(2, history.Undos.Count);
|
||||
Assert.Empty(history.Redos);
|
||||
Assert.Equal(new int[] { 2, 10, 2, 3 }, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -2,206 +2,205 @@
|
|||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace ReactiveHistory.UnitTests
|
||||
namespace ReactiveHistory.UnitTests;
|
||||
|
||||
public class StackHistoryTests
|
||||
{
|
||||
public class StackHistoryTests
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Undos_And_Redos_Shuould_Be_Initialized()
|
||||
{
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Undos_And_Redos_Shuould_Be_Initialized()
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
Assert.NotNull(target.Undos);
|
||||
Assert.NotNull(target.Redos);
|
||||
Assert.Empty(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
Assert.False(target.IsPaused);
|
||||
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Dispose_Should_Release_Allocated_Resources()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => { }, () => { });
|
||||
target.Snapshot(() => { }, () => { });
|
||||
var result = target.Undo();
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Single(target.Redos);
|
||||
Assert.True(result);
|
||||
|
||||
target.Dispose();
|
||||
|
||||
Assert.Empty(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
|
||||
Assert.Throws<ObjectDisposedException>(() => target.CanUndo.Subscribe(_ => { }));
|
||||
Assert.Throws<ObjectDisposedException>(() => target.CanRedo.Subscribe(_ => { }));
|
||||
Assert.Throws<ObjectDisposedException>(() => target.CanClear.Subscribe(_ => { }));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void First_Snapshot_Should_Push_One_Undo_State()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => { }, () => { });
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
Assert.Equal(new bool[] { true }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { true }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Snapshot_Should_Clear_Redos()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => { }, () => { });
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
|
||||
var result = target.Undo();
|
||||
Assert.Empty(target.Undos);
|
||||
Assert.Single(target.Redos);
|
||||
Assert.True(result);
|
||||
|
||||
target.Snapshot(() => { }, () => { });
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
Assert.Equal(new bool[] { true, false, true }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { true, false }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { true, true, true }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Invoking_Undo_Should_Not_Throw_When_Undos_Are_Empty()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
var result = target.Undo();
|
||||
Assert.False(result);
|
||||
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Invoking_Redo_Should_Not_Throw_When_Redos_Are_Empty()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
var result = target.Redo();
|
||||
Assert.False(result);
|
||||
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Invoking_Undo_Should_Invoke_Undo_Action_And_Push_State_To_Redos()
|
||||
{
|
||||
int undoCount = 0;
|
||||
int redoCount = 0;
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => undoCount++, () => redoCount++);
|
||||
var undo = target.Undos.Peek();
|
||||
var result = target.Undo();
|
||||
Assert.Equal(1, undoCount);
|
||||
Assert.Equal(0, redoCount);
|
||||
Assert.Empty(target.Undos);
|
||||
Assert.Single(target.Redos);
|
||||
Assert.True(result);
|
||||
Assert.Equal(new bool[] { true, false }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { true }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { true, true }, helper.CanClears.ToArray());
|
||||
|
||||
var redo = target.Redos.Peek();
|
||||
Assert.Equal(undo, redo);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Invoking_Redo_Should_Invoke_Redo_Action_And_Push_State_To_Undos()
|
||||
{
|
||||
int undoCount = 0;
|
||||
int redoCount = 0;
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => undoCount++, () => redoCount++);
|
||||
var undo1 = target.Undos.Peek();
|
||||
var result1 = target.Undo();
|
||||
var redo1 = target.Redos.Peek();
|
||||
var result2 = target.Redo();
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
Assert.True(result1);
|
||||
Assert.True(result2);
|
||||
Assert.Equal(new bool[] { true, false, true }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { true, false }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { true, true, true }, helper.CanClears.ToArray());
|
||||
|
||||
var undo2 = target.Undos.Peek();
|
||||
Assert.Equal(undo1, undo2);
|
||||
Assert.Equal(undo1, redo1);
|
||||
Assert.Equal(1, undoCount);
|
||||
Assert.Equal(1, redoCount);
|
||||
Assert.True(result1);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Undo_Sets_IsPaused_True_While_Invoking_Undo_Redo_State()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
target.Snapshot(
|
||||
undo: () => Assert.True(target.IsPaused),
|
||||
redo: () => Assert.True(target.IsPaused));
|
||||
|
||||
Assert.False(target.IsPaused);
|
||||
target.Undo();
|
||||
Assert.False(target.IsPaused);
|
||||
|
||||
Assert.False(target.IsPaused);
|
||||
target.Redo();
|
||||
Assert.NotNull(target.Undos);
|
||||
Assert.NotNull(target.Redos);
|
||||
Assert.Empty(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
Assert.False(target.IsPaused);
|
||||
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Dispose_Should_Release_Allocated_Resources()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => { }, () => { });
|
||||
target.Snapshot(() => { }, () => { });
|
||||
var result = target.Undo();
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Single(target.Redos);
|
||||
Assert.True(result);
|
||||
|
||||
target.Dispose();
|
||||
|
||||
Assert.Empty(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
|
||||
Assert.Throws<ObjectDisposedException>(() => target.CanUndo.Subscribe(_ => { }));
|
||||
Assert.Throws<ObjectDisposedException>(() => target.CanRedo.Subscribe(_ => { }));
|
||||
Assert.Throws<ObjectDisposedException>(() => target.CanClear.Subscribe(_ => { }));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void First_Snapshot_Should_Push_One_Undo_State()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => { }, () => { });
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
Assert.Equal(new bool[] { true }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { true }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Snapshot_Should_Clear_Redos()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => { }, () => { });
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
|
||||
var result = target.Undo();
|
||||
Assert.Empty(target.Undos);
|
||||
Assert.Single(target.Redos);
|
||||
Assert.True(result);
|
||||
|
||||
target.Snapshot(() => { }, () => { });
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
Assert.Equal(new bool[] { true, false, true }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { true, false }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { true, true, true }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Invoking_Undo_Should_Not_Throw_When_Undos_Are_Empty()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
var result = target.Undo();
|
||||
Assert.False(result);
|
||||
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Invoking_Redo_Should_Not_Throw_When_Redos_Are_Empty()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
var result = target.Redo();
|
||||
Assert.False(result);
|
||||
Assert.Equal(new bool[] { }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { }, helper.CanClears.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Invoking_Undo_Should_Invoke_Undo_Action_And_Push_State_To_Redos()
|
||||
{
|
||||
int undoCount = 0;
|
||||
int redoCount = 0;
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => undoCount++, () => redoCount++);
|
||||
var undo = target.Undos.Peek();
|
||||
var result = target.Undo();
|
||||
Assert.Equal(1, undoCount);
|
||||
Assert.Equal(0, redoCount);
|
||||
Assert.Empty(target.Undos);
|
||||
Assert.Single(target.Redos);
|
||||
Assert.True(result);
|
||||
Assert.Equal(new bool[] { true, false }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { true }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { true, true }, helper.CanClears.ToArray());
|
||||
|
||||
var redo = target.Redos.Peek();
|
||||
Assert.Equal(undo, redo);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Invoking_Redo_Should_Invoke_Redo_Action_And_Push_State_To_Undos()
|
||||
{
|
||||
int undoCount = 0;
|
||||
int redoCount = 0;
|
||||
var target = new StackHistory();
|
||||
|
||||
using (var helper = new HistoryHelper(target))
|
||||
{
|
||||
target.Snapshot(() => undoCount++, () => redoCount++);
|
||||
var undo1 = target.Undos.Peek();
|
||||
var result1 = target.Undo();
|
||||
var redo1 = target.Redos.Peek();
|
||||
var result2 = target.Redo();
|
||||
Assert.Single(target.Undos);
|
||||
Assert.Empty(target.Redos);
|
||||
Assert.True(result1);
|
||||
Assert.True(result2);
|
||||
Assert.Equal(new bool[] { true, false, true }, helper.CanUndos.ToArray());
|
||||
Assert.Equal(new bool[] { true, false }, helper.CanRedos.ToArray());
|
||||
Assert.Equal(new bool[] { true, true, true }, helper.CanClears.ToArray());
|
||||
|
||||
var undo2 = target.Undos.Peek();
|
||||
Assert.Equal(undo1, undo2);
|
||||
Assert.Equal(undo1, redo1);
|
||||
Assert.Equal(1, undoCount);
|
||||
Assert.Equal(1, redoCount);
|
||||
Assert.True(result1);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "StackHistory")]
|
||||
public void Undo_Sets_IsPaused_True_While_Invoking_Undo_Redo_State()
|
||||
{
|
||||
var target = new StackHistory();
|
||||
|
||||
target.Snapshot(
|
||||
undo: () => Assert.True(target.IsPaused),
|
||||
redo: () => Assert.True(target.IsPaused));
|
||||
|
||||
Assert.False(target.IsPaused);
|
||||
target.Undo();
|
||||
Assert.False(target.IsPaused);
|
||||
|
||||
Assert.False(target.IsPaused);
|
||||
target.Redo();
|
||||
Assert.False(target.IsPaused);
|
||||
}
|
||||
}
|
|
@ -1,21 +1,20 @@
|
|||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace ReactiveHistory.UnitTests
|
||||
namespace ReactiveHistory.UnitTests;
|
||||
|
||||
public class StateTests
|
||||
{
|
||||
public class StateTests
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "State")]
|
||||
public void Constructor_Should_Set_Undo_And_Redo_Fields()
|
||||
{
|
||||
[Fact]
|
||||
[Trait("ReactiveHistory", "State")]
|
||||
public void Constructor_Should_Set_Undo_And_Redo_Fields()
|
||||
{
|
||||
void undo()
|
||||
{ }
|
||||
void redo()
|
||||
{ }
|
||||
var target = new State(undo, redo, string.Empty, string.Empty);
|
||||
Assert.Equal(undo, target.Undo);
|
||||
Assert.Equal(redo, target.Redo);
|
||||
}
|
||||
void undo()
|
||||
{ }
|
||||
void redo()
|
||||
{ }
|
||||
var target = new State(undo, redo, string.Empty, string.Empty);
|
||||
Assert.Equal(undo, target.Undo);
|
||||
Assert.Equal(redo, target.Redo);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче