Skip to content

Commit

Permalink
Add panning methods to NodifyEditor (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
miroiu authored Nov 26, 2024
1 parent 08b6739 commit 4521531
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 141 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 34 additions & 3 deletions Nodify/EditorStates/EditorPanningState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class EditorPanningState : EditorState
private Point _previousMousePosition;
private Point _currentMousePosition;

private bool Canceled { get; set; } = NodifyEditor.AllowPanningCancellation;

/// <summary>Constructs an instance of the <see cref="EditorPanningState"/> state.</summary>
/// <param name="editor">The owner of the state.</param>
public EditorPanningState(NodifyEditor editor) : base(editor)
Expand All @@ -18,22 +20,34 @@ public EditorPanningState(NodifyEditor editor) : base(editor)

/// <inheritdoc />
public override void Exit()
=> Editor.IsPanning = false;
{
if (Canceled)
{
Editor.CancelPanning();
}
else
{
Editor.EndPanning();
}
}

/// <inheritdoc />
public override void Enter(EditorState? from)
{
Canceled = false;

_initialMousePosition = Mouse.GetPosition(Editor);
_previousMousePosition = _initialMousePosition;
_currentMousePosition = _initialMousePosition;
Editor.IsPanning = true;

Editor.BeginPanning();
}

/// <inheritdoc />
public override void HandleMouseMove(MouseEventArgs e)
{
_currentMousePosition = e.GetPosition(Editor);
Editor.ViewportLocation -= (_currentMousePosition - _previousMousePosition) / Editor.ViewportZoom;
Editor.UpdatePanning((_currentMousePosition - _previousMousePosition) / Editor.ViewportZoom);
_previousMousePosition = _currentMousePosition;
}

Expand Down Expand Up @@ -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();
}
}
}
}
215 changes: 215 additions & 0 deletions Nodify/NodifyEditor.Panning.cs
Original file line number Diff line number Diff line change
@@ -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);
}

/// <summary>
/// Gets or sets whether panning should be disabled.
/// </summary>
public bool DisablePanning
{
get => (bool)GetValue(DisablePanningProperty);
set => SetValue(DisablePanningProperty, value);
}

/// <summary>
/// Gets or sets whether to disable the auto panning when selecting or dragging near the edge of the editor configured by <see cref="AutoPanEdgeDistance"/>.
/// </summary>
public bool DisableAutoPanning
{
get => (bool)GetValue(DisableAutoPanningProperty);
set => SetValue(DisableAutoPanningProperty, value);
}

/// <summary>
/// Gets or sets the speed used when auto-panning scaled by <see cref="AutoPanningTickRate"/>
/// </summary>
public double AutoPanSpeed
{
get => (double)GetValue(AutoPanSpeedProperty);
set => SetValue(AutoPanSpeedProperty, value);
}

/// <summary>
/// Gets or sets the maximum distance in pixels from the edge of the editor that will trigger auto-panning.
/// </summary>
public double AutoPanEdgeDistance
{
get => (double)GetValue(AutoPanEdgeDistanceProperty);
set => SetValue(AutoPanEdgeDistanceProperty, value);
}

/// <summary>
/// Gets a value that indicates whether a panning operation is in progress.
/// </summary>
public bool IsPanning
{
get => (bool)GetValue(IsPanningProperty);
private set => SetValue(IsPanningPropertyKey, value);
}

/// <summary>
/// Gets or sets whether panning cancellation is allowed (see <see cref="EditorGestures.NodifyEditorGestures.CancelAction"/>).
/// </summary>
public static bool AllowPanningCancellation { get; set; }

/// <summary>
/// Gets or sets the maximum number of pixels allowed to move the mouse before cancelling the mouse event.
/// Useful for <see cref="ContextMenu"/>s to appear if mouse only moved a bit or not at all.
/// </summary>
public static double HandleRightClickAfterPanningThreshold { get; set; } = 12d;

/// <summary>
/// Gets or sets how often the new <see cref="ViewportLocation"/> is calculated in milliseconds when <see cref="DisableAutoPanning"/> is false.
/// </summary>
public static double AutoPanningTickRate { get; set; } = 1;

private DispatcherTimer? _autoPanningTimer;

private Point _initialPanningLocation;

/// <summary>
/// Starts the panning operation from the specified location. Call <see cref="EndPanning"/> to end the panning operation.
/// </summary>
/// <remarks>This method has no effect if a panning operation is already in progress.</remarks>
/// <param name="location">The initial location where panning starts, in graph space coordinates.</param>
public void BeginPanning(Point location)
{
if (IsPanning)
{
return;
}

_initialPanningLocation = location;
ViewportLocation = location;
IsPanning = true;
}

/// <summary>
/// Starts the panning operation from the current <see cref="ViewportLocation" />.
/// </summary>
public void BeginPanning()
=> BeginPanning(ViewportLocation);

/// <summary>
/// Pans the viewport by the specified amount.
/// </summary>
/// <param name="amount">The amount to pan the viewport.</param>
/// <remarks>
/// This method adjusts the current <see cref="ViewportLocation"/> incrementally based on the provided amount.
/// It should only be called while a panning operation is active (see <see cref="BeginPanning(Point)"/>).
/// </remarks>
public void UpdatePanning(Vector amount)
{
Debug.Assert(IsPanning);
ViewportLocation -= amount;
}

/// <summary>
/// Cancels the current panning operation and reverts the viewport to its initial location if <see cref="AllowPanningCancellation"/> is true.
/// </summary>
/// <remarks>This method has no effect if there's no panning operation in progress.</remarks>
public void CancelPanning()
{
if (!AllowPanningCancellation || !IsPanning)
{
return;
}

ViewportLocation = _initialPanningLocation;
IsPanning = false;
}

/// <summary>
/// Ends the current panning operation, retaining the current <see cref="ViewportLocation"/>.
/// </summary>
/// <remarks>This method has no effect if there's no panning operation in progress.</remarks>
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));
}
}

/// <summary>
/// Called when the <see cref="DisableAutoPanning"/> changes.
/// </summary>
/// <param name="shouldDisable">Whether to enable or disable auto panning.</param>
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
}
}
Loading

0 comments on commit 4521531

Please sign in to comment.