diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c11a5ab..5b1f19a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,12 +3,13 @@
#### **In development**
> - Breaking Changes:
+> - Made the setter of NodifyEditor.IsPanning private
> - Features:
+> - Added BeginPanning, UpdatePanning, EndPanning, CancelPanning and AllowPanningCancellation to NodifyEditor
> - Bugfixes:
#### **Version 6.6.0**
-> - Breaking Changes:
> - Features:
> - Added InputGroupStyle and OutputGroupStyle to Node
> - Added PanWithMouseWheel, PanHorizontalModifierKey and PanVerticalModifierKey to EditorGestures.Editor
diff --git a/Nodify/EditorStates/EditorPanningState.cs b/Nodify/EditorStates/EditorPanningState.cs
index 74b7be5b..d85aef9d 100644
--- a/Nodify/EditorStates/EditorPanningState.cs
+++ b/Nodify/EditorStates/EditorPanningState.cs
@@ -10,6 +10,8 @@ public class EditorPanningState : EditorState
private Point _previousMousePosition;
private Point _currentMousePosition;
+ private bool Canceled { get; set; } = NodifyEditor.AllowPanningCancellation;
+
/// Constructs an instance of the state.
/// The owner of the state.
public EditorPanningState(NodifyEditor editor) : base(editor)
@@ -18,22 +20,34 @@ public EditorPanningState(NodifyEditor editor) : base(editor)
///
public override void Exit()
- => Editor.IsPanning = false;
+ {
+ if (Canceled)
+ {
+ Editor.CancelPanning();
+ }
+ else
+ {
+ Editor.EndPanning();
+ }
+ }
///
public override void Enter(EditorState? from)
{
+ Canceled = false;
+
_initialMousePosition = Mouse.GetPosition(Editor);
_previousMousePosition = _initialMousePosition;
_currentMousePosition = _initialMousePosition;
- Editor.IsPanning = true;
+
+ Editor.BeginPanning();
}
///
public override void HandleMouseMove(MouseEventArgs e)
{
_currentMousePosition = e.GetPosition(Editor);
- Editor.ViewportLocation -= (_currentMousePosition - _previousMousePosition) / Editor.ViewportZoom;
+ Editor.UpdatePanning((_currentMousePosition - _previousMousePosition) / Editor.ViewportZoom);
_previousMousePosition = _currentMousePosition;
}
@@ -65,6 +79,23 @@ public override void HandleMouseUp(MouseButtonEventArgs e)
PushState(new EditorPanningState(Editor));
}
}
+ else if (NodifyEditor.AllowPanningCancellation && gestures.CancelAction.Matches(e.Source, e))
+ {
+ Canceled = true;
+ e.Handled = true; // prevents opening context menu
+
+ PopState();
+ }
+ }
+
+ public override void HandleKeyUp(KeyEventArgs e)
+ {
+ EditorGestures.NodifyEditorGestures gestures = EditorGestures.Mappings.Editor;
+ if (NodifyEditor.AllowPanningCancellation && gestures.CancelAction.Matches(e.Source, e))
+ {
+ Canceled = true;
+ PopState();
+ }
}
}
}
diff --git a/Nodify/NodifyEditor.Panning.cs b/Nodify/NodifyEditor.Panning.cs
new file mode 100644
index 00000000..bae965ad
--- /dev/null
+++ b/Nodify/NodifyEditor.Panning.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Threading;
+
+namespace Nodify
+{
+ public partial class NodifyEditor
+ {
+ public static readonly DependencyProperty AutoPanSpeedProperty = DependencyProperty.Register(nameof(AutoPanSpeed), typeof(double), typeof(NodifyEditor), new FrameworkPropertyMetadata(15d));
+ public static readonly DependencyProperty AutoPanEdgeDistanceProperty = DependencyProperty.Register(nameof(AutoPanEdgeDistance), typeof(double), typeof(NodifyEditor), new FrameworkPropertyMetadata(15d));
+ public static readonly DependencyProperty DisableAutoPanningProperty = DependencyProperty.Register(nameof(DisableAutoPanning), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False, OnDisableAutoPanningChanged));
+ public static readonly DependencyProperty DisablePanningProperty = DependencyProperty.Register(nameof(DisablePanning), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False, OnDisablePanningChanged));
+
+ protected static readonly DependencyPropertyKey IsPanningPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPanning), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False));
+ public static readonly DependencyProperty IsPanningProperty = IsPanningPropertyKey.DependencyProperty;
+
+ private static void OnDisableAutoPanningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ => ((NodifyEditor)d).OnDisableAutoPanningChanged((bool)e.NewValue);
+
+ private static void OnDisablePanningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var editor = (NodifyEditor)d;
+ editor.OnDisableAutoPanningChanged(editor.DisableAutoPanning || editor.DisablePanning);
+ }
+
+ ///
+ /// Gets or sets whether panning should be disabled.
+ ///
+ public bool DisablePanning
+ {
+ get => (bool)GetValue(DisablePanningProperty);
+ set => SetValue(DisablePanningProperty, value);
+ }
+
+ ///
+ /// Gets or sets whether to disable the auto panning when selecting or dragging near the edge of the editor configured by .
+ ///
+ public bool DisableAutoPanning
+ {
+ get => (bool)GetValue(DisableAutoPanningProperty);
+ set => SetValue(DisableAutoPanningProperty, value);
+ }
+
+ ///
+ /// Gets or sets the speed used when auto-panning scaled by
+ ///
+ public double AutoPanSpeed
+ {
+ get => (double)GetValue(AutoPanSpeedProperty);
+ set => SetValue(AutoPanSpeedProperty, value);
+ }
+
+ ///
+ /// Gets or sets the maximum distance in pixels from the edge of the editor that will trigger auto-panning.
+ ///
+ public double AutoPanEdgeDistance
+ {
+ get => (double)GetValue(AutoPanEdgeDistanceProperty);
+ set => SetValue(AutoPanEdgeDistanceProperty, value);
+ }
+
+ ///
+ /// Gets a value that indicates whether a panning operation is in progress.
+ ///
+ public bool IsPanning
+ {
+ get => (bool)GetValue(IsPanningProperty);
+ private set => SetValue(IsPanningPropertyKey, value);
+ }
+
+ ///
+ /// Gets or sets whether panning cancellation is allowed (see ).
+ ///
+ public static bool AllowPanningCancellation { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of pixels allowed to move the mouse before cancelling the mouse event.
+ /// Useful for s to appear if mouse only moved a bit or not at all.
+ ///
+ public static double HandleRightClickAfterPanningThreshold { get; set; } = 12d;
+
+ ///
+ /// Gets or sets how often the new is calculated in milliseconds when is false.
+ ///
+ public static double AutoPanningTickRate { get; set; } = 1;
+
+ private DispatcherTimer? _autoPanningTimer;
+
+ private Point _initialPanningLocation;
+
+ ///
+ /// Starts the panning operation from the specified location. Call to end the panning operation.
+ ///
+ /// This method has no effect if a panning operation is already in progress.
+ /// The initial location where panning starts, in graph space coordinates.
+ public void BeginPanning(Point location)
+ {
+ if (IsPanning)
+ {
+ return;
+ }
+
+ _initialPanningLocation = location;
+ ViewportLocation = location;
+ IsPanning = true;
+ }
+
+ ///
+ /// Starts the panning operation from the current .
+ ///
+ public void BeginPanning()
+ => BeginPanning(ViewportLocation);
+
+ ///
+ /// Pans the viewport by the specified amount.
+ ///
+ /// The amount to pan the viewport.
+ ///
+ /// This method adjusts the current incrementally based on the provided amount.
+ /// It should only be called while a panning operation is active (see ).
+ ///
+ public void UpdatePanning(Vector amount)
+ {
+ Debug.Assert(IsPanning);
+ ViewportLocation -= amount;
+ }
+
+ ///
+ /// Cancels the current panning operation and reverts the viewport to its initial location if is true.
+ ///
+ /// This method has no effect if there's no panning operation in progress.
+ public void CancelPanning()
+ {
+ if (!AllowPanningCancellation || !IsPanning)
+ {
+ return;
+ }
+
+ ViewportLocation = _initialPanningLocation;
+ IsPanning = false;
+ }
+
+ ///
+ /// Ends the current panning operation, retaining the current .
+ ///
+ /// This method has no effect if there's no panning operation in progress.
+ public void EndPanning()
+ {
+ IsPanning = false;
+ }
+
+ #region Auto panning
+
+ private void HandleAutoPanning(object? sender, EventArgs e)
+ {
+ if (!IsPanning && IsMouseCaptureWithin)
+ {
+ Point mousePosition = Mouse.GetPosition(this);
+ double edgeDistance = AutoPanEdgeDistance;
+ double autoPanSpeed = Math.Min(AutoPanSpeed, AutoPanSpeed * AutoPanningTickRate) / (ViewportZoom * 2);
+ double x = ViewportLocation.X;
+ double y = ViewportLocation.Y;
+
+ if (mousePosition.X <= edgeDistance)
+ {
+ x -= autoPanSpeed;
+ }
+ else if (mousePosition.X >= ActualWidth - edgeDistance)
+ {
+ x += autoPanSpeed;
+ }
+
+ if (mousePosition.Y <= edgeDistance)
+ {
+ y -= autoPanSpeed;
+ }
+ else if (mousePosition.Y >= ActualHeight - edgeDistance)
+ {
+ y += autoPanSpeed;
+ }
+
+ ViewportLocation = new Point(x, y);
+ MouseLocation = Mouse.GetPosition(ItemsHost);
+
+ State.HandleAutoPanning(new MouseEventArgs(Mouse.PrimaryDevice, 0));
+ }
+ }
+
+ ///
+ /// Called when the changes.
+ ///
+ /// Whether to enable or disable auto panning.
+ private void OnDisableAutoPanningChanged(bool shouldDisable)
+ {
+ if (shouldDisable)
+ {
+ _autoPanningTimer?.Stop();
+ }
+ else if (_autoPanningTimer == null)
+ {
+ _autoPanningTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(AutoPanningTickRate),
+ DispatcherPriority.Background, HandleAutoPanning, Dispatcher);
+ }
+ else
+ {
+ _autoPanningTimer.Interval = TimeSpan.FromMilliseconds(AutoPanningTickRate);
+ _autoPanningTimer.Start();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Nodify/NodifyEditor.cs b/Nodify/NodifyEditor.cs
index 97d936b4..d90182a9 100644
--- a/Nodify/NodifyEditor.cs
+++ b/Nodify/NodifyEditor.cs
@@ -10,7 +10,6 @@
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Shapes;
-using System.Windows.Threading;
namespace Nodify
{
@@ -249,9 +248,6 @@ private void ApplyRenderingOptimizations()
public static readonly DependencyProperty BringIntoViewSpeedProperty = DependencyProperty.Register(nameof(BringIntoViewSpeed), typeof(double), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.Double1000));
public static readonly DependencyProperty BringIntoViewMaxDurationProperty = DependencyProperty.Register(nameof(BringIntoViewMaxDuration), typeof(double), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.Double1));
public static readonly DependencyProperty DisplayConnectionsOnTopProperty = DependencyProperty.Register(nameof(DisplayConnectionsOnTop), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False));
- public static readonly DependencyProperty DisableAutoPanningProperty = DependencyProperty.Register(nameof(DisableAutoPanning), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False, OnDisableAutoPanningChanged));
- public static readonly DependencyProperty AutoPanSpeedProperty = DependencyProperty.Register(nameof(AutoPanSpeed), typeof(double), typeof(NodifyEditor), new FrameworkPropertyMetadata(15d));
- public static readonly DependencyProperty AutoPanEdgeDistanceProperty = DependencyProperty.Register(nameof(AutoPanEdgeDistance), typeof(double), typeof(NodifyEditor), new FrameworkPropertyMetadata(15d));
public static readonly DependencyProperty ConnectionTemplateProperty = DependencyProperty.Register(nameof(ConnectionTemplate), typeof(DataTemplate), typeof(NodifyEditor));
public static readonly DependencyProperty DecoratorTemplateProperty = DependencyProperty.Register(nameof(DecoratorTemplate), typeof(DataTemplate), typeof(NodifyEditor));
public static readonly DependencyProperty PendingConnectionTemplateProperty = DependencyProperty.Register(nameof(PendingConnectionTemplate), typeof(DataTemplate), typeof(NodifyEditor));
@@ -259,9 +255,6 @@ private void ApplyRenderingOptimizations()
public static readonly DependencyProperty CuttingLineStyleProperty = DependencyProperty.Register(nameof(CuttingLineStyle), typeof(Style), typeof(NodifyEditor));
public static readonly DependencyProperty DecoratorContainerStyleProperty = DependencyProperty.Register(nameof(DecoratorContainerStyle), typeof(Style), typeof(NodifyEditor));
- private static void OnDisableAutoPanningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- => ((NodifyEditor)d).OnDisableAutoPanningChanged((bool)e.NewValue);
-
///
/// Gets or sets the maximum animation duration in seconds for bringing a location into view.
///
@@ -290,33 +283,6 @@ public bool DisplayConnectionsOnTop
set => SetValue(DisplayConnectionsOnTopProperty, value);
}
- ///
- /// Gets or sets whether to disable the auto panning when selecting or dragging near the edge of the editor configured by .
- ///
- public bool DisableAutoPanning
- {
- get => (bool)GetValue(DisableAutoPanningProperty);
- set => SetValue(DisableAutoPanningProperty, value);
- }
-
- ///
- /// Gets or sets the speed used when auto-panning scaled by
- ///
- public double AutoPanSpeed
- {
- get => (double)GetValue(AutoPanSpeedProperty);
- set => SetValue(AutoPanSpeedProperty, value);
- }
-
- ///
- /// Gets or sets the maximum distance in pixels from the edge of the editor that will trigger auto-panning.
- ///
- public double AutoPanEdgeDistance
- {
- get => (double)GetValue(AutoPanEdgeDistanceProperty);
- set => SetValue(AutoPanEdgeDistanceProperty, value);
- }
-
///
/// Gets or sets the to use when generating a new .
///
@@ -390,9 +356,6 @@ public Style DecoratorContainerStyle
protected static readonly DependencyPropertyKey IsCuttingPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsCutting), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False, OnIsCuttingChanged));
public static readonly DependencyProperty IsCuttingProperty = IsCuttingPropertyKey.DependencyProperty;
- public static readonly DependencyPropertyKey IsPanningPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPanning), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False));
- public static readonly DependencyProperty IsPanningProperty = IsPanningPropertyKey.DependencyProperty;
-
protected static readonly DependencyPropertyKey MouseLocationPropertyKey = DependencyProperty.RegisterReadOnly(nameof(MouseLocation), typeof(Point), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.Point));
public static readonly DependencyProperty MouseLocationProperty = MouseLocationPropertyKey.DependencyProperty;
@@ -483,15 +446,6 @@ public bool IsCutting
private set => SetValue(IsCuttingPropertyKey, value);
}
- ///
- /// Gets a value that indicates whether a panning operation is in progress.
- ///
- public bool IsPanning
- {
- get => (bool)GetValue(IsPanningProperty);
- protected internal set => SetValue(IsPanningPropertyKey, value);
- }
-
///
/// Gets the current mouse location in graph space coordinates (relative to the ).
///
@@ -512,7 +466,6 @@ public Point MouseLocation
public static readonly DependencyProperty PendingConnectionProperty = DependencyProperty.Register(nameof(PendingConnection), typeof(object), typeof(NodifyEditor));
public static readonly DependencyProperty GridCellSizeProperty = DependencyProperty.Register(nameof(GridCellSize), typeof(uint), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.UInt1, OnGridCellSizeChanged, OnCoerceGridCellSize));
public static readonly DependencyProperty DisableZoomingProperty = DependencyProperty.Register(nameof(DisableZooming), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False));
- public static readonly DependencyProperty DisablePanningProperty = DependencyProperty.Register(nameof(DisablePanning), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False, OnDisablePanningChanged));
public static readonly DependencyProperty EnableRealtimeSelectionProperty = DependencyProperty.Register(nameof(EnableRealtimeSelection), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False));
public static readonly DependencyProperty DecoratorsProperty = DependencyProperty.Register(nameof(Decorators), typeof(IEnumerable), typeof(NodifyEditor));
public static readonly DependencyProperty CanSelectMultipleConnectionsProperty = DependencyProperty.Register(nameof(CanSelectMultipleConnections), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.True));
@@ -532,12 +485,6 @@ private static object OnCoerceGridCellSize(DependencyObject d, object value)
private static void OnGridCellSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
- private static void OnDisablePanningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var editor = (NodifyEditor)d;
- editor.OnDisableAutoPanningChanged(editor.DisableAutoPanning || editor.DisablePanning);
- }
-
///
/// Gets or sets the items that will be rendered in the decorators layer via s.
///
@@ -610,15 +557,6 @@ public bool DisableZooming
set => SetValue(DisableZoomingProperty, value);
}
- ///
- /// Gets or sets whether panning should be disabled.
- ///
- public bool DisablePanning
- {
- get => (bool)GetValue(DisablePanningProperty);
- set => SetValue(DisablePanningProperty, value);
- }
-
///
/// Enables selecting and deselecting items while the changes.
/// Disable for maximum performance when hundreds of items are generated.
@@ -762,22 +700,11 @@ public ICommand? CuttingCompletedCommand
#region Fields
- ///
- /// Gets or sets the maximum number of pixels allowed to move the mouse before cancelling the mouse event.
- /// Useful for s to appear if mouse only moved a bit or not at all.
- ///
- public static double HandleRightClickAfterPanningThreshold { get; set; } = 12d;
-
///
/// Correct 's position after moving if starting position is not snapped to grid.
///
public static bool EnableSnappingCorrection { get; set; } = true;
- ///
- /// Gets or sets how often the new is calculated in milliseconds when is false.
- ///
- public static double AutoPanningTickRate { get; set; } = 1;
-
///
/// Gets or sets if s should enable optimizations based on and .
///
@@ -833,7 +760,6 @@ public ICommand? CuttingCompletedCommand
protected internal UIElement ConnectionsHost { get; private set; } = default!;
private IDraggingStrategy? _draggingStrategy;
- private DispatcherTimer? _autoPanningTimer;
///
/// Gets a list of s that are selected.
@@ -989,7 +915,7 @@ public void BringIntoView(Point point, bool animated = true, Action? onFinish =
if (animated && newLocation != ViewportLocation)
{
- IsPanning = true;
+ BeginPanning();
DisablePanning = true;
DisableZooming = true;
@@ -999,7 +925,7 @@ public void BringIntoView(Point point, bool animated = true, Action? onFinish =
this.StartAnimation(ViewportLocationProperty, newLocation, duration, (s, e) =>
{
- IsPanning = false;
+ EndPanning();
DisablePanning = false;
DisableZooming = false;
@@ -1044,67 +970,6 @@ public void FitToScreen(Rect? area = null)
#endregion
- #region Auto panning
-
- private void HandleAutoPanning(object? sender, EventArgs e)
- {
- if (!IsPanning && IsMouseCaptureWithin)
- {
- Point mousePosition = Mouse.GetPosition(this);
- double edgeDistance = AutoPanEdgeDistance;
- double autoPanSpeed = Math.Min(AutoPanSpeed, AutoPanSpeed * AutoPanningTickRate) / (ViewportZoom * 2);
- double x = ViewportLocation.X;
- double y = ViewportLocation.Y;
-
- if (mousePosition.X <= edgeDistance)
- {
- x -= autoPanSpeed;
- }
- else if (mousePosition.X >= ActualWidth - edgeDistance)
- {
- x += autoPanSpeed;
- }
-
- if (mousePosition.Y <= edgeDistance)
- {
- y -= autoPanSpeed;
- }
- else if (mousePosition.Y >= ActualHeight - edgeDistance)
- {
- y += autoPanSpeed;
- }
-
- ViewportLocation = new Point(x, y);
- MouseLocation = Mouse.GetPosition(ItemsHost);
-
- State.HandleAutoPanning(new MouseEventArgs(Mouse.PrimaryDevice, 0));
- }
- }
-
- ///
- /// Called when the changes.
- ///
- /// Whether to enable or disable auto panning.
- protected virtual void OnDisableAutoPanningChanged(bool shouldDisable)
- {
- if (shouldDisable)
- {
- _autoPanningTimer?.Stop();
- }
- else if (_autoPanningTimer == null)
- {
- _autoPanningTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(AutoPanningTickRate),
- DispatcherPriority.Background, HandleAutoPanning, Dispatcher);
- }
- else
- {
- _autoPanningTimer.Interval = TimeSpan.FromMilliseconds(AutoPanningTickRate);
- _autoPanningTimer.Start();
- }
- }
-
- #endregion
-
#region Connector handling
private void OnConnectorDisconnected(object sender, ConnectorEventArgs e)