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,39 @@
using System.Windows.Input;
namespace Nodify.Interactivity
{
public static partial class EditorState
{
/// <summary>
/// Represents the cutting state in the <see cref="NodifyEditor"/>, allowing users to cut connections between elements using a drag gesture.
/// </summary>
public class Cutting : DragState<NodifyEditor>
{
protected override bool HasContextMenu => Element.HasContextMenu;
protected override bool CanBegin => !Element.IsSelecting && !Element.IsPanning && !Element.IsPushingItems;
protected override bool CanCancel => NodifyEditor.AllowCuttingCancellation;
protected override bool IsToggle => EnableToggledCuttingMode;
/// <summary>
/// Initializes a new instance of the <see cref="Cutting"/> class.
/// </summary>
/// <param name="editor">The <see cref="NodifyEditor"/> associated with this state.</param>
public Cutting(NodifyEditor editor)
: base(editor, EditorGestures.Mappings.Editor.Cutting, EditorGestures.Mappings.Editor.CancelAction)
{
}
protected override void OnBegin(InputEventArgs e)
=> Element.BeginCutting();
protected override void OnMouseMove(MouseEventArgs e)
=> Element.UpdateCuttingLine(Element.MouseLocation);
protected override void OnEnd(InputEventArgs e)
=> Element.EndCutting();
protected override void OnCancel(InputEventArgs e)
=> Element.CancelCutting();
}
}
}

View File

