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,28 @@
using System.Windows;
namespace Nodify
{
public static class BoxValue
{
public static readonly object Point = default(Point);
public static readonly object Size = default(Size);
public static readonly object Rect = default(Rect);
public static readonly object False = false;
public static readonly object True = true;
public static readonly object DoubleHalf = 0.5d;
public static readonly object Double0 = 0d;
public static readonly object Double1 = 1d;
public static readonly object Double2 = 2d;
public static readonly object Double5 = 5d;
public static readonly object Double45 = 45d;
public static readonly object Double1000 = 1000d;
public static readonly object Int0 = 0;
public static readonly object Int1 = 1;
public static readonly object UInt1 = 1u;
public static readonly object UInt0 = 0u;
public static readonly object Thickness2 = new Thickness(2);
public static readonly object ArrowSize = new Size(8, 8);
public static readonly object ConnectionOffset = new Size(14, 0);
}
}

View File

@@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace Nodify
{
internal static class DependencyObjectExtensions
{
public static T? GetParentOfType<T>(this DependencyObject current)
where T : DependencyObject
{
while ((current = VisualTreeHelper.GetParent(current)) != null)
{
if (current is T match)
{
return match;
}
}
return null;
}
public static DependencyObject? GetParent(this DependencyObject current, Func<DependencyObject, bool> condition)
{
while ((current = VisualTreeHelper.GetParent(current)) != null)
{
if (condition(current))
{
return current;
}
}
return null;
}
public static T? GetChildOfType<T>(this DependencyObject? depObj) where T : DependencyObject
{
if (depObj == null)
{
return default;
}
var count = VisualTreeHelper.GetChildrenCount(depObj);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
if (child is T result)
{
return result;
}
if (GetChildOfType<T>(child) is T r)
{
return r;
}
}
return default;
}
public static T? GetElementAtPosition<T>(this UIElement container, Point position)
where T : UIElement
{
T? result = default;
VisualTreeHelper.HitTest(container, depObj =>
{
if (depObj is UIElement elem && elem.IsHitTestVisible)
{
if (elem is T r)
{
result = r;
return HitTestFilterBehavior.Stop;
}
return HitTestFilterBehavior.Continue;
}
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}, hitResult =>
{
if (hitResult.VisualHit is T r)
{
result = r;
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
}, new PointHitTestParameters(position));
return result;
}
public static List<FrameworkElement> GetIntersectingElements(this UIElement container, Geometry geometry, IReadOnlyCollection<Type> supportedTypes)
{
var result = new List<FrameworkElement>();
VisualTreeHelper.HitTest(container, depObj =>
{
if (depObj is FrameworkElement elem && elem.IsHitTestVisible)
{
if (supportedTypes.Contains(elem.GetType()))
{
return HitTestFilterBehavior.ContinueSkipChildren;
}
return HitTestFilterBehavior.ContinueSkipSelf;
}
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}, hitResult =>
{
result.Add((FrameworkElement)hitResult.VisualHit);
return HitTestResultBehavior.Continue;
}, new GeometryHitTestParameters(geometry));
return result;
}
public static IEnumerable<T> GetIntersectingElements<T>(this UIElement container, Rect area, Func<T, Rect> getBounds)
where T : Visual
{
var stack = new Stack<DependencyObject>();
stack.Push(container);
while (stack.Count > 0)
{
DependencyObject current = stack.Pop();
int childrenCount = VisualTreeHelper.GetChildrenCount(current);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(current, i);
if (child is T tChild)
{
var bounds = getBounds(tChild);
if (bounds.IntersectsWith(area))
{
yield return tChild;
continue;
}
}
stack.Push(child);
}
}
}
#region Animation
public static void StartAnimation(this UIElement animatableElement, DependencyProperty dependencyProperty, Point toValue, double animationDurationSeconds, EventHandler? completedEvent = null)
{
var fromValue = (Point)animatableElement.GetValue(dependencyProperty);
PointAnimation animation = new PointAnimation
{
From = fromValue,
To = toValue,
Duration = TimeSpan.FromSeconds(animationDurationSeconds)
};
animation.Completed += delegate (object? sender, EventArgs e)
{
animatableElement.SetValue(dependencyProperty, animatableElement.GetValue(dependencyProperty));
CancelAnimation(animatableElement, dependencyProperty);
completedEvent?.Invoke(sender, e);
};
animation.Freeze();
animatableElement.BeginAnimation(dependencyProperty, animation);
}
public static void StartAnimation(this UIElement animatableElement, DependencyProperty dependencyProperty, double toValue, double animationDurationSeconds, EventHandler? completedEvent = null)
{
var fromValue = (double)animatableElement.GetValue(dependencyProperty);
DoubleAnimation animation = new DoubleAnimation
{
From = fromValue,
To = toValue,
Duration = TimeSpan.FromSeconds(animationDurationSeconds)
};
animation.Completed += delegate (object? sender, EventArgs e)
{
animatableElement.SetValue(dependencyProperty, animatableElement.GetValue(dependencyProperty));
CancelAnimation(animatableElement, dependencyProperty);
completedEvent?.Invoke(sender, e);
};
animation.Freeze();
animatableElement.BeginAnimation(dependencyProperty, animation);
}
public static void StartLoopingAnimation(this UIElement animatableElement, DependencyProperty dependencyProperty, double toValue, double durationInSeconds)
{
var fromValue = (double)animatableElement.GetValue(dependencyProperty);
var animation = new DoubleAnimation
{
From = fromValue,
To = toValue,
Duration = TimeSpan.FromSeconds(durationInSeconds)
};
animation.RepeatBehavior = RepeatBehavior.Forever;
animation.Freeze();
animatableElement.BeginAnimation(dependencyProperty, animation);
}
public static void CancelAnimation(this UIElement animatableElement, DependencyProperty dependencyProperty)
=> animatableElement.BeginAnimation(dependencyProperty, null);
#endregion
}
}

View File

@@ -0,0 +1,79 @@
using Nodify.Interactivity;
using System.Windows;
using System.Windows.Input;
namespace Nodify
{
internal static class EditorGesturesExtensions
{
public static SelectionType GetSelectionType(this EditorGestures.SelectionGestures gestures, InputEventArgs e)
{
if (gestures.Append.Matches(e.Source, e))
{
return SelectionType.Append;
}
if (gestures.Invert.Matches(e.Source, e))
{
return SelectionType.Invert;
}
if (gestures.Remove.Matches(e.Source, e))
{
return SelectionType.Remove;
}
return SelectionType.Replace;
}
public static bool TryGetFocusDirection(this EditorGestures.DirectionalNavigationGestures gestures, InputEventArgs e, out FocusNavigationDirection direction)
{
direction = default;
if (gestures.Left.Matches(e.Source, e))
{
direction = FocusNavigationDirection.Left;
return true;
}
if (gestures.Right.Matches(e.Source, e))
{
direction = FocusNavigationDirection.Right;
return true;
}
if (gestures.Up.Matches(e.Source, e))
{
direction = FocusNavigationDirection.Up;
return true;
}
if (gestures.Down.Matches(e.Source, e))
{
direction = FocusNavigationDirection.Down;
return true;
}
return false;
}
public static bool TryGetNavigationDirection(this EditorGestures.DirectionalNavigationGestures gestures, InputEventArgs e, out Vector direction)
{
double y = gestures.Up.Matches(e.Source, e) ? 1 : gestures.Down.Matches(e.Source, e) ? -1 : 0;
double x = gestures.Left.Matches(e.Source, e) ? -1 : gestures.Right.Matches(e.Source, e) ? 1 : 0;
direction = new Vector(x, y);
return x != 0 || y != 0;
}
public static bool IsOppositeOf(this FocusNavigationDirection direction, FocusNavigationDirection other)
{
return (direction == FocusNavigationDirection.Left && other == FocusNavigationDirection.Right)
|| (direction == FocusNavigationDirection.Right && other == FocusNavigationDirection.Left)
|| (direction == FocusNavigationDirection.Up && other == FocusNavigationDirection.Down)
|| (direction == FocusNavigationDirection.Down && other == FocusNavigationDirection.Up)
|| (direction == FocusNavigationDirection.Next && other == FocusNavigationDirection.Previous)
|| (direction == FocusNavigationDirection.Previous && other == FocusNavigationDirection.Next)
|| (direction == FocusNavigationDirection.First && other == FocusNavigationDirection.Last)
|| (direction == FocusNavigationDirection.Last && other == FocusNavigationDirection.First);
}
}
}

