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

Smooth connections #149

Merged
merged 1 commit into from
Nov 16, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
> - Breaking Changes:
> - Features:
> - Added InputGroupStyle and OutputGroupStyle to Node
> - Added CornerRadius dependency property to LineConnection, CircuitConnection and StepConnection
> - Bugfixes:

#### **Version 6.5.0**
Expand Down
15 changes: 9 additions & 6 deletions Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -178,22 +178,25 @@
</Style>

<DataTemplate x:Key="CircuitConnectionTemplate">
<nodify:CircuitConnection Angle="{Binding CircuitConnectionAngle, Source={x:Static local:EditorSettings.Instance}}"
Style="{StaticResource ConnectionStyle}" />
<nodify:CircuitConnection Style="{StaticResource ConnectionStyle}"
Angle="{Binding CircuitConnectionAngle, Source={x:Static local:EditorSettings.Instance}}"
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}" />
</DataTemplate>

<DataTemplate x:Key="StepConnectionTemplate">
<nodify:StepConnection Style="{StaticResource ConnectionStyle}"
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}"
SourcePosition="{Binding ., Converter={StaticResource FlowToConnectorPositionConverter}, ConverterParameter=Output}"
TargetPosition="{Binding ., Converter={StaticResource FlowToConnectorPositionConverter}, ConverterParameter=Input}" />
</DataTemplate>

<DataTemplate x:Key="ConnectionTemplate">
<nodify:Connection Style="{StaticResource ConnectionStyle}" />
<DataTemplate x:Key="LineConnectionTemplate">
<nodify:LineConnection Style="{StaticResource ConnectionStyle}"
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}" />
</DataTemplate>

<DataTemplate x:Key="LineConnectionTemplate">
<nodify:LineConnection Style="{StaticResource ConnectionStyle}" />
<DataTemplate x:Key="ConnectionTemplate">
<nodify:Connection Style="{StaticResource ConnectionStyle}" />
</DataTemplate>

<ControlTemplate x:Key="SquareConnector"
Expand Down
12 changes: 12 additions & 0 deletions Examples/Nodify.Playground/EditorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ private EditorSettings()
val => Instance.CircuitConnectionAngle = val,
"Connection angle: ",
"Applies to circuit connection style"),
new ProxySettingViewModel<double>(
() => Instance.ConnectionCornerRadius,
val => Instance.ConnectionCornerRadius = val,
"Connection corner radius: ",
"The corner radius between the line segments."),
new ProxySettingViewModel<double>(
() => Instance.ConnectionSpacing,
val => Instance.ConnectionSpacing = val,
Expand Down Expand Up @@ -457,6 +462,13 @@ public double CircuitConnectionAngle
set => SetProperty(ref _circuitConnectionAngle, value);
}

private double _connectionCornerRadius = 10;
public double ConnectionCornerRadius
{
get => _connectionCornerRadius;
set => SetProperty(ref _connectionCornerRadius, value);
}

private double _connectionSpacing = 20;
public double ConnectionSpacing
{
Expand Down
15 changes: 12 additions & 3 deletions Nodify/Connections/CircuitConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,18 @@ protected override ((Point ArrowStartSource, Point ArrowStartTarget), (Point Arr
var (p0, p1, p2) = GetLinePoints(source, target);

context.BeginFigure(source, false, false);
context.LineTo(p0, true, true);
context.LineTo(p1, true, true);
context.LineTo(p2, true, true);
if (CornerRadius > 0)
{
AddSmoothCorner(context, source, p0, p1, CornerRadius);
AddSmoothCorner(context, p0, p1, p2, CornerRadius);
AddSmoothCorner(context, p1, p2, target, CornerRadius);
}
else
{
context.LineTo(p0, true, true);
context.LineTo(p1, true, true);
context.LineTo(p2, true, true);
}
context.LineTo(target, true, true);

if (Spacing < 1d)
Expand Down
47 changes: 45 additions & 2 deletions Nodify/Connections/LineConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ namespace Nodify
/// </summary>
public class LineConnection : BaseConnection
{
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(nameof(CornerRadius), typeof(double), typeof(LineConnection), new FrameworkPropertyMetadata(BoxValue.Double5, FrameworkPropertyMetadataOptions.AffectsRender));

/// <summary>
/// The radius of the corners between the line segments.
/// </summary>
public double CornerRadius
{
get => (double)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}

static LineConnection()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LineConnection), new FrameworkPropertyMetadata(typeof(LineConnection)));
Expand All @@ -21,8 +32,16 @@ protected override ((Point ArrowStartSource, Point ArrowStartTarget), (Point Arr
var (p0, p1) = GetLinePoints(source, target);

context.BeginFigure(source, false, false);
context.LineTo(p0, true, true);
context.LineTo(p1, true, true);
if (CornerRadius > 0 && Spacing > 0)
{
AddSmoothCorner(context, source, p0, p1, CornerRadius);
AddSmoothCorner(context, p0, p1, target, CornerRadius);
}
else
{
context.LineTo(p0, true, true);
context.LineTo(p1, true, true);
}
context.LineTo(target, true, true);

return ((target, source), (source, target));
Expand Down Expand Up @@ -126,5 +145,29 @@ protected static ((Point SegmentStart, Point SegmentEnd), Point InterpolatedPoin

return ((p1, p2), InterpolateLineSegment(p1, p2, (t - ratio1) / ratio2));
}

protected static void AddSmoothCorner(StreamGeometryContext context, Point start, Point corner, Point end, double radius)
{
double distAB = (corner - start).LengthSquared;
double distBC = (end - corner).LengthSquared;

double bendSize = Math.Sqrt(Math.Min(distAB, distBC)) / 2;
radius = Math.Min(bendSize, radius);

Vector directionToCorner = corner - start;
Vector directionFromCorner = end - corner;

if (directionToCorner.LengthSquared != 0)
directionToCorner.Normalize();

if (directionFromCorner.LengthSquared != 0)
directionFromCorner.Normalize();

Point curveStart = corner - directionToCorner * radius;
Point curveEnd = corner + directionFromCorner * radius;

context.LineTo(curveStart, true, true);
context.QuadraticBezierTo(corner, curveEnd, true, true);
}
}
}
44 changes: 38 additions & 6 deletions Nodify/Connections/StepConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,51 @@ protected override ((Point ArrowStartSource, Point ArrowStartTarget), (Point Arr
{
var (p0, p1, p2, p3) = GetLinePoints(source, target);

context.BeginFigure(source, false, false);
context.LineTo(p0, true, true);
context.LineTo(p1, true, true);
context.LineTo(p2, true, true);
context.LineTo(p3, true, true);
context.LineTo(target, true, true);
if (CornerRadius > 0)
{
DrawSmoothLine(context);
}
else
{
DrawDefaultLine(context);
}

if (Spacing < 1d)
{
return ((p1, source), (p2, target));
}

return ((target, source), (source, target));

void DrawDefaultLine(StreamGeometryContext context)
{
context.BeginFigure(source, false, false);
context.LineTo(p0, true, true);
context.LineTo(p1, true, true);
context.LineTo(p2, true, true);
context.LineTo(p3, true, true);
context.LineTo(target, true, true);
}

void DrawSmoothLine(StreamGeometryContext context)
{
context.BeginFigure(source, false, false);
AddSmoothCorner(context, source, p0, p1, CornerRadius);

if (p1 == p2)
{
// skip p1 or p2 because they overlap
AddSmoothCorner(context, p0, p1, p3, CornerRadius);
}
else
{
AddSmoothCorner(context, p0, p1, p2, CornerRadius);
AddSmoothCorner(context, p1, p2, p3, CornerRadius);
}

AddSmoothCorner(context, p2, p3, target, CornerRadius);
context.LineTo(target, true, true);
}
}

protected override Point GetTextPosition(FormattedText text, Point source, Point target)
Expand Down
Loading