Skip to content

Commit

Permalink
Merge pull request #178 from miroiu/feature/146-editor-state-flags
Browse files Browse the repository at this point in the history
Enable toggled interactions for various editor operations
  • Loading branch information
miroiu authored Dec 15, 2024
2 parents 3786ac7 + debdbb2 commit f15a015
Show file tree
Hide file tree
Showing 27 changed files with 547 additions and 126 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
> - Breaking Changes:
> - Made the setter of NodifyEditor.IsPanning private
> - Made SelectionHelper internal
> - Made Minimap sealed
> - Renamed HandleRightClickAfterPanningThreshold to MouseActionSuppressionThreshold in NodifyEditor
> - Renamed StartCutting to BeginCutting in NodifyEditor
> - Renamed Connector.EnableStickyConnections to ConnectorState.EnabledToggledConnectingMode
> - 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
Expand All @@ -24,7 +24,7 @@
> - Added Select, BeginSelecting, UpdateSelection, EndSelecting, CancelSelecting and AllowSelectionCancellation to NodifyEditor
> - Added IsDragging, BeginDragging, UpdateDragging, EndDragging and CancelDragging to NodifyEditor
> - Added AlignSelection and AlignContainers methods to NodifyEditor
> - Added HasCustomContextMenu dependency property to NodifyEditor, ItemContainer and Connector
> - Added HasCustomContextMenu dependency property to NodifyEditor, ItemContainer, Connector and BaseConnection
> - Added Select, BeginDragging, UpdateDragging, EndDragging and CancelDragging to ItemContainer
> - Added PreserveSelectionOnRightClick configuration field to ItemContainer
> - Added BeginConnecting, UpdatePendingConnection, EndConnecting, CancelConnecting and RemoveConnections methods to Connector
Expand All @@ -35,6 +35,10 @@
> - 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
> - Bugfixes:
> - Fixed an issue where the ItemContainer was selected by releasing the mouse button on it, even when the mouse was not captured
> - Fixed an issue where the ItemContainer could open its context menu even when it was not selected
Expand Down
150 changes: 147 additions & 3 deletions Examples/Nodify.Playground/EditorSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Nodify.Interactivity;
using System.Collections.Generic;
using System.Windows;

namespace Nodify.Playground
Expand Down Expand Up @@ -230,6 +231,11 @@ private EditorSettings()
val => Instance.AutoPanningTickRate = val,
"Auto panning tick rate: ",
"How often is the new position calculated in milliseconds. Disable and enable auto panning for this to have effect."),
new ProxySettingViewModel<bool>(
() => Instance.AllowMinimapPanningCancellation,
val => Instance.AllowMinimapPanningCancellation = val,
"Allow minimap panning cancellation: ",
"Right click or escape to cancel panning."),
new ProxySettingViewModel<bool>(
() => Instance.AllowCuttingCancellation,
val => Instance.AllowCuttingCancellation = val,
Expand Down Expand Up @@ -310,6 +316,66 @@ private EditorSettings()
val => Instance.FitToScreenExtentMargin = val,
"Fit to screen extent margin: ",
"Adds some margin to the nodes extent when fit to screen"),
new ProxySettingViewModel<bool>(
() => Instance.EnableToggledCutting,
val => Instance.EnableToggledCutting = val,
"Enable toggled cutting mode: ",
"The interaction will be completed in two steps using the same gesture to start and end."),
new ProxySettingViewModel<bool>(
() => Instance.EnableToggledPushingItems,
val => Instance.EnableToggledPushingItems = val,
"Enable toggled pushing items mode: ",
"The interaction will be completed in two steps using the same gesture to start and end."),
new ProxySettingViewModel<bool>(
() => Instance.EnableToggledPanning,
val => Instance.EnableToggledPanning = val,
"Enable toggled panning mode: ",
"The interaction will be completed in two steps using the same gesture to start and end."),
new ProxySettingViewModel<bool>(
() => Instance.EnableToggledSelecting,
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.EnableMinimapToggledPanning,
val => Instance.EnableMinimapToggledPanning = val,
"Enable minimap toggled panning mode: ",
"The interaction will be completed in two steps using the same gesture to start and end."),
new ProxySettingViewModel<bool>(
() => Instance.AllowPanningWhileSelecting,
val => Instance.AllowPanningWhileSelecting = val,
"Allow panning while selecting: ",
"Whether panning is allowed while selecting items in the editor."),
new ProxySettingViewModel<bool>(
() => Instance.AllowPanningWhileCutting,
val => Instance.AllowPanningWhileCutting = val,
"Allow panning while cutting: ",
"Whether panning is allowed while cutting connections in the editor."),
new ProxySettingViewModel<bool>(
() => Instance.AllowPanningWhilePushingItems,
val => Instance.AllowPanningWhilePushingItems = val,
"Allow panning while pushing items: ",
"Whether panning is allowed while pushing items items in the editor."),
new ProxySettingViewModel<bool>(
() => Instance.AllowZoomingWhileSelecting,
val => Instance.AllowZoomingWhileSelecting = val,
"Allow zooming while selecting: ",
"Whether zooming is allowed while selecting items in the editor."),
new ProxySettingViewModel<bool>(
() => Instance.AllowZoomingWhileCutting,
val => Instance.AllowZoomingWhileCutting = val,
"Allow zooming while cutting: ",
"Whether zooming is allowed while cutting connections in the editor."),
new ProxySettingViewModel<bool>(
() => Instance.AllowZoomingWhilePushingItems,
val => Instance.AllowZoomingWhilePushingItems = val,
"Allow zooming while pushing items: ",
"Whether zooming is allowed while pushing items connections in the editor."),
new ProxySettingViewModel<bool>(
() => Instance.AllowZoomingWhilePanning,
val => Instance.AllowZoomingWhilePanning = val,
"Allow zooming while panning: ",
"Whether zooming is allowed while panning connections in the editor."),
};

EnableCuttingLinePreview = true;
Expand Down Expand Up @@ -623,6 +689,12 @@ public double AutoPanningTickRate
set => NodifyEditor.AutoPanningTickRate = value;
}

public bool AllowMinimapPanningCancellation
{
get => Minimap.AllowPanningCancellation;
set => Minimap.AllowPanningCancellation = value;
}

public bool AllowCuttingCancellation
{
get => NodifyEditor.AllowCuttingCancellation;
Expand Down Expand Up @@ -721,8 +793,80 @@ public bool EnableDraggingOptimizations

public bool EnableStickyConnectors
{
get => Connector.EnableStickyConnections;
set => Connector.EnableStickyConnections = value;
get => ConnectorState.EnableToggledConnectingMode;
set => ConnectorState.EnableToggledConnectingMode = value;
}

public bool EnableToggledPanning
{
get => EditorState.EnableToggledPanningMode;
set => EditorState.EnableToggledPanningMode = value;
}

public bool EnableToggledCutting
{
get => EditorState.EnableToggledCuttingMode;
set => EditorState.EnableToggledCuttingMode = value;
}

public bool EnableToggledPushingItems
{
get => EditorState.EnableToggledPushingItemsMode;
set => EditorState.EnableToggledPushingItemsMode = value;
}

public bool EnableToggledSelecting
{
get => EditorState.EnableToggledSelectingMode;
set => EditorState.EnableToggledSelectingMode = value;
}

public bool EnableMinimapToggledPanning
{
get => MinimapState.EnableToggledPanningMode;
set => MinimapState.EnableToggledPanningMode = value;
}

public bool AllowPanningWhileSelecting
{
get => EditorState.AllowPanningWhileSelecting;
set => EditorState.AllowPanningWhileSelecting = value;
}

public bool AllowPanningWhileCutting
{
get => EditorState.AllowPanningWhileCutting;
set => EditorState.AllowPanningWhileCutting = value;
}

public bool AllowPanningWhilePushingItems
{
get => EditorState.AllowPanningWhilePushingItems;
set => EditorState.AllowPanningWhilePushingItems = value;
}

public bool AllowZoomingWhileSelecting
{
get => EditorState.AllowZoomingWhileSelecting;
set => EditorState.AllowZoomingWhileSelecting = value;
}

public bool AllowZoomingWhileCutting
{
get => EditorState.AllowZoomingWhileCutting;
set => EditorState.AllowZoomingWhileCutting = value;
}

public bool AllowZoomingWhilePushingItems
{
get => EditorState.AllowZoomingWhilePushingItems;
set => EditorState.AllowZoomingWhilePushingItems = value;
}

public bool AllowZoomingWhilePanning
{
get => EditorState.AllowZoomingWhilePanning;
set => EditorState.AllowZoomingWhilePanning = value;
}

#endregion
Expand Down
12 changes: 12 additions & 0 deletions Examples/Nodify.StateMachine/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,13 @@
Spacing="0"
SourceOffsetMode="Edge"
TargetOffsetMode="Edge"
OutlineThickness="5"
Tag="{Binding}">
<nodify:LineConnection.Style>
<Style TargetType="{x:Type nodify:LineConnection}"
BasedOn="{StaticResource {x:Type nodify:LineConnection}}">
<Setter Property="OutlineBrush"
Value="Transparent" />
<Setter Property="StrokeThickness"
Value="3" />
<Style.Triggers>
Expand All @@ -88,6 +91,15 @@
<Setter Property="StrokeThickness"
Value="6" />
</DataTrigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="OutlineBrush">
<Setter.Value>
<SolidColorBrush Color="{StaticResource LineConnection.StrokeColor}"
Opacity="0.15" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</nodify:LineConnection.Style>
Expand Down
2 changes: 1 addition & 1 deletion Examples/Nodify.StateMachine/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public MainWindow()
{
InitializeComponent();

Connector.EnableStickyConnections = true;
ConnectorState.EnableToggledConnectingMode = true;
NodifyEditor.EnableCuttingLinePreview = true;

EditorGestures.Mappings.Connection.Disconnect.Value = MultiGesture.None;
Expand Down
16 changes: 16 additions & 0 deletions Nodify/Connections/BaseConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public abstract class BaseConnection : Shape

public static readonly DependencyProperty IsSelectableProperty = DependencyProperty.RegisterAttached("IsSelectable", typeof(bool), typeof(BaseConnection), new FrameworkPropertyMetadata(BoxValue.False));
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached("IsSelected", typeof(bool), typeof(BaseConnection), new FrameworkPropertyMetadata(BoxValue.False, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsSelectedChanged));
public static readonly DependencyProperty HasCustomContextMenuProperty = NodifyEditor.HasCustomContextMenuProperty.AddOwner(typeof(BaseConnection));

private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Expand Down Expand Up @@ -438,6 +439,21 @@ public FontStretch FontStretch
set => SetValue(FontStretchProperty, value);
}

/// <summary>
/// Gets or sets a value indicating whether the connection uses a custom context menu.
/// </summary>
/// <remarks>When set to true, the connection handles the right-click event for specific interactions.</remarks>
public bool HasCustomContextMenu
{
get => (bool)GetValue(HasCustomContextMenuProperty);
set => SetValue(HasCustomContextMenuProperty, value);
}

/// <summary>
/// Gets a value indicating whether the connection has a context menu.
/// </summary>
public bool HasContextMenu => ContextMenu != null || HasCustomContextMenu;

#endregion

#region Routed Events
Expand Down
1 change: 1 addition & 0 deletions Nodify/Connections/States/ConnectionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ internal static void RegisterDefaultHandlers()
{
InputProcessor.Shared<BaseConnection>.RegisterHandlerFactory(elem => new Disconnect(elem));
InputProcessor.Shared<BaseConnection>.RegisterHandlerFactory(elem => new Split(elem));
InputProcessor.Shared<BaseConnection>.RegisterHandlerFactory(elem => new Default(elem));
}
}
}
31 changes: 31 additions & 0 deletions Nodify/Connections/States/Default.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Windows.Input;

namespace Nodify.Interactivity
{
public static partial class ConnectionState
{
/// <summary>
/// Represents the default input state for a <see cref="BaseConnection"/>.
/// </summary>
public class Default : InputElementState<BaseConnection>
{
/// <summary>
/// Initializes a new instance of the <see cref="Default"/> class.
/// </summary>
/// <param name="connection">The <see cref="BaseConnection"/> element associated with this state.</param>
public Default(BaseConnection connection) : base(connection)
{
}

protected override void OnMouseDown(MouseButtonEventArgs e)
{
// Allow context menu to appear
if (e.ChangedButton == MouseButton.Right && Element.HasContextMenu)
{
Element.Focus();
e.Handled = true; // prevents the editor capturing the mouse
}
}
}
}
}
30 changes: 13 additions & 17 deletions Nodify/Connectors/Connector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public ICommand? DisconnectCommand
/// <summary>
/// Gets or sets a value indicating whether the connector uses a custom context menu.
/// </summary>
/// <remarks>When set to true, the connector handles the right-click event for specific operations.</remarks>
/// <remarks>When set to true, the connector handles the right-click event for specific interactions.</remarks>
public bool HasCustomContextMenu
{
get => (bool)GetValue(HasCustomContextMenuProperty);
Expand Down Expand Up @@ -157,11 +157,6 @@ public bool HasCustomContextMenu
/// </summary>
public static bool AllowPendingConnectionCancellation { get; set; } = true;

/// <summary>
/// Gets or sets whether the connection should be completed in two steps.
/// </summary>
public static bool EnableStickyConnections { get; set; }

private Point _lastUpdatedContainerPosition;
private Point _pendingConnectionEndPosition;
private bool _isHooked;
Expand Down Expand Up @@ -344,8 +339,8 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
{
InputProcessor.Process(e);

// Release the mouse capture if all the mouse buttons are released and there's no sticky connection pending
if (!IsPendingConnection && IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released)
// 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())
{
ReleaseMouseCapture();
}
Expand All @@ -368,22 +363,23 @@ protected override void OnKeyUp(KeyEventArgs e)
{
InputProcessor.Process(e);

if (!IsPendingConnection && IsMouseCaptured)
// 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())
{
ReleaseMouseCapture();
}
}

/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
InputProcessor.Process(e);
protected override void OnKeyDown(KeyEventArgs e)
=> InputProcessor.Process(e);

// Release the mouse capture if all the mouse buttons are released and there's no sticky connection pending
if (!IsPendingConnection && IsMouseCaptured && Mouse.RightButton == MouseButtonState.Released && Mouse.LeftButton == MouseButtonState.Released && Mouse.MiddleButton == MouseButtonState.Released)
{
ReleaseMouseCapture();
}
/// <summary>
/// Determines whether any toggled interaction is currently in progress.
/// </summary>
protected virtual bool IsToggledInteractionInProgress()
{
return ConnectorState.EnableToggledConnectingMode && IsPendingConnection;
}

#endregion
Expand Down
Loading

0 comments on commit f15a015

Please sign in to comment.