Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dragging methods to ItemContainer #160

Merged
merged 4 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@

> - Breaking Changes:
> - Made the setter of NodifyEditor.IsPanning private
> - Made SelectionHelper internal
> - Renamed StartCutting to BeginCutting in NodifyEditor
> - Renamed PushItems to UpdatePushedArea and StartPushingItems to BeginPushingItems in NodifyEditor
> - Renamed UnselectAllConnection to UnselectAllConnections in NodifyEditor
> - Removed DragStarted, DragDelta and DragCompleted routed events from ItemContainer
> - Features:
> - Added BeginPanning, UpdatePanning, EndPanning, CancelPanning and AllowPanningCancellation to NodifyEditor
> - Added UpdateCuttingLine to NodifyEditor
> - Added BeginSelecting, UpdateSelection, EndSelecting, CancelSelecting and AllowSelectionCancellation to NodifyEditor
> - Added IsDragging, BeginDragging, UpdateDragging, EndDragging and CancelDragging to NodifyEditor
> - Added Select, BeginDragging, UpdateDragging, EndDragging and CancelDragging to ItemContainer
> - Bugfixes:
> - Fixed ItemContainer being selected by releasing the mouse button on it without having the mouse captured

#### **Version 6.6.0**

Expand Down
1 change: 0 additions & 1 deletion Examples/Nodify.Calculator/EditorView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public EditorView()
InitializeComponent();

EventManager.RegisterClassHandler(typeof(NodifyEditor), MouseLeftButtonDownEvent, new MouseButtonEventHandler(CloseOperationsMenu));
EventManager.RegisterClassHandler(typeof(ItemContainer), ItemContainer.DragStartedEvent, new RoutedEventHandler(CloseOperationsMenu));
EventManager.RegisterClassHandler(typeof(NodifyEditor), MouseRightButtonUpEvent, new MouseButtonEventHandler(OpenOperationsMenu));
}

Expand Down
44 changes: 26 additions & 18 deletions Nodify/Connections/ConnectionContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,28 +102,36 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
EditorGestures.ConnectionGestures gestures = EditorGestures.Mappings.Connection;
if (gestures.Selection.Select.Matches(e.Source, e))
{
if (gestures.Selection.Append.Matches(e.Source, e))
var selectionType = gestures.Selection.GetSelectionType(e);
bool allowContextMenu = e.ChangedButton == MouseButton.Right && IsSelected;
if (!(selectionType == SelectionType.Replace && allowContextMenu))
{
IsSelected = true;
Select(selectionType);
}
else if (gestures.Selection.Invert.Matches(e.Source, e))
{
IsSelected = !IsSelected;
}
else if (gestures.Selection.Remove.Matches(e.Source, e))
{
IsSelected = false;
}
else
{
// Allow context menu on selection
if (!(e.ChangedButton == MouseButton.Right && e.RightButton == MouseButtonState.Released) || !IsSelected)
{
Selector.UnselectAll();
}
}
}

/// <summary>
/// Modifies the selection state of the current item based on the specified selection type.
/// </summary>
/// <param name="type">The type of selection to perform.</param>
private void Select(SelectionType type)
{
switch (type)
{
case SelectionType.Append:
IsSelected = true;
}
break;
case SelectionType.Remove:
IsSelected = false;
break;
case SelectionType.Invert:
IsSelected = !IsSelected;
break;
case SelectionType.Replace:
Selector.UnselectAll();
IsSelected = true;
break;
}
}
}
Expand Down
92 changes: 41 additions & 51 deletions Nodify/EditorStates/ContainerDefaultState.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System.Windows.Input;
using System.Windows;
using System.Windows.Input;

