Add project files.

This commit is contained in:
Ankitkumar Satapara
2026-04-17 22:31:58 +05:30
commit 21aaef6776
473 changed files with 50152 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace Nodify.UndoRedo
{
public interface IActionsHistory : INotifyPropertyChanged
{
int MaxSize { get; set; }
bool CanUndo { get; }
bool CanRedo { get; }
bool IsEnabled { get; set; }
IAction? Current { get; }
void Undo();
void Redo();
void Clear();
/// <summary>
/// All future modifications will be merged together to create a single history item until batch is disposed.
/// </summary>
IDisposable Batch(string? label = default);
void Record(IAction action);
/// <summary>
/// All future modifications will be merged together to create a single history item until history is resumed.
/// </summary>
void Pause(string? label = default);
/// <summary>Each future modifications will create a new history item.</summary>
void Resume();
}
public interface IAction
{
string? Label { get; }
void Execute();
void Undo();
}
public static class ActionsHistoryExtensions
{
public static void Record(this IActionsHistory history, Action execute, Action unexecute, string? label = default)
=> history.Record(new DelegateAction(execute, unexecute, label));
public static void ExecuteAction(this IActionsHistory history, IAction action)
{
history.Record(action);
action.Execute();
}
}
public class ActionsHistory : IActionsHistory
{
private readonly List<IAction> _history = new List<IAction>();
private readonly List<IAction> _batchHistory = new List<IAction>();
private int _position = -1;
private bool _isApplyingOperation = false;
private string? _batchLabel;
private int _batchDepth;
private static readonly PropertyChangedEventArgs _canRedoArgs = new PropertyChangedEventArgs(nameof(CanRedo));
private static readonly PropertyChangedEventArgs _canUndoArgs = new PropertyChangedEventArgs(nameof(CanUndo));
public event PropertyChangedEventHandler? PropertyChanged;
public static readonly ActionsHistory Global = new ActionsHistory();
public bool IsBatching { get; private set; }
public int MaxSize { get; set; } = 50;
public bool CanRedo => _history.Count > 0 && _position < _history.Count - 1;
public bool CanUndo => _position > -1;
public bool IsEnabled { get; set; } = true;
public IAction? Current => CanUndo ? _history[_position] : null;
public IDisposable Batch(string? label = default)
=> new BatchOperation(label, this);
public void Record(IAction op)
{
// Prevent recording the undo or redo operation
if (_isApplyingOperation || !IsEnabled)
{
return;
}
if (IsBatching)
{
_batchHistory.Add(op);
}
else
{
AddToUndoStack(op);
}
}
private void AddToUndoStack(IAction op)
{
if (_position < _history.Count - 1)
{
_history.RemoveRange(_position + 1, _history.Count - _position - 1);
}
if (_history.Count >= MaxSize)
{
_history.RemoveAt(0);
_position--;
}
_history.Add(op);
_position++;
PropertyChanged?.Invoke(this, _canRedoArgs);
PropertyChanged?.Invoke(this, _canUndoArgs);
}
public void Undo()
{
if (IsBatching)
{
throw new InvalidOperationException($"{nameof(Undo)} is not allowed during a batch.");
}
if (CanUndo)
{
var op = _history[_position];
_isApplyingOperation = true;
op.Undo();
_isApplyingOperation = false;
_position--;
}
}
public void Redo()
{
if (IsBatching)
{
throw new InvalidOperationException($"{nameof(Redo)} is not allowed during a batch.");
}
if (CanRedo)
{
_position++;
var op = _history[_position];
_isApplyingOperation = true;
op.Execute();
_isApplyingOperation = false;
}
}
public void Clear()
{
_history.Clear();
_batchHistory.Clear();
}
public void Pause(string? label = default)
{
if (_batchDepth > 0)
{
return;
}
_batchLabel = label;
IsBatching = true;
}
public void Resume()
{
if (_batchDepth > 0)
{
return;
}
if (_batchHistory.Count > 0)
{
AddToUndoStack(new BatchAction(_batchLabel, _batchHistory));
_batchHistory.Clear();
}
_batchLabel = null;
IsBatching = false;
}
private class BatchOperation : IDisposable
{
private readonly ActionsHistory _history;
private bool _disposed;
public BatchOperation(string? label, ActionsHistory history)
{
_history = history;
_history.Pause(label);
_history._batchDepth++;
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_history._batchDepth--;
_history.Resume();
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
namespace Nodify.UndoRedo
{
public class BatchAction : IAction
{
public BatchAction(string? label, IEnumerable<IAction> history)
{
History = history.Reverse().ToList();
Label = label;
}
public IReadOnlyList<IAction> History { get; }
public string? Label { get; }
public void Execute()
{
for (int i = History.Count - 1; i >= 0; i--)
{
History[i].Execute();
}
}
public void Undo()
{
for (int i = 0; i < History.Count; i++)
{
History[i].Undo();
}
}
public override string? ToString()
=> Label;
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Nodify.UndoRedo
{
public class DelegateAction : IAction
{
private readonly Action _execute;
private readonly Action _undo;
public string? Label { get; }
public DelegateAction(Action apply, Action unapply, string? label)
{
_execute = apply;
_undo = unapply;
Label = label;
}
public void Execute() => _execute();
public void Undo() => _undo();
public override string? ToString()
=> Label;
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Nodify.UndoRedo
{
public interface IPropertyAccessor
{
object? GetValue(object instance);
void SetValue(object instance, object? value);
bool CanRead { get; }
bool CanWrite { get; }
}
public sealed class PropertyAccessor<TInstanceType, TPropertyType> : IPropertyAccessor where TInstanceType : class
{
private readonly Func<TInstanceType, TPropertyType> _getter;
private readonly Action<TInstanceType, TPropertyType> _setter;
public bool CanRead { get; }
public bool CanWrite { get; }
public PropertyAccessor(Func<TInstanceType, TPropertyType> getter, Action<TInstanceType, TPropertyType> setter)
{
_getter = getter;
_setter = setter;
CanRead = getter != null;
CanWrite = setter != null;
}
public object? GetValue(object instance)
=> _getter((TInstanceType)instance);
public void SetValue(object instance, object? value)
=> _setter((TInstanceType)instance, (TPropertyType)value!);
}
public class PropertyCache
{
private static readonly Dictionary<string, IPropertyAccessor> _properties = new Dictionary<string, IPropertyAccessor>();
public static IPropertyAccessor Get(Type type, string name)
{
string propKey = $"{type.FullName}.{name}";
if (!_properties.TryGetValue(propKey, out var result))
{
var prop = type.GetProperty(name);
result = Create(type, prop!);
_properties.Add(propKey, result);
}
return result;
}
public static IPropertyAccessor Get<T>(string name)
=> Get(typeof(T), name);
private static IPropertyAccessor Create(Type type, PropertyInfo property)
{
Delegate? getterInvocation = default;
Delegate? setterInvocation = default;
if (property.CanRead)
{
MethodInfo getMethod = property.GetGetMethod(true)!;
Type getterType = typeof(Func<,>).MakeGenericType(type, property.PropertyType);
getterInvocation = Delegate.CreateDelegate(getterType, getMethod);
}
if (property.CanWrite)
{
MethodInfo setMethod = property.GetSetMethod(true)!;
Type setterType = typeof(Action<,>).MakeGenericType(type, property.PropertyType);
setterInvocation = Delegate.CreateDelegate(setterType, setMethod);
}
Type adapterType = typeof(PropertyAccessor<,>).MakeGenericType(type, property.PropertyType);
return (IPropertyAccessor)Activator.CreateInstance(adapterType, getterInvocation, setterInvocation)!;
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Expression = System.Linq.Expressions.Expression;
namespace Nodify.UndoRedo
{
[Flags]
public enum PropertyFlags
{
Disable = 0,
Enable = 1
}
public abstract class Undoable : ObservableObject
{
private readonly HashSet<string> _trackedProperties = new HashSet<string>();
public IActionsHistory History { get; }
private void RecordHistory<TPropType>(string propName, TPropType previous, TPropType current)
{
if (_trackedProperties.Contains(propName))
{
var prop = PropertyCache.Get(GetType(), propName);
History.Record(() => prop.SetValue(this, current), () => prop.SetValue(this, previous), propName);
}
}
protected void RecordProperty(string propName, PropertyFlags flags = PropertyFlags.Enable)
{
if (flags == PropertyFlags.Disable)
{
_trackedProperties.Remove(propName);
}
else if (flags.HasFlag(PropertyFlags.Enable))
{
_trackedProperties.Add(propName);
}
}
protected void RecordProperty<TType>(Expression<Func<TType, object?>> selector, PropertyFlags flags = PropertyFlags.Enable)
{
string name = GetPropertyName(selector);
RecordProperty(name, flags);
}
private static string GetPropertyName(Expression memberAccess)
=> memberAccess switch
{
LambdaExpression lambda => GetPropertyName(lambda.Body),
MemberExpression mbr => mbr.Member.Name,
UnaryExpression unary => GetPropertyName(unary.Operand),
_ => throw new Exception($"Member name could not be extracted from {memberAccess}.")
};
protected override bool SetProperty<TPropType>(ref TPropType field, TPropType value, [CallerMemberName] string propertyName = "")
{
TPropType prev = field;
if (base.SetProperty(ref field, value, propertyName))
{
RecordHistory(propertyName, prev, value);
return true;
}
return false;
}
public Undoable()
{
History = ActionsHistory.Global;
}
public Undoable(IActionsHistory history)
{
History = history;
}
}
}