@@ -0,0 +1,71 @@
namespace Nodify.Interactivity
{
public static partial class EditorState
{
/// <summary>
/// Gets or sets a value indicating whether panning is allowed while selecting items in the editor.
/// </summary>
public static bool AllowPanningWhileSelecting { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether panning is allowed while cutting connections in the editor.
/// </summary>
public static bool AllowPanningWhileCutting { get; set; }
/// <summary>
/// Gets or sets a value indicating whether panning is allowed while pushing items in the editor.
/// </summary>
public static bool AllowPanningWhilePushingItems { get; set; }
/// <summary>
/// Gets or sets a value indicating whether zooming is allowed while selecting items in the editor.
/// </summary>
public static bool AllowZoomingWhileSelecting { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether zooming is allowed while cutting connections in the editor.
/// </summary>
public static bool AllowZoomingWhileCutting { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether zooming is allowed while pushing items in the editor.
/// </summary>
public static bool AllowZoomingWhilePushingItems { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether zooming is allowed while panning the editor viewport.
/// </summary>
public static bool AllowZoomingWhilePanning { get; set; } = true;
/// <summary>
/// Determines whether toggled selecting mode is enabled, allowing the user to start and end the interaction in two steps with the same input gesture.
/// </summary>
public static bool EnableToggledSelectingMode { get; set; }
/// <summary>
/// Determines whether toggled panning mode is enabled, allowing the user to start and end the interaction in two steps with the same input gesture.
/// </summary>
public static bool EnableToggledPanningMode { get; set; }
/// <summary>
/// Determines whether toggled cutting mode is enabled, allowing the user to start and end the interaction in two steps with the same input gesture.
/// </summary>
public static bool EnableToggledCuttingMode { get; set; }
/// <summary>
/// Determines whether toggled pushing items mode is enabled, allowing the user to start and end the interaction in two steps with the same input gesture.
/// </summary>
public static bool EnableToggledPushingItemsMode { get; set; }
internal static void RegisterDefaultHandlers()
{
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(elem => new Panning(elem));
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(elem => new PanningWithMouseWheel(elem));
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(elem => new Selecting(elem));
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(elem => new Zooming(elem));
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(elem => new PushingItems(elem));
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(elem => new Cutting(elem));
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(elem => new KeyboardNavigation(elem));
}
}
}

View File

@@ -0,0 +1,147 @@
using System;
using System.Windows;
using System.Windows.Input;
namespace Nodify.Interactivity
{
public static partial class EditorState
{
/// <summary>
/// Represents the keyboard navigation state of the <see cref="NodifyEditor"/>, allowing users to navigate and interact with nodes and connections using the keyboard.
/// </summary>
public class KeyboardNavigation : InputElementState<NodifyEditor>
{
/// <summary>
/// Initializes a new instance of the <see cref="KeyboardNavigation"/> class.
/// </summary>
/// <param name="element">The <see cref="NodifyEditor"/> associated with this state.</param>
public KeyboardNavigation(NodifyEditor element) : base(element)
{
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (!Element.IsKeyboardFocusWithin || !(e.OriginalSource is DependencyObject originalSource))
{
return;
}
double navigationStepSize = GetNavigationStepSize();
var gestures = EditorGestures.Mappings.Editor.Keyboard;
if (e.Key == Key.Tab && Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
{
var parentContainer = originalSource.GetParent(Element.IsNavigationTrigger) as UIElement;
e.Handled = parentContainer?.Focus() is true;
}
else if (Element.IsNavigationTrigger(originalSource))
{
if (gestures.Pan.TryGetNavigationDirection(e, out var panDirection))
{
var panning = new Vector(-panDirection.X * navigationStepSize, panDirection.Y * navigationStepSize);
Element.UpdatePanning(panning);
e.Handled = true;
}
else if (CanDragSelection() && gestures.DragSelection.TryGetNavigationDirection(e, out var dragDirection))
{
var dragging = new Vector(dragDirection.X * navigationStepSize, -dragDirection.Y * navigationStepSize);
Element.BeginDragging();
Element.UpdateDragging(dragging);
Element.EndDragging();
if (NodifyEditor.PanViewportOnKeyboardDrag)
{
var panning = new Vector(-dragDirection.X * navigationStepSize, dragDirection.Y * navigationStepSize);
Element.UpdatePanning(panning);
}
e.Handled = true;
}
else if (gestures.NavigateSelection.TryGetFocusDirection(e, out var direction))
{
Element.MoveFocus(direction);
e.Handled = true;
}
}
}
protected override void OnKeyUp(KeyEventArgs e)
{
var gestures = EditorGestures.Mappings.Editor.Keyboard;
if (gestures.ToggleSelected.Matches(e.Source, e))
{
if (Keyboard.FocusedElement is ItemContainer itemContainer)
{
itemContainer.Select(SelectionType.Invert);
if (NodifyEditor.AutoPanOnNodeFocus)
{
Element.BringIntoView(itemContainer.Bounds, NodifyEditor.BringIntoViewEdgeOffset);
}
}
else if (Keyboard.FocusedElement is ConnectionContainer connectionContainer)
{
connectionContainer.Select(SelectionType.Invert);
if (NodifyEditor.AutoPanOnNodeFocus)
{
Element.BringIntoView(connectionContainer.Bounds, NodifyEditor.BringIntoViewEdgeOffset);
}
}
e.Handled = true;
}
else if (gestures.DeselectAll.Matches(e.Source, e))
{
if (Element.SelectedContainersCount > 0 && Element.ActiveNavigationLayer?.Id == KeyboardNavigationLayerId.Nodes)
{
Element.UnselectAll();
e.Handled = true;
}
// TODO: How to get the selected connections count without a hard reference to the connections multi selector?
// This currently assumes we have a binding to the SelectedConnectionsProperty dependency property
else if (Element.SelectedConnections?.Count > 0 && Element.ActiveNavigationLayer?.Id == KeyboardNavigationLayerId.Connections)
{
Element.UnselectAllConnections();
e.Handled = true;
}
}
else if (gestures.NextNavigationLayer.Matches(e.Source, e))
{
Element.ActivateNextNavigationLayer();
e.Handled = true;
}
else if (gestures.PrevNavigationLayer.Matches(e.Source, e))
{
Element.ActivatePreviousNavigationLayer();
e.Handled = true;
}
else if (Keyboard.FocusedElement is ItemContainer { IsSelected: true } container
&& EditorGestures.Mappings.GroupingNode.ToggleContentSelection.Matches(e.Source, e))
{
var groupingNode = container.GetChildOfType<GroupingNode>();
if (groupingNode != null)
{
groupingNode.ToggleContentSelection();
e.Handled = true;
}
}
}
private bool CanDragSelection()
{
return Element.ActiveNavigationLayer?.Id == KeyboardNavigationLayerId.Nodes && Element.SelectedContainersCount > 0;
}
private double GetNavigationStepSize()
{
double cellSize = Element.GridCellSize;
if (cellSize >= NodifyEditor.MinimumNavigationStepSize)
return cellSize;
int factor = (int)Math.Ceiling(NodifyEditor.MinimumNavigationStepSize / cellSize);
return factor * cellSize / Element.ViewportZoom;
}
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Windows;
using System.Windows.Input;
namespace Nodify.Interactivity
{
public static partial class EditorState
{
/// <summary>
/// Represents the panning state of the <see cref="NodifyEditor"/>, allowing the user to pan the viewport by clicking and dragging.
/// </summary>
public class Panning : DragState<NodifyEditor>
{
protected override bool HasContextMenu => Element.HasContextMenu;
protected override bool CanBegin => IsPanningAllowed();
protected override bool CanCancel => NodifyEditor.AllowPanningCancellation;
protected override bool IsToggle => EnableToggledPanningMode;
private Point _prevPosition;
/// <summary>
/// Initializes a new instance of the <see cref="Panning"/> class.
/// </summary>
/// <param name="editor">The <see cref="NodifyEditor"/> associated with this state.</param>
public Panning(NodifyEditor editor)
: base(editor, EditorGestures.Mappings.Editor.Pan, EditorGestures.Mappings.Editor.CancelAction)
{
}
protected override void OnBegin(InputEventArgs e)
{
_prevPosition = Mouse.GetPosition(Element);
Element.BeginPanning();
}
protected override void OnMouseMove(MouseEventArgs e)
{
var currentMousePosition = e.GetPosition(Element);
Element.UpdatePanning((currentMousePosition - _prevPosition) / Element.ViewportZoom);
_prevPosition = currentMousePosition;
}
protected override void OnEnd(InputEventArgs e)
=> Element.EndPanning();
protected override void OnCancel(InputEventArgs e)
=> Element.CancelPanning();
private bool IsPanningAllowed()
{
return !Element.DisablePanning
&& (AllowPanningWhileSelecting || !Element.IsSelecting)
&& (AllowPanningWhileCutting || !Element.IsCutting)
&& (AllowPanningWhilePushingItems || !Element.IsPushingItems);
}
}
/// <summary>
/// Represents the panning state of the <see cref="NodifyEditor"/> using the mouse wheel.
/// Allows the user to pan horizontally or vertically by holding modifier keys while scrolling the mouse wheel.
/// </summary>
public class PanningWithMouseWheel : InputElementState<NodifyEditor>
{
/// <summary>
/// Initializes a new instance of the <see cref="PanningWithMouseWheel"/> class.
/// </summary>
/// <param name="editor">The <see cref="NodifyEditor"/> associated with this state.</param>
public PanningWithMouseWheel(NodifyEditor editor) : base(editor)
{
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
EditorGestures.NodifyEditorGestures gestures = EditorGestures.Mappings.Editor;
if (gestures.PanWithMouseWheel && Keyboard.Modifiers == gestures.PanHorizontalModifierKey)
{
double offset = Math.Sign(e.Delta) * Mouse.MouseWheelDeltaForOneLine / 2 / Element.ViewportZoom;
Element.UpdatePanning(new Vector(offset, 0d));
e.Handled = true;
}
else if (gestures.PanWithMouseWheel && Keyboard.Modifiers == gestures.PanVerticalModifierKey)
{
double offset = Math.Sign(e.Delta) * Mouse.MouseWheelDeltaForOneLine / 2 / Element.ViewportZoom;
Element.UpdatePanning(new Vector(0d, offset));
e.Handled = true;
}
}
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Nodify.Interactivity
{
public static partial class EditorState
{
/// <summary>
/// Represents the state of the <see cref="NodifyEditor"/> during a "push items" operation, allowing users to move items within the editor by dragging.
/// </summary>
public class PushingItems : DragState<NodifyEditor>
{
protected override bool HasContextMenu => Element.HasContextMenu;
protected override bool CanBegin => !Element.IsSelecting && !Element.IsPanning && !Element.IsCutting;
protected override bool CanCancel => NodifyEditor.AllowPushItemsCancellation;
protected override bool IsToggle => EnableToggledPushingItemsMode;
private Point _prevPosition;
/// <summary>
/// Initializes a new instance of the <see cref="PushingItems"/> class.
/// </summary>
/// <param name="editor">The <see cref="NodifyEditor"/> associated with this state.</param>
public PushingItems(NodifyEditor editor)
: base(editor, EditorGestures.Mappings.Editor.PushItems, EditorGestures.Mappings.Editor.CancelAction)
{
}
protected override void OnBegin(InputEventArgs e)
=> _prevPosition = Element.MouseLocation;
protected override void OnMouseMove(MouseEventArgs e)
{
if (Element.IsPushingItems)
{
Element.UpdatePushedArea(Element.MouseLocation - _prevPosition);
_prevPosition = Element.MouseLocation;
}
else
{
if (Math.Abs(Element.MouseLocation.X - _prevPosition.X) >= NodifyEditor.MouseActionSuppressionThreshold)
{
Element.BeginPushingItems(_prevPosition, Orientation.Horizontal);
}
else if (Math.Abs(Element.MouseLocation.Y - _prevPosition.Y) >= NodifyEditor.MouseActionSuppressionThreshold)
{
Element.BeginPushingItems(_prevPosition, Orientation.Vertical);
}
}
}
protected override void OnEnd(InputEventArgs e)
=> Element.EndPushingItems();
protected override void OnCancel(InputEventArgs e)
=> Element.CancelPushingItems();
}
}
}

View File

@@ -0,0 +1,43 @@
using System.Windows.Input;
namespace Nodify.Interactivity
{
public static partial class EditorState
{
/// <summary>
/// Represents the selecting state of the <see cref="NodifyEditor"/>.
/// This state is responsible for handling item selection within the editor.
/// </summary>
public class Selecting : DragState<NodifyEditor>
{
protected override bool HasContextMenu => Element.HasContextMenu;
protected override bool CanBegin => Element.CanSelectMultipleItems && !Element.IsPanning && !Element.IsCutting && !Element.IsPushingItems;
protected override bool CanCancel => NodifyEditor.AllowSelectionCancellation;
protected override bool IsToggle => EnableToggledSelectingMode;
/// <summary>
/// Initializes a new instance of the <see cref="Selecting"/> class.
/// </summary>
/// <param name="editor">The <see cref="NodifyEditor"/> associated with this state.</param>
public Selecting(NodifyEditor editor)
: base(editor, EditorGestures.Mappings.Editor.Selection.Select, EditorGestures.Mappings.Editor.Selection.Cancel)
{
}
protected override void OnBegin(InputEventArgs e)
{
var selectionType = EditorGestures.Mappings.Editor.Selection.GetSelectionType(e);
Element.BeginSelecting(selectionType);
}
protected override void OnMouseMove(MouseEventArgs e)
=> Element.UpdateSelection(Element.MouseLocation);
protected override void OnEnd(InputEventArgs e)
=> Element.EndSelecting();
protected override void OnCancel(InputEventArgs e)
=> Element.CancelSelecting();
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Windows.Input;
namespace Nodify.Interactivity
{
public static partial class EditorState
{
/// <summary>
/// Represents the zooming state of the <see cref="NodifyEditor"/>.
/// This state handles zooming operations using the mouse wheel with an optional modifier key.
/// </summary>
public class Zooming : InputElementState<NodifyEditor>
{
/// <summary>
/// Initializes a new instance of the <see cref="Zooming"/> class.
/// </summary>
/// <param name="editor">The <see cref="NodifyEditor"/> associated with this state.</param>
public Zooming(NodifyEditor editor) : base(editor)
{
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
EditorGestures.NodifyEditorGestures gestures = EditorGestures.Mappings.Editor;
if (gestures.ZoomModifierKey == Keyboard.Modifiers && IsZoomingAllowed())
{
double zoom = Math.Pow(2.0, e.Delta / 3.0 / Mouse.MouseWheelDeltaForOneLine);
Element.ZoomAtPosition(zoom, Element.MouseLocation);
e.Handled = true;
}
}
private bool IsZoomingAllowed()
{
return !Element.DisableZooming
&& (AllowZoomingWhileSelecting || !Element.IsSelecting)
&& (AllowZoomingWhileCutting || !Element.IsCutting)
&& (AllowZoomingWhilePushingItems || !Element.IsPushingItems)
&& (AllowZoomingWhilePanning || !Element.IsPanning);
}
}
}
}