namespace Nodify
{
/// <summary>The default state of the <see cref="ItemContainer"/>.</summary>
public class ContainerDefaultState : ContainerState
{
private bool _canBeDragging;
private bool _canceled;
private Point _initialPosition;
private SelectionType? _selectionType;
private bool _isDragging;

/// <summary>Creates a new instance of the <see cref="ContainerDefaultState"/>.</summary>
/// <param name="container">The owner of the state.</param>
Expand All @@ -17,80 +19,68 @@ public ContainerDefaultState(ItemContainer container) : base(container)
/// <inheritdoc />
public override void ReEnter(ContainerState from)
{
if (from is ContainerDraggingState drag)
{
Container.IsSelected = true;
_canceled = drag.Canceled;
}

_canBeDragging = false;
_isDragging = false;
_selectionType = null;
_initialPosition = Editor.MouseLocation;
}

/// <inheritdoc />
public override void HandleMouseDown(MouseButtonEventArgs e)
{
_canceled = false;

EditorGestures.ItemContainerGestures gestures = EditorGestures.Mappings.ItemContainer;
if (gestures.Drag.Matches(e.Source, e))
{
_canBeDragging = Container.IsDraggable;
_isDragging = Container.IsDraggable;
}

// Clear the selection if dragging an item that is not part of the selection will not add it to the selection
if (_canBeDragging && !Container.IsSelected && !gestures.Selection.Append.Matches(e.Source, e) && !gestures.Selection.Invert.Matches(e.Source, e))
{
Editor.UnselectAll();
}
if (gestures.Selection.Select.Matches(e.Source, e))
{
_selectionType = gestures.Selection.GetSelectionType(e);
}

_initialPosition = Editor.MouseLocation;
}

/// <inheritdoc />
public override void HandleMouseUp(MouseButtonEventArgs e)
public override void HandleMouseMove(MouseEventArgs e)
{
EditorGestures.ItemContainerGestures gestures = EditorGestures.Mappings.ItemContainer;
if (!_canceled && gestures.Selection.Select.Matches(e.Source, e))
double dragThreshold = NodifyEditor.HandleRightClickAfterPanningThreshold * NodifyEditor.HandleRightClickAfterPanningThreshold;
double dragDistance = (Editor.MouseLocation - _initialPosition).LengthSquared;

if (_isDragging && (dragDistance > dragThreshold))
{
if (gestures.Selection.Append.Matches(e.Source, e))
{
Container.IsSelected = true;
}
else if (gestures.Selection.Invert.Matches(e.Source, e))
{
Container.IsSelected = !Container.IsSelected;
}
else if (gestures.Selection.Remove.Matches(e.Source, e))
{
Container.IsSelected = false;
}
else
if (!Container.IsSelected)
{
// Allow context menu on selection
if (!(e.ChangedButton == MouseButton.Right && e.RightButton == MouseButtonState.Released) || !Container.IsSelected)
{
Editor.UnselectAll();
}

Container.IsSelected = true;
var selectionType = GetSelectionTypeForDragging(_selectionType);
Container.Select(selectionType);
}

_canBeDragging = false;
PushState(new ContainerDraggingState(Container));
}
}

if(!_canceled && gestures.Drag.Matches(e.Source, e))
/// <inheritdoc />
public override void HandleMouseUp(MouseButtonEventArgs e)
{
if (_selectionType.HasValue)
{
_canBeDragging = false;
bool allowContextMenu = e.ChangedButton == MouseButton.Right && Container.IsSelected;
if (!(_selectionType == SelectionType.Replace && allowContextMenu))
{
Container.Select(_selectionType.Value);
}
}

_canceled = false;
_isDragging = false;
_selectionType = null;
}

/// <inheritdoc />
public override void HandleMouseMove(MouseEventArgs e)
private static SelectionType GetSelectionTypeForDragging(SelectionType? selectionType)
{
if (_canBeDragging)
{
PushState(new ContainerDraggingState(Container));
}
// Always select the container when dragging
return selectionType == SelectionType.Remove
? SelectionType.Replace
: selectionType.GetValueOrDefault(SelectionType.Replace);
}
}
}
68 changes: 32 additions & 36 deletions Nodify/EditorStates/ContainerDraggingState.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace Nodify
Expand All @@ -9,8 +8,8 @@ public class ContainerDraggingState : ContainerState
{
private Point _initialMousePosition;
private Point _previousMousePosition;
private Point _currentMousePosition;
public bool Canceled { get; set; } = ItemContainer.AllowDraggingCancellation; // Because of LostMouseCapture that calls Exit

private bool Canceled { get; set; } = ItemContainer.AllowDraggingCancellation;

/// <summary>Constructs an instance of the <see cref="ContainerDraggingState"/> state.</summary>
/// <param name="container">The owner of the state.</param>
Expand All @@ -21,74 +20,71 @@ public ContainerDraggingState(ItemContainer container) : base(container)
/// <inheritdoc />
public override void Enter(ContainerState? from)
{
_initialMousePosition = Mouse.GetPosition(Editor.ItemsHost);

Container.IsSelected = true;
Container.IsPreviewingLocation = true;
Container.RaiseEvent(new DragStartedEventArgs(_initialMousePosition.X, _initialMousePosition.Y)
{
RoutedEvent = ItemContainer.DragStartedEvent
});
Canceled = false;

_initialMousePosition = Editor.MouseLocation;
_previousMousePosition = _initialMousePosition;

Container.BeginDragging();
}

/// <inheritdoc />
public override void Exit()
{
Container.IsPreviewingLocation = false;
var delta = _currentMousePosition - _initialMousePosition;
Container.RaiseEvent(new DragCompletedEventArgs(delta.X, delta.Y, Canceled)
// TODO: This is not canceled on LostMouseCapture (add OnLostMouseCapture/OnCancel callback?)
if (Canceled)
{
RoutedEvent = ItemContainer.DragCompletedEvent
});
Container.CancelDragging();
}
else
{
Container.EndDragging();
}
}

