Skip to content

Commit

Permalink
Improved support for toggled interactions
Browse files Browse the repository at this point in the history
  • Loading branch information
miroiu committed Dec 20, 2024
1 parent 7ea774c commit 27440f6
Show file tree
Hide file tree
Showing 19 changed files with 166 additions and 220 deletions.
12 changes: 7 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
> - 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
> - Replaced the System.Windows.Input.MouseGesture with Nodify.MouseGesture
> - Replaced the System.Windows.Input.MouseGesture with Nodify.Interactivity.MouseGesture for default EditorGesture mappings
> - Removed State, GetInitialState, PushState, PopState and PopAllStates from NodifyEditor and ItemContainer
> - Replaced EditorState and ContainerState with InputElementState
> - Moved AllowCuttingCancellation from CuttingLine to NodifyEditor
> - Moved AllowDraggingCancellation from ItemContainer to NodifyEditor
> - Moved EditorGestures under the Nodify.Interactivity namespace
> - Moved Editor events under the Nodify.Events namespace
> - Moved editor events under the Nodify.Events namespace
> - Features:
> - Added BeginPanning, UpdatePanning, EndPanning, CancelPanning and AllowPanningCancellation to NodifyEditor and Minimap
> - Added MouseLocation, ZoomAtPosition and GetLocationInsideMinimap to Minimap
Expand All @@ -34,15 +34,16 @@
> - Added FindTargetConnector and FindConnectionTarget methods to Connector
> - Added a custom MouseGesture with support for key combinations
> - Added InputProcessor to NodifyEditor, ItemContainer, Connector, BaseConnection and Minimap, enabling the extension of controls with custom states
> - Added DragState to simplify creating click-and-drag operations, with support for initiating and completing them using the keyboard
> - Added InputElementStateStack to manage transitions between states in UI elements
> - Added DragState to simplify creating click-and-drag interactions, with support for initiating and completing them using the keyboard
> - Added InputElementStateStack, InputElementStateStack.DragState and InputElementStateStack.InputElementState to manage transitions between states in UI elements
> - Added InputProcessor.Shared to enable the addition of global input handlers
> - Move the viewport to the mouse position when zooming on the Minimap if ResizeToViewport is false
> - Added SplitAtLocation and Remove methods to BaseConnection
> - Added AllowPanningWhileSelecting, AllowPanningWhileCutting and AllowPanningWhilePushingItems to EditorState
> - Added AllowZoomingWhilePanning, AllowZoomingWhileSelecting, AllowZoomingWhileCutting and AllowZoomingWhilePushingItems to EditorState
> - Added EnableToggledSelectingMode, EnableToggledPanningMode, EnableToggledPushingItemsMode and EnableToggledCuttingMode to EditorState
> - Added MinimapState.EnableToggledPanningMode
> - Added ContainerState.EnableToggledDraggingMode
> - Added Unbind to InputGestureRef and EditorGestures.SelectionGestures
> - Added EnableHitTesting to PendingConnection
> - Bugfixes:
Expand All @@ -54,7 +55,8 @@
> - Fixed an issue where controls would capture the mouse unnecessarily; they now capture it only in response to a defined gesture
> - Fixed an issue where the minimap could update the viewport without having the mouse captured
> - Fixed ItemContainer.Select and NodifyEditor.SelectArea to clear the existing selection and select the containers within the same transaction
> - Fixed an issue where editor operations failed to cancel upon losing mouse capture
> - Fixed an issue where editor interactions failed to cancel upon losing mouse capture
> - Fixed an issue where selecting a new connection would not clear the previous selection within the same transaction
#### **Version 6.6.0**

Expand Down
5 changes: 3 additions & 2 deletions Examples/Nodify.Calculator/EditorView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@
</DataTemplate>
</nodify:GroupingNode.HeaderTemplate>
<Grid>
<ScrollViewer CanContentScroll="True">
<ScrollViewer CanContentScroll="True"
Visibility="{Binding IsExpanded, Converter={shared:BooleanToVisibilityConverter}}">
<nodify:NodifyEditor Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}"
DataContext="{Binding InnerCalculator}"
ItemsSource="{Binding Operations}"
Expand Down Expand Up @@ -242,7 +243,7 @@
</CompositeCollection>
</nodify:NodifyEditor>
</ScrollViewer>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
Expand Down
1 change: 1 addition & 0 deletions Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public NodifyEditorView()

