Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CuttingLine control to Nodify #127

Merged
merged 3 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading