diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d508f7..7654da54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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** diff --git a/Examples/Nodify.Calculator/EditorView.xaml.cs b/Examples/Nodify.Calculator/EditorView.xaml.cs index a0004a4c..87a0f6e3 100644 --- a/Examples/Nodify.Calculator/EditorView.xaml.cs +++ b/Examples/Nodify.Calculator/EditorView.xaml.cs @@ -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)); } diff --git a/Nodify/Connections/ConnectionContainer.cs b/Nodify/Connections/ConnectionContainer.cs index 38479941..671c2acb 100644 --- a/Nodify/Connections/ConnectionContainer.cs +++ b/Nodify/Connections/ConnectionContainer.cs @@ -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(); - } + } + } + /// + /// Modifies the selection state of the current item based on the specified selection type. + /// + /// The type of selection to perform. + 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; } } } diff --git a/Nodify/EditorStates/ContainerDefaultState.cs b/Nodify/EditorStates/ContainerDefaultState.cs index d69469e0..02aac831 100644 --- a/Nodify/EditorStates/ContainerDefaultState.cs +++ b/Nodify/EditorStates/ContainerDefaultState.cs @@ -1,12 +1,14 @@ -using System.Windows.Input; +using System.Windows; +using System.Windows.Input; namespace Nodify { /// The default state of the . public class ContainerDefaultState : ContainerState { - private bool _canBeDragging; - private bool _canceled; + private Point _initialPosition; + private SelectionType? _selectionType; + private bool _isDragging; /// Creates a new instance of the . /// The owner of the state. @@ -17,80 +19,68 @@ public ContainerDefaultState(ItemContainer container) : base(container) /// 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; } /// 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; } /// - 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)) + /// + 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; } - /// - 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); } } } diff --git a/Nodify/EditorStates/ContainerDraggingState.cs b/Nodify/EditorStates/ContainerDraggingState.cs index 2ce2343f..6c0ca312 100644 --- a/Nodify/EditorStates/ContainerDraggingState.cs +++ b/Nodify/EditorStates/ContainerDraggingState.cs @@ -1,5 +1,4 @@ using System.Windows; -using System.Windows.Controls.Primitives; using System.Windows.Input; namespace Nodify @@ -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; /// Constructs an instance of the state. /// The owner of the state. @@ -21,64 +20,60 @@ public ContainerDraggingState(ItemContainer container) : base(container) /// 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(); } /// 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(); + } } /// 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; } /// 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(); } } @@ -86,9 +81,10 @@ public override void HandleMouseUp(MouseButtonEventArgs e) /// 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(); } } diff --git a/Nodify/EditorStates/EditorDefaultState.cs b/Nodify/EditorStates/EditorDefaultState.cs index eba8eed1..373a37eb 100644 --- a/Nodify/EditorStates/EditorDefaultState.cs +++ b/Nodify/EditorStates/EditorDefaultState.cs @@ -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)) diff --git a/Nodify/Helpers/SelectionHelper.cs b/Nodify/Helpers/SelectionHelper.cs index 699a90fd..b6293f4e 100644 --- a/Nodify/Helpers/SelectionHelper.cs +++ b/Nodify/Helpers/SelectionHelper.cs @@ -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; diff --git a/Nodify/ItemContainer.cs b/Nodify/ItemContainer.cs index 7b855afb..3d902db8 100644 --- a/Nodify/ItemContainer.cs +++ b/Nodify/ItemContainer.cs @@ -150,9 +150,6 @@ private static void OnLocationChanged(DependencyObject d, DependencyPropertyChan #region Routed Events - public static readonly RoutedEvent DragStartedEvent = EventManager.RegisterRoutedEvent(nameof(DragStarted), RoutingStrategy.Bubble, typeof(DragStartedEventHandler), typeof(ItemContainer)); - public static readonly RoutedEvent DragCompletedEvent = EventManager.RegisterRoutedEvent(nameof(DragCompleted), RoutingStrategy.Bubble, typeof(DragCompletedEventHandler), typeof(ItemContainer)); - public static readonly RoutedEvent DragDeltaEvent = EventManager.RegisterRoutedEvent(nameof(DragDelta), RoutingStrategy.Bubble, typeof(DragDeltaEventHandler), typeof(ItemContainer)); public static readonly RoutedEvent SelectedEvent = Selector.SelectedEvent.AddOwner(typeof(ItemContainer)); public static readonly RoutedEvent UnselectedEvent = Selector.UnselectedEvent.AddOwner(typeof(ItemContainer)); public static readonly RoutedEvent LocationChangedEvent = EventManager.RegisterRoutedEvent(nameof(LocationChanged), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ItemContainer)); @@ -166,33 +163,6 @@ public event RoutedEventHandler LocationChanged remove => RemoveHandler(LocationChangedEvent, value); } - /// - /// Occurs when this is the instigator of a drag operation. - /// - public event DragStartedEventHandler DragStarted - { - add => AddHandler(DragStartedEvent, value); - remove => RemoveHandler(DragStartedEvent, value); - } - - /// - /// Occurs when this is being dragged. - /// - public event DragDeltaEventHandler DragDelta - { - add => AddHandler(DragDeltaEvent, value); - remove => RemoveHandler(DragDeltaEvent, value); - } - - /// - /// Occurs when this completed the drag operation. - /// - public event DragCompletedEventHandler DragCompleted - { - add => AddHandler(DragCompletedEvent, value); - remove => RemoveHandler(DragCompletedEvent, value); - } - /// /// Occurs when this is selected. /// @@ -335,6 +305,54 @@ public virtual bool IsSelectableInArea(Rect area, bool isContained) return isContained ? area.Contains(bounds) : area.IntersectsWith(bounds); } + /// + /// Replaces the selection if the container is not selected. + public void BeginDragging() + { + if (!IsSelected) + { + Select(SelectionType.Replace); + } + + Editor.BeginDragging(); + } + + /// + public void UpdateDragging(Vector amount) + => Editor.UpdateDragging(amount); + + /// + public void CancelDragging() + => Editor.CancelDragging(); + + /// + public void EndDragging() + => Editor.EndDragging(); + + /// + /// Modifies the selection state of the current item based on the specified selection type. + /// + /// The type of selection to perform. + public 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: + Editor.UnselectAll(); + IsSelected = true; + break; + } + } + #region State Handling private readonly Stack _states = new Stack(); @@ -399,7 +417,7 @@ protected override void OnMouseDown(MouseButtonEventArgs e) /// protected override void OnMouseUp(MouseButtonEventArgs e) { - if (IsSelectableLocation(e.GetPosition(this)) || IsMouseCaptured) + if (IsMouseCaptured) { State.HandleMouseUp(e); } @@ -412,16 +430,12 @@ protected override void OnMouseUp(MouseButtonEventArgs e) } /// - protected override void OnMouseMove(MouseEventArgs e) - { - State.HandleMouseMove(e); - } + protected override void OnMouseMove(MouseEventArgs e) + => State.HandleMouseMove(e); /// - protected override void OnMouseWheel(MouseWheelEventArgs e) - { - State.HandleMouseWheel(e); - } + protected override void OnMouseWheel(MouseWheelEventArgs e) + => State.HandleMouseWheel(e); /// protected override void OnLostMouseCapture(MouseEventArgs e) diff --git a/Nodify/NodifyEditor.Dragging.cs b/Nodify/NodifyEditor.Dragging.cs index 38703a76..290048c2 100644 --- a/Nodify/NodifyEditor.Dragging.cs +++ b/Nodify/NodifyEditor.Dragging.cs @@ -1,7 +1,6 @@ using System.Windows.Input; using System.Windows; using System.Collections.Generic; -using System.Windows.Controls.Primitives; using System.Diagnostics; namespace Nodify @@ -74,13 +73,6 @@ public bool IsDragging private IDraggingStrategy? _draggingStrategy; - private void RegisterDragEvents() - { - AddHandler(ItemContainer.DragStartedEvent, new DragStartedEventHandler(OnItemsDragStarted)); - AddHandler(ItemContainer.DragCompletedEvent, new DragCompletedEventHandler(OnItemsDragCompleted)); - AddHandler(ItemContainer.DragDeltaEvent, new DragDeltaEventHandler(OnItemsDragDelta)); - } - /// /// Initiates the dragging operation using the currently selected s. /// @@ -163,28 +155,5 @@ private IDraggingStrategy CreateDraggingStrategy(IEnumerable cont return new DraggingSimple(containers, GridCellSize); } - - private void OnItemsDragStarted(object sender, DragStartedEventArgs e) - { - BeginDragging(); - e.Handled = true; - } - - private void OnItemsDragDelta(object sender, DragDeltaEventArgs e) - { - UpdateDragging(new Vector(e.HorizontalChange, e.VerticalChange)); - } - - private void OnItemsDragCompleted(object sender, DragCompletedEventArgs e) - { - if (e.Canceled) - { - CancelDragging(); - } - else - { - EndDragging(); - } - } } } diff --git a/Nodify/NodifyEditor.cs b/Nodify/NodifyEditor.cs index 4ace8a65..433ef79d 100644 --- a/Nodify/NodifyEditor.cs +++ b/Nodify/NodifyEditor.cs @@ -529,8 +529,6 @@ public NodifyEditor() AddHandler(BaseConnection.DisconnectEvent, new ConnectionEventHandler(OnRemoveConnection)); - RegisterDragEvents(); - var transform = new TransformGroup(); transform.Children.Add(ScaleTransform); transform.Children.Add(TranslateTransform);