static NodifyEditorView()
{
InputProcessor.Shared<Connector>.ReplaceHandlerFactory<ConnectorState.Connecting>(elem => new CustomConnecting(elem));
InputProcessor.Shared<Connector>.RegisterHandlerFactory(elem => new RetargetConnections(elem));
}

Expand Down
28 changes: 27 additions & 1 deletion Examples/Nodify.Playground/Editor/RetargetConnections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,52 @@

namespace Nodify.Playground
{
/// <summary>
/// Connecting state that prevents connecting when <see cref="RetargetConnections"/> is in progress.
/// </summary>
public class CustomConnecting : ConnectorState.Connecting
{
protected override bool CanBegin => !RetargetConnections.InProgress;

public CustomConnecting(Connector connector) : base(connector)
{
}
}

/// <summary>
/// Hold CTRL+LeftClick on a connector to start reconnecting it.
/// </summary>
public class RetargetConnections : DragState<Connector>
{
public static InputGestureRef Reconnect { get; } = new Interactivity.MouseGesture(MouseAction.LeftClick, ModifierKeys.Control);
public static InputGestureRef Reconnect { get; } = new Interactivity.MouseGesture(MouseAction.LeftClick, ModifierKeys.Control)
{
IgnoreModifierKeysOnRelease = true
};

/// <summary>
/// Used to prevent connecting when <see cref="EditorSettings.EnableStickyConnectors"/> is enabled.
/// </summary>
public static bool InProgress { get; private set; }

protected override bool CanBegin => ViewModel.IsConnected && ViewModel.Flow == ConnectorFlow.Input;
protected override bool IsToggle => EditorSettings.Instance.EnableStickyConnectors;

private ConnectorViewModel ViewModel => (ConnectorViewModel)Element.DataContext;
private Vector _connectorOffset;
private Connector? _targetConnector;

public RetargetConnections(Connector element) : base(element, Reconnect, EditorGestures.Mappings.Connector.CancelAction)
{
PositionElement = Element.Editor ?? (IInputElement)Element;
}

protected override void OnBegin(InputEventArgs e)
{
_connectorOffset = ViewModel.Node.Orientation == Orientation.Horizontal
? (Vector)EditorSettings.Instance.ConnectionTargetOffset.Value
: new Vector(EditorSettings.Instance.ConnectionTargetOffset.Value.Y, EditorSettings.Instance.ConnectionTargetOffset.Value.X);

InProgress = true;
}

protected override void OnMouseMove(MouseEventArgs e)
Expand Down Expand Up @@ -70,6 +94,7 @@ protected override void OnEnd(InputEventArgs e)

// Reset the position of connections that were not rewired
Element.UpdateAnchor();
InProgress = false;
}

protected override void OnCancel(InputEventArgs e)
Expand All @@ -78,6 +103,7 @@ protected override void OnCancel(InputEventArgs e)

// Reset the position of connections that were not rewired
Element.UpdateAnchor();
InProgress = false;
}

/// <summary>
Expand Down
11 changes: 11 additions & 0 deletions Examples/Nodify.Playground/EditorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,11 @@ private EditorSettings()
val => Instance.EnableToggledSelecting = val,
"Enable toggled selecting mode: ",
"The interaction will be completed in two steps using the same gesture to start and end."),
new ProxySettingViewModel<bool>(
() => Instance.EnableToggledDragging,
val => Instance.EnableToggledDragging = val,
"Enable toggled dragging mode: ",
"The interaction will be completed in two steps using the same gesture to start and end."),
new ProxySettingViewModel<bool>(
() => Instance.EnableMinimapToggledPanning,
val => Instance.EnableMinimapToggledPanning = val,
Expand Down Expand Up @@ -832,6 +837,12 @@ public bool EnableToggledSelecting
set => EditorState.EnableToggledSelectingMode = value;
}

public bool EnableToggledDragging
{
get => ContainerState.EnableToggledDraggingMode;
set => ContainerState.EnableToggledDraggingMode = value;
}

public bool EnableMinimapToggledPanning
{
get => MinimapState.EnableToggledPanningMode;
Expand Down
4 changes: 2 additions & 2 deletions Nodify/Connections/BaseConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released
if (IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released)
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
Expand All @@ -835,7 +835,7 @@ protected override void OnKeyUp(KeyEventArgs e)
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released
if (IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released)
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
Expand Down
14 changes: 3 additions & 11 deletions Nodify/Connectors/Connector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ public void UpdateAnchor()

#region Gesture Handling

protected InputProcessor InputProcessor { get; } = new InputProcessor { ProcessHandledEvents = true };
protected InputProcessor InputProcessor { get; } = new InputProcessor();

/// <inheritdoc />
protected override void OnMouseDown(MouseButtonEventArgs e)
Expand All @@ -340,7 +340,7 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
if (IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released && !IsToggledInteractionInProgress())
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
Expand All @@ -364,7 +364,7 @@ protected override void OnKeyUp(KeyEventArgs e)
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
if (IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released && !IsToggledInteractionInProgress())
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
Expand All @@ -374,14 +374,6 @@ protected override void OnKeyUp(KeyEventArgs e)
protected override void OnKeyDown(KeyEventArgs e)
=> InputProcessor.ProcessEvent(e);

/// <summary>
/// Determines whether any toggled interaction is currently in progress.
/// </summary>
protected virtual bool IsToggledInteractionInProgress()
{
return ConnectorState.EnableToggledConnectingMode && IsPendingConnection;
}

#endregion

#region Methods
Expand Down
9 changes: 2 additions & 7 deletions Nodify/Containers/ItemContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
if (IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released && !IsToggledInteractionInProgress())
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
Expand All @@ -396,7 +396,7 @@ protected override void OnKeyUp(KeyEventArgs e)
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
if (IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released && !IsToggledInteractionInProgress())
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
Expand All @@ -406,11 +406,6 @@ protected override void OnKeyUp(KeyEventArgs e)
protected override void OnKeyDown(KeyEventArgs e)
=> InputProcessor.ProcessEvent(e);

/// <summary>
/// Determines whether any toggled interaction is currently in progress.
/// </summary>
protected virtual bool IsToggledInteractionInProgress() => false;

#endregion
}
}
5 changes: 5 additions & 0 deletions Nodify/Containers/States/ContainerState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
{
public static partial class ContainerState
{
/// <summary>
/// Determines whether toggled dragging mode is enabled, allowing the user to start and end the interaction in two steps with the same input gesture.
/// </summary>
public static bool EnableToggledDraggingMode { get; set; }

internal static void RegisterDefaultHandlers()
{
InputProcessor.Shared<ItemContainer>.RegisterHandlerFactory(elem => new Default(elem));
Expand Down
4 changes: 2 additions & 2 deletions Nodify/Containers/States/Dragging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public static partial class ContainerState
internal sealed class Dragging : InputElementStateStack<ItemContainer>.DragState
{
protected override bool CanCancel => NodifyEditor.AllowDraggingCancellation;
protected override bool IsToggle => EnableToggledDraggingMode;

private Point _previousMousePosition;

Expand All @@ -20,13 +21,12 @@ public Dragging(InputElementStateStack<ItemContainer> stack)
PositionElement = Element.Editor;
}

protected override void OnBegin(InputElementStateStack<ItemContainer>.IInputElementState? from)
protected override void OnBegin(InputEventArgs e)
{
_previousMousePosition = Element.Editor.MouseLocation;
Element.BeginDragging();
}

/// <inheritdoc />
protected override void OnMouseMove(MouseEventArgs e)
{
Element.UpdateDragging(Element.Editor.MouseLocation - _previousMousePosition);
Expand Down
15 changes: 2 additions & 13 deletions Nodify/Editor/NodifyEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
if (IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released && !IsToggledInteractionInProgress())
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
Expand Down Expand Up @@ -861,7 +861,7 @@ protected override void OnKeyUp(KeyEventArgs e)
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
if (IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released && !IsToggledInteractionInProgress())
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
Expand All @@ -871,17 +871,6 @@ protected override void OnKeyUp(KeyEventArgs e)
protected override void OnKeyDown(KeyEventArgs e)
=> InputProcessor.ProcessEvent(e);

/// <summary>
/// Determines whether any toggled interaction is currently in progress.
/// </summary>
protected virtual bool IsToggledInteractionInProgress()
{
return EditorState.EnableToggledPanningMode && IsPanning
|| EditorState.EnableToggledSelectingMode && IsSelecting
|| EditorState.EnableToggledPushingItemsMode && IsPushingItems
|| EditorState.EnableToggledCuttingMode && IsCutting;
}

#endregion

/// <inheritdoc />
Expand Down
Loading

0 comments on commit 27440f6

Please sign in to comment.