/// <inheritdoc />
public override void HandleMouseMove(MouseEventArgs e)
{
_currentMousePosition = e.GetPosition(Editor.ItemsHost);
var delta = _currentMousePosition - _previousMousePosition;
Container.RaiseEvent(new DragDeltaEventArgs(delta.X, delta.Y)
{
RoutedEvent = ItemContainer.DragDeltaEvent
});

_previousMousePosition = _currentMousePosition;
Container.UpdateDragging(Editor.MouseLocation - _previousMousePosition);
_previousMousePosition = Editor.MouseLocation;
}

/// <inheritdoc />
public override void HandleMouseUp(MouseButtonEventArgs e)
{
EditorGestures.ItemContainerGestures gestures = EditorGestures.Mappings.ItemContainer;

bool canCancel = gestures.CancelAction.Matches(e.Source, e) && ItemContainer.AllowDraggingCancellation;
bool canComplete = gestures.Drag.Matches(e.Source, e);
if (canCancel || canComplete)
if (gestures.Drag.Matches(e.Source, e))
{
// Prevent canceling if drag and cancel are bound to the same mouse action
Canceled = !canComplete && canCancel;

// Handle right click if dragging or canceled and moved the mouse more than threshold so context menus don't open
// Suppress the context menu if the mouse moved beyond the defined drag threshold
if (e.ChangedButton == MouseButton.Right)
{
double contextMenuTreshold = NodifyEditor.HandleRightClickAfterPanningThreshold * NodifyEditor.HandleRightClickAfterPanningThreshold;
if ((_currentMousePosition - _initialMousePosition).LengthSquared > contextMenuTreshold)
double dragThreshold = NodifyEditor.HandleRightClickAfterPanningThreshold * NodifyEditor.HandleRightClickAfterPanningThreshold;
double dragDistance = (Editor.MouseLocation - _initialMousePosition).LengthSquared;

if (dragDistance > dragThreshold)
{
e.Handled = true;
}
}

PopState();
}
else if (ItemContainer.AllowDraggingCancellation && gestures.CancelAction.Matches(e.Source, e))
{
Canceled = true;
e.Handled = true;

PopState();
}
}

/// <inheritdoc />
public override void HandleKeyUp(KeyEventArgs e)
{
Canceled = EditorGestures.Mappings.ItemContainer.CancelAction.Matches(e.Source, e) && ItemContainer.AllowDraggingCancellation;
if (Canceled)
EditorGestures.ItemContainerGestures gestures = EditorGestures.Mappings.ItemContainer;
if (ItemContainer.AllowDraggingCancellation && gestures.CancelAction.Matches(e.Source, e))
{
Canceled = true;
PopState();
}
}
Expand Down
2 changes: 1 addition & 1 deletion Nodify/EditorStates/EditorDefaultState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public override void HandleMouseDown(MouseButtonEventArgs e)
EditorGestures.NodifyEditorGestures gestures = EditorGestures.Mappings.Editor;
if (Editor.CanSelectMultipleItems && gestures.Selection.Select.Matches(e.Source, e))
{
SelectionType selectionType = SelectionHelper.GetSelectionType(e);
SelectionType selectionType = gestures.Selection.GetSelectionType(e);
PushState(new EditorSelectingState(Editor, selectionType));
}
else if (!Editor.DisablePanning && gestures.Pan.Matches(e.Source, e))
Expand Down
6 changes: 4 additions & 2 deletions Nodify/Helpers/SelectionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,12 @@ private void PreviewInvertSelection(Rect area, bool fit = false)
}
}
}
}

internal static SelectionType GetSelectionType(MouseButtonEventArgs e)
internal static class SelectionGesturesExtensions
{
public static SelectionType GetSelectionType(this EditorGestures.SelectionGestures gestures, MouseButtonEventArgs e)
{
EditorGestures.SelectionGestures gestures = EditorGestures.Mappings.Editor.Selection;
if (gestures.Append.Matches(e.Source, e))
{
return SelectionType.Append;
Expand Down
Loading
Loading