Skip to content

Commit

Permalink
Merge pull request #90 from miroiu/feature/connections-text
Browse files Browse the repository at this point in the history
Allow displaying text on connections
  • Loading branch information
miroiu authored Feb 29, 2024
2 parents d4c550b + 77792bc commit d00ce70
Showing 8 changed files with 160 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@

> - Breaking Changes:
> - Features:
> - Added Text to BaseConnection, allowing displaying of text on connections
> - Added Foreground, FontSize, FontWeight, FontStyle, FontStretch and FontFamily to BaseConnection, allowing styling the displaying text
> - Bugfixes:
#### **Version 5.1.0**
1 change: 1 addition & 0 deletions Examples/Nodify.Playground/BaseSettingViewModel.cs
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ public BaseSettingViewModel(string name, string? description = default)
Description = description;
Type = typeof(T) switch
{
{ } t when t == typeof(string) => SettingsType.Text,
{ } t when t == typeof(bool) => SettingsType.Boolean,
{ } t when t == typeof(uint) || t == typeof(double) => SettingsType.Number,
{ } t when t == typeof(PointEditor) => SettingsType.Point,
1 change: 1 addition & 0 deletions Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Original file line number Diff line number Diff line change
@@ -95,6 +95,7 @@
<Setter Property="ArrowShape" Value="{Binding ArrowHeadShape, Source={x:Static local:EditorSettings.Instance}}" />
<Setter Property="Spacing" Value="{Binding ConnectionSpacing, Source={x:Static local:EditorSettings.Instance}}" />
<Setter Property="Direction" Value="{Binding Output.Flow, Converter={StaticResource FlowToDirectionConverter}}" />
<Setter Property="Text" Value="{Binding ConnectionText, Source={x:Static local:EditorSettings.Instance}}" />
</Style>

<DataTemplate x:Key="CircuitConnectionTemplate">
35 changes: 23 additions & 12 deletions Examples/Nodify.Playground/EditorSettings.cs
Original file line number Diff line number Diff line change
@@ -85,6 +85,14 @@ private EditorSettings()
val => Instance.AutoPanningEdgeDistance = val,
"Auto panning edge distance: ",
"Distance from edge to trigger auto panning"),
new ProxySettingViewModel<ConnectionStyle>(
() => Instance.ConnectionStyle,
val => Instance.ConnectionStyle = val,
"Connection style: "),
new ProxySettingViewModel<string>(
() => Instance.ConnectionText,
val => Instance.ConnectionText = val,
"Connection text: "),
new ProxySettingViewModel<double>(
() => Instance.CircuitConnectionAngle,
val => Instance.CircuitConnectionAngle = val,
@@ -100,10 +108,6 @@ private EditorSettings()
val => Instance.ConnectionArrowSize = val,
"Connection arrowhead size: ",
"The size of the arrowhead."),
new ProxySettingViewModel<ConnectionStyle>(
() => Instance.ConnectionStyle,
val => Instance.ConnectionStyle = val,
"Connection style: "),
new ProxySettingViewModel<ArrowHeadEnds>(
() => Instance.ArrowHeadEnds,
val => Instance.ArrowHeadEnds = val,
@@ -230,13 +234,6 @@ private EditorSettings()

#region Default settings

private ConnectionStyle _connectionStyle;
public ConnectionStyle ConnectionStyle
{
get => _connectionStyle;
set => SetProperty(ref _connectionStyle, value);
}

private bool _enablePendingConnectionSnapping = true;
public bool EnablePendingConnectionSnapping
{
@@ -335,6 +332,20 @@ public PointEditor Location
set => SetProperty(ref _location, value);
}

private ConnectionStyle _connectionStyle;
public ConnectionStyle ConnectionStyle
{
get => _connectionStyle;
set => SetProperty(ref _connectionStyle, value);
}

private string _connectionText;
public string ConnectionText
{
get => _connectionText;
set => SetProperty(ref _connectionText, value);
}

private double _circuitConnectionAngle = 45;
public double CircuitConnectionAngle
{
@@ -366,7 +377,7 @@ public ConnectionOffsetMode ConnectionTargetOffsetMode
private ArrowHeadEnds _arrowHeadEnds = ArrowHeadEnds.End;
public ArrowHeadEnds ArrowHeadEnds
{
get => _arrowHeadEnds;
get => _arrowHeadEnds;
set => SetProperty(ref _arrowHeadEnds, value);
}

3 changes: 2 additions & 1 deletion Examples/Nodify.Playground/ISettingViewModel.cs
Original file line number Diff line number Diff line change
@@ -5,7 +5,8 @@ public enum SettingsType
Boolean,
Number,
Option,
Point
Point,
Text
}

public interface ISettingViewModel
7 changes: 7 additions & 0 deletions Examples/Nodify.Playground/SettingsView.xaml
Original file line number Diff line number Diff line change
@@ -11,6 +11,9 @@
mc:Ignorable="d">
<ItemsControl ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.Resources>
<DataTemplate x:Key="TextEditorTemplate" DataType="{x:Type local:ISettingViewModel}">
<TextBox Text="{Binding Value}" TextWrapping="Wrap" AcceptsReturn="True" />
</DataTemplate>
<DataTemplate x:Key="NumberEditorTemplate" DataType="{x:Type local:ISettingViewModel}">
<TextBox Text="{Binding Value}" />
</DataTemplate>
@@ -56,6 +59,10 @@
<Setter Property="ContentTemplate"
Value="{StaticResource ResourceKey=OptionEditorTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="Text">
<Setter Property="ContentTemplate"
Value="{StaticResource ResourceKey=TextEditorTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
99 changes: 92 additions & 7 deletions Nodify/Connections/BaseConnection.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
@@ -120,6 +124,13 @@ public abstract class BaseConnection : Shape
public static readonly DependencyProperty ArrowShapeProperty = DependencyProperty.Register(nameof(ArrowShape), typeof(ArrowHeadShape), typeof(BaseConnection), new FrameworkPropertyMetadata(ArrowHeadShape.Arrowhead, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty SplitCommandProperty = DependencyProperty.Register(nameof(SplitCommand), typeof(ICommand), typeof(BaseConnection));
public static readonly DependencyProperty DisconnectCommandProperty = Connector.DisconnectCommandProperty.AddOwner(typeof(BaseConnection));
public static readonly DependencyProperty ForegroundProperty = TextBlock.ForegroundProperty.AddOwner(typeof(BaseConnection));
public static readonly DependencyProperty TextProperty = TextBlock.TextProperty.AddOwner(typeof(BaseConnection), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(typeof(BaseConnection));
public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(typeof(BaseConnection));
public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(typeof(BaseConnection));
public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(typeof(BaseConnection));
public static readonly DependencyProperty FontStretchtProperty = TextElement.FontStretchProperty.AddOwner(typeof(BaseConnection));

/// <summary>
/// Gets or sets the start point of this connection.
@@ -187,8 +198,8 @@ public ConnectionDirection Direction
/// <summary>
/// Gets or sets the arrowhead ends.
/// </summary>
public ArrowHeadEnds ArrowEnds
{
public ArrowHeadEnds ArrowEnds
{
get => (ArrowHeadEnds)GetValue(ArrowEndsProperty);
set => SetValue(ArrowEndsProperty, value);
}
@@ -240,6 +251,60 @@ public ICommand? DisconnectCommand
set => SetValue(DisconnectCommandProperty, value);
}

/// <summary>
/// The brush used to render the <see cref="Text"/>.
/// </summary>
public Brush? Foreground
{
get => (Brush?)GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value);
}

/// <summary>
/// Gets or sets the text contents of the <see cref="BaseConnection"/>.
/// </summary>
public string? Text
{
get => (string?)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}

/// <inheritdoc cref="TextElement.FontSize" />
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get => (double)GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}

/// <inheritdoc cref="TextElement.FontFamily" />
public FontFamily FontFamily
{
get => (FontFamily)GetValue(FontFamilyProperty);
set => SetValue(FontFamilyProperty, value);
}

/// <inheritdoc cref="TextElement.FontStyle" />
public FontStyle FontStyle
{
get => (FontStyle)GetValue(FontStyleProperty);
set => SetValue(FontStyleProperty, value);
}

/// <inheritdoc cref="TextElement.FontWeight" />
public FontWeight FontWeight
{
get => (FontWeight)GetValue(FontWeightProperty);
set => SetValue(FontWeightProperty, value);
}

/// <inheritdoc cref="TextElement.FontStretch" />
public FontStretch FontStretch
{
get => (FontStretch)GetValue(FontStretchtProperty);
set => SetValue(FontStretchtProperty, value);
}

#endregion

#region Routed Events
@@ -280,10 +345,7 @@ protected override Geometry DefiningGeometry
using (StreamGeometryContext context = _geometry.Open())
{
(Vector sourceOffset, Vector targetOffset) = GetOffset();
Point source = Source + sourceOffset;
Point target = Target + targetOffset;

var (arrowStart, arrowEnd) = DrawLineGeometry(context, source, target);
var (arrowStart, arrowEnd) = DrawLineGeometry(context, Source + sourceOffset, Target + targetOffset);

if (ArrowSize.Width != 0d && ArrowSize.Height != 0d)
{
@@ -352,7 +414,7 @@ protected virtual void DrawRectangleArrowhead(StreamGeometryContext context, Poi
double direction = arrowDirection == ConnectionDirection.Forward ? 1d : -1d;
var bottomRight = new Point(target.X, target.Y + headHeight);
var bottomLeft = new Point(target.X - headWidth * direction, target.Y + headHeight);
var topLeft = new Point(target.X - headWidth * direction, target.Y - headHeight);
var topLeft = new Point(target.X - headWidth * direction, target.Y - headHeight);
var topRight = new Point(target.X, target.Y - headHeight);

context.BeginFigure(target, true, true);
@@ -464,6 +526,15 @@ static Vector GetRectangleModeOffset(Vector delta, Size offset)
}
}

protected virtual Point GetTextPosition(FormattedText text)
{
(Vector sourceOffset, Vector targetOffset) = GetOffset();
Point source = Source + sourceOffset;
Point target = Target + targetOffset;

return new Point((source.X + target.X - text.Width) / 2, (source.Y + target.Y - text.Height) / 2);
}

protected override void OnMouseDown(MouseButtonEventArgs e)
{
Focus();
@@ -521,5 +592,19 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
ReleaseMouseCapture();
}
}

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

if (!string.IsNullOrEmpty(Text))
{
double dpi = VisualTreeHelper.GetDpi(this).PixelsPerDip;
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var text = new FormattedText(Text, CultureInfo.CurrentUICulture, FlowDirection, typeface, FontSize, Foreground ?? Stroke, dpi);

drawingContext.DrawText(text, GetTextPosition(text));
}
}
}
}
41 changes: 32 additions & 9 deletions Nodify/Connections/CircuitConnection.cs
Original file line number Diff line number Diff line change
@@ -29,14 +29,7 @@ static CircuitConnection()