View File

@@ -0,0 +1,28 @@
namespace Nodify
{
internal static class MathExtensions
{
/// <summary> Wraps a value within a specified range.</summary>
public static double WrapToRange(this double value, double min, double max)
{
double range = max - min;
value = (value - min) % range;
return value < 0 ? value + range + min : value + min;
}
public static double Clamp(this double value, double min, double max)
{
if (value < min)
{
return min;
}
else if (value > max)
{
return max;
}
return value;
}
}
}

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace Nodify
{
/// <summary>
/// Helps with selecting <see cref="ItemContainer"/>s.
/// </summary>
internal sealed class SelectionHelper
{
private Point _startLocation;
private Point _endLocation;
private bool _isRealtime;
private IReadOnlyList<ItemContainer> _items = Array.Empty<ItemContainer>();
private IReadOnlyList<ItemContainer> _initialSelection = Array.Empty<ItemContainer>();
private Rect _selectedArea;
public SelectionType Type { get; private set; }
/// <summary>Attempts to start a new selection.</summary>
/// <param name="containers">The containers that can be part of the selection.</param>
/// <param name="location">The location inside the graph.</param>
/// <param name="selectionType">The type of selection.</param>
/// <remarks>Will not do anything if selection is in progress.</remarks>
public Rect Start(IEnumerable<ItemContainer> containers, Point location, SelectionType selectionType, bool realtime)
{
_items = containers.Where(x => x.IsSelectable).ToList();
_initialSelection = containers.Where(x => x.IsSelected).ToList();
Type = selectionType;
_isRealtime = realtime;
_startLocation = location;
_endLocation = location;
_selectedArea = new Rect();
return _selectedArea;
}
/// <summary>Update the end location for the selection.</summary>
/// <param name="endLocation">An absolute location.</param>
public Rect Update(Point endLocation)
{
_endLocation = endLocation;
double left = _endLocation.X < _startLocation.X ? _endLocation.X : _startLocation.X;
double top = _endLocation.Y < _startLocation.Y ? _endLocation.Y : _startLocation.Y;
double width = Math.Abs(_endLocation.X - _startLocation.X);
double height = Math.Abs(_endLocation.Y - _startLocation.Y);
_selectedArea = new Rect(left, top, width, height);
if (_isRealtime)
{
PreviewSelection(_selectedArea);
}
return _selectedArea;
}
/// <summary>Increase the selected area by the specified amount.</summary>
public Rect Update(Vector amount)
{
_endLocation += amount;
return Update(_endLocation);
}
/// <summary>Commits the current selection to the editor.</summary>
public Rect End()
{
PreviewSelection(_selectedArea);
_items = Array.Empty<ItemContainer>();
_initialSelection = Array.Empty<ItemContainer>();
return _selectedArea;
}
public void Cancel()
{
ClearPreviewingSelection();
_items = Array.Empty<ItemContainer>();
_initialSelection = Array.Empty<ItemContainer>();
}
#region Selection preview
private void PreviewSelection(Rect area)
{
switch (Type)
{
case SelectionType.Replace:
PreviewSelectArea(area);
break;
case SelectionType.Remove:
PreviewSelectContainers(_initialSelection);
PreviewUnselectArea(area);
break;
case SelectionType.Append:
PreviewUnselectAll();
PreviewSelectContainers(_initialSelection);
PreviewSelectArea(area, true);
break;
case SelectionType.Invert:
PreviewUnselectAll();
PreviewSelectContainers(_initialSelection);
PreviewInvertSelection(area);
break;
default:
throw new NotImplementedException(nameof(SelectionType));
}
}
private void PreviewUnselectAll()
{
for (int i = 0; i < _items.Count; i++)
{
_items[i].IsPreviewingSelection = false;
}
}
private void PreviewSelectArea(Rect area, bool append = false, bool fit = false)
{
if (!append)
{
PreviewUnselectAll();
}
if (area.X != 0 || area.Y != 0 || area.Width > 0 || area.Height > 0)
{
for (int i = 0; i < _items.Count; i++)
{
ItemContainer? container = _items[i];
if (container.IsSelectableInArea(area, fit))
{
container.IsPreviewingSelection = true;
}
}
}
}
private void PreviewUnselectArea(Rect area, bool fit = false)
{
for (int i = 0; i < _items.Count; i++)
{
ItemContainer? container = _items[i];
if (container.IsSelectableInArea(area, fit))
{
container.IsPreviewingSelection = false;
}
}
}
private static void PreviewSelectContainers(IReadOnlyList<ItemContainer> containers)
{
for (int i = 0; i < containers.Count; i++)
{
containers[i].IsPreviewingSelection = true;
}
}
private void PreviewInvertSelection(Rect area, bool fit = false)
{
for (int i = 0; i < _items.Count; i++)
{
ItemContainer? container = _items[i];
if (container.IsSelectableInArea(area, fit))
{
container.IsPreviewingSelection = !container.IsPreviewingSelection;
}
}
}
private void ClearPreviewingSelection()
{
for (int i = 0; i < _items.Count; i++)
{
_items[i].IsPreviewingSelection = null;
}
}
#endregion
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace Nodify
{
internal sealed class UnscaleTransformConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Transform result = (Transform)((TransformGroup)value).Children[0].Inverse;
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
internal sealed class ScaleDoubleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = (double)values[0] * (double)values[1];
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
internal sealed class ScalePointConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
Point result = (Point)((Vector)(Point)values[0] * (double)values[1]);
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Nodify
{
/// <summary>
/// A collection of weak references to objects of type <typeparamref name="T"/>.
/// Automatically removes dead references after a configurable number of additions.
/// </summary>
/// <typeparam name="T">The reference type stored in the collection.</typeparam>
internal class WeakReferenceCollection<T> : IEnumerable<T> where T : class
{
private readonly int _cleanupThreshold;
private readonly List<WeakReference<T>> _references;
private int _cleanupCounter = 0;
/// <summary>
/// Initializes a new instance of the <see cref="WeakReferenceCollection{T}"/> class.
/// </summary>
/// <param name="initialCapacity">Initial capacity of the internal list.</param>
/// <param name="cleanupThreshold">Number of additions after which cleanup is triggered.</param>
public WeakReferenceCollection(int initialCapacity, int cleanupThreshold = 32)
{
_cleanupThreshold = cleanupThreshold;
_references = new List<WeakReference<T>>(initialCapacity);
}
/// <summary>
/// Adds a new weak reference to the specified item to the collection.
/// Automatically triggers cleanup after a set number of additions.
/// </summary>
/// <param name="item">The item to add.</param>
public void Add(T item)
{
_references.Add(new WeakReference<T>(item));
_cleanupCounter++;
if (_cleanupCounter >= _cleanupThreshold)
{
_cleanupCounter = 0;
Cleanup();
}
}
/// <summary>
/// Removes all weak references from the collection.
/// </summary>
public void Clear()
=> _references.Clear();
/// <summary>
/// Returns a fast enumerator that iterates only over live references.
/// </summary>
public IEnumerator<T> GetEnumerator() => new Enumerator(_references);
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
/// <summary>
/// Removes dead references from the internal list.
/// </summary>
private void Cleanup()
{
int writeIndex = 0;
for (int readIndex = 0; readIndex < _references.Count; readIndex++)
{
var reference = _references[readIndex];
if (reference.TryGetTarget(out _))
{
if (writeIndex != readIndex)
_references[writeIndex] = reference;
writeIndex++;
}
}
if (writeIndex < _references.Count)
{
_references.RemoveRange(writeIndex, _references.Count - writeIndex);
}
}
/// <summary>
/// Struct-based enumerator for <see cref="WeakReferenceCollection{T}"/>.
/// Efficiently enumerates live references only.
/// </summary>
public struct Enumerator : IEnumerator<T>
{
private readonly List<WeakReference<T>> _references;
private int _index;
private T? _current;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="references">The backing list of weak references.</param>
public Enumerator(List<WeakReference<T>> references)
{
_references = references;
_index = -1;
_current = default;
}
public T Current => _current!;
object IEnumerator.Current => _current!;
public bool MoveNext()
{
while (++_index < _references.Count)
{
if (_references[_index].TryGetTarget(out var target))
{
_current = target;
return true;
}
}
_current = null;
return false;
}
public void Reset()
{
_index = -1;
_current = null;
}
public readonly void Dispose() { }
}
}
}