Add project files.
This commit is contained in:
21
Examples/Nodify.Playground/Editor/CommentNodeViewModel.cs
Normal file
21
Examples/Nodify.Playground/Editor/CommentNodeViewModel.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class CommentNodeViewModel : NodeViewModel
|
||||
{
|
||||
private string? _title;
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
private Size _size;
|
||||
public Size Size
|
||||
{
|
||||
get => _size;
|
||||
set => SetProperty(ref _size, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Examples/Nodify.Playground/Editor/ConnectionViewModel.cs
Normal file
51
Examples/Nodify.Playground/Editor/ConnectionViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class ConnectionViewModel : ObservableObject
|
||||
{
|
||||
private NodifyEditorViewModel _graph = default!;
|
||||
public NodifyEditorViewModel Graph
|
||||
{
|
||||
get => _graph;
|
||||
internal set => SetProperty(ref _graph, value);
|
||||
}
|
||||
|
||||
private ConnectorViewModel _input = default!;
|
||||
public ConnectorViewModel Input
|
||||
{
|
||||
get => _input;
|
||||
set => SetProperty(ref _input, value);
|
||||
}
|
||||
|
||||
private ConnectorViewModel _output = default!;
|
||||
public ConnectorViewModel Output
|
||||
{
|
||||
get => _output;
|
||||
set => SetProperty(ref _output, value);
|
||||
}
|
||||
|
||||
private bool _isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public ICommand SplitCommand { get; }
|
||||
public ICommand DisconnectCommand { get; }
|
||||
|
||||
public ConnectionViewModel()
|
||||
{
|
||||
SplitCommand = new DelegateCommand<Point>(Split);
|
||||
DisconnectCommand = new DelegateCommand(Remove);
|
||||
}
|
||||
|
||||
public void Split(Point point)
|
||||
=> Graph.Schema.SplitConnection(this, point);
|
||||
|
||||
public void Remove()
|
||||
=> Graph.Connections.Remove(this);
|
||||
}
|
||||
}
|
||||
109
Examples/Nodify.Playground/Editor/ConnectorViewModel.cs
Normal file
109
Examples/Nodify.Playground/Editor/ConnectorViewModel.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public enum ConnectorFlow
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
}
|
||||
|
||||
public enum ConnectorShape
|
||||
{
|
||||
Circle,
|
||||
Triangle,
|
||||
Square,
|
||||
}
|
||||
|
||||
public class ConnectorViewModel : ObservableObject
|
||||
{
|
||||
private string? _title;
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private Point _anchor;
|
||||
public Point Anchor
|
||||
{
|
||||
get => _anchor;
|
||||
set => SetProperty(ref _anchor, value);
|
||||
}
|
||||
|
||||
private NodeViewModel _node = default!;
|
||||
public NodeViewModel Node
|
||||
{
|
||||
get => _node;
|
||||
internal set
|
||||
{
|
||||
if (SetProperty(ref _node, value))
|
||||
{
|
||||
OnNodeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConnectorShape _shape;
|
||||
public ConnectorShape Shape
|
||||
{
|
||||
get => _shape;
|
||||
set => SetProperty(ref _shape, value);
|
||||
}
|
||||
|
||||
public ConnectorFlow Flow { get; private set; }
|
||||
|
||||
public int MaxConnections { get; set; } = 2;
|
||||
|
||||
public NodifyObservableCollection<ConnectionViewModel> Connections { get; } = new NodifyObservableCollection<ConnectionViewModel>();
|
||||
|
||||
public ConnectorViewModel()
|
||||
{
|
||||
Connections.WhenAdded(c =>
|
||||
{
|
||||
c.Input.IsConnected = true;
|
||||
c.Output.IsConnected = true;
|
||||
}).WhenRemoved(c =>
|
||||
{
|
||||
if (c.Input.Connections.Count == 0)
|
||||
{
|
||||
c.Input.IsConnected = false;
|
||||
}
|
||||
|
||||
if (c.Output.Connections.Count == 0)
|
||||
{
|
||||
c.Output.IsConnected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void OnNodeChanged()
|
||||
{
|
||||
if (Node is FlowNodeViewModel flow)
|
||||
{
|
||||
Flow = flow.Input.Contains(this) ? ConnectorFlow.Input : ConnectorFlow.Output;
|
||||
}
|
||||
else if (Node is KnotNodeViewModel knot)
|
||||
{
|
||||
Flow = knot.Flow;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConnectedTo(ConnectorViewModel con)
|
||||
=> Connections.Any(c => c.Input == con || c.Output == con);
|
||||
|
||||
public virtual bool AllowsNewConnections()
|
||||
=> Connections.Count < MaxConnections;
|
||||
|
||||
public void Disconnect()
|
||||
=> Node.Graph.Schema.DisconnectConnector(this);
|
||||
}
|
||||
}
|
||||
34
Examples/Nodify.Playground/Editor/FlowNodeViewModel.cs
Normal file
34
Examples/Nodify.Playground/Editor/FlowNodeViewModel.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class FlowNodeViewModel : NodeViewModel
|
||||
{
|
||||
private string? _title;
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
public NodifyObservableCollection<ConnectorViewModel> Input { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||
public NodifyObservableCollection<ConnectorViewModel> Output { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||
|
||||
public FlowNodeViewModel()
|
||||
{
|
||||
Orientation = Orientation.Horizontal;
|
||||
|
||||
Input.WhenAdded(c => c.Node = this)
|
||||
.WhenRemoved(c => c.Disconnect());
|
||||
|
||||
Output.WhenAdded(c => c.Node = this)
|
||||
.WhenRemoved(c => c.Disconnect());
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Input.Clear();
|
||||
Output.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Examples/Nodify.Playground/Editor/GraphSchema.cs
Normal file
133
Examples/Nodify.Playground/Editor/GraphSchema.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class GraphSchema
|
||||
{
|
||||
#region Add Connection
|
||||
|
||||
public bool CanAddConnection(ConnectorViewModel source, object target)
|
||||
{
|
||||
if (target is ConnectorViewModel con)
|
||||
{
|
||||
return source != con
|
||||
&& source.Node != con.Node
|
||||
&& source.Node.Graph == con.Node.Graph
|
||||
&& source.Shape == con.Shape
|
||||
&& source.AllowsNewConnections()
|
||||
&& con.AllowsNewConnections()
|
||||
&& (source.Flow != con.Flow || con.Node is KnotNodeViewModel)
|
||||
&& !source.IsConnectedTo(con);
|
||||
}
|
||||
else if (source.AllowsNewConnections() && target is FlowNodeViewModel node)
|
||||
{
|
||||
var allConnectors = source.Flow == ConnectorFlow.Input ? node.Output : node.Input;
|
||||
return allConnectors.Any(c => c.AllowsNewConnections());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryAddConnection(ConnectorViewModel source, object? target)
|
||||
{
|
||||
if (target != null && CanAddConnection(source, target))
|
||||
{
|
||||
if (target is ConnectorViewModel connector)
|
||||
{
|
||||
AddConnection(source, connector);
|
||||
return true;
|
||||
}
|
||||
else if (target is FlowNodeViewModel node)
|
||||
{
|
||||
AddConnection(source, node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AddConnection(ConnectorViewModel source, ConnectorViewModel target)
|
||||
{
|
||||
var sourceIsInput = source.Flow == ConnectorFlow.Input;
|
||||
|
||||
source.Node.Graph.Connections.Add(new ConnectionViewModel
|
||||
{
|
||||
Input = sourceIsInput ? source : target,
|
||||
Output = sourceIsInput ? target : source
|
||||
});
|
||||
}
|
||||
|
||||
private void AddConnection(ConnectorViewModel source, FlowNodeViewModel target)
|
||||
{
|
||||
var allConnectors = source.Flow == ConnectorFlow.Input ? target.Output : target.Input;
|
||||
var connector = allConnectors.First(c => c.AllowsNewConnections());
|
||||
|
||||
AddConnection(source, connector);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void DisconnectConnector(ConnectorViewModel connector)
|
||||
{
|
||||
var graph = connector.Node.Graph;
|
||||
var connections = connector.Connections.ToList();
|
||||
connections.ForEach(c => graph.Connections.Remove(c));
|
||||
}
|
||||
|
||||
public void SplitConnection(ConnectionViewModel connection, Point location)
|
||||
{
|
||||
var knot = new KnotNodeViewModel(connection.Output.Node.Orientation)
|
||||
{
|
||||
Location = location,
|
||||
Flow = connection.Output.Flow,
|
||||
Connector = new ConnectorViewModel
|
||||
{
|
||||
MaxConnections = connection.Output.MaxConnections + connection.Input.MaxConnections,
|
||||
Shape = connection.Input.Shape
|
||||
}
|
||||
};
|
||||
connection.Graph.Nodes.Add(knot);
|
||||
|
||||
AddConnection(connection.Output, knot.Connector);
|
||||
AddConnection(knot.Connector, connection.Input);
|
||||
|
||||
connection.Remove();
|
||||
}
|
||||
|
||||
public void AddCommentAroundNodes(IList<NodeViewModel> nodes, string? text = default)
|
||||
{
|
||||
var rect = nodes.GetBoundingBox(50);
|
||||
var comment = new CommentNodeViewModel
|
||||
{
|
||||
Location = rect.Location,
|
||||
Size = rect.Size,
|
||||
Title = text ?? "New comment"
|
||||
};
|
||||
|
||||
nodes[0].Graph.Nodes.Add(comment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rewires all connections from the source connector to the target connector if possible.
|
||||
/// </summary>
|
||||
/// <remarks>The source must be an input connector.</remarks>
|
||||
public void Rewire(ConnectorViewModel source, ConnectorViewModel target)
|
||||
{
|
||||
if (source == target || source.Flow != ConnectorFlow.Input)
|
||||
return;
|
||||
|
||||
var connectionsToRewire = source.Connections.ToList();
|
||||
foreach (var connection in connectionsToRewire)
|
||||
{
|
||||
if (CanAddConnection(connection.Output, target))
|
||||
{
|
||||
source.Node.Graph.Connections.Remove(connection);
|
||||
AddConnection(connection.Output, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Examples/Nodify.Playground/Editor/KnotNodeViewModel.cs
Normal file
31
Examples/Nodify.Playground/Editor/KnotNodeViewModel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class KnotNodeViewModel : NodeViewModel
|
||||
{
|
||||
public KnotNodeViewModel(Orientation orientation)
|
||||
{
|
||||
Orientation = orientation;
|
||||
}
|
||||
|
||||
public KnotNodeViewModel() : this(Orientation.Horizontal)
|
||||
{
|
||||
}
|
||||
|
||||
private ConnectorViewModel _connector = default!;
|
||||
public ConnectorViewModel Connector
|
||||
{
|
||||
get => _connector;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _connector, value))
|
||||
{
|
||||
_connector.Node = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectorFlow Flow { get; set; }
|
||||
}
|
||||
}
|
||||
32
Examples/Nodify.Playground/Editor/NodeViewModel.cs
Normal file
32
Examples/Nodify.Playground/Editor/NodeViewModel.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public abstract class NodeViewModel : ObservableObject
|
||||
{
|
||||
private NodifyEditorViewModel _graph = default!;
|
||||
public NodifyEditorViewModel Graph
|
||||
{
|
||||
get => _graph;
|
||||
internal set => SetProperty(ref _graph, value);
|
||||
}
|
||||
|
||||
private Point _location;
|
||||
public Point Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
public Orientation Orientation { get; protected set; }
|
||||
|
||||
public ICommand DeleteCommand { get; }
|
||||
|
||||
public NodeViewModel()
|
||||
{
|
||||
DeleteCommand = new DelegateCommand(() => Graph.Nodes.Remove(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
735
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Normal file
735
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Normal file
@@ -0,0 +1,735 @@
|
||||
<UserControl x:Class="Nodify.Playground.NodifyEditorView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Nodify.Playground"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
mc:Ignorable="d"
|
||||
Background="{DynamicResource NodifyEditor.BackgroundBrush}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
|
||||
<UserControl.DataContext>
|
||||
<local:NodifyEditorViewModel />
|
||||
</UserControl.DataContext>
|
||||
|
||||
<UserControl.Resources>
|
||||
<shared:RandomBrushConverter x:Key="RandomBrushConverter" />
|
||||
<local:FlowToDirectionConverter x:Key="FlowToDirectionConverter" />
|
||||
<local:FlowToConnectorPositionConverter x:Key="FlowToConnectorPositionConverter" />
|
||||
|
||||
<GeometryDrawing x:Key="SmallGridGeometry"
|
||||
Geometry="M0,0 L0,1 0.03,1 0.03,0.03 1,0.03 1,0 Z"
|
||||
Brush="{DynamicResource GridLinesBrush}" />
|
||||
|
||||
<GeometryDrawing x:Key="LargeGridGeometry"
|
||||
Geometry="M0,0 L0,1 0.015,1 0.015,0.015 1,0.015 1,0 Z"
|
||||
Brush="{DynamicResource GridLinesBrush}" />
|
||||
|
||||
<DrawingBrush x:Key="SmallGridLinesDrawingBrush"
|
||||
TileMode="Tile"
|
||||
ViewportUnits="Absolute"
|
||||
Viewport="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}, Converter={local:UIntToRectConverter}}"
|
||||
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||
Drawing="{StaticResource SmallGridGeometry}" />
|
||||
|
||||
<DrawingBrush x:Key="LargeGridLinesDrawingBrush"
|
||||
TileMode="Tile"
|
||||
ViewportUnits="Absolute"
|
||||
Opacity="0.5"
|
||||
Viewport="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}, Converter={local:UIntToRectConverter Multiplier=10}}"
|
||||
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||
Drawing="{StaticResource LargeGridGeometry}" />
|
||||
|
||||
<SolidColorBrush x:Key="SquareConnectorColor"
|
||||
Color="MediumSlateBlue" />
|
||||
<SolidColorBrush x:Key="TriangleConnectorColor"
|
||||
Color="MediumVioletRed" />
|
||||
<SolidColorBrush x:Key="SquareConnectorOutline"
|
||||
Color="MediumSlateBlue"
|
||||
Opacity="0.15" />
|
||||
<SolidColorBrush x:Key="TriangleConnectorOutline"
|
||||
Color="MediumVioletRed"
|
||||
Opacity="0.15" />
|
||||
|
||||
<UIElement x:Key="ConnectionAnimationPlaceholder"
|
||||
Opacity="1" />
|
||||
|
||||
<Storyboard x:Key="HighlightConnection">
|
||||
<DoubleAnimation Storyboard.Target="{StaticResource ConnectionAnimationPlaceholder}"
|
||||
Storyboard.TargetProperty="(UIElement.Opacity)"
|
||||
Duration="0:0:0.3"
|
||||
From="1"
|
||||
To="0.3" />
|
||||
</Storyboard>
|
||||
|
||||
<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}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
<Setter Property="OutlineBrush"
|
||||
Value="{StaticResource SquareConnectorOutline}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Input.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
<Setter Property="OutlineBrush"
|
||||
Value="{StaticResource TriangleConnectorOutline}" />
|
||||
</DataTrigger>
|
||||
<Trigger Property="IsMouseDirectlyOver"
|
||||
Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard Name="HighlightConnection"
|
||||
Storyboard="{StaticResource HighlightConnection}" />
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<RemoveStoryboard BeginStoryboardName="HighlightConnection" />
|
||||
</Trigger.ExitActions>
|
||||
<Setter Property="Opacity"
|
||||
Value="1" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelectable"
|
||||
Value="True">
|
||||
<Setter Property="Cursor"
|
||||
Value="Hand" />
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseDirectlyOver"
|
||||
Value="False" />
|
||||
<Condition Property="IsSelected"
|
||||
Value="False" />
|
||||
</MultiTrigger.Conditions>
|
||||
<MultiTrigger.Setters>
|
||||
<Setter Property="OutlineBrush"
|
||||
Value="Transparent" />
|
||||
</MultiTrigger.Setters>
|
||||
</MultiTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="Opacity"
|
||||
Value="{Binding Source={StaticResource ConnectionAnimationPlaceholder}, Path=Opacity}" />
|
||||
<Setter Property="Stroke"
|
||||
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||
<Setter Property="OutlineBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="{DynamicResource Connection.StrokeColor}"
|
||||
Opacity="0.15" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="ToolTip"
|
||||
Value="Double click to split" />
|
||||
<Setter Property="Source"
|
||||
Value="{Binding Output.Anchor}" />
|
||||
<Setter Property="Target"
|
||||
Value="{Binding Input.Anchor}" />
|
||||
<Setter Property="SplitCommand"
|
||||
Value="{Binding SplitCommand}" />
|
||||
<Setter Property="DisconnectCommand"
|
||||
Value="{Binding DisconnectCommand}" />
|
||||
<Setter Property="SourceOffsetMode"
|
||||
Value="{Binding ConnectionSourceOffsetMode, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="TargetOffsetMode"
|
||||
Value="{Binding ConnectionTargetOffsetMode, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="SourceOffset"
|
||||
Value="{Binding ConnectionSourceOffset.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="TargetOffset"
|
||||
Value="{Binding ConnectionTargetOffset.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="ArrowSize"
|
||||
Value="{Binding ConnectionArrowSize.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="ArrowEnds"
|
||||
Value="{Binding ArrowHeadEnds, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<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="SourceOrientation"
|
||||
Value="{Binding Output.Node.Orientation}" />
|
||||
<Setter Property="TargetOrientation"
|
||||
Value="{Binding Input.Node.Orientation}" />
|
||||
<Setter Property="DirectionalArrowsCount"
|
||||
Value="{Binding DirectionalArrowsCount, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="DirectionalArrowsOffset"
|
||||
Value="{Binding DirectionalArrowsOffset, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="IsAnimatingDirectionalArrows"
|
||||
Value="{Binding IsAnimatingConnections, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="DirectionalArrowsAnimationDuration"
|
||||
Value="{Binding DirectionalArrowsAnimationDuration, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="Text"
|
||||
Value="{Binding ConnectionText, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="IsSelectable"
|
||||
Value="{Binding SelectableConnections, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="IsSelected"
|
||||
Value="{Binding IsSelected}" />
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="{Binding ConnectionStrokeThickness, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="OutlineThickness"
|
||||
Value="{Binding ConnectionOutlineThickness, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="FocusVisualPadding"
|
||||
Value="{Binding ConnectionFocusVisualPadding, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="CircuitConnectionTemplate">
|
||||
<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="LineConnectionTemplate">
|
||||
<nodify:LineConnection Style="{StaticResource ConnectionStyle}"
|
||||
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ConnectionTemplate">
|
||||
<nodify:Connection Style="{StaticResource ConnectionStyle}" />
|
||||
</DataTemplate>
|
||||
|
||||
<ControlTemplate x:Key="SquareConnector"
|
||||
TargetType="Control">
|
||||
<Rectangle Width="14"
|
||||
Height="14"
|
||||
StrokeDashCap="Round"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
Fill="{TemplateBinding Background}"
|
||||
StrokeThickness="2" />
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TriangleConnector"
|
||||
TargetType="Control">
|
||||
<Polygon Width="14"
|
||||
Height="14"
|
||||
Points="1,13 13,13 7,1"
|
||||
StrokeDashCap="Round"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
Fill="{TemplateBinding Background}"
|
||||
StrokeThickness="2" />
|
||||
</ControlTemplate>
|
||||
|
||||
<Storyboard x:Key="MarchingAnts">
|
||||
<DoubleAnimation RepeatBehavior="Forever"
|
||||
Storyboard.TargetProperty="StrokeDashOffset"
|
||||
BeginTime="00:00:00"
|
||||
Duration="0:3:0"
|
||||
From="1000"
|
||||
To="0" />
|
||||
</Storyboard>
|
||||
|
||||
<Style x:Key="SelectionRectangleStyle"
|
||||
TargetType="Rectangle"
|
||||
BasedOn="{StaticResource NodifyEditor.SelectionRectangleStyle}">
|
||||
<Setter Property="StrokeDashArray"
|
||||
Value="4 4" />
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="2" />
|
||||
<Style.Triggers>
|
||||
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
|
||||
<BeginStoryboard Storyboard="{StaticResource MarchingAnts}" />
|
||||
</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>
|
||||
<nodify:NodifyEditor x:Name="Editor"
|
||||
ItemsSource="{Binding Nodes}"
|
||||
SelectedItem="{Binding SelectedNode}"
|
||||
SelectedItems="{Binding SelectedNodes}"
|
||||
CanSelectMultipleItems="{Binding CanSelectMultipleNodes, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Connections="{Binding Connections}"
|
||||
SelectedConnection="{Binding SelectedConnection}"
|
||||
SelectedConnections="{Binding SelectedConnections}"
|
||||
CanSelectMultipleConnections="{Binding CanSelectMultipleConnections, Source={x:Static local:EditorSettings.Instance}}"
|
||||
PendingConnection="{Binding PendingConnection}"
|
||||
DisconnectConnectorCommand="{Binding DisconnectConnectorCommand}"
|
||||
ViewportLocation="{Binding Location.Value, Source={x:Static local:EditorSettings.Instance}}"
|
||||
ViewportSize="{Binding ViewportSize, Mode=OneWayToSource}"
|
||||
ViewportZoom="{Binding Zoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||
MinViewportZoom="{Binding MinZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||
MaxViewportZoom="{Binding MaxZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||
AutoPanSpeed="{Binding AutoPanningSpeed, Source={x:Static local:EditorSettings.Instance}}"
|
||||
AutoPanEdgeDistance="{Binding AutoPanningEdgeDistance, Source={x:Static local:EditorSettings.Instance}}"
|
||||
GridCellSize="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}}"
|
||||
EnableRealtimeSelection="{Binding EnableRealtimeSelection, Source={x:Static local:EditorSettings.Instance}}"
|
||||
DisableAutoPanning="{Binding DisableAutoPanning, Source={x:Static local:EditorSettings.Instance}}"
|
||||
DisablePanning="{Binding DisablePanning, Source={x:Static local:EditorSettings.Instance}}"
|
||||
DisableZooming="{Binding DisableZooming, Source={x:Static local:EditorSettings.Instance}}"
|
||||
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}"
|
||||
CuttingLineStyle="{StaticResource CuttingLineStyle}">
|
||||
<nodify:NodifyEditor.Style>
|
||||
<Style TargetType="{x:Type nodify:NodifyEditor}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodifyEditor}}">
|
||||
<Setter Property="ConnectionTemplate"
|
||||
Value="{StaticResource ConnectionTemplate}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowGridLines, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||
Value="True">
|
||||
<Setter Property="Background"
|
||||
Value="{StaticResource SmallGridLinesDrawingBrush}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Value="Line">
|
||||
<Setter Property="ConnectionTemplate"
|
||||
Value="{StaticResource LineConnectionTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Value="Circuit">
|
||||
<Setter Property="ConnectionTemplate"
|
||||
Value="{StaticResource CircuitConnectionTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Value="Step">
|
||||
<Setter Property="ConnectionTemplate"
|
||||
Value="{StaticResource StepConnectionTemplate}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:NodifyEditor.Style>
|
||||
|
||||
<nodify:NodifyEditor.InputBindings>
|
||||
<KeyBinding Key="Delete"
|
||||
Command="{Binding DeleteSelectionCommand}" />
|
||||
<KeyBinding Key="C"
|
||||
Command="{Binding CommentSelectionCommand}" />
|
||||
</nodify:NodifyEditor.InputBindings>
|
||||
|
||||
<nodify:NodifyEditor.Resources>
|
||||
<Style TargetType="{x:Type nodify:PendingConnection}"
|
||||
BasedOn="{StaticResource {x:Type nodify:PendingConnection}}">
|
||||
<Setter Property="CompletedCommand"
|
||||
Value="{Binding Graph.CreateConnectionCommand}" />
|
||||
<Setter Property="Source"
|
||||
Value="{Binding Source, Mode=OneWayToSource}" />
|
||||
<Setter Property="Target"
|
||||
Value="{Binding PreviewTarget, Mode=OneWayToSource}" />
|
||||
<Setter Property="PreviewTarget"
|
||||
Value="{Binding PreviewTarget, Mode=OneWayToSource}" />
|
||||
<Setter Property="Content"
|
||||
Value="{Binding PreviewText}" />
|
||||
<Setter Property="EnablePreview"
|
||||
Value="{Binding EnablePendingConnectionPreview, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="EnableSnapping"
|
||||
Value="{Binding EnablePendingConnectionSnapping, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="AllowOnlyConnectors"
|
||||
Value="{Binding AllowConnectingToConnectorsOnly, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="Direction"
|
||||
Value="{Binding Source.Flow, Converter={StaticResource FlowToDirectionConverter}}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type nodify:PendingConnection}">
|
||||
<Canvas>
|
||||
<nodify:Connection Source="{TemplateBinding SourceAnchor}"
|
||||
Target="{TemplateBinding TargetAnchor}"
|
||||
Direction="{TemplateBinding Direction}"
|
||||
SourceOrientation="{Binding Source.Node.Orientation}"
|
||||
TargetOrientation="{Binding TargetOrientation}"
|
||||
DirectionalArrowsCount="{Binding DirectionalArrowsCount, Source={x:Static local:EditorSettings.Instance}}"
|
||||
StrokeThickness="{TemplateBinding StrokeThickness}"
|
||||
SourceOffset="{Binding ConnectionSourceOffset.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||
TargetOffset="{Binding ConnectionTargetOffset.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||
SourceOffsetMode="{Binding ConnectionSourceOffsetMode, Source={x:Static local:EditorSettings.Instance}}"
|
||||
TargetOffsetMode="None"
|
||||
ArrowSize="{Binding ConnectionArrowSize.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||
ArrowEnds="{Binding ArrowHeadEnds, Source={x:Static local:EditorSettings.Instance}}"
|
||||
ArrowShape="{Binding ArrowHeadShape, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Spacing="{Binding ConnectionSpacing, Source={x:Static local:EditorSettings.Instance}}">
|
||||
<nodify:Connection.Style>
|
||||
<Style TargetType="nodify:Connection"
|
||||
BasedOn="{StaticResource {x:Type nodify:Connection}}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Source.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Source.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:Connection.Style>
|
||||
</nodify:Connection>
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
Canvas.Left="{Binding TargetAnchor.X, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Canvas.Top="{Binding TargetAnchor.Y, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="{Binding PreviewText, Converter={shared:StringToVisibilityConverter}}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
CornerRadius="3"
|
||||
Margin="15">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
</Canvas>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type nodify:Connector}"
|
||||
BasedOn="{StaticResource {x:Type nodify:Connector}}">
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type nodify:NodeInput}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodeInput}}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="ConnectorTemplate"
|
||||
Value="{StaticResource SquareConnector}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Title}"
|
||||
Margin="0 0 5 0" />
|
||||
<TextBox Text="{Binding MaxConnections}"
|
||||
MinWidth="30" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="ConnectorTemplate"
|
||||
Value="{StaticResource TriangleConnector}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Title}"
|
||||
Margin="0 0 5 0"
|
||||
VerticalAlignment="Center" />
|
||||
<CheckBox />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Header"
|
||||
Value="{Binding}" />
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type nodify:NodeOutput}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodeOutput}}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="ConnectorTemplate"
|
||||
Value="{StaticResource SquareConnector}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="ConnectorTemplate"
|
||||
Value="{StaticResource TriangleConnector}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="Header"
|
||||
Value="{Binding Title}" />
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:KnotNodeViewModel}">
|
||||
<nodify:KnotNode Content="{Binding Connector}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:CommentNodeViewModel}">
|
||||
<nodify:GroupingNode ActualSize="{Binding Size}"
|
||||
Header="{Binding Title}"
|
||||
MovementMode="{Binding GroupingNodeMovement, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:FlowNodeViewModel}">
|
||||
<nodify:Node Input="{Binding Input}"
|
||||
Output="{Binding Output}"
|
||||
Header="{Binding Title}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:VerticalNodeViewModel}">
|
||||
<nodify:Node Header="{Binding Input}"
|
||||
Footer="{Binding Output}"
|
||||
Content="{Binding Title}">
|
||||
<nodify:Node.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}"
|
||||
Margin="5" />
|
||||
</DataTemplate>
|
||||
</nodify:Node.ContentTemplate>
|
||||
<nodify:Node.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<nodify:NodeInput Orientation="Vertical" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</nodify:Node.HeaderTemplate>
|
||||
<nodify:Node.FooterTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<nodify:NodeOutput Orientation="Vertical" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</nodify:Node.FooterTemplate>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
</nodify:NodifyEditor.Resources>
|
||||
|
||||
<nodify:NodifyEditor.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type nodify:ItemContainer}"
|
||||
BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
|
||||
<Setter Property="BorderThickness"
|
||||
Value="2" />
|
||||
<Setter Property="SelectedBorderThickness"
|
||||
Value="4" />
|
||||
<Setter Property="IsSelectable"
|
||||
Value="{Binding SelectableNodes, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="IsDraggable"
|
||||
Value="{Binding DraggableNodes, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="CacheMode">
|
||||
<Setter.Value>
|
||||
<BitmapCache RenderAtScale="{Binding MaxZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||
EnableClearType="True" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected"
|
||||
Value="True">
|
||||
<Setter Property="Panel.ZIndex"
|
||||
Value="1" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:NodifyEditor.ItemContainerStyle>
|
||||
</nodify:NodifyEditor>
|
||||
|
||||
<Grid Background="{StaticResource LargeGridLinesDrawingBrush}"
|
||||
Visibility="{Binding ShowGridLines, Source={x:Static local:PlaygroundSettings.Instance}, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Panel.ZIndex="-2" />
|
||||
|
||||
<nodify:Minimap ItemsSource="{Binding ItemsSource, ElementName=Editor}"
|
||||
ViewportSize="{Binding ViewportSize, ElementName=Editor}"
|
||||
ViewportLocation="{Binding ViewportLocation, ElementName=Editor}"
|
||||
Visibility="{Binding ShowMinimap, Source={x:Static local:PlaygroundSettings.Instance}, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
IsReadOnly="{Binding DisableMinimapControls, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||
ResizeToViewport="{Binding ResizeToViewport, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||
MaxViewportOffset="{Binding MinimapMaxViewportOffset.Size, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||
Zoom="Minimap_Zoom"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Width="300"
|
||||
Height="200"
|
||||
Margin="5 40">
|
||||
<nodify:Minimap.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:NodeViewModel}">
|
||||
<Grid />
|
||||
</DataTemplate>
|
||||
</nodify:Minimap.ItemTemplate>
|
||||
<nodify:Minimap.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type nodify:MinimapItem}"
|
||||
BasedOn="{StaticResource {x:Type nodify:MinimapItem}}">
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
<Setter Property="Width"
|
||||
Value="150" />
|
||||
<Setter Property="Height"
|
||||
Value="130" />
|
||||
</Style>
|
||||
</nodify:Minimap.ItemContainerStyle>
|
||||
</nodify:Minimap>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="5 60"
|
||||
Width="250">
|
||||
<Border CornerRadius="3"
|
||||
Visibility="{Binding SelectedConnection, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderBrush}"
|
||||
Margin="0 0 0 10">
|
||||
<StackPanel Margin="10">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}"
|
||||
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||
<Setter Property="Margin"
|
||||
Value="0 0 0 5" />
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
|
||||
<StackPanel Margin="0 0 0 14">
|
||||
<TextBlock Text="Selected connection"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}"
|
||||
FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Margin="0 0 0 14"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||
<Run>From</Run>
|
||||
<Run Text="{Binding SelectedConnection.Output.Node.Title}"
|
||||
Foreground="Red" />
|
||||
<Run> - </Run>
|
||||
<Run Text="{Binding SelectedConnection.Output.Title}"
|
||||
Foreground="Red" />
|
||||
<Run>to</Run>
|
||||
<Run Text="{Binding SelectedConnection.Input.Node.Title}"
|
||||
Foreground="Red" />
|
||||
<Run> - </Run>
|
||||
<Run Text="{Binding SelectedConnection.Input.Title}"
|
||||
Foreground="Red" />
|
||||
</TextBlock>
|
||||
|
||||
<Button Command="{Binding SelectedConnection.DisconnectCommand}"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource HollowButton}"
|
||||
Content="Delete" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border CornerRadius="3"
|
||||
Visibility="{Binding SelectedNode, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderBrush}">
|
||||
<StackPanel Margin="10">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}"
|
||||
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||
<Setter Property="Margin"
|
||||
Value="0 0 0 5" />
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
|
||||
<StackPanel Margin="0 0 0 14">
|
||||
<TextBlock Text="Selected node"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}"
|
||||
FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Margin="0 0 0 14"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||
<Run>Title: </Run>
|
||||
<Run Text="{Binding SelectedNode.Title}"
|
||||
Foreground="Red" />
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Margin="0 0 0 14"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||
<Run>Location: </Run>
|
||||
<Run Text="{Binding SelectedNode.Location}"
|
||||
Foreground="Red" />
|
||||
</TextBlock>
|
||||
|
||||
<Button Command="{Binding SelectedNode.DeleteCommand}"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource HollowButton}"
|
||||
Content="Delete" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
51
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs
Normal file
51
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Nodify.Events;
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public partial class NodifyEditorView : UserControl
|
||||
{
|
||||
public NodifyEditor EditorInstance => Editor;
|
||||
|
||||
public NodifyEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
EditorInstance.ActiveNavigationLayerChanged += DisplayActiveNavigationLayer;
|
||||
}
|
||||
|
||||
static NodifyEditorView()
|
||||
{
|
||||
InputProcessor.Shared<Connector>.ReplaceHandlerFactory<ConnectorState.Connecting>(elem => new CustomConnecting(elem));
|
||||
InputProcessor.Shared<Connector>.RegisterHandlerFactory(elem => new RetargetConnections(elem));
|
||||
}
|
||||
|
||||
private void Minimap_Zoom(object sender, ZoomEventArgs e)
|
||||
{
|
||||
EditorInstance.ZoomAtPosition(e.Zoom, e.Location);
|
||||
}
|
||||
|
||||
private void DisplayActiveNavigationLayer(KeyboardNavigationLayerId layerId)
|
||||
{
|
||||
var editorVm = (NodifyEditorViewModel)EditorInstance.DataContext;
|
||||
|
||||
if (layerId == KeyboardNavigationLayerId.Nodes)
|
||||
{
|
||||
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Nodes);
|
||||
}
|
||||
else if (layerId == KeyboardNavigationLayerId.Connections)
|
||||
{
|
||||
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Connections);
|
||||
}
|
||||
else if (layerId == KeyboardNavigationLayerId.Decorators)
|
||||
{
|
||||
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Decorators);
|
||||
}
|
||||
else
|
||||
{
|
||||
editorVm.KeyboardNavigationLayer = "Custom";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs
Normal file
133
Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class NodifyEditorViewModel : ObservableObject
|
||||
{
|
||||
public NodifyEditorViewModel()
|
||||
{
|
||||
Schema = new GraphSchema();
|
||||
|
||||
PendingConnection = new PendingConnectionViewModel
|
||||
{
|
||||
Graph = this
|
||||
};
|
||||
|
||||
DeleteSelectionCommand = new DelegateCommand(DeleteSelection, () => SelectedNodes.Count > 0 || SelectedConnections.Count > 0);
|
||||
CommentSelectionCommand = new RequeryCommand(() => Schema.AddCommentAroundNodes(SelectedNodes, "New comment"), () => SelectedNodes.Count > 0);
|
||||
DisconnectConnectorCommand = new DelegateCommand<ConnectorViewModel>(c => c.Disconnect());
|
||||
CreateConnectionCommand = new DelegateCommand<object>(target => Schema.TryAddConnection(PendingConnection.Source!, target),
|
||||
target => PendingConnection.Source != null && target != null && Schema.CanAddConnection(PendingConnection.Source, target));
|
||||
|
||||
Connections.WhenAdded(c =>
|
||||
{
|
||||
c.Graph = this;
|
||||
c.Input.Connections.Add(c);
|
||||
c.Output.Connections.Add(c);
|
||||
})
|
||||
// Called when the collection is cleared
|
||||
.WhenRemoved(c =>
|
||||
{
|
||||
c.Input.Connections.Remove(c);
|
||||
c.Output.Connections.Remove(c);
|
||||
});
|
||||
|
||||
Nodes.WhenAdded(x => x.Graph = this)
|
||||
// Not called when the collection is cleared
|
||||
.WhenRemoved(x =>
|
||||
{
|
||||
if (x is FlowNodeViewModel flow)
|
||||
{
|
||||
flow.Disconnect();
|
||||
}
|
||||
else if (x is KnotNodeViewModel knot)
|
||||
{
|
||||
knot.Connector.Disconnect();
|
||||
}
|
||||
})
|
||||
.WhenCleared(x => Connections.Clear());
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<NodeViewModel> _nodes = new NodifyObservableCollection<NodeViewModel>();
|
||||
public NodifyObservableCollection<NodeViewModel> Nodes
|
||||
{
|
||||
get => _nodes;
|
||||
set => SetProperty(ref _nodes, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<NodeViewModel> _selectedNodes = new NodifyObservableCollection<NodeViewModel>();
|
||||
public NodifyObservableCollection<NodeViewModel> SelectedNodes
|
||||
{
|
||||
get => _selectedNodes;
|
||||
set => SetProperty(ref _selectedNodes, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<ConnectionViewModel> _selectedConnections = new NodifyObservableCollection<ConnectionViewModel>();
|
||||
public NodifyObservableCollection<ConnectionViewModel> SelectedConnections
|
||||
{
|
||||
get => _selectedConnections;
|
||||
set => SetProperty(ref _selectedConnections, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<ConnectionViewModel> _connections = new NodifyObservableCollection<ConnectionViewModel>();
|
||||
public NodifyObservableCollection<ConnectionViewModel> Connections
|
||||
{
|
||||
get => _connections;
|
||||
set => SetProperty(ref _connections, value);
|
||||
}
|
||||
|
||||
private Size _viewportSize;
|
||||
public Size ViewportSize
|
||||
{
|
||||
get => _viewportSize;
|
||||
set => SetProperty(ref _viewportSize, value);
|
||||
}
|
||||
|
||||
public PendingConnectionViewModel PendingConnection { get; }
|
||||
|
||||
private ConnectionViewModel? _selectedConnection;
|
||||
public ConnectionViewModel? SelectedConnection
|
||||
{
|
||||
get => _selectedConnection;
|
||||
set => SetProperty(ref _selectedConnection, value);
|
||||
}
|
||||
|
||||
private NodeViewModel? _selectedNode;
|
||||
public NodeViewModel? SelectedNode
|
||||
{
|
||||
get => _selectedNode;
|
||||
set => SetProperty(ref _selectedNode, value);
|
||||
}
|
||||
|
||||
public GraphSchema Schema { get; }
|
||||
|
||||
private string? _keyboardNavigationLayer;
|
||||
public string? KeyboardNavigationLayer
|
||||
{
|
||||
get => _keyboardNavigationLayer;
|
||||
set => SetProperty(ref _keyboardNavigationLayer, value);
|
||||
}
|
||||
|
||||
public ICommand DeleteSelectionCommand { get; }
|
||||
public ICommand DisconnectConnectorCommand { get; }
|
||||
public ICommand CreateConnectionCommand { get; }
|
||||
public ICommand CommentSelectionCommand { get; }
|
||||
|
||||
private void DeleteSelection()
|
||||
{
|
||||
foreach (var connection in SelectedConnections.ToList())
|
||||
{
|
||||
connection.Remove();
|
||||
}
|
||||
|
||||
var selected = SelectedNodes.ToList();
|
||||
|
||||
for (int i = 0; i < selected.Count; i++)
|
||||
{
|
||||
Nodes.Remove(selected[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class PendingConnectionViewModel : ObservableObject
|
||||
{
|
||||
private NodifyEditorViewModel _graph = default!;
|
||||
public NodifyEditorViewModel Graph
|
||||
{
|
||||
get => _graph;
|
||||
internal set => SetProperty(ref _graph, value);
|
||||
}
|
||||
|
||||
private ConnectorViewModel? _source;
|
||||
public ConnectorViewModel? Source
|
||||
{
|
||||
get => _source;
|
||||
set
|
||||
{
|
||||
if(SetProperty(ref _source, value))
|
||||
{
|
||||
SetTargetOrientation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object? _previewTarget;
|
||||
public object? PreviewTarget
|
||||
{
|
||||
get => _previewTarget;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _previewTarget, value))
|
||||
{
|
||||
OnPreviewTargetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string? _previewText;
|
||||
public string? PreviewText
|
||||
{
|
||||
get => _previewText;
|
||||
set => SetProperty(ref _previewText, value);
|
||||
}
|
||||
|
||||
private Orientation _targetOrientation;
|
||||
public Orientation TargetOrientation
|
||||
{
|
||||
get => _targetOrientation;
|
||||
set => SetProperty(ref _targetOrientation, value);
|
||||
}
|
||||
|
||||
protected virtual void OnPreviewTargetChanged()
|
||||
{
|
||||
bool canConnect = PreviewTarget != null && Graph.Schema.CanAddConnection(Source!, PreviewTarget);
|
||||
PreviewText = PreviewTarget switch
|
||||
{
|
||||
ConnectorViewModel con when con == Source => $"Can't connect to self",
|
||||
ConnectorViewModel con => $"{(canConnect ? "Connect" : "Can't connect")} to {con.Title ?? "pin"}",
|
||||
FlowNodeViewModel flow => $"{(canConnect ? "Connect" : "Can't connect")} to {flow.Title ?? "node"}",
|
||||
_ => null
|
||||
};
|
||||
|
||||
SetTargetOrientation();
|
||||
}
|
||||
|
||||
private void SetTargetOrientation()
|
||||
{
|
||||
TargetOrientation = PreviewTarget switch
|
||||
{
|
||||
ConnectorViewModel con when con.Node is FlowNodeViewModel flow => flow.Orientation,
|
||||
FlowNodeViewModel flow => flow.Orientation,
|
||||
NodifyEditorViewModel editor when Source?.Node is FlowNodeViewModel flow => flow.Orientation,
|
||||
_ => Orientation.Horizontal,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
132
Examples/Nodify.Playground/Editor/RetargetConnections.cs
Normal file
132
Examples/Nodify.Playground/Editor/RetargetConnections.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
/// <summary>
|
||||
/// Connecting state that prevents connecting when <see cref="RetargetConnections"/> is in progress.
|
||||
/// </summary>
|
||||
public class CustomConnecting : ConnectorState.Connecting
|
||||
{
|
||||
protected override bool CanBegin => !RetargetConnections.InProgress;
|
||||
|
||||
public CustomConnecting(Connector connector) : base(connector)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hold CTRL+LeftClick on a connector to start reconnecting it.
|
||||
/// </summary>
|
||||
public class RetargetConnections : DragState<Connector>
|
||||
{
|
||||
public static InputGestureRef Reconnect { get; } = new Interactivity.MouseGesture(MouseAction.LeftClick, ModifierKeys.Control)
|
||||
{
|
||||
IgnoreModifierKeysOnRelease = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Used to prevent connecting when <see cref="EditorSettings.EnableStickyConnectors"/> is enabled.
|
||||
/// </summary>
|
||||
public static bool InProgress { get; private set; }
|
||||
|
||||
protected override bool CanBegin => ViewModel.IsConnected && ViewModel.Flow == ConnectorFlow.Input;
|
||||
protected override bool IsToggle => EditorSettings.Instance.EnableStickyConnectors;
|
||||
|
||||
private ConnectorViewModel ViewModel => (ConnectorViewModel)Element.DataContext;
|
||||
private Vector _connectorOffset;
|
||||
private Connector? _targetConnector;
|
||||
|
||||
public RetargetConnections(Connector element) : base(element, Reconnect, EditorGestures.Mappings.Connector.CancelAction)
|
||||
{
|
||||
PositionElement = Element.Editor ?? (IInputElement)Element;
|
||||
}
|
||||
|
||||
protected override void OnBegin(InputEventArgs e)
|
||||
{
|
||||
_connectorOffset = ViewModel.Node.Orientation == Orientation.Horizontal
|
||||
? (Vector)EditorSettings.Instance.ConnectionTargetOffset.Value
|
||||
: new Vector(EditorSettings.Instance.ConnectionTargetOffset.Value.Y, EditorSettings.Instance.ConnectionTargetOffset.Value.X);
|
||||
|
||||
InProgress = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
var position = Element.Editor!.MouseLocation;
|
||||
|
||||
if (EditorSettings.Instance.EnablePendingConnectionHitTesting)
|
||||
{
|
||||
var connector = Element.FindTargetConnector(position);
|
||||
connector?.UpdateAnchor();
|
||||
|
||||
SetTargetConnector(connector);
|
||||
UpdateConnections(connector != null ? connector.Anchor : position + _connectorOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateConnections(position + _connectorOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateConnections(Point position)
|
||||
{
|
||||
foreach (var connection in ViewModel.Connections)
|
||||
{
|
||||
connection.Input.Anchor = position;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnd(InputEventArgs e)
|
||||
{
|
||||
var position = Element.Editor!.MouseLocation;
|
||||
var target = Element.FindTargetConnector(position);
|
||||
target?.UpdateAnchor();
|
||||
|
||||
if (target?.DataContext is ConnectorViewModel targetVM && ViewModel != targetVM)
|
||||
{
|
||||
ViewModel.Node.Graph.Schema.Rewire(ViewModel, targetVM);
|
||||
}
|
||||
|
||||
SetTargetConnector(null);
|
||||
|
||||
// Reset the position of connections that were not rewired
|
||||
Element.UpdateAnchor();
|
||||
InProgress = false;
|
||||
}
|
||||
|
||||
protected override void OnCancel(InputEventArgs e)
|
||||
{
|
||||
SetTargetConnector(null);
|
||||
|
||||
// Reset the position of connections that were not rewired
|
||||
Element.UpdateAnchor();
|
||||
InProgress = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the connection target and updates the visual state of the target element.
|
||||
/// </summary>
|
||||
private void SetTargetConnector(Connector? target)
|
||||
{
|
||||
if (target == _targetConnector)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_targetConnector != null)
|
||||
{
|
||||
PendingConnection.SetIsOverElement(_targetConnector, false);
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
PendingConnection.SetIsOverElement(target, true);
|
||||
}
|
||||
|
||||
_targetConnector = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Examples/Nodify.Playground/Editor/VerticalNodeViewModel.cs
Normal file
12
Examples/Nodify.Playground/Editor/VerticalNodeViewModel.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class VerticalNodeViewModel : FlowNodeViewModel
|
||||
{
|
||||
public VerticalNodeViewModel()
|
||||
{
|
||||
Orientation = Orientation.Vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user