Skip to content

Commit

Permalink
Add CuttingLine control to Nodify (#127)
Browse files Browse the repository at this point in the history
* Added CuttingLine control to Nodify
  • Loading branch information
miroiu authored Jul 27, 2024
1 parent 2cb5cce commit 1c21e74
Show file tree
Hide file tree
Showing 32 changed files with 677 additions and 59 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

> - Breaking Changes:
> - Features:
> - Added a CuttingLine control that removes intersecting connections
> - Added CuttingLineStyle, CuttingStartedCommand, CuttingCompletedCommand, IsCutting, EnableCuttingLinePreview and CuttingConnectionTypes to NodifyEditor
> - Added EditorGestures.Editor.Cutting and EditorGestures.Editor.CancelAction
> - Bugfixes:
> - Fixed connection styles not inheriting from the BaseConnection style
#### **Version 6.2.0**

Expand Down
3 changes: 2 additions & 1 deletion Examples/Nodify.Calculator/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Windows;
using System.Windows.Input;

namespace Nodify.Calculator
{
Expand All @@ -8,6 +7,8 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();

EditorGestures.Mappings.Editor.Cutting.Value = MultiGesture.None;
}
}
}
15 changes: 13 additions & 2 deletions Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
Duration="0:0:0.3" From="1" To="0.3" />
</Storyboard>

<Style x:Key="ConnectionStyle" TargetType="{x:Type nodify:BaseConnection}">
<Style x:Key="ConnectionStyle" TargetType="{x:Type nodify:BaseConnection}"
BasedOn="{StaticResource {x:Type nodify:BaseConnection}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Input.Shape}"
Value="{x:Static local:ConnectorShape.Square}">
Expand Down Expand Up @@ -165,6 +166,15 @@
</EventTrigger>
</Style.Triggers>
</Style>

<Style x:Key="CuttingLineStyle"
TargetType="{x:Type nodify:CuttingLine}"
BasedOn="{StaticResource {x:Type nodify:CuttingLine}}">
<Setter Property="StrokeDashArray"
Value="1 1" />
<Setter Property="StrokeThickness"
Value="2" />
</Style>
</UserControl.Resources>

<Grid>
Expand All @@ -189,7 +199,8 @@
DisplayConnectionsOnTop="{Binding DisplayConnectionsOnTop, Source={x:Static local:EditorSettings.Instance}}"
BringIntoViewSpeed="{Binding BringIntoViewSpeed, Source={x:Static local:EditorSettings.Instance}}"
BringIntoViewMaxDuration="{Binding BringIntoViewMaxDuration, Source={x:Static local:EditorSettings.Instance}}"
SelectionRectangleStyle="{StaticResource SelectionRectangleStyle}">
SelectionRectangleStyle="{StaticResource SelectionRectangleStyle}"
CuttingLineStyle="{StaticResource CuttingLineStyle}">
<nodify:NodifyEditor.Style>
<Style TargetType="{x:Type nodify:NodifyEditor}"
BasedOn="{StaticResource {x:Type nodify:NodifyEditor}}">
Expand Down
13 changes: 12 additions & 1 deletion Examples/Nodify.Playground/EditorInputMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ public enum EditorInputMode
{
Default,
PanOnly,
SelectOnly
SelectOnly,
CutOnly
}

public enum EditorGesturesMappings
Expand All @@ -25,12 +26,22 @@ public static void Apply(this EditorGestures mappings, EditorInputMode inputMode
{
case EditorInputMode.PanOnly:
mappings.Editor.Selection.Apply(EditorGestures.SelectionGestures.None);
mappings.Editor.Cutting.Value = MultiGesture.None;
mappings.ItemContainer.Selection.Apply(EditorGestures.SelectionGestures.None);
mappings.ItemContainer.Drag.Value = MultiGesture.None;
mappings.Connector.Connect.Value = MultiGesture.None;
break;
case EditorInputMode.SelectOnly:
mappings.Editor.Pan.Value = MultiGesture.None;
mappings.Editor.Cutting.Value = MultiGesture.None;
mappings.ItemContainer.Drag.Value = MultiGesture.None;
mappings.Connector.Connect.Value = MultiGesture.None;
break;
case EditorInputMode.CutOnly:
mappings.Editor.Cutting.Value = new MouseGesture(MouseAction.LeftClick);
mappings.Editor.Selection.Apply(EditorGestures.SelectionGestures.None);
mappings.Editor.Pan.Value = MultiGesture.None;
mappings.ItemContainer.Selection.Apply(EditorGestures.SelectionGestures.None);
mappings.ItemContainer.Drag.Value = MultiGesture.None;
mappings.Connector.Connect.Value = MultiGesture.None;
break;
Expand Down
24 changes: 24 additions & 0 deletions Examples/Nodify.Playground/EditorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,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.AllowCuttingCancellation,
val => Instance.AllowCuttingCancellation = val,
"Allow cutting cancellation: ",
"Right click to cancel cutting."),
new ProxySettingViewModel<bool>(
() => Instance.AllowDraggingCancellation,
val => Instance.AllowDraggingCancellation = val,
Expand All @@ -193,6 +198,11 @@ private EditorSettings()
val => Instance.EnableSnappingCorrection = val,
"Enable snapping correction: ",
"Correct the final position when moving a selection"),
new ProxySettingViewModel<bool>(
() => Instance.EnableCuttingLinePreview,
val => Instance.EnableCuttingLinePreview = val,
"Enable cutting line preview: ",
"Applies custom connection style on intersection (hurts performance due to hit testing)."),
new ProxySettingViewModel<bool>(
() => Instance.EnableConnectorOptimizations,
val => Instance.EnableConnectorOptimizations = val,
Expand Down Expand Up @@ -239,6 +249,8 @@ private EditorSettings()
"Enable sticky connectors: ",
"The connection can be completed in two steps (e.g. click to create pending connection, click to connect)"),
};

EnableCuttingLinePreview = true;
}

public static EditorSettings Instance { get; } = new EditorSettings();
Expand Down Expand Up @@ -478,6 +490,12 @@ public double AutoPanningTickRate
set => NodifyEditor.AutoPanningTickRate = value;
}

public bool AllowCuttingCancellation
{
get => CuttingLine.AllowCuttingCancellation;
set => CuttingLine.AllowCuttingCancellation = value;
}

public bool AllowDraggingCancellation
{
get => ItemContainer.AllowDraggingCancellation;
Expand All @@ -496,6 +514,12 @@ public bool EnableSnappingCorrection
set => NodifyEditor.EnableSnappingCorrection = value;
}

public bool EnableCuttingLinePreview
{
get => NodifyEditor.EnableCuttingLinePreview;
set => NodifyEditor.EnableCuttingLinePreview = value;
}

public bool EnableConnectorOptimizations
{
get => Connector.EnableOptimizations;
Expand Down
1 change: 1 addition & 0 deletions Examples/Nodify.Shapes/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public MainWindow()
InitializeComponent();

NodifyEditor.EnableDraggingContainersOptimizations = false;
NodifyEditor.EnableCuttingLinePreview = true;
}
}
}
1 change: 1 addition & 0 deletions Examples/Nodify.StateMachine/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
PendingConnection="{Binding PendingTransition}"
DisconnectConnectorCommand="{Binding DisconnectStateCommand}"
ConnectionCompletedCommand="{Binding CreateTransitionCommand}"
RemoveConnectionCommand="{Binding DeleteTransitionCommand}"
Grid.Column="1">
<nodify:NodifyEditor.PendingConnectionTemplate>
<DataTemplate DataType="{x:Type local:TransitionViewModel}">
Expand Down
3 changes: 3 additions & 0 deletions Examples/Nodify.StateMachine/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ public MainWindow()
InitializeComponent();

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

EditorGestures.Mappings.Connection.Disconnect.Value = MultiGesture.None;
}
}
}
68 changes: 38 additions & 30 deletions Nodify/Connections/BaseConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -677,44 +677,52 @@ protected override void OnMouseDown(MouseButtonEventArgs e)
if (gestures.Split.Matches(e.Source, e))
{
Point splitLocation = e.GetPosition(this);
object? connection = DataContext;
var args = new ConnectionEventArgs(connection)
{
RoutedEvent = SplitEvent,
SplitLocation = splitLocation,
Source = this
};

RaiseEvent(args);

// Raise SplitCommand if SplitEvent is not handled
if (!args.Handled && (SplitCommand?.CanExecute(splitLocation) ?? false))
{
SplitCommand.Execute(splitLocation);
}
OnSplit(splitLocation);

e.Handled = true;
}
else if (gestures.Disconnect.Matches(e.Source, e))
{
Point splitLocation = e.GetPosition(this);
object? connection = DataContext;
var args = new ConnectionEventArgs(connection)
{
RoutedEvent = DisconnectEvent,
SplitLocation = splitLocation,
Source = this
};
OnDisconnect();

RaiseEvent(args);
e.Handled = true;
}
}

// Raise DisconnectCommand if DisconnectEvent is not handled
if (!args.Handled && (DisconnectCommand?.CanExecute(splitLocation) ?? false))
{
DisconnectCommand.Execute(splitLocation);
}
protected internal void OnSplit(Point splitLocation)
{
object? connection = DataContext;
var args = new ConnectionEventArgs(connection)
{
RoutedEvent = SplitEvent,
SplitLocation = splitLocation,
Source = this
};

e.Handled = true;
RaiseEvent(args);

// Raise SplitCommand if SplitEvent is not handled
if (!args.Handled && (SplitCommand?.CanExecute(splitLocation) ?? false))
{
SplitCommand.Execute(splitLocation);
}
}

protected internal void OnDisconnect()
{
object? connection = DataContext;
var args = new ConnectionEventArgs(connection)
{
RoutedEvent = DisconnectEvent,
Source = this
};

RaiseEvent(args);

// Raise DisconnectCommand if DisconnectEvent is not handled
if (!args.Handled && (DisconnectCommand?.CanExecute(null) ?? false))
{
DisconnectCommand.Execute(null);
}
}

Expand Down
1 change: 1 addition & 0 deletions Nodify/Connections/CircuitConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public double Angle
static CircuitConnection()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CircuitConnection), new FrameworkPropertyMetadata(typeof(CircuitConnection)));
NodifyEditor.CuttingConnectionTypes.Add(typeof(CircuitConnection));
}

protected override ((Point ArrowStartSource, Point ArrowStartTarget), (Point ArrowEndSource, Point ArrowEndTarget)) DrawLineGeometry(StreamGeometryContext context, Point source, Point target)
Expand Down
1 change: 1 addition & 0 deletions Nodify/Connections/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class Connection : BaseConnection
static Connection()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Connection), new FrameworkPropertyMetadata(typeof(Connection)));
NodifyEditor.CuttingConnectionTypes.Add(typeof(Connection));
}

private const double _baseOffset = 100d;
Expand Down
79 changes: 79 additions & 0 deletions Nodify/Connections/CuttingLine.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Nodify
{
public class CuttingLine : Shape
{
public static readonly DependencyProperty StartPointProperty = DependencyProperty.Register(nameof(StartPoint), typeof(Point), typeof(CuttingLine), new FrameworkPropertyMetadata(BoxValue.Point, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty EndPointProperty = DependencyProperty.Register(nameof(EndPoint), typeof(Point), typeof(CuttingLine), new FrameworkPropertyMetadata(BoxValue.Point, FrameworkPropertyMetadataOptions.AffectsRender));

/// <summary>
/// Will be set for <see cref="BaseConnection"/>s and custom connections when the cutting line intersects with them if <see cref="NodifyEditor.EnableCuttingLinePreview"/> is true.
/// </summary>
public static readonly DependencyProperty IsOverElementProperty = PendingConnection.IsOverElementProperty.AddOwner(typeof(CuttingLine));

public static bool GetIsOverElement(UIElement elem)
=> (bool)elem.GetValue(IsOverElementProperty);

public static void SetIsOverElement(UIElement elem, bool value)
=> elem.SetValue(IsOverElementProperty, value);

/// <summary>
/// Gets or sets whether cancelling a cutting operation is allowed.
/// </summary>
public static bool AllowCuttingCancellation { get; set; } = true;

/// <summary>
/// Gets or sets the start point.
/// </summary>
public Point StartPoint
{
get => (Point)GetValue(StartPointProperty);
set => SetValue(StartPointProperty, value);
}

/// <summary>
/// Gets or sets the end point.
/// </summary>
public Point EndPoint
{
get => (Point)GetValue(EndPointProperty);
set => SetValue(EndPointProperty, value);
}

private readonly StreamGeometry _geometry = new StreamGeometry
{
FillRule = FillRule.EvenOdd
};

protected override Geometry DefiningGeometry
{
get
{
using (StreamGeometryContext context = _geometry.Open())
{
context.BeginFigure(StartPoint, false, false);
context.LineTo(EndPoint, true, true);
}

return _geometry;
}
}

static CuttingLine()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CuttingLine), new FrameworkPropertyMetadata(typeof(CuttingLine)));
IsHitTestVisibleProperty.OverrideMetadata(typeof(CuttingLine), new FrameworkPropertyMetadata(BoxValue.False));
}

protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);

drawingContext.DrawEllipse(Fill, null, StartPoint, StrokeThickness * 1.2, StrokeThickness * 1.2);
drawingContext.DrawEllipse(Fill, null, EndPoint, StrokeThickness * 1.2, StrokeThickness * 1.2);
}
}
}
1 change: 1 addition & 0 deletions Nodify/Connections/LineConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class LineConnection : BaseConnection
static LineConnection()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LineConnection), new FrameworkPropertyMetadata(typeof(LineConnection)));
NodifyEditor.CuttingConnectionTypes.Add(typeof(LineConnection));
}

protected override ((Point ArrowStartSource, Point ArrowStartTarget), (Point ArrowEndSource, Point ArrowEndTarget)) DrawLineGeometry(StreamGeometryContext context, Point source, Point target)
Expand Down
1 change: 1 addition & 0 deletions Nodify/Connections/StepConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ static StepConnection()
SourceOrientationProperty.OverrideMetadata(typeof(StepConnection), new FrameworkPropertyMetadata(Orientation.Horizontal, null, CoerceSourceOrientation));
TargetOrientationProperty.OverrideMetadata(typeof(StepConnection), new FrameworkPropertyMetadata(Orientation.Horizontal, null, CoerceTargetOrientation));
DirectionProperty.OverrideMetadata(typeof(StepConnection), new FrameworkPropertyMetadata(ConnectionDirection.Forward, null, CoerceConnectionDirection));
NodifyEditor.CuttingConnectionTypes.Add(typeof(StepConnection));
}

private static object CoerceSourceOrientation(DependencyObject d, object baseValue)
Expand Down
Loading

0 comments on commit 1c21e74

Please sign in to comment.