protected override ((Point ArrowStartSource, Point ArrowStartTarget), (Point ArrowEndSource, Point ArrowEndTarget)) DrawLineGeometry(StreamGeometryContext context, Point source, Point target)
{
double direction = Direction == ConnectionDirection.Forward ? 1d : -1d;
var spacing = new Vector(Spacing * direction, 0d);
var arrowOffset = new Vector(ArrowSize.Width * direction, 0d);
Point endPoint = Spacing > 0 ? target - arrowOffset : target;

Point p1 = source + spacing;
Point p3 = endPoint - spacing;
Point p2 = GetControlPoint(p1, p3);
var (p1, p2, p3) = GetLinePoints(source, target);

context.BeginFigure(source, false, false);
context.LineTo(p1, true, true);
@@ -52,7 +45,21 @@ protected override ((Point ArrowStartSource, Point ArrowStartTarget), (Point Arr
return ((p1, source), (p2, target));
}

private Point GetControlPoint(Point source, Point target)
private (Point P1, Point P2, Point P3) GetLinePoints(in Point source, in Point target)
{
double direction = Direction == ConnectionDirection.Forward ? 1d : -1d;
var spacing = new Vector(Spacing * direction, 0d);
var arrowOffset = new Vector(ArrowSize.Width * direction, 0d);
Point endPoint = Spacing > 0 ? target - arrowOffset : target;

Point p1 = source + spacing;
Point p3 = endPoint - spacing;
Point p2 = GetControlPoint(p1, p3);

return (p1, p2, p3);
}

private Point GetControlPoint(in Point source, in Point target)
{
Vector delta = target - source;
double tangent = Math.Tan(Angle * Degrees);
@@ -84,5 +91,21 @@ private Point GetControlPoint(Point source, Point target)

return new Point(target.X, source.Y - slopeHeight);
}

protected override Point GetTextPosition(FormattedText text)
{
(Vector sourceOffset, Vector targetOffset) = GetOffset();
var (p1, p2, p3) = GetLinePoints(Source + sourceOffset, Target + targetOffset);

Vector deltaSource = p1 - p2;
Vector deltaTarget = p3 - p2;

if (deltaSource.LengthSquared > deltaTarget.LengthSquared)
{
return new Point((p1.X + p2.X - text.Width) / 2, (p1.Y + p2.Y - text.Height) / 2);
}

return new Point((p3.X + p2.X - text.Width) / 2, (p3.Y + p2.Y - text.Height) / 2);
}
}
}

0 comments on commit d00ce70

Please sign in to comment.