Skip to content

Commit b71fa77

Browse files
committed
ItemContainer default state fixes
1 parent ea5ff5d commit b71fa77

15 files changed

+118
-65
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
> - Added a custom MouseGesture with support for key combinations
2727
> - Added InputProcessor to NodifyEditor, ItemContainer and Connector, enabling the extension of controls with custom states
2828
> - Added ElementOperationState to simplify the creation of complex control states
29+
> - Move the viewport to the mouse position when zooming on the Minimap if ResizeToViewport is false
2930
> - Bugfixes:
3031
> - Fixed an issue where the ItemContainer was selected by releasing the mouse button on it, even when the mouse was not captured
32+
> - Fixed an issue where the ItemContainer could open its context menu even when it was not selected
3133
> - Fixed an issue where the Home button caused the editor to fail to display items when contained within a ScrollViewer
3234
> - Fixed an issue where connector optimization did not work when SelectedItems was not data-bound
3335
> - Fixed an issue where EditorCommands.Align caused multiple arrange invalidations, one for each aligned container
3436
> - Fixed an issue where controls would capture the mouse unnecessarily; they now capture it only in response to a defined gesture
37+
> - Fixed an issue where the minimap could update the viewport without having the mouse captured
3538
3639
#### **Version 6.6.0**
3740

Examples/Nodify.StateMachine/StateMachineViewModel.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,16 @@ public StateMachineViewModel()
1717
Conditions = new NodifyObservableCollection<BlackboardItemReferenceViewModel>(BlackboardDescriptor.GetAvailableItems<IBlackboardCondition>())
1818
};
1919

20-
Transitions.WhenAdded(c => c.Source.Transitions.Add(c.Target))
21-
.WhenRemoved(c => c.Source.Transitions.Remove(c.Target))
20+
Transitions.WhenAdded(c =>
21+
{
22+
c.Source.Transitions.Add(c.Target);
23+
c.Target.Transitions.Add(c.Source);
24+
})
25+
.WhenRemoved(c =>
26+
{
27+
c.Source.Transitions.Remove(c.Target);
28+
c.Target.Transitions.Remove(c.Source);
29+
})
2230
.WhenCleared(c => c.ForEach(i =>
2331
{
2432
i.Source.Transitions.Clear();

Nodify/EditorGestures.cs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,27 @@ public class SelectionGestures
1313
/// <summary>Disable selection gestures.</summary>
1414
public static readonly SelectionGestures None = new SelectionGestures(MouseAction.None);
1515

16-
public SelectionGestures(MouseAction mouseAction)
16+
public SelectionGestures(MouseAction mouseAction, bool ignoreModifierKeysOnRelease)
1717
{
1818
Replace = new MouseGesture(mouseAction);
19-
Remove = new MouseGesture(mouseAction, ModifierKeys.Alt);
20-
Append = new MouseGesture(mouseAction, ModifierKeys.Shift);
21-
Invert = new MouseGesture(mouseAction, ModifierKeys.Control);
19+
Remove = new MouseGesture(mouseAction, ModifierKeys.Alt, ignoreModifierKeysOnRelease);
20+
Append = new MouseGesture(mouseAction, ModifierKeys.Shift, ignoreModifierKeysOnRelease);
21+
Invert = new MouseGesture(mouseAction, ModifierKeys.Control, ignoreModifierKeysOnRelease);
2222
Select = new AnyGesture(Replace, Remove, Append, Invert);
2323
Cancel = new KeyGesture(Key.Escape);
2424
}
2525

26-
public SelectionGestures() : this(MouseAction.LeftClick)
26+
public SelectionGestures(MouseAction mouseAction)
27+
: this(mouseAction, true)
28+
{
29+
}
30+
31+
public SelectionGestures(bool ignoreModifierKeysOnRelease)
32+
: this(MouseAction.LeftClick, ignoreModifierKeysOnRelease)
33+
{
34+
}
35+
36+
public SelectionGestures() : this(true)
2737
{
2838
}
2939

@@ -73,8 +83,7 @@ public ItemContainerGestures()
7383
Selection.Replace,
7484
Selection.Remove,
7585
Selection.Append,
76-
Selection.Invert,
77-
new MouseGesture(MouseAction.RightClick));
86+
Selection.Invert);
7887

7988
Drag = new AnyGesture(Selection.Replace, Selection.Remove, Selection.Append, Selection.Invert);
8089
CancelAction = new AnyGesture(new MouseGesture(MouseAction.RightClick), new KeyGesture(Key.Escape));
@@ -110,12 +119,12 @@ public class NodifyEditorGestures
110119
public NodifyEditorGestures()
111120
{
112121
Selection = new SelectionGestures();
113-
Cutting = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt | ModifierKeys.Shift);
114-
PushItems = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control | ModifierKeys.Shift);
122+
Cutting = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt | ModifierKeys.Shift, true);
123+
PushItems = new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control | ModifierKeys.Shift, true);
115124
Pan = new AnyGesture(new MouseGesture(MouseAction.RightClick), new MouseGesture(MouseAction.MiddleClick));
116125
ZoomModifierKey = ModifierKeys.None;
117-
ZoomIn = new MultiGesture(MultiGesture.Match.Any, new KeyGesture(Key.OemPlus, ModifierKeys.Control), new KeyGesture(Key.Add, ModifierKeys.Control));
118-
ZoomOut = new MultiGesture(MultiGesture.Match.Any, new KeyGesture(Key.OemMinus, ModifierKeys.Control), new KeyGesture(Key.Subtract, ModifierKeys.Control));
126+
ZoomIn = new AnyGesture(new KeyGesture(Key.OemPlus, ModifierKeys.Control), new KeyGesture(Key.Add, ModifierKeys.Control));
127+
ZoomOut = new AnyGesture(new KeyGesture(Key.OemMinus, ModifierKeys.Control), new KeyGesture(Key.Subtract, ModifierKeys.Control));
119128
ResetViewportLocation = new KeyGesture(Key.Home);
120129
FitToScreen = new KeyGesture(Key.Home, ModifierKeys.Shift);
121130
CancelAction = new AnyGesture(new MouseGesture(MouseAction.RightClick), new KeyGesture(Key.Escape));

Nodify/EditorStates/ConnectorConnectingState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Nodify
55
{
6-
public class ConnectorConnectingState : ElementOperationState<Connector>
6+
public class ConnectorConnectingState : DragState<Connector>
77
{
88
public ConnectorConnectingState(Connector connector)
99
: base(connector, EditorGestures.Mappings.Connector.Connect, EditorGestures.Mappings.Connector.CancelAction)

Nodify/EditorStates/ContainerDefaultState.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@ public class ContainerDefaultState : InputElementStateStack<ItemContainer>
88
{
99
public ContainerDefaultState(ItemContainer container) : base(container)
1010
{
11-
PushState(new Implementation(this));
11+
PushState(new SelectingState(this));
1212
}
1313

14-
private sealed class Implementation : InputElementState
14+
private sealed class SelectingState : InputElementState
1515
{
1616
private Point _initialPosition;
1717
private SelectionType? _selectionType;
1818
private bool _isDragging;
1919

20+
private bool PreserveSelectionOnRightClick => Element.HasContextMenu || ItemContainer.PreserveSelectionOnRightClick;
21+
2022
/// <summary>Creates a new instance of the <see cref="ContainerSelectingState"/>.</summary>
2123
/// <param name="container">The owner of the state.</param>
22-
public Implementation(InputElementStateStack<ItemContainer> stack) : base(stack)
24+
public SelectingState(InputElementStateStack<ItemContainer> stack) : base(stack)
2325
{
2426
}
2527

@@ -41,22 +43,39 @@ protected override void OnMouseDown(MouseButtonEventArgs e)
4143
EditorGestures.ItemContainerGestures gestures = EditorGestures.Mappings.ItemContainer;
4244
if (gestures.Drag.Matches(e.Source, e))
4345
{
44-
_isDragging = Element.IsDraggable;
46+
_isDragging = Element.IsDraggable && CaptureMouseSafe();
4547
}
4648

4749
if (gestures.Selection.Select.Matches(e.Source, e))
4850
{
4951
_selectionType = gestures.Selection.GetSelectionType(e);
5052
}
53+
// Replaces the current selection when right-clicking on an element that has a context menu and is not selected.
54+
// Applies only when the select gesture is not right click.
55+
else if (e.ChangedButton == MouseButton.Right && PreserveSelectionOnRightClick)
56+
{
57+
_selectionType = Element.IsSelected ? SelectionType.Append : SelectionType.Replace;
58+
}
5159

5260
_initialPosition = Element.Editor.MouseLocation;
5361

54-
// Capture the mouse only if we have an operation
5562
if (_isDragging || _selectionType.HasValue)
5663
{
5764
Element.Focus();
65+
e.Handled = true;
66+
}
67+
}
68+
69+
private bool CaptureMouseSafe()
70+
{
71+
// Avoid stealing mouse capture from other elements
72+
if (Mouse.Captured == null || Element.IsMouseCaptured)
73+
{
5874
Element.CaptureMouse();
75+
return true;
5976
}
77+
78+
return false;
6079
}
6180

6281
/// <inheritdoc />
@@ -87,9 +106,8 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
87106
// explicit context menu or is configured to preserve the selection on right-click, the selection
88107
// remains unchanged. This ensures that the context menu applies to the entire selection rather
89108
// than only the clicked item.
90-
bool hasContextMenu = Element.HasContextMenu || ItemContainer.PreserveSelectionOnRightClick;
91-
bool allowContextMenu = e.ChangedButton == MouseButton.Right && Element.IsSelected && hasContextMenu;
92-
if (!(_selectionType == SelectionType.Replace && allowContextMenu))
109+
bool allowContextMenu = e.ChangedButton == MouseButton.Right && Element.IsSelected && PreserveSelectionOnRightClick;
110+
if (!allowContextMenu)
93111
{
94112
Element.Select(_selectionType.Value);
95113
}

Nodify/EditorStates/ContainerDraggingState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace Nodify
55
{
66
/// <summary>Dragging state of the container.</summary>
7-
public sealed class ContainerDraggingState : InputElementStateStack<ItemContainer>.ElementOperationState
7+
internal sealed class ContainerDraggingState : InputElementStateStack<ItemContainer>.DragState
88
{
99
private Point _previousMousePosition;
1010

Nodify/EditorStates/ElementOperationState.cs renamed to Nodify/EditorStates/DragState.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Nodify
55
{
6-
public abstract class ElementOperationState<TElement> : InputElementState<TElement>, IInputHandler
6+
public abstract class DragState<TElement> : InputElementState<TElement>, IInputHandler
77
where TElement : FrameworkElement
88
{
99
protected InputGesture? CancelGesture { get; }
@@ -19,13 +19,13 @@ public abstract class ElementOperationState<TElement> : InputElementState<TEleme
1919

2020
/// <summary>Constructs a new <see cref="EditorState"/>.</summary>
2121
/// <param name="element">The owner of the state.</param>
22-
public ElementOperationState(TElement element, InputGesture beginGesture) : base(element)
22+
public DragState(TElement element, InputGesture beginGesture) : base(element)
2323
{
2424
BeginGesture = beginGesture;
2525
PositionElement = element;
2626
}
2727

28-
public ElementOperationState(TElement element, InputGesture beginGesture, InputGesture cancelGesture)
28+
public DragState(TElement element, InputGesture beginGesture, InputGesture cancelGesture)
2929
: this(element, beginGesture)
3030
{
3131
CancelGesture = cancelGesture;
@@ -35,19 +35,19 @@ void IInputHandler.HandleEvent(InputEventArgs e)
3535
{
3636
if (!_canReceiveInput && IsInputEventPressed(e) && BeginGesture.Matches(e.Source, e) && CanBegin)
3737
{
38-
BeginOperation(e);
38+
BeginDrag(e);
3939
return;
4040
}
4141

4242
if (_canReceiveInput && (IsToggle ? IsInputEventPressed(e) : IsInputEventReleased(e)) && BeginGesture.Matches(e.Source, e))
4343
{
44-
EndOperation(e);
44+
EndDrag(e);
4545
return;
4646
}
4747

4848
if (_canReceiveInput && (e.RoutedEvent == UIElement.LostMouseCaptureEvent || (CancelGesture?.Matches(e.Source, e) is true && IsInputEventReleased(e))))
4949
{
50-
CancelOperation(e);
50+
CancelDrag(e);
5151
return;
5252
}
5353

@@ -57,7 +57,7 @@ void IInputHandler.HandleEvent(InputEventArgs e)
5757
}
5858
}
5959

60-
private void CancelOperation(InputEventArgs e)
60+
private void CancelDrag(InputEventArgs e)
6161
{
6262
_canReceiveInput = false;
6363
HandleEvent(e);
@@ -66,12 +66,13 @@ private void CancelOperation(InputEventArgs e)
6666
e.Handled = true;
6767
}
6868

69-
private void BeginOperation(InputEventArgs e)
69+
private void BeginDrag(InputEventArgs e)
7070
{
71+
// Avoid stealing mouse capture from other elements
7172
if (Mouse.Captured == null || Element.IsMouseCaptured)
7273
{
7374
_canReceiveInput = true;
74-
HandleEvent(e); // handle the event, otherwise CaptureMouse will send a MouseMove event and the current event will be handled out of order
75+
HandleEvent(e); // Handle the event, otherwise CaptureMouse will send a MouseMove event and the current event will be handled out of order
7576
OnBegin(e);
7677

7778
e.Handled = true;
@@ -86,7 +87,7 @@ private void BeginOperation(InputEventArgs e)
8687
}
8788
}
8889

89-
private void EndOperation(InputEventArgs e)
90+
private void EndDrag(InputEventArgs e)
9091
{
9192
_canReceiveInput = false;
9293
HandleEvent(e);
@@ -114,7 +115,7 @@ private void EndOperation(InputEventArgs e)
114115
}
115116
}
116117

117-
private static bool IsInputEventReleased(InputEventArgs e)
118+
protected virtual bool IsInputEventReleased(InputEventArgs e)
118119
{
119120
if (e is MouseButtonEventArgs mbe && mbe.ButtonState == MouseButtonState.Released)
120121
return true;
@@ -128,7 +129,7 @@ private static bool IsInputEventReleased(InputEventArgs e)
128129
return false;
129130
}
130131

131-
private static bool IsInputEventPressed(InputEventArgs e)
132+
protected virtual bool IsInputEventPressed(InputEventArgs e)
132133
{
133134
if (e is MouseButtonEventArgs mbe && mbe.ButtonState == MouseButtonState.Pressed)
134135
return true;

Nodify/EditorStates/EditorCuttingState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Nodify
44
{
5-
public class EditorCuttingState : ElementOperationState<NodifyEditor>
5+
public class EditorCuttingState : DragState<NodifyEditor>
66
{
77
protected override bool HasContextMenu => Element.HasContextMenu;
88

Nodify/EditorStates/EditorPanningState.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Nodify
66
{
77
/// <summary>The panning state of the editor.</summary>
8-
public class EditorPanningState : ElementOperationState<NodifyEditor>
8+
public class EditorPanningState : DragState<NodifyEditor>
99
{
1010
protected override bool HasContextMenu => Element.HasContextMenu;
1111
protected override bool CanBegin => !Element.DisablePanning;
@@ -21,9 +21,7 @@ public EditorPanningState(NodifyEditor editor)
2121

2222
protected override void OnBegin(InputEventArgs e)
2323
{
24-
var initialMousePosition = Mouse.GetPosition(Element);
25-
_prevPosition = initialMousePosition;
26-
24+
_prevPosition = Mouse.GetPosition(Element);
2725
Element.BeginPanning();
2826
}
2927

Nodify/EditorStates/EditorPushingItemsState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace Nodify
77
{
8-
public class EditorPushingItemsState : ElementOperationState<NodifyEditor>
8+
public class EditorPushingItemsState : DragState<NodifyEditor>
99
{
1010
protected override bool HasContextMenu => Element.HasContextMenu;
1111

Nodify/EditorStates/EditorSelectingState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Nodify
44
{
55
/// <summary>The selecting state of the editor.</summary>
6-
public class EditorSelectingState : ElementOperationState<NodifyEditor>
6+
public class EditorSelectingState : DragState<NodifyEditor>
77
{
88
protected override bool HasContextMenu => Element.HasContextMenu;
99
protected override bool CanBegin => Element.CanSelectMultipleItems && !Element.IsPanning;

Nodify/EditorStates/InputElementStateStack.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void PopState()
8888
=> Stack.PopState();
8989
}
9090

91-
public abstract class ElementOperationState : InputElementState, IInputHandler
91+
public abstract class DragState : InputElementState, IInputHandler
9292
{
9393
protected InputGesture ExitGesture { get; }
9494
protected InputGesture? CancelGesture { get; }
@@ -99,13 +99,13 @@ public abstract class ElementOperationState : InputElementState, IInputHandler
9999
private bool _canReceiveInput;
100100
private Point _initialPosition;
101101

102-
public ElementOperationState(InputElementStateStack<TElement> stack, InputGesture exitGesture) : base(stack)
102+
public DragState(InputElementStateStack<TElement> stack, InputGesture exitGesture) : base(stack)
103103
{
104104
ExitGesture = exitGesture;
105105
PositionElement = stack.Element;
106106
}
107107

108-
public ElementOperationState(InputElementStateStack<TElement> stack, InputGesture exitGesture, InputGesture cancelGesture)
108+
public DragState(InputElementStateStack<TElement> stack, InputGesture exitGesture, InputGesture cancelGesture)
109109
: this(stack, exitGesture)
110110
{
111111
CancelGesture = cancelGesture;
@@ -137,13 +137,13 @@ void IInputHandler.HandleEvent(InputEventArgs e)
137137

138138
if (_canReceiveInput && IsInputEventReleased(e) && ExitGesture.Matches(e.Source, e))
139139
{
140-
EndOperation(e);
140+
EndDrag(e);
141141
return;
142142
}
143143

144144
if (_canReceiveInput && (e.RoutedEvent == UIElement.LostMouseCaptureEvent || (CancelGesture?.Matches(e.Source, e) is true && IsInputEventReleased(e))))
145145
{
146-
CancelOperation(e);
146+
CancelDrag(e);
147147
return;
148148
}
149149

@@ -153,7 +153,7 @@ void IInputHandler.HandleEvent(InputEventArgs e)
153153
}
154154
}
155155

156-
private void CancelOperation(InputEventArgs e)
156+
private void CancelDrag(InputEventArgs e)
157157
{
158158
_canReceiveInput = false;
159159

@@ -165,7 +165,7 @@ private void CancelOperation(InputEventArgs e)
165165
PopState();
166166
}
167167

168-
private void EndOperation(InputEventArgs e)
168+
private void EndDrag(InputEventArgs e)
169169
{
170170
_canReceiveInput = false;
171171

0 commit comments

Comments